mirror of
https://github.com/nlohmann/json.git
synced 2026-09-29 11:10:30 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90f85d6d75 | ||
|
|
4aaeb01ea4 | ||
|
|
2038838eea | ||
|
|
aa5084d713 | ||
|
|
33c4dfdc18 | ||
|
|
b41e43fffc | ||
|
|
958e0a906b | ||
|
|
49f038b86a | ||
|
|
722c2bb561 | ||
|
|
f41296276c | ||
|
|
481b8d17fa | ||
|
|
484f644b86 | ||
|
|
08e30eca78 | ||
|
|
582223eb8a | ||
|
|
c14208a8e0 |
@@ -100,7 +100,7 @@ jobs:
|
|||||||
container: ubuntu:focal
|
container: ubuntu:focal
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||||
steps:
|
steps:
|
||||||
- name: Install build-essential
|
- name: Install build-essential
|
||||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ option(JSON_Diagnostic_Positions "Enable diagnostic positions." OFF)
|
|||||||
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
|
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
|
||||||
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
|
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
|
||||||
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
|
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
|
||||||
option(JSON_DisableTupleReferenceConversion "Disable conversion from a one-element tuple of a JSON reference." OFF)
|
|
||||||
option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF)
|
option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF)
|
||||||
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
||||||
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
||||||
@@ -102,10 +101,6 @@ if (JSON_DisableEnumSerialization)
|
|||||||
message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)")
|
message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (JSON_DisableTupleReferenceConversion)
|
|
||||||
message(STATUS "Tuple reference conversion is disabled (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1)")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (JSON_LegacyDiscardedValueComparison)
|
if (JSON_LegacyDiscardedValueComparison)
|
||||||
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
|
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
|
||||||
endif()
|
endif()
|
||||||
@@ -148,7 +143,6 @@ target_compile_definitions(
|
|||||||
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
|
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
|
||||||
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
|
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
|
||||||
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
|
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
|
||||||
$<$<BOOL:${JSON_DisableTupleReferenceConversion}>:JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1>
|
|
||||||
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
|
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
|
||||||
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
||||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||||
|
|||||||
@@ -276,20 +276,6 @@ add_custom_target(ci_test_disableenumserialization
|
|||||||
COMMENT "Compile and test with enum serialization disabled"
|
COMMENT "Compile and test with enum serialization disabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Disable conversion from a one-element tuple of a JSON reference.
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
add_custom_target(ci_test_disabletuplereferenceconversion
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
|
||||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DisableTupleReferenceConversion=ON
|
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
|
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
|
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
|
||||||
COMMENT "Compile and test with tuple reference conversion disabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Skip the multiple-inclusion library version check.
|
# Skip the multiple-inclusion library version check.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|||||||
@@ -210,7 +210,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'a
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_TUPLE_REFERENCE_CONVERSION', 'Macro', 'api/macros/json_disable_tuple_reference_conversion/index.html');
|
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html');
|
||||||
|
|||||||
@@ -159,8 +159,6 @@ basic_json(basic_json&& other) noexcept;
|
|||||||
- `CompatibleType` is not `basic_json` (to avoid hijacking copy/move constructors),
|
- `CompatibleType` is not `basic_json` (to avoid hijacking copy/move constructors),
|
||||||
- `CompatibleType` is not a different `basic_json` type (i.e. with different template arguments)
|
- `CompatibleType` is not a different `basic_json` type (i.e. with different template arguments)
|
||||||
- `CompatibleType` is not a `basic_json` nested type (e.g., `json_pointer`, `iterator`, etc.)
|
- `CompatibleType` is not a `basic_json` nested type (e.g., `json_pointer`, `iterator`, etc.)
|
||||||
- if [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../macros/json_disable_tuple_reference_conversion.md) is defined
|
|
||||||
to `1`: `CompatibleType` is not a one-element `std::tuple` holding a reference to `basic_json`
|
|
||||||
- `json_serializer<U>` (with `U = uncvref_t<CompatibleType>`) has a `to_json(basic_json_t&, CompatibleType&&)`
|
- `json_serializer<U>` (with `U = uncvref_t<CompatibleType>`) has a `to_json(basic_json_t&, CompatibleType&&)`
|
||||||
method
|
method
|
||||||
|
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
the end of the file was not reached when `strict` was set to true
|
the end of the file was not reached when `strict` was set to true
|
||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
||||||
used in the given input or if the input is not valid CBOR
|
used in the given input or if the input is not valid CBOR
|
||||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
|
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
|
||||||
types are not supported, as JSON object keys are always strings) or a string is malformed
|
but not found
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
the end of the file was not reached when `strict` was set to true
|
the end of the file was not reached when `strict` was set to true
|
||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
|
||||||
MessagePack were used in the given input or if the input is not valid MessagePack
|
MessagePack were used in the given input or if the input is not valid MessagePack
|
||||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
|
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
|
||||||
types are not supported, as JSON object keys are always strings) or a string is malformed
|
but not found
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
|
|
||||||
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
|
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
|
||||||
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
||||||
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
|
|
||||||
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
||||||
|
|
||||||
## Comparison behavior
|
## Comparison behavior
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
# JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION /* value */
|
|
||||||
```
|
|
||||||
|
|
||||||
When defined to `1`, a `basic_json` value can no longer be constructed from a one-element `std::tuple` whose element is
|
|
||||||
a reference to that `basic_json` type, such as `std::tuple<json&>`, `std::tuple<const json&>`, or `std::tuple<json&&>`.
|
|
||||||
These are the tuples created by `std::forward_as_tuple(j)`.
|
|
||||||
|
|
||||||
## Default definition
|
|
||||||
|
|
||||||
The default value is `0` (disabled — existing behavior is preserved).
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
!!! note "Background"
|
|
||||||
|
|
||||||
By default, `basic_json` can be constructed from any `std::tuple` whose elements can be converted to JSON; the result
|
|
||||||
is an array. This includes `std::tuple<json&>`, which becomes a one-element array.
|
|
||||||
|
|
||||||
`std::tuple` only converts another tuple element by element if its element type cannot be constructed from the whole
|
|
||||||
source tuple. Because `json` *can* be constructed from `std::tuple<json&>`, `std::tuple` instead converts the whole
|
|
||||||
tuple into a single `json` value. This has two surprising effects:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
json j = true;
|
|
||||||
|
|
||||||
// rejected by some standard libraries (e.g., libc++); with others, the
|
|
||||||
// reference binds to a temporary that is destroyed right away
|
|
||||||
std::tuple<const json&> t1(std::forward_as_tuple(j));
|
|
||||||
|
|
||||||
// compiles, but std::get<0>(t2) is [true], not true
|
|
||||||
std::tuple<json> t2(std::forward_as_tuple(j));
|
|
||||||
```
|
|
||||||
|
|
||||||
Enabling this macro removes the conversion, so both tuples are converted element by element: `std::get<0>(t1)`
|
|
||||||
refers to `j`, and `std::get<0>(t2)` is a copy of `j` (see [#2226](https://github.com/nlohmann/json/issues/2226)).
|
|
||||||
|
|
||||||
!!! warning "Opt-in only"
|
|
||||||
|
|
||||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
|
|
||||||
|
|
||||||
!!! note "Affected conversions"
|
|
||||||
|
|
||||||
Only one-element tuples holding a reference to the **same** `basic_json` type are affected. Constructing a JSON value
|
|
||||||
from them no longer compiles:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
json j = true;
|
|
||||||
json a = std::forward_as_tuple(j); // error with the macro enabled
|
|
||||||
json b = json::array({j}); // use this instead: [true]
|
|
||||||
```
|
|
||||||
|
|
||||||
Tuples holding a JSON value (`std::make_tuple(j)`), tuples with more than one element, and tuples holding references
|
|
||||||
to other types (including other `basic_json` specializations) are converted to arrays as before.
|
|
||||||
|
|
||||||
!!! hint "CMake option"
|
|
||||||
|
|
||||||
This behavior can also be controlled with the CMake option
|
|
||||||
[`JSON_DisableTupleReferenceConversion`](../../integration/cmake.md#json_disabletuplereferenceconversion)
|
|
||||||
(`OFF` by default) which defines `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` accordingly.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
??? example "Default behavior (macro not defined)"
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
json j = true;
|
|
||||||
|
|
||||||
std::tuple<json> t(std::forward_as_tuple(j));
|
|
||||||
// std::get<0>(t) is [true] -- the whole tuple was converted
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
??? example "Conversion disabled (macro defined to 1)"
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
json j = true;
|
|
||||||
|
|
||||||
std::tuple<json> t(std::forward_as_tuple(j));
|
|
||||||
// std::get<0>(t) is true -- a copy of j
|
|
||||||
|
|
||||||
std::tuple<const json&> r(std::forward_as_tuple(j));
|
|
||||||
// std::get<0>(r) refers to j
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [**basic_json(CompatibleType&&)**](../basic_json/basic_json.md) - the affected constructor
|
|
||||||
- [:simple-cmake: JSON_DisableTupleReferenceConversion](../../integration/cmake.md#json_disabletuplereferenceconversion) -
|
|
||||||
CMake option to control the macro
|
|
||||||
|
|
||||||
## Version history
|
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
|
||||||
@@ -174,20 +174,7 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
|
|
||||||
!!! warning "Object keys"
|
!!! warning "Object keys"
|
||||||
|
|
||||||
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps
|
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected.
|
||||||
with keys other than text strings (major type 3) are rejected with a
|
|
||||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions` set
|
|
||||||
to `false`, a discarded value) naming the type of the key that was found, for instance:
|
|
||||||
|
|
||||||
```
|
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
|
|
||||||
```
|
|
||||||
|
|
||||||
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
|
|
||||||
on. This is a deliberate restriction of the library's JSON value model, not an oversight: formats built on CBOR
|
|
||||||
maps with integer keys, such as COSE ([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052.html)) or CWT
|
|
||||||
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
|
|
||||||
general-purpose CBOR library instead.
|
|
||||||
|
|
||||||
!!! warning "UTF-8 validation of text strings"
|
!!! warning "UTF-8 validation of text strings"
|
||||||
|
|
||||||
|
|||||||
@@ -138,21 +138,6 @@ The library maps MessagePack types to JSON value types as follows:
|
|||||||
|
|
||||||
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
||||||
|
|
||||||
!!! warning "Object keys"
|
|
||||||
|
|
||||||
MessagePack allows map keys of any type, whereas JSON only allows strings as keys in object values. Like the
|
|
||||||
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the
|
|
||||||
MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are
|
|
||||||
rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
|
||||||
`allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance:
|
|
||||||
|
|
||||||
```
|
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
|
|
||||||
```
|
|
||||||
|
|
||||||
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
|
|
||||||
on. Such input needs a general-purpose MessagePack library instead.
|
|
||||||
|
|
||||||
!!! warning "UTF-8 validation of string values"
|
!!! warning "UTF-8 validation of string values"
|
||||||
|
|
||||||
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
||||||
|
|||||||
@@ -83,13 +83,6 @@ When defined, default parse and serialize functions for enums are excluded and h
|
|||||||
|
|
||||||
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
||||||
|
|
||||||
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
|
||||||
|
|
||||||
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
|
||||||
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
|
|
||||||
|
|
||||||
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
|
||||||
|
|
||||||
## `JSON_NO_IO`
|
## `JSON_NO_IO`
|
||||||
|
|
||||||
When defined, headers `<cstdio>`, `<ios>`, `<iosfwd>`, `<istream>`, and `<ostream>` are not included and parse functions
|
When defined, headers `<cstdio>`, `<ios>`, `<iosfwd>`, `<istream>`, and `<ostream>` are not included and parse functions
|
||||||
|
|||||||
@@ -343,20 +343,13 @@ A string could not be read from a [binary format](../features/binary_formats/ind
|
|||||||
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
||||||
the string's bytes are not valid UTF-8.
|
the string's bytes are not valid UTF-8.
|
||||||
|
|
||||||
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
|
|
||||||
type (for instance integers or `null`) are therefore not supported; see the notes on
|
|
||||||
[CBOR](../features/binary_formats/cbor.md) and [MessagePack](../features/binary_formats/messagepack.md).
|
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF
|
||||||
```
|
|
||||||
```
|
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C
|
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
||||||
|
|||||||
@@ -169,12 +169,6 @@ Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../a
|
|||||||
Disable default `enum` serialization by defining the macro
|
Disable default `enum` serialization by defining the macro
|
||||||
[`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). This option is `OFF` by default.
|
[`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). This option is `OFF` by default.
|
||||||
|
|
||||||
### `JSON_DisableTupleReferenceConversion`
|
|
||||||
|
|
||||||
Disable the conversion from a one-element `std::tuple` holding a reference to a JSON value by defining the macro
|
|
||||||
[`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). This option is
|
|
||||||
`OFF` by default.
|
|
||||||
|
|
||||||
### `JSON_FastTests`
|
### `JSON_FastTests`
|
||||||
|
|
||||||
Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||||
|
|||||||
@@ -287,7 +287,6 @@ nav:
|
|||||||
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
||||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||||
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
|
||||||
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md
|
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md
|
||||||
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||||
- 'JSON_HAS_RANGES': api/macros/json_has_ranges.md
|
- 'JSON_HAS_RANGES': api/macros/json_has_ranges.md
|
||||||
|
|||||||
@@ -471,13 +471,11 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
|||||||
j = { std::get<Idx>(t)... };
|
j = { std::get<Idx>(t)... };
|
||||||
}
|
}
|
||||||
|
|
||||||
// A one-element braced list does not reliably wrap its element: with
|
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||||
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
|
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||||
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
|
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||||
// 15 and 16) copy an element that is itself a basic_json even without it, so
|
// rather than [5]. Build what the default deduction builds instead: an object
|
||||||
// std::tuple<json>{true} became true rather than [true]. Build what the default
|
// if the element is a [string, value] pair, a one-element array otherwise.
|
||||||
// deduction builds instead: an object if the element is a [string, value] pair,
|
|
||||||
// a one-element array otherwise.
|
|
||||||
template<typename BasicJsonType, typename Tuple>
|
template<typename BasicJsonType, typename Tuple>
|
||||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||||
{
|
{
|
||||||
@@ -495,6 +493,7 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
|||||||
j = BasicJsonType::array({std::move(element)});
|
j = BasicJsonType::array({std::move(element)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template<typename BasicJsonType, typename Tuple>
|
template<typename BasicJsonType, typename Tuple>
|
||||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||||
|
|||||||
@@ -1324,80 +1324,6 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief reads a CBOR object key
|
|
||||||
|
|
||||||
RFC 8949 allows any data item as a map key, but only strings have a
|
|
||||||
counterpart in JSON. A key of any other type is rejected with a message
|
|
||||||
naming that type, rather than the one @ref get_cbor_string gives for a
|
|
||||||
malformed string.
|
|
||||||
|
|
||||||
@param[out] result created key
|
|
||||||
|
|
||||||
@return whether key creation completed
|
|
||||||
*/
|
|
||||||
bool get_cbor_object_key(string_t& result)
|
|
||||||
{
|
|
||||||
// EOF and major type 3 (text string) are left to get_cbor_string
|
|
||||||
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
|
|
||||||
{
|
|
||||||
return get_cbor_string(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* found = nullptr;
|
|
||||||
switch (static_cast<unsigned int>(current) >> 5u)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
found = "an unsigned integer";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
found = "a negative integer";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
found = "a byte string";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
found = "an array";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
found = "a map";
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
found = "a tag";
|
|
||||||
break;
|
|
||||||
default: // major type 7
|
|
||||||
switch (current)
|
|
||||||
{
|
|
||||||
case 0xF4:
|
|
||||||
case 0xF5:
|
|
||||||
found = "a boolean";
|
|
||||||
break;
|
|
||||||
case 0xF6:
|
|
||||||
found = "null";
|
|
||||||
break;
|
|
||||||
case 0xF7:
|
|
||||||
found = "undefined";
|
|
||||||
break;
|
|
||||||
case 0xF9:
|
|
||||||
case 0xFA:
|
|
||||||
case 0xFB:
|
|
||||||
found = "a floating-point number";
|
|
||||||
break;
|
|
||||||
case 0xFF:
|
|
||||||
found = "a break stop code";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
found = "a simple value";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto last_token = get_token_string();
|
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
|
||||||
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR byte array
|
@brief reads a definite-length CBOR byte array
|
||||||
|
|
||||||
@@ -1642,7 +1568,7 @@ class binary_reader
|
|||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2143,98 +2069,6 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief reads a MessagePack object key
|
|
||||||
|
|
||||||
The MessagePack specification allows any type as a map key, but only
|
|
||||||
strings have a counterpart in JSON. A key of any other type is rejected
|
|
||||||
with a message naming that type, rather than the one @ref
|
|
||||||
get_msgpack_string gives for a malformed string.
|
|
||||||
|
|
||||||
@param[out] result created key
|
|
||||||
|
|
||||||
@return whether key creation completed
|
|
||||||
*/
|
|
||||||
bool get_msgpack_object_key(string_t& result)
|
|
||||||
{
|
|
||||||
const char* found = nullptr;
|
|
||||||
switch (current)
|
|
||||||
{
|
|
||||||
case 0xC0:
|
|
||||||
found = "nil";
|
|
||||||
break;
|
|
||||||
case 0xC2:
|
|
||||||
case 0xC3:
|
|
||||||
found = "a boolean";
|
|
||||||
break;
|
|
||||||
case 0xCA:
|
|
||||||
case 0xCB:
|
|
||||||
found = "a float";
|
|
||||||
break;
|
|
||||||
case 0xC4:
|
|
||||||
case 0xC5:
|
|
||||||
case 0xC6:
|
|
||||||
found = "a bin";
|
|
||||||
break;
|
|
||||||
case 0xC7:
|
|
||||||
case 0xC8:
|
|
||||||
case 0xC9:
|
|
||||||
case 0xD4:
|
|
||||||
case 0xD5:
|
|
||||||
case 0xD6:
|
|
||||||
case 0xD7:
|
|
||||||
case 0xD8:
|
|
||||||
found = "an ext";
|
|
||||||
break;
|
|
||||||
case 0xCC:
|
|
||||||
case 0xCD:
|
|
||||||
case 0xCE:
|
|
||||||
case 0xCF:
|
|
||||||
case 0xD0:
|
|
||||||
case 0xD1:
|
|
||||||
case 0xD2:
|
|
||||||
case 0xD3:
|
|
||||||
found = "an integer";
|
|
||||||
break;
|
|
||||||
case 0xDC:
|
|
||||||
case 0xDD:
|
|
||||||
found = "an array";
|
|
||||||
break;
|
|
||||||
case 0xDE:
|
|
||||||
case 0xDF:
|
|
||||||
found = "a map";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// fixint, fixmap, and fixarray; strings, EOF, and the unused
|
|
||||||
// byte 0xC1 are left to get_msgpack_string
|
|
||||||
if (current == char_traits<char_type>::eof())
|
|
||||||
{
|
|
||||||
return get_msgpack_string(result);
|
|
||||||
}
|
|
||||||
if (current <= 0x7F || current >= 0xE0)
|
|
||||||
{
|
|
||||||
found = "an integer";
|
|
||||||
}
|
|
||||||
else if (current <= 0x8F)
|
|
||||||
{
|
|
||||||
found = "a map";
|
|
||||||
}
|
|
||||||
else if (current <= 0x9F)
|
|
||||||
{
|
|
||||||
found = "an array";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return get_msgpack_string(result);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto last_token = get_token_string();
|
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
|
||||||
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a MessagePack byte array
|
@brief reads a MessagePack byte array
|
||||||
|
|
||||||
@@ -2397,7 +2231,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
|
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
|
||||||
, discard_number_values(discard_number_values_)
|
, discard_number_values(discard_number_values_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -221,7 +222,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// locales
|
// locales
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
/// return the decimal point of the current locale
|
/// return the locale-dependent decimal point
|
||||||
|
JSON_HEDLEY_PURE
|
||||||
static char get_decimal_point() noexcept
|
static char get_decimal_point() noexcept
|
||||||
{
|
{
|
||||||
const auto* loc = localeconv();
|
const auto* loc = localeconv();
|
||||||
@@ -1090,10 +1092,9 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
token_type::value_float if number could be successfully scanned,
|
token_type::value_float if number could be successfully scanned,
|
||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note The scanner is independent of the current locale: token_buffer
|
@note The scanner is independent of the current locale. Internally, the
|
||||||
always holds `.`. Only the std::strtod fallback of convert_number()
|
locale's decimal point is used instead of `.` to work with the
|
||||||
depends on the locale, and it looks up the decimal point right
|
locale-dependent converters.
|
||||||
before converting (see convert_float_locale_aware()).
|
|
||||||
*/
|
*/
|
||||||
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
||||||
{
|
{
|
||||||
@@ -1182,7 +1183,7 @@ scan_number_zero:
|
|||||||
{
|
{
|
||||||
case '.':
|
case '.':
|
||||||
{
|
{
|
||||||
add(current);
|
add(decimal_point_char);
|
||||||
decimal_point_position = token_buffer.size() - 1;
|
decimal_point_position = token_buffer.size() - 1;
|
||||||
goto scan_number_decimal1;
|
goto scan_number_decimal1;
|
||||||
}
|
}
|
||||||
@@ -1219,7 +1220,7 @@ scan_number_any1:
|
|||||||
|
|
||||||
case '.':
|
case '.':
|
||||||
{
|
{
|
||||||
add(current);
|
add(decimal_point_char);
|
||||||
decimal_point_position = token_buffer.size() - 1;
|
decimal_point_position = token_buffer.size() - 1;
|
||||||
goto scan_number_decimal1;
|
goto scan_number_decimal1;
|
||||||
}
|
}
|
||||||
@@ -1461,9 +1462,9 @@ scan_number_done:
|
|||||||
|
|
||||||
// Only a number below 1 can carry further insignificant zeros, and only
|
// Only a number below 1 can carry further insignificant zeros, and only
|
||||||
// while the count stays at the limit does removing them change the
|
// while the count stays at the limit does removing them change the
|
||||||
// answer - so this loop is skipped for all but a few tokens. The
|
// answer - so this loop is skipped for all but a few tokens. Note
|
||||||
// fraction is located through decimal_point_position rather than by
|
// token_buffer holds the locale's decimal point, so the fraction is
|
||||||
// searching '.'.
|
// located through decimal_point_position rather than by searching '.'.
|
||||||
if (lead_zero != 0)
|
if (lead_zero != 0)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||||
@@ -1481,8 +1482,8 @@ scan_number_done:
|
|||||||
@brief convert the number text in token_buffer to its value and token type
|
@brief convert the number text in token_buffer to its value and token type
|
||||||
|
|
||||||
The digit sequence in token_buffer has already been validated (by the
|
The digit sequence in token_buffer has already been validated (by the
|
||||||
scan_number() state machine or by the contiguous fast path) and holds '.'
|
scan_number() state machine or by the contiguous fast path) and holds the
|
||||||
as decimal point, independent of the locale. Integers are parsed first and fall
|
locale decimal point in place of '.'. Integers are parsed first and fall
|
||||||
back to floating point on overflow. This is shared so both scanners produce
|
back to floating point on overflow. This is shared so both scanners produce
|
||||||
identical results.
|
identical results.
|
||||||
|
|
||||||
@@ -1562,7 +1563,7 @@ scan_number_done:
|
|||||||
// integer conversion above overflowed. Prefer std::from_chars
|
// integer conversion above overflowed. Prefer std::from_chars
|
||||||
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||||
// otherwise the exact Clinger fast path (double only); otherwise the
|
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||||
// locale-aware strtof/strtod/strtold.
|
// locale-aware strtof/strtod.
|
||||||
if (parse_float_from_chars(num_begin, num_end, value_float))
|
if (parse_float_from_chars(num_begin, num_end, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
@@ -1571,75 +1572,26 @@ scan_number_done:
|
|||||||
// extra pass over the token's bytes, which otherwise shows up on
|
// extra pass over the token's bytes, which otherwise shows up on
|
||||||
// high-precision inputs such as canada.json
|
// high-precision inputs such as canada.json
|
||||||
if (mantissa_fits_clinger(mantissa_end)
|
if (mantissa_fits_clinger(mantissa_end)
|
||||||
&& parse_float_fast(num_begin, num_end, value_float))
|
&& parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
}
|
}
|
||||||
|
|
||||||
convert_float_locale_aware();
|
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||||
|
strtof(value_float, token_buffer.data(), &endptr);
|
||||||
|
|
||||||
|
// we checked the number format before
|
||||||
|
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
|
||||||
|
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief convert the float in token_buffer with strtof/strtod/strtold
|
|
||||||
|
|
||||||
These functions expect the decimal point of the *current* locale, so it is
|
|
||||||
looked up right before the conversion instead of once when the lexer is
|
|
||||||
constructed: a locale change in between (by a parser callback, a SAX
|
|
||||||
handler, or another thread) must not truncate the value (#5198). The
|
|
||||||
token has been validated before, so if the conversion stops early and the
|
|
||||||
decimal point changed in the meantime, the locale changed between the
|
|
||||||
lookup and the call, and the conversion is repeated with the new decimal
|
|
||||||
point. If the decimal point did not change, a retry cannot succeed: the
|
|
||||||
locale's decimal point is not a single character (e.g., the two-byte
|
|
||||||
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
|
|
||||||
The value strtod parsed up to that point is kept, as before this change.
|
|
||||||
|
|
||||||
Note that changing the locale in another thread *while* strtod runs is
|
|
||||||
undefined behavior of the C library, which this function cannot prevent.
|
|
||||||
*/
|
|
||||||
void convert_float_locale_aware()
|
|
||||||
{
|
|
||||||
const bool has_dot = decimal_point_position != std::string::npos;
|
|
||||||
char decimal_point = get_decimal_point();
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
const bool substitute = has_dot && decimal_point != '.';
|
|
||||||
if (substitute)
|
|
||||||
{
|
|
||||||
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
|
||||||
strtof(value_float, token_buffer.data(), &endptr);
|
|
||||||
|
|
||||||
if (substitute)
|
|
||||||
{
|
|
||||||
// get_string() hands the token to the SAX interface with '.'
|
|
||||||
token_buffer[decimal_point_position] = '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// retry only if the locale changed; otherwise, this would loop forever
|
|
||||||
const char current_decimal_point = get_decimal_point();
|
|
||||||
if (current_decimal_point == decimal_point)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
decimal_point = current_decimal_point;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief contiguous fast path for scanning a number
|
@brief contiguous fast path for scanning a number
|
||||||
|
|
||||||
Parses the whole number token straight from the input buffer, avoiding the
|
Parses the whole number token straight from the input buffer, avoiding the
|
||||||
per-character get()/add() of scan_number(). On success it fills token_buffer
|
per-character get()/add() of scan_number(). On success it fills token_buffer
|
||||||
(as scan_number() does) and
|
(with the locale decimal point substituted, as scan_number() does) and
|
||||||
returns the token type. On anything it does not fully recognize as a
|
returns the token type. On anything it does not fully recognize as a
|
||||||
well-formed number it makes no state change and returns
|
well-formed number it makes no state change and returns
|
||||||
token_type::uninitialized, so the caller falls back to scan_number(), which
|
token_type::uninitialized, so the caller falls back to scan_number(), which
|
||||||
@@ -1755,11 +1707,16 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// materialize the token exactly as scan_number() would. reset() already
|
// materialize the token exactly as scan_number() would, substituting the
|
||||||
// cleared token_buffer, so append() fills it (assign() is avoided
|
// locale decimal point so convert_number()'s strtof fallback stays valid.
|
||||||
// because custom string_t types need not provide it)
|
// reset() already cleared token_buffer, so append() fills it (assign() is
|
||||||
|
// avoided because custom string_t types need not provide it)
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
decimal_point_position = dot_index;
|
if (dot_index != std::string::npos)
|
||||||
|
{
|
||||||
|
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
|
||||||
|
decimal_point_position = dot_index;
|
||||||
|
}
|
||||||
|
|
||||||
ia.bulk_skip(len - 1);
|
ia.bulk_skip(len - 1);
|
||||||
position.chars_read_total += (len - 1);
|
position.chars_read_total += (len - 1);
|
||||||
@@ -2026,7 +1983,11 @@ scan_number_done:
|
|||||||
/// return current string value (implicitly resets the token; useful only once)
|
/// return current string value (implicitly resets the token; useful only once)
|
||||||
string_t& get_string()
|
string_t& get_string()
|
||||||
{
|
{
|
||||||
// a number token holds '.' regardless of the locale (#4084)
|
// translate decimal points from locale back to '.' (#4084)
|
||||||
|
if (decimal_point_char != '.' && decimal_point_position != std::string::npos)
|
||||||
|
{
|
||||||
|
token_buffer[decimal_point_position] = '.';
|
||||||
|
}
|
||||||
return token_buffer;
|
return token_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2322,7 +2283,9 @@ scan_number_done:
|
|||||||
number_unsigned_t value_unsigned = 0;
|
number_unsigned_t value_unsigned = 0;
|
||||||
number_float_t value_float = 0;
|
number_float_t value_float = 0;
|
||||||
|
|
||||||
/// the position of the decimal point in token_buffer
|
/// the decimal point
|
||||||
|
const char_int_type decimal_point_char = '.';
|
||||||
|
/// the position of the decimal point in the input
|
||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
||||||
|
|||||||
@@ -118,12 +118,14 @@ std::strtod. The parser only activates for number_float_t == double; float and
|
|||||||
long double keep the std::strtof/std::strtold paths (see the templated overload
|
long double keep the std::strtof/std::strtold paths (see the templated overload
|
||||||
below).
|
below).
|
||||||
|
|
||||||
@param[in] first pointer to the first character of the number
|
@param[in] first pointer to the first character of the number
|
||||||
@param[in] last pointer past the last character
|
@param[in] last pointer past the last character
|
||||||
@param[out] out the parsed value on success
|
@param[in] decimal_point the (locale-dependent) decimal point character
|
||||||
|
@param[out] out the parsed value on success
|
||||||
@return true if the value was parsed exactly; false to fall back to strtod
|
@return true if the value was parsed exactly; false to fall back to strtod
|
||||||
*/
|
*/
|
||||||
inline bool parse_float_fast(const char* first, const char* last, double& out) noexcept
|
template<typename DecimalPointType>
|
||||||
|
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
|
||||||
{
|
{
|
||||||
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||||
// Clinger's fast path is only exact when double operations are evaluated in
|
// Clinger's fast path is only exact when double operations are evaluated in
|
||||||
@@ -134,6 +136,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
|
|||||||
// std::from_chars / std::strtod path.
|
// std::from_chars / std::strtod path.
|
||||||
static_cast<void>(first);
|
static_cast<void>(first);
|
||||||
static_cast<void>(last);
|
static_cast<void>(last);
|
||||||
|
static_cast<void>(decimal_point);
|
||||||
static_cast<void>(out);
|
static_cast<void>(out);
|
||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
@@ -172,7 +175,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
|
|||||||
++num_digits;
|
++num_digits;
|
||||||
fractional_digits += static_cast<int>(seen_dot);
|
fractional_digits += static_cast<int>(seen_dot);
|
||||||
}
|
}
|
||||||
else if (c == '.')
|
else if (static_cast<DecimalPointType>(c) == decimal_point)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
||||||
{
|
{
|
||||||
@@ -257,8 +260,8 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// fast float path is only exact for `double`; decline for float/long double
|
/// fast float path is only exact for `double`; decline for float/long double
|
||||||
template<typename FloatType>
|
template<typename DecimalPointType, typename FloatType>
|
||||||
bool parse_float_fast(const char* /*first*/, const char* /*last*/, FloatType& /*out*/) noexcept
|
bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -270,7 +273,9 @@ std::from_chars is locale-independent, correctly rounded, and - via the
|
|||||||
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
||||||
over the whole value range (not just the Clinger subset). It is used only when
|
over the whole value range (not just the Clinger subset). It is used only when
|
||||||
__cpp_lib_to_chars indicates full floating-point support and only when it
|
__cpp_lib_to_chars indicates full floating-point support and only when it
|
||||||
consumes the entire token ([first, last)). An under-/overflow (result_out_of_range) also declines, so
|
consumes the entire token ([first, last)); a partial parse means the buffer
|
||||||
|
uses a non-'.' locale decimal point, in which case the caller falls back to the
|
||||||
|
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
|
||||||
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
||||||
expects (side-stepping the P4168 divergence between implementations).
|
expects (side-stepping the P4168 divergence between implementations).
|
||||||
|
|
||||||
|
|||||||
@@ -916,10 +916,6 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef JSON_USE_GLOBAL_UDLS
|
#ifndef JSON_USE_GLOBAL_UDLS
|
||||||
#define JSON_USE_GLOBAL_UDLS 1
|
#define JSON_USE_GLOBAL_UDLS 1
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
#undef JSON_INLINE_VARIABLE
|
#undef JSON_INLINE_VARIABLE
|
||||||
#undef JSON_NO_UNIQUE_ADDRESS
|
#undef JSON_NO_UNIQUE_ADDRESS
|
||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
#undef JSON_USE_GLOBAL_UDLS
|
#undef JSON_USE_GLOBAL_UDLS
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
|
|||||||
@@ -636,18 +636,6 @@ template<typename BasicJsonType, typename CompatibleType>
|
|||||||
struct is_compatible_type
|
struct is_compatible_type
|
||||||
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
||||||
|
|
||||||
// a one-element std::tuple holding a reference to BasicJsonType, as created by
|
|
||||||
// std::forward_as_tuple(j); see JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
template<typename BasicJsonType, typename T>
|
|
||||||
struct is_basic_json_reference_tuple : std::false_type {};
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename T>
|
|
||||||
struct is_basic_json_reference_tuple<BasicJsonType, std::tuple<T>>
|
|
||||||
{
|
|
||||||
static constexpr bool value =
|
|
||||||
std::is_reference<T>::value && std::is_same<uncvref_t<T>, BasicJsonType>::value;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename CompatibleArrayType>
|
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||||
struct is_compatible_binary_type
|
struct is_compatible_binary_type
|
||||||
{
|
{
|
||||||
|
|||||||
+406
-173
@@ -1595,12 +1595,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
template < typename CompatibleType,
|
template < typename CompatibleType,
|
||||||
typename U = detail::uncvref_t<CompatibleType>,
|
typename U = detail::uncvref_t<CompatibleType>,
|
||||||
detail::enable_if_t <
|
detail::enable_if_t <
|
||||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value
|
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
|
||||||
#if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
// see https://github.com/nlohmann/json/issues/2226
|
|
||||||
&& !detail::is_basic_json_reference_tuple<basic_json_t, U>::value
|
|
||||||
#endif
|
|
||||||
, int > = 0 >
|
|
||||||
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
||||||
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
||||||
std::forward<CompatibleType>(val))))
|
std::forward<CompatibleType>(val))))
|
||||||
@@ -6066,21 +6061,240 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
{
|
{
|
||||||
// the patch
|
// the patch
|
||||||
basic_json result(value_t::array);
|
basic_json result(value_t::array);
|
||||||
|
diff_recursively(result, source, target, path, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// if the values are the same, return an empty patch
|
private:
|
||||||
|
/// @brief two arrays or two objects @ref diff_iteratively is diffing
|
||||||
|
struct diff_frame
|
||||||
|
{
|
||||||
|
diff_frame(const basic_json* source_, const basic_json* target_, const std::size_t path_length_) noexcept
|
||||||
|
: source(source_), target(target_), path_length(path_length_)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// declared for GCC's -Weffc++, which asks for them in a class with
|
||||||
|
// pointer members and a non-trivial destructor; the exception
|
||||||
|
// specifications are left implicit, as GCC 4.8 rejects explicit ones
|
||||||
|
// that differ from them
|
||||||
|
diff_frame(const diff_frame&) = default;
|
||||||
|
diff_frame(diff_frame&&) = default;
|
||||||
|
diff_frame& operator=(const diff_frame&) = default;
|
||||||
|
diff_frame& operator=(diff_frame&&) = default;
|
||||||
|
~diff_frame() = default;
|
||||||
|
|
||||||
|
/// the values being diffed, both arrays or both objects
|
||||||
|
const basic_json* source;
|
||||||
|
const basic_json* target;
|
||||||
|
/// the length of their path in `current_path`
|
||||||
|
std::size_t path_length;
|
||||||
|
/// arrays: the next index to diff
|
||||||
|
std::size_t index = 0;
|
||||||
|
/// objects: the next member of source to look at
|
||||||
|
const_iterator member{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
/// objects: the keys common to both, in source's order
|
||||||
|
std::vector<typename object_t::key_type> common_keys{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
/// objects: the next entry of common_keys
|
||||||
|
std::size_t next_common = 0;
|
||||||
|
/// objects: the "add" operations for keys only target has
|
||||||
|
basic_json added_ops{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
};
|
||||||
|
|
||||||
|
// The operations of a diff are built by the functions below rather than
|
||||||
|
// where they are needed: building one takes several temporaries, and
|
||||||
|
// unoptimized builds give each temporary a stack slot of its own in the
|
||||||
|
// function it appears in. In diff_recursively, which is on the call stack
|
||||||
|
// once per nesting level, that made every level cost kilobytes of stack.
|
||||||
|
|
||||||
|
/// @brief append a "replace" operation for @a path with @a value to @a result
|
||||||
|
static void diff_replace(basic_json& result, const string_t& path, const basic_json& value)
|
||||||
|
{
|
||||||
|
result.push_back(
|
||||||
|
{
|
||||||
|
{"op", "replace"}, {"path", path}, {"value", value}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief append a "remove" operation for @a path to @a result
|
||||||
|
static void diff_remove(basic_json& result, const string_t& path)
|
||||||
|
{
|
||||||
|
result.push_back(object(
|
||||||
|
{
|
||||||
|
{"op", "remove"}, {"path", path}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief append an "add" operation for @a path with @a value to @a result
|
||||||
|
static void diff_add(basic_json& result, const string_t& path, const basic_json& value)
|
||||||
|
{
|
||||||
|
result.push_back(
|
||||||
|
{
|
||||||
|
{"op", "add"}, {"path", path}, {"value", value}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief append the "remove" operations for the elements of array
|
||||||
|
/// @a source from @a index on, and the "add" operations for the
|
||||||
|
/// elements of array @a target from source's size on, to @a result
|
||||||
|
static void diff_array_tails(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
|
const string_t& path, const std::size_t index)
|
||||||
|
{
|
||||||
|
// remove my remaining elements, highest index first; appending
|
||||||
|
// in that order avoids the quadratic reinsertion done before
|
||||||
|
for (std::size_t j = source.size(); j > index; --j)
|
||||||
|
{
|
||||||
|
diff_remove(result, detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add other remaining elements
|
||||||
|
for (std::size_t i = source.size(); i < target.size(); ++i)
|
||||||
|
{
|
||||||
|
diff_add(result, detail::concat<string_t>(path, "/-"), target[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief compare the keys of objects @a source and @a target
|
||||||
|
|
||||||
|
If the keys both objects have are in the same order in both, and the keys
|
||||||
|
only @a target has come after them, stores the keys common to both in
|
||||||
|
source's order in @a common_keys, stores the "add" operations for the keys
|
||||||
|
only @a target has in @a added_ops, and returns true: the caller then diffs
|
||||||
|
the objects member by member. Otherwise, appends operations that remove
|
||||||
|
every member of @a source and add every member of @a target to @a result,
|
||||||
|
and returns false.
|
||||||
|
*/
|
||||||
|
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
|
const string_t& path, std::vector<typename object_t::key_type>& common_keys,
|
||||||
|
basic_json& added_ops)
|
||||||
|
{
|
||||||
|
// first pass: record, for every source key, whether it is
|
||||||
|
// common to both objects (in source's iteration order) or
|
||||||
|
// was deleted (i.e., in source but not in target) -- this is
|
||||||
|
// a by-product of the target.find() call already needed to
|
||||||
|
// tell the two cases apart, so it adds no extra lookups. The
|
||||||
|
// "remove" ops themselves are emitted later, interleaved
|
||||||
|
// with the per-key diffs in the caller's fast path, to match
|
||||||
|
// source's original iteration order (as the original,
|
||||||
|
// pre-reordering-aware implementation did) instead of
|
||||||
|
// grouping all removes before all per-key diffs.
|
||||||
|
std::vector<typename object_t::key_type> common_keys_source_order;
|
||||||
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
|
{
|
||||||
|
if (target.find(it.key()) != target.end())
|
||||||
|
{
|
||||||
|
common_keys_source_order.push_back(it.key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// second pass: find keys that were added (i.e., in target but
|
||||||
|
// not in source), and record the keys common to both, in
|
||||||
|
// target's iteration order -- again a by-product of the
|
||||||
|
// source.find() call already needed to detect added keys. At
|
||||||
|
// the same time, determine whether every added key comes
|
||||||
|
// after every common key in target's order (a precondition
|
||||||
|
// for the fast path, which only ever appends new keys
|
||||||
|
// at the very end): for an object_t whose iteration order is
|
||||||
|
// a pure function of the key set (e.g. the default std::map,
|
||||||
|
// which always iterates in sorted key order), the order
|
||||||
|
// check further below is always true and this whole
|
||||||
|
// mechanism is effectively a no-op; it only matters for a
|
||||||
|
// reorderable object_t such as the one backing `ordered_json`.
|
||||||
|
// The patch ops for keys that were added (i.e., in target but not
|
||||||
|
// in source) are built here so the fast path can reuse
|
||||||
|
// them without a second source.find() per target key. Only
|
||||||
|
// used by the fast path -- the slow (reordering) path
|
||||||
|
// rebuilds "add" ops for every key itself.
|
||||||
|
std::vector<typename object_t::key_type> common_keys_target_order;
|
||||||
|
bool new_keys_form_suffix = true;
|
||||||
|
bool seen_new_key = false;
|
||||||
|
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||||
|
{
|
||||||
|
if (source.find(it.key()) == source.end())
|
||||||
|
{
|
||||||
|
seen_new_key = true;
|
||||||
|
diff_add(added_ops, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
common_keys_target_order.push_back(it.key());
|
||||||
|
if (seen_new_key)
|
||||||
|
{
|
||||||
|
new_keys_form_suffix = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
|
||||||
|
{
|
||||||
|
// fast path: order of common keys already matches (or the
|
||||||
|
// object_t's iteration order does not depend on
|
||||||
|
// insertion history), so a plain per-key diff is correct
|
||||||
|
// and minimal, as before
|
||||||
|
common_keys = std::move(common_keys_source_order);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slow path: the common keys are in a different relative
|
||||||
|
// order in source and target (only possible for a
|
||||||
|
// reorderable object_t like ordered_map). Building a
|
||||||
|
// minimal reordering patch is a nontrivial (LCS-like)
|
||||||
|
// problem; instead, remove every source key -- both
|
||||||
|
// deleted keys (which must be removed regardless) and
|
||||||
|
// common keys (removed so they can be re-added in
|
||||||
|
// target's order) -- and re-add every key that should
|
||||||
|
// remain, with its final target value, in target's
|
||||||
|
// order. basic_json::patch()'s "add" operation on an
|
||||||
|
// object uses operator[], which appends at the end for a
|
||||||
|
// vector-backed insertion-ordered map when the key does
|
||||||
|
// not already exist -- so removing a key and then adding
|
||||||
|
// it moves it to the end, fixing its position.
|
||||||
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
|
{
|
||||||
|
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add every key that is either common (just removed
|
||||||
|
// above) or brand new, in target's iteration order, so
|
||||||
|
// that the final order after applying the patch matches
|
||||||
|
// target exactly
|
||||||
|
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||||
|
{
|
||||||
|
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief @ref diff, for values at nesting level @a depth, appending the
|
||||||
|
operations to @a result
|
||||||
|
|
||||||
|
Diffing two arrays or objects calls this function again, once per nesting
|
||||||
|
level, so values nested deeply enough used to exhaust the call stack and
|
||||||
|
terminate the process. The descent is bounded here: once @ref
|
||||||
|
detail::recursion_depth_limit levels have been entered, @ref
|
||||||
|
diff_iteratively diffs what is left without the call stack.
|
||||||
|
*/
|
||||||
|
static void diff_recursively(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
|
const string_t& path, const std::size_t depth)
|
||||||
|
{
|
||||||
|
// if the values are the same, there is nothing to do
|
||||||
if (source == target)
|
if (source == target)
|
||||||
{
|
{
|
||||||
return result;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||||
|
{
|
||||||
|
diff_iteratively(result, source, target, path);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source.type() != target.type())
|
if (source.type() != target.type())
|
||||||
{
|
{
|
||||||
// different types: replace value
|
// different types: replace value
|
||||||
result.push_back(
|
diff_replace(result, path, target);
|
||||||
{
|
return;
|
||||||
{"op", "replace"}, {"path", path}, {"value", target}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (source.type())
|
switch (source.type())
|
||||||
@@ -6092,185 +6306,50 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
while (i < source.size() && i < target.size())
|
while (i < source.size() && i < target.size())
|
||||||
{
|
{
|
||||||
// recursive call to compare array values at index i
|
// recursive call to compare array values at index i
|
||||||
auto temp_diff = diff(source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)));
|
diff_recursively(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
|
||||||
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We now reached the end of at least one array
|
// We now reached the end of at least one array
|
||||||
// in a second pass, traverse the remaining elements
|
// in a second pass, traverse the remaining elements
|
||||||
|
diff_array_tails(result, source, target, path, i);
|
||||||
// remove my remaining elements, highest index first; appending
|
|
||||||
// in that order avoids the quadratic reinsertion done before
|
|
||||||
for (std::size_t j = source.size(); j > i; --j)
|
|
||||||
{
|
|
||||||
result.push_back(object(
|
|
||||||
{
|
|
||||||
{"op", "remove"},
|
|
||||||
{"path", detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1))}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
i = source.size();
|
|
||||||
|
|
||||||
// add other remaining elements
|
|
||||||
while (i < target.size())
|
|
||||||
{
|
|
||||||
result.push_back(
|
|
||||||
{
|
|
||||||
{"op", "add"},
|
|
||||||
{"path", detail::concat<string_t>(path, "/-")},
|
|
||||||
{"value", target[i]}
|
|
||||||
});
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
// first pass: record, for every source key, whether it is
|
std::vector<typename object_t::key_type> common_keys;
|
||||||
// common to both objects (in source's iteration order) or
|
|
||||||
// was deleted (i.e., in source but not in target) -- this is
|
|
||||||
// a by-product of the target.find() call already needed to
|
|
||||||
// tell the two cases apart, so it adds no extra lookups. The
|
|
||||||
// "remove" ops themselves are emitted later, interleaved
|
|
||||||
// with the recursive per-key diffs in the fast path below,
|
|
||||||
// to match source's original iteration order (as the
|
|
||||||
// original, pre-reordering-aware implementation did) instead
|
|
||||||
// of grouping all removes before all recursive diffs.
|
|
||||||
std::vector<typename object_t::key_type> common_keys_source_order;
|
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
|
||||||
{
|
|
||||||
if (target.find(it.key()) != target.end())
|
|
||||||
{
|
|
||||||
common_keys_source_order.push_back(it.key());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// second pass: find keys that were added (i.e., in target but
|
|
||||||
// not in source), and record the keys common to both, in
|
|
||||||
// target's iteration order -- again a by-product of the
|
|
||||||
// source.find() call already needed to detect added keys. At
|
|
||||||
// the same time, determine whether every added key comes
|
|
||||||
// after every common key in target's order (a precondition
|
|
||||||
// for the fast path below, which only ever appends new keys
|
|
||||||
// at the very end): for an object_t whose iteration order is
|
|
||||||
// a pure function of the key set (e.g. the default std::map,
|
|
||||||
// which always iterates in sorted key order), the order
|
|
||||||
// check further below is always true and this whole
|
|
||||||
// mechanism is effectively a no-op; it only matters for a
|
|
||||||
// reorderable object_t such as the one backing `ordered_json`.
|
|
||||||
// patch ops for keys that were added (i.e., in target but not
|
|
||||||
// in source); built here so the fast path below can reuse
|
|
||||||
// them without a second source.find() per target key. Only
|
|
||||||
// used by the fast path -- the slow (reordering) path
|
|
||||||
// rebuilds "add" ops for every key itself.
|
|
||||||
std::vector<typename object_t::key_type> common_keys_target_order;
|
|
||||||
basic_json added_ops(value_t::array);
|
basic_json added_ops(value_t::array);
|
||||||
bool new_keys_form_suffix = true;
|
if (diff_object_keys(result, source, target, path, common_keys, added_ops))
|
||||||
bool seen_new_key = false;
|
|
||||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
|
||||||
{
|
{
|
||||||
if (source.find(it.key()) == source.end())
|
// fast path: common_keys is, by construction, the
|
||||||
{
|
// subsequence of source's keys that are common to both
|
||||||
seen_new_key = true;
|
// objects, in source's iteration order -- so it can be
|
||||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
// walked in lockstep with `source` using a cheap key
|
||||||
added_ops.push_back(
|
// comparison instead of another lookup. Deleted keys
|
||||||
{
|
// (those source keys not in common_keys) are interleaved
|
||||||
{"op", "add"}, {"path", path_key},
|
// here too, in source's original order, to match the
|
||||||
{"value", it.value()}
|
// historical (pre-reordering-aware) output order.
|
||||||
});
|
auto common_it = common_keys.cbegin();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
common_keys_target_order.push_back(it.key());
|
|
||||||
if (seen_new_key)
|
|
||||||
{
|
|
||||||
new_keys_form_suffix = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
|
|
||||||
{
|
|
||||||
// fast path: order of common keys already matches (or the
|
|
||||||
// object_t's iteration order does not depend on
|
|
||||||
// insertion history), so a plain per-key recursive diff
|
|
||||||
// is correct and minimal, as before. common_keys_source_order
|
|
||||||
// is, by construction, the subsequence of source's keys
|
|
||||||
// that are common to both objects, in source's iteration
|
|
||||||
// order -- so it can be walked in lockstep with `source`
|
|
||||||
// using a cheap key comparison instead of another lookup.
|
|
||||||
// Deleted keys (those source keys not in common_keys_source_order)
|
|
||||||
// are interleaved here too, in source's original order, to
|
|
||||||
// match the historical (pre-reordering-aware) output order.
|
|
||||||
auto common_it = common_keys_source_order.cbegin();
|
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
{
|
{
|
||||||
if (common_it != common_keys_source_order.cend() && it.key() == *common_it)
|
if (common_it != common_keys.cend() && it.key() == *common_it)
|
||||||
{
|
{
|
||||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
diff_recursively(result, it.value(), target[it.key()], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
|
||||||
auto temp_diff = diff(it.value(), target[it.key()], path_key);
|
|
||||||
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
|
||||||
++common_it;
|
++common_it;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// found a key that is not in target -> remove it
|
// found a key that is not in target -> remove it
|
||||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
|
||||||
result.push_back(object(
|
|
||||||
{
|
|
||||||
{"op", "remove"}, {"path", path_key}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the "add" ops for brand-new keys collected above
|
// append the "add" ops for brand-new keys collected by
|
||||||
// during the pass over target -- no second source.find()
|
// diff_object_keys -- no second source.find() per target
|
||||||
// per target key needed
|
// key needed
|
||||||
result.insert(result.end(), added_ops.begin(), added_ops.end());
|
result.insert(result.end(), added_ops.begin(), added_ops.end());
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// slow path: the common keys are in a different relative
|
|
||||||
// order in source and target (only possible for a
|
|
||||||
// reorderable object_t like ordered_map). Building a
|
|
||||||
// minimal reordering patch is a nontrivial (LCS-like)
|
|
||||||
// problem; instead, remove every source key -- both
|
|
||||||
// deleted keys (which must be removed regardless) and
|
|
||||||
// common keys (removed so they can be re-added in
|
|
||||||
// target's order) -- and re-add every key that should
|
|
||||||
// remain, with its final target value, in target's
|
|
||||||
// order. basic_json::patch()'s "add" operation on an
|
|
||||||
// object uses operator[], which appends at the end for a
|
|
||||||
// vector-backed insertion-ordered map when the key does
|
|
||||||
// not already exist -- so removing a key and then adding
|
|
||||||
// it moves it to the end, fixing its position.
|
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
|
||||||
{
|
|
||||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
|
||||||
result.push_back(object(
|
|
||||||
{
|
|
||||||
{"op", "remove"}, {"path", path_key}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// add every key that is either common (just removed
|
|
||||||
// above) or brand new, in target's iteration order, so
|
|
||||||
// that the final order after applying the patch matches
|
|
||||||
// target exactly
|
|
||||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
|
||||||
{
|
|
||||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
|
||||||
result.push_back(
|
|
||||||
{
|
|
||||||
{"op", "add"}, {"path", path_key},
|
|
||||||
{"value", it.value()}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6285,16 +6364,170 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// both primitive types: replace value
|
// both primitive types: replace value
|
||||||
result.push_back(
|
diff_replace(result, path, target);
|
||||||
{
|
|
||||||
{"op", "replace"}, {"path", path}, {"value", target}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief @ref diff without the call stack, appending the operations to
|
||||||
|
@a result
|
||||||
|
|
||||||
|
Produces the same operations as @ref diff_recursively. Only reached for
|
||||||
|
values nested more deeply than @ref detail::recursion_depth_limit.
|
||||||
|
*/
|
||||||
|
static void diff_iteratively(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
|
const string_t& path)
|
||||||
|
{
|
||||||
|
// The arrays and objects being diffed are kept on an explicit stack,
|
||||||
|
// and every pair of elements is still diffed completely before the
|
||||||
|
// next one, so the operations come out in the same order as in
|
||||||
|
// diff_recursively. The path of the values being diffed is kept in
|
||||||
|
// one buffer that grows and shrinks with the stack, rather than in a
|
||||||
|
// new string per level.
|
||||||
|
std::vector<diff_frame> stack;
|
||||||
|
string_t current_path = path;
|
||||||
|
|
||||||
|
// diff `s` against `t`, whose path is current_path: primitives,
|
||||||
|
// values of different types, and objects whose members were reordered
|
||||||
|
// are handled right away; arrays and other objects get a frame
|
||||||
|
const auto enter = [&result, &stack, ¤t_path](const basic_json & s, const basic_json & t)
|
||||||
|
{
|
||||||
|
// if the values are the same, there is nothing to do. Arrays and
|
||||||
|
// objects are not compared up front: comparing them visits
|
||||||
|
// everything below them, so doing that at every level would take
|
||||||
|
// quadratic time in the nesting depth - equal ones yield no
|
||||||
|
// operations anyway.
|
||||||
|
if ((!s.is_structured() || !t.is_structured()) && s == t)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.type() != t.type())
|
||||||
|
{
|
||||||
|
// different types: replace value
|
||||||
|
diff_replace(result, current_path, t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (s.type())
|
||||||
|
{
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
stack.emplace_back(&s, &t, current_path.size());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::object:
|
||||||
|
{
|
||||||
|
std::vector<typename object_t::key_type> common_keys;
|
||||||
|
basic_json added_ops(value_t::array);
|
||||||
|
if (diff_object_keys(result, s, t, current_path, common_keys, added_ops))
|
||||||
|
{
|
||||||
|
// fast path: the frame walks source in lockstep with
|
||||||
|
// common_keys, as diff_recursively does, and appends
|
||||||
|
// added_ops once all members are done
|
||||||
|
stack.emplace_back(&s, &t, current_path.size());
|
||||||
|
stack.back().member = s.cbegin();
|
||||||
|
stack.back().common_keys = std::move(common_keys);
|
||||||
|
stack.back().added_ops = std::move(added_ops);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::null:
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::boolean:
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
case value_t::number_float:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::discarded:
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// both primitive types: replace value
|
||||||
|
diff_replace(result, current_path, t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enter(source, target);
|
||||||
|
while (!stack.empty())
|
||||||
|
{
|
||||||
|
// the frame is copied out member by member and changed through
|
||||||
|
// stack.back(): enter() may push a frame and the end of the loop
|
||||||
|
// pops it, either of which would invalidate a reference to it
|
||||||
|
const basic_json* const s = stack.back().source;
|
||||||
|
const basic_json* const t = stack.back().target;
|
||||||
|
const std::size_t path_length = stack.back().path_length;
|
||||||
|
const std::size_t depth = stack.size();
|
||||||
|
|
||||||
|
if (s->is_array())
|
||||||
|
{
|
||||||
|
const auto& source_array = *s->m_data.m_value.array;
|
||||||
|
const auto& target_array = *t->m_data.m_value.array;
|
||||||
|
|
||||||
|
// first pass: traverse common elements
|
||||||
|
const std::size_t i = stack.back().index;
|
||||||
|
if (i < source_array.size() && i < target_array.size())
|
||||||
|
{
|
||||||
|
++stack.back().index;
|
||||||
|
detail::concat_into(current_path, '/', detail::to_string<string_t>(i));
|
||||||
|
enter(source_array[i], target_array[i]);
|
||||||
|
if (stack.size() == depth)
|
||||||
|
{
|
||||||
|
current_path.resize(path_length);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now reached the end of at least one array
|
||||||
|
// in a second pass, traverse the remaining elements
|
||||||
|
diff_array_tails(result, *s, *t, current_path, i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const const_iterator it = stack.back().member;
|
||||||
|
if (it != s->cend())
|
||||||
|
{
|
||||||
|
++stack.back().member;
|
||||||
|
const std::size_t next_common = stack.back().next_common;
|
||||||
|
if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common])
|
||||||
|
{
|
||||||
|
++stack.back().next_common;
|
||||||
|
const basic_json& target_value = (*t)[it.key()];
|
||||||
|
detail::concat_into(current_path, '/', detail::escape(it.key()));
|
||||||
|
enter(it.value(), target_value);
|
||||||
|
if (stack.size() == depth)
|
||||||
|
{
|
||||||
|
current_path.resize(path_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// found a key that is not in target -> remove it
|
||||||
|
diff_remove(result, detail::concat<string_t>(current_path, '/', detail::escape(it.key())));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the "add" ops for brand-new keys collected when the
|
||||||
|
// object was entered
|
||||||
|
result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// this array or object is done: continue with the one it is in
|
||||||
|
stack.pop_back();
|
||||||
|
if (!stack.empty())
|
||||||
|
{
|
||||||
|
current_path.resize(stack.back().path_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
|
|||||||
+466
-449
File diff suppressed because it is too large
Load Diff
+2
-43
@@ -1830,51 +1830,10 @@ TEST_CASE("CBOR")
|
|||||||
SECTION("invalid string in map")
|
SECTION("invalid string in map")
|
||||||
{
|
{
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("non-string key (see #2766 and #3381)")
|
|
||||||
{
|
|
||||||
// only text strings map to JSON object keys; any other key is
|
|
||||||
// rejected with a message naming its type
|
|
||||||
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
|
|
||||||
{
|
|
||||||
{{0xA1, 0x01, 0x01}, "an unsigned integer; last byte: 0x01"},
|
|
||||||
{{0xA1, 0x20, 0x01}, "a negative integer; last byte: 0x20"},
|
|
||||||
{{0xA1, 0x41, 0x61, 0x01}, "a byte string; last byte: 0x41"},
|
|
||||||
{{0xA1, 0x80, 0x01}, "an array; last byte: 0x80"},
|
|
||||||
{{0xA1, 0xA0, 0x01}, "a map; last byte: 0xA0"},
|
|
||||||
{{0xA1, 0xC0, 0x61, 0x61, 0x01}, "a tag; last byte: 0xC0"},
|
|
||||||
{{0xA1, 0xF4, 0x01}, "a boolean; last byte: 0xF4"},
|
|
||||||
{{0xA1, 0xF5, 0x01}, "a boolean; last byte: 0xF5"},
|
|
||||||
{{0xA1, 0xF6, 0x01}, "null; last byte: 0xF6"},
|
|
||||||
{{0xA1, 0xF7, 0x01}, "undefined; last byte: 0xF7"},
|
|
||||||
{{0xA1, 0xF9, 0x3C, 0x00, 0x01}, "a floating-point number; last byte: 0xF9"},
|
|
||||||
{{0xA1, 0xFA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFA"},
|
|
||||||
{{0xA1, 0xFB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFB"},
|
|
||||||
{{0xA1, 0xE0, 0x01}, "a simple value; last byte: 0xE0"},
|
|
||||||
{{0xA1, 0xF8, 0x20, 0x01}, "a simple value; last byte: 0xF8"},
|
|
||||||
// indefinite-length map
|
|
||||||
{{0xBF, 0x01, 0x01, 0xFF}, "an unsigned integer; last byte: 0x01"},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& c : cases)
|
|
||||||
{
|
|
||||||
CAPTURE(c.first)
|
|
||||||
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found " + c.second;
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(c.first), expected.c_str(), json::parse_error&);
|
|
||||||
CHECK(json::from_cbor(c.first, true, false).is_discarded());
|
|
||||||
}
|
|
||||||
|
|
||||||
// a key of major type 3 with a reserved length is still reported as
|
|
||||||
// a malformed string, and a missing key as the end of input
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("invalid UTF-8 in string (see #5529)")
|
SECTION("invalid UTF-8 in string (see #5529)")
|
||||||
{
|
{
|
||||||
// a two-character text string (major type 3) whose bytes are not
|
// a two-character text string (major type 3) whose bytes are not
|
||||||
@@ -2325,7 +2284,7 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
|||||||
SECTION("a break marker outside an indefinite-length string is not a string")
|
SECTION("a break marker outside an indefinite-length string is not a string")
|
||||||
{
|
{
|
||||||
// 0xFF only closes a string that was opened; on its own it is not one
|
// 0xFF only closes a string that was opened; on its own it is not one
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -666,7 +666,7 @@ TEST_CASE("parse_float_fast declines what it cannot convert exactly")
|
|||||||
// always safe: the caller then falls back to a slower, exact conversion.
|
// always safe: the caller then falls back to a slower, exact conversion.
|
||||||
const auto fast = [](const std::string & s, double & out)
|
const auto fast = [](const std::string & s, double & out)
|
||||||
{
|
{
|
||||||
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), out);
|
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out);
|
||||||
};
|
};
|
||||||
double out = 0;
|
double out = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
// This file tests the opt-in JSON_DISABLE_TUPLE_REFERENCE_CONVERSION, so it
|
|
||||||
// defines the macro itself rather than relying on a -D flag, and runs in every
|
|
||||||
// build.
|
|
||||||
#ifdef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
using nlohmann::ordered_json;
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <tuple>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
|
||||||
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
|
||||||
// the tuple constructors make them instantiate basic_json's conversion operator
|
|
||||||
// for libstdc++'s internal tuple bases, which fails hard
|
|
||||||
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
|
||||||
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("JSON_DISABLE_TUPLE_REFERENCE_CONVERSION")
|
|
||||||
{
|
|
||||||
SECTION("json is not constructible from a one-element tuple of a json reference")
|
|
||||||
{
|
|
||||||
CHECK_FALSE(std::is_constructible<json, std::tuple<json&>>::value);
|
|
||||||
CHECK_FALSE(std::is_constructible<json, std::tuple<const json&>>::value);
|
|
||||||
CHECK_FALSE(std::is_constructible < json, std::tuple < json && >>::value);
|
|
||||||
CHECK_FALSE(std::is_constructible<json, const std::tuple<json&>&>::value);
|
|
||||||
CHECK_FALSE(std::is_constructible<ordered_json, std::tuple<ordered_json&>>::value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
||||||
SECTION("issue #2226 - tuple<const json&> from tuple<json&> keeps the reference")
|
|
||||||
{
|
|
||||||
json j = true;
|
|
||||||
const std::tuple<const json&> tup(std::forward_as_tuple(j));
|
|
||||||
CHECK(&std::get<0>(tup) == &j);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("tuple<json> from tuple<json&> copies the element")
|
|
||||||
{
|
|
||||||
const json j = {{"key", "value"}};
|
|
||||||
const std::tuple<json> t1(std::forward_as_tuple(j));
|
|
||||||
CHECK(std::get<0>(t1) == j);
|
|
||||||
|
|
||||||
json j2 = "text";
|
|
||||||
const std::tuple<json> t2(std::forward_as_tuple(std::move(j2)));
|
|
||||||
CHECK(std::get<0>(t2) == "text");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("other tuple conversions are not affected")
|
|
||||||
{
|
|
||||||
const json j = true;
|
|
||||||
|
|
||||||
// one-element tuple holding a json value
|
|
||||||
CHECK(json(std::make_tuple(j)) == json::array({true}));
|
|
||||||
|
|
||||||
// tuples with more than one element, even when holding references
|
|
||||||
int i = 1;
|
|
||||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
||||||
CHECK(json(std::forward_as_tuple(i, j)) == json::array({1, true}));
|
|
||||||
CHECK(json(std::forward_as_tuple(j, j)) == json::array({true, true}));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// one-element tuples holding references to other types
|
|
||||||
std::string s = "text";
|
|
||||||
CHECK(json(std::forward_as_tuple(s)) == json::array({"text"}));
|
|
||||||
CHECK(json(std::forward_as_tuple(i)) == json::array({1}));
|
|
||||||
|
|
||||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
||||||
// a reference to a different basic_json specialization
|
|
||||||
ordered_json oj = true;
|
|
||||||
CHECK(json(std::forward_as_tuple(oj)) == json::array({true}));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,8 +15,65 @@ using nlohmann::json;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// alternating objects and arrays nested `depth` levels deep, with members that
|
||||||
|
// depend on `variant` at some levels, so diffing two variants yields
|
||||||
|
// operations on many levels: replacing the innermost value, adding, removing,
|
||||||
|
// and (for ordered_json) reordering members, and changing array lengths
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
BasicJsonType nested(const std::size_t depth, const int variant)
|
||||||
|
{
|
||||||
|
BasicJsonType value = variant;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
if (i % 2 == 0)
|
||||||
|
{
|
||||||
|
BasicJsonType object = BasicJsonType::object();
|
||||||
|
if ((i + static_cast<std::size_t>(variant)) % 7 == 0)
|
||||||
|
{
|
||||||
|
object["x"] = i;
|
||||||
|
}
|
||||||
|
if (variant == 2 && i % 11 == 0)
|
||||||
|
{
|
||||||
|
object["z"] = "z";
|
||||||
|
}
|
||||||
|
object["a"] = std::move(value);
|
||||||
|
if (variant == 1 && i % 5 == 0)
|
||||||
|
{
|
||||||
|
object["y"] = 1;
|
||||||
|
}
|
||||||
|
value = std::move(object);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BasicJsonType array = BasicJsonType::array({std::move(value)});
|
||||||
|
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
|
||||||
|
{
|
||||||
|
array.push_back(i);
|
||||||
|
}
|
||||||
|
value = std::move(array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a path of `depth` reference tokens, as nested() nests its values
|
||||||
|
std::string nested_path(const std::size_t depth)
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
for (std::size_t i = depth; i > 0; --i)
|
||||||
|
{
|
||||||
|
path += (i - 1) % 2 == 0 ? "/a" : "/0";
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("JSON patch")
|
TEST_CASE("JSON patch")
|
||||||
{
|
{
|
||||||
SECTION("examples from RFC 6902")
|
SECTION("examples from RFC 6902")
|
||||||
@@ -1752,6 +1809,102 @@ TEST_CASE("JSON patch - diff emits array removals in descending index order")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON patch: diff of deeply nested values")
|
||||||
|
{
|
||||||
|
SECTION("the diff reproduces the target at every depth")
|
||||||
|
{
|
||||||
|
// depths on either side of the nesting depth up to which diff()
|
||||||
|
// recurses (detail::recursion_depth_limit(), 128); not every depth up
|
||||||
|
// to 300, as the test would then time out under Valgrind
|
||||||
|
std::vector<std::size_t> depths;
|
||||||
|
for (std::size_t depth = 0; depth <= 16; ++depth)
|
||||||
|
{
|
||||||
|
depths.push_back(depth);
|
||||||
|
}
|
||||||
|
for (std::size_t depth = 120; depth <= 136; ++depth)
|
||||||
|
{
|
||||||
|
depths.push_back(depth);
|
||||||
|
}
|
||||||
|
depths.push_back(300);
|
||||||
|
|
||||||
|
for (const auto depth : depths)
|
||||||
|
{
|
||||||
|
CAPTURE(depth);
|
||||||
|
for (int from = 0; from < 3; ++from)
|
||||||
|
{
|
||||||
|
for (int to = 0; to < 3; ++to)
|
||||||
|
{
|
||||||
|
CAPTURE(from);
|
||||||
|
CAPTURE(to);
|
||||||
|
const auto source = nested<json>(depth, from);
|
||||||
|
const auto target = nested<json>(depth, to);
|
||||||
|
const auto patch = json::diff(source, target);
|
||||||
|
CHECK(source.patch(patch) == target);
|
||||||
|
CHECK(patch.empty() == (from == to));
|
||||||
|
|
||||||
|
const auto ordered_source = nested<nlohmann::ordered_json>(depth, from);
|
||||||
|
const auto ordered_target = nested<nlohmann::ordered_json>(depth, to);
|
||||||
|
CHECK(ordered_source.patch(nlohmann::ordered_json::diff(ordered_source, ordered_target)) == ordered_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a difference only in the innermost value is one replace operation")
|
||||||
|
{
|
||||||
|
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||||
|
{
|
||||||
|
CAPTURE(depth);
|
||||||
|
json source = 1;
|
||||||
|
json target = 2;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
source = i % 2 == 0 ? json::object({{"a", std::move(source)}}) : json::array({std::move(source)});
|
||||||
|
target = i % 2 == 0 ? json::object({{"a", std::move(target)}}) : json::array({std::move(target)});
|
||||||
|
}
|
||||||
|
CHECK(json::diff(source, target, "/root") == json::array({{{"op", "replace"}, {"path", "/root" + nested_path(depth)}, {"value", 2}}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("values nested too deeply for the call stack (#5393)")
|
||||||
|
{
|
||||||
|
// diff() used to recurse once per nesting level, and compared the
|
||||||
|
// values with operator== on every level. The values are only
|
||||||
|
// parsed and diffed, never copied or compared, since those recurse
|
||||||
|
// too.
|
||||||
|
const std::size_t depth = 100000;
|
||||||
|
for (const bool objects :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(objects);
|
||||||
|
std::string source_text;
|
||||||
|
std::string target_text;
|
||||||
|
std::string equal_text;
|
||||||
|
std::string path;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
source_text += objects ? "{\"a\":" : "[";
|
||||||
|
path += objects ? "/a" : "/0";
|
||||||
|
}
|
||||||
|
target_text = source_text + "2";
|
||||||
|
equal_text = source_text + "1";
|
||||||
|
source_text += "1";
|
||||||
|
const std::string closing(depth, objects ? '}' : ']');
|
||||||
|
const auto source = json::parse(source_text + closing);
|
||||||
|
|
||||||
|
const auto patch = json::diff(source, json::parse(target_text + closing));
|
||||||
|
REQUIRE(patch.size() == 1);
|
||||||
|
CHECK(patch[0]["op"] == "replace");
|
||||||
|
CHECK(patch[0]["path"] == path);
|
||||||
|
CHECK(patch[0]["value"] == 2);
|
||||||
|
|
||||||
|
CHECK(json::diff(source, json::parse(equal_text + closing)).empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("JSON patch - every operation on ordered_json")
|
TEST_CASE("JSON patch - every operation on ordered_json")
|
||||||
{
|
{
|
||||||
using nlohmann::ordered_json;
|
using nlohmann::ordered_json;
|
||||||
|
|||||||
@@ -12,12 +12,7 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <clocale>
|
#include <clocale>
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct ParserImpl final: public nlohmann::json_sax<json>
|
struct ParserImpl final: public nlohmann::json_sax<json>
|
||||||
{
|
{
|
||||||
@@ -180,208 +175,3 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
|
|||||||
MESSAGE("locale de_DE is not usable");
|
MESSAGE("locale de_DE is not usable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// records the numbers of a flat array and switches LC_NUMERIC to the given
|
|
||||||
// locale once the array opens - after the lexer was constructed, but before
|
|
||||||
// any number in the array is lexed
|
|
||||||
struct LocaleSwitchingSax final: public nlohmann::json_sax<json>
|
|
||||||
{
|
|
||||||
explicit LocaleSwitchingSax(const char* switch_to)
|
|
||||||
: locale_after_open(switch_to)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool null() override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool boolean(bool /*val*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool number_integer(json::number_integer_t /*val*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool number_unsigned(json::number_unsigned_t /*val*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool number_float(json::number_float_t val, const json::string_t& s) override
|
|
||||||
{
|
|
||||||
values.push_back(val);
|
|
||||||
strings.push_back(s);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool string(json::string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool binary(json::binary_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool start_object(std::size_t /*val*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool key(json::string_t& /*val*/) override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool end_object() override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool start_array(std::size_t /*val*/) override
|
|
||||||
{
|
|
||||||
switched = std::setlocale(LC_NUMERIC, locale_after_open.c_str()) != nullptr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool end_array() override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool parse_error(std::size_t /*val*/, const std::string& /*val*/, const nlohmann::detail::exception& /*val*/) override
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string locale_after_open;
|
|
||||||
bool switched = false;
|
|
||||||
std::vector<json::number_float_t> values {}; // NOLINT(readability-redundant-member-init)
|
|
||||||
std::vector<json::string_t> strings {}; // NOLINT(readability-redundant-member-init)
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("locale changes between lexer construction and number conversion (#5198)")
|
|
||||||
{
|
|
||||||
// The numbers are chosen so that the conversion also takes the strtod
|
|
||||||
// fallback, which honors the locale that is current at conversion time:
|
|
||||||
// too many significant digits for Clinger's fast path, an underflow that
|
|
||||||
// std::from_chars rejects, and a plain value.
|
|
||||||
const std::vector<std::string> numbers = {"3.14159265358979323846", "1.5e-400", "12.34", "-0.000123456789012345678"};
|
|
||||||
std::string text = "[";
|
|
||||||
for (const auto& n : numbers)
|
|
||||||
{
|
|
||||||
text += (text.size() == 1 ? "" : ",") + n;
|
|
||||||
}
|
|
||||||
text += "]";
|
|
||||||
|
|
||||||
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
|
||||||
|
|
||||||
// reference values, parsed without a locale switch
|
|
||||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
|
||||||
const json expected = json::parse(text);
|
|
||||||
const long_double_json expected_ld = long_double_json::parse(text);
|
|
||||||
|
|
||||||
const std::array<std::pair<const char*, const char*>, 2> transitions =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
{"C", "de_DE"},
|
|
||||||
{"de_DE", "C"}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& transition : transitions)
|
|
||||||
{
|
|
||||||
CAPTURE(transition.first);
|
|
||||||
CAPTURE(transition.second);
|
|
||||||
|
|
||||||
if (std::setlocale(LC_NUMERIC, transition.first) == nullptr)
|
|
||||||
{
|
|
||||||
MESSAGE("locale is not usable");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAX parsing
|
|
||||||
{
|
|
||||||
LocaleSwitchingSax sax(transition.second);
|
|
||||||
CHECK(json::sax_parse(text, &sax));
|
|
||||||
if (sax.switched)
|
|
||||||
{
|
|
||||||
CHECK(sax.values == expected.get<std::vector<json::number_float_t>>());
|
|
||||||
CHECK(sax.strings == numbers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DOM parsing with a callback
|
|
||||||
{
|
|
||||||
bool switched = false;
|
|
||||||
const auto cb = [&](int /*depth*/, json::parse_event_t event, json& /*parsed*/) noexcept
|
|
||||||
{
|
|
||||||
if (event == json::parse_event_t::array_start)
|
|
||||||
{
|
|
||||||
switched = std::setlocale(LC_NUMERIC, transition.second) != nullptr;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
const json j = json::parse(text, cb);
|
|
||||||
if (switched)
|
|
||||||
{
|
|
||||||
CHECK(j == expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a long double goes through std::strtold unless std::from_chars supports it
|
|
||||||
{
|
|
||||||
bool switched = false;
|
|
||||||
const auto cb = [&](int /*depth*/, long_double_json::parse_event_t event, long_double_json& /*parsed*/) noexcept
|
|
||||||
{
|
|
||||||
if (event == long_double_json::parse_event_t::array_start)
|
|
||||||
{
|
|
||||||
switched = std::setlocale(LC_NUMERIC, transition.second) != nullptr;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
const long_double_json j = long_double_json::parse(text, cb);
|
|
||||||
if (switched)
|
|
||||||
{
|
|
||||||
CHECK(j == expected_ld);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("locale with a multi-byte decimal point")
|
|
||||||
{
|
|
||||||
// Some locales use a decimal point that is not a single character, e.g.
|
|
||||||
// U+066B ARABIC DECIMAL SEPARATOR (two bytes in UTF-8). It cannot be
|
|
||||||
// substituted in place for '.', so the strtod fallback stops early. The
|
|
||||||
// conversion must still terminate rather than retry forever.
|
|
||||||
const std::array<const char*, 6> names = {{"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"}};
|
|
||||||
bool tested = false;
|
|
||||||
for (const char* name : names)
|
|
||||||
{
|
|
||||||
if (std::setlocale(LC_NUMERIC, name) == nullptr)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const std::string decimal_point = std::localeconv()->decimal_point;
|
|
||||||
if (decimal_point.size() < 2)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
CAPTURE(name);
|
|
||||||
tested = true;
|
|
||||||
|
|
||||||
// too many significant digits for Clinger's fast path, and an underflow
|
|
||||||
// that std::from_chars rejects: both reach the strtod fallback
|
|
||||||
json j;
|
|
||||||
CHECK_NOTHROW(j = json::parse("[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]"));
|
|
||||||
CHECK(j.is_array());
|
|
||||||
CHECK(json::accept("3.14159265358979323846"));
|
|
||||||
|
|
||||||
// a value the locale-independent paths convert is not affected
|
|
||||||
CHECK(json::parse("12.5") == 12.5);
|
|
||||||
}
|
|
||||||
if (!tested)
|
|
||||||
{
|
|
||||||
MESSAGE("no locale with a multi-byte decimal point is usable");
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1551,69 +1551,10 @@ TEST_CASE("MessagePack")
|
|||||||
SECTION("invalid string in map")
|
SECTION("invalid string in map")
|
||||||
{
|
{
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found an integer; last byte: 0xFF", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF", json::parse_error&);
|
||||||
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("non-string key (see #3381)")
|
|
||||||
{
|
|
||||||
// only strings map to JSON object keys; any other key is rejected
|
|
||||||
// with a message naming its type
|
|
||||||
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
|
|
||||||
{
|
|
||||||
{{0x81, 0xC0, 0x01}, "nil; last byte: 0xC0"},
|
|
||||||
{{0x81, 0xC2, 0x01}, "a boolean; last byte: 0xC2"},
|
|
||||||
{{0x81, 0xC3, 0x01}, "a boolean; last byte: 0xC3"},
|
|
||||||
{{0x81, 0xCA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a float; last byte: 0xCA"},
|
|
||||||
{{0x81, 0xCB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a float; last byte: 0xCB"},
|
|
||||||
{{0x81, 0xC4, 0x00, 0x01}, "a bin; last byte: 0xC4"},
|
|
||||||
{{0x81, 0xC5, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC5"},
|
|
||||||
{{0x81, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC6"},
|
|
||||||
{{0x81, 0xC7, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC7"},
|
|
||||||
{{0x81, 0xC8, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC8"},
|
|
||||||
{{0x81, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC9"},
|
|
||||||
{{0x81, 0xD4, 0x01, 0x00, 0x01}, "an ext; last byte: 0xD4"},
|
|
||||||
{{0x81, 0xD5, 0x01, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD5"},
|
|
||||||
{{0x81, 0xD6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD6"},
|
|
||||||
{{0x81, 0xD7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD7"},
|
|
||||||
{{0x81, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD8"},
|
|
||||||
{{0x81, 0xCC, 0x01, 0x01}, "an integer; last byte: 0xCC"},
|
|
||||||
{{0x81, 0xCD, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCD"},
|
|
||||||
{{0x81, 0xCE, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCE"},
|
|
||||||
{{0x81, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCF"},
|
|
||||||
{{0x81, 0xD0, 0x01, 0x01}, "an integer; last byte: 0xD0"},
|
|
||||||
{{0x81, 0xD1, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD1"},
|
|
||||||
{{0x81, 0xD2, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD2"},
|
|
||||||
{{0x81, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD3"},
|
|
||||||
{{0x81, 0x00, 0x01}, "an integer; last byte: 0x00"},
|
|
||||||
{{0x81, 0x7F, 0x01}, "an integer; last byte: 0x7F"},
|
|
||||||
{{0x81, 0xE0, 0x01}, "an integer; last byte: 0xE0"},
|
|
||||||
{{0x81, 0x80, 0x01}, "a map; last byte: 0x80"},
|
|
||||||
{{0x81, 0x8F, 0x01}, "a map; last byte: 0x8F"},
|
|
||||||
{{0x81, 0xDE, 0x00, 0x00, 0x01}, "a map; last byte: 0xDE"},
|
|
||||||
{{0x81, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x01}, "a map; last byte: 0xDF"},
|
|
||||||
{{0x81, 0x90, 0x01}, "an array; last byte: 0x90"},
|
|
||||||
{{0x81, 0x9F, 0x01}, "an array; last byte: 0x9F"},
|
|
||||||
{{0x81, 0xDC, 0x00, 0x00, 0x01}, "an array; last byte: 0xDC"},
|
|
||||||
{{0x81, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x01}, "an array; last byte: 0xDD"},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& c : cases)
|
|
||||||
{
|
|
||||||
CAPTURE(c.first)
|
|
||||||
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found " + c.second;
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(c.first), expected.c_str(), json::parse_error&);
|
|
||||||
CHECK(json::from_msgpack(c.first, true, false).is_discarded());
|
|
||||||
}
|
|
||||||
|
|
||||||
json _;
|
|
||||||
// the unused byte 0xC1 is still reported as a malformed string
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xC1, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xC1", json::parse_error&);
|
|
||||||
// a missing key is still reported as the end of input
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("invalid UTF-8 in string (see #5529)")
|
SECTION("invalid UTF-8 in string (see #5529)")
|
||||||
{
|
{
|
||||||
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
||||||
|
|||||||
@@ -1018,7 +1018,7 @@ TEST_CASE("regression tests 1")
|
|||||||
};
|
};
|
||||||
|
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an array; last byte: 0x98", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x98", json::parse_error&);
|
||||||
|
|
||||||
// related test case: nonempty UTF-8 string (indefinite length)
|
// related test case: nonempty UTF-8 string (indefinite length)
|
||||||
std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
|
std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
|
||||||
@@ -1065,7 +1065,7 @@ TEST_CASE("regression tests 1")
|
|||||||
};
|
};
|
||||||
|
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
|
||||||
|
|
||||||
// related test case: double-precision
|
// related test case: double-precision
|
||||||
std::vector<uint8_t> const vec2
|
std::vector<uint8_t> const vec2
|
||||||
@@ -1077,7 +1077,7 @@ TEST_CASE("regression tests 1")
|
|||||||
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
|
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
|
||||||
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
|
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
|
||||||
};
|
};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
|
SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
|
||||||
|
|||||||
@@ -18,19 +18,6 @@
|
|||||||
// for some reason including this after the json header leads to linker errors with VS 2017...
|
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
|
||||||
// skip tests if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1 (#2226)
|
|
||||||
#if defined(JSON_DISABLE_TUPLE_REFERENCE_CONVERSION) && (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION == 1)
|
|
||||||
#define SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
|
|
||||||
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
|
|
||||||
// the tuple constructors make them instantiate basic_json's conversion operator
|
|
||||||
// for libstdc++'s internal tuple bases, which fails hard
|
|
||||||
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
|
|
||||||
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define JSON_TESTS_PRIVATE
|
#define JSON_TESTS_PRIVATE
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@@ -41,7 +28,6 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <tuple>
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -556,20 +542,6 @@ TEST_CASE("regression tests 2")
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
|
|
||||||
SECTION("issue #2226 - std::tuple dangling reference - implicit conversion")
|
|
||||||
{
|
|
||||||
// by default, a one-element tuple holding a json reference converts to
|
|
||||||
// a one-element array; JSON_DISABLE_TUPLE_REFERENCE_CONVERSION removes
|
|
||||||
// this conversion (see unit-disable-tuple-reference-conversion.cpp)
|
|
||||||
const json j = true;
|
|
||||||
CHECK(std::is_constructible<json, std::tuple<const json&>>::value);
|
|
||||||
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
|
|
||||||
CHECK(json(std::forward_as_tuple(j)) == json::array({true}));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("PR #2181 - regression bug with lvalue")
|
SECTION("PR #2181 - regression bug with lvalue")
|
||||||
{
|
{
|
||||||
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
||||||
|
|||||||
Reference in New Issue
Block a user