mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 04:00:31 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8366d86068 | ||
|
|
2edeefaa43 |
@@ -100,7 +100,7 @@ jobs:
|
||||
container: ubuntu:focal
|
||||
strategy:
|
||||
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:
|
||||
- name: Install build-essential
|
||||
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_ImplicitConversions "Enable implicit conversions." ON)
|
||||
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_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
||||
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)")
|
||||
endif()
|
||||
|
||||
if (JSON_DisableTupleReferenceConversion)
|
||||
message(STATUS "Tuple reference conversion is disabled (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_LegacyDiscardedValueComparison)
|
||||
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
|
||||
endif()
|
||||
@@ -148,7 +143,6 @@ target_compile_definitions(
|
||||
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
|
||||
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
|
||||
$<$<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_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=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"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# 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.
|
||||
###############################################################################
|
||||
|
||||
@@ -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_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_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_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');
|
||||
|
||||
@@ -159,8 +159,6 @@ basic_json(basic_json&& other) noexcept;
|
||||
- `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 `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&&)`
|
||||
method
|
||||
|
||||
|
||||
@@ -56,8 +56,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
|
||||
is false.
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -91,5 +89,4 @@ Linear in the size of the JSON value `j`.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.11.0.
|
||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
||||
- Throwing `type_error.316` for a string or object key that is not valid UTF-8 added in version 3.13.0.
|
||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
||||
@@ -46,8 +46,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
|
||||
exceeds 255, the maximum of the BSON binary subtype; example:
|
||||
`"subtype 70000 is too large for the BSON binary subtype (max 255)"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
||||
not valid UTF-8
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -84,5 +82,3 @@ pass before anything is written.
|
||||
- Added in version 3.4.0.
|
||||
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
||||
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
||||
- Throwing `type_error.316` for a string value or object key that is not valid UTF-8, detected before anything is
|
||||
written, added in version 3.13.0.
|
||||
|
||||
@@ -35,11 +35,6 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
||||
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the JSON value `j`.
|
||||
@@ -73,4 +68,3 @@ Linear in the size of the JSON value `j`.
|
||||
|
||||
- Added in version 2.0.9.
|
||||
- Compact representation of floating-point numbers added in version 3.8.0.
|
||||
- Throwing `type_error.316` for a string or object key that is not valid UTF-8 added in version 3.13.0.
|
||||
|
||||
@@ -49,8 +49,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
|
||||
is false.
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -84,4 +82,3 @@ Linear in the size of the JSON value `j`.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.1.0.
|
||||
- Throwing `type_error.316` for a string or object key that is not valid UTF-8 added in version 3.13.0.
|
||||
|
||||
@@ -53,7 +53,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_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
|
||||
|
||||
## 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.
|
||||
@@ -63,12 +63,6 @@ The library uses the following mapping from JSON values types to BJData types ac
|
||||
|
||||
- strings with more than 18446744073709551615 bytes, i.e., $2^{64}-1$ bytes (theoretical)
|
||||
|
||||
!!! warning "UTF-8 validation of string values and object keys"
|
||||
|
||||
BJData strings must use UTF-8 encoding. `to_bjdata()` validates the bytes of every string value and object key
|
||||
and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, so a
|
||||
value with such a string cannot be serialized in the first place.
|
||||
|
||||
!!! info "Unused BJData markers"
|
||||
|
||||
The following markers are not used in the conversion:
|
||||
@@ -214,13 +208,6 @@ The library maps BJData types to JSON value types as follows:
|
||||
|
||||
The mapping is **complete** in the sense that any BJData value can be converted to a JSON value.
|
||||
|
||||
!!! warning "UTF-8 validation of string values and object keys"
|
||||
|
||||
This library validates the bytes of every string value and object key at decode time and rejects ill-formed
|
||||
UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value
|
||||
is dumped.
|
||||
|
||||
!!! info "Round trips"
|
||||
|
||||
A value returned by [`from_bjdata`](../../api/basic_json/from_bjdata.md) can be serialized with
|
||||
|
||||
@@ -115,12 +115,8 @@ The library maps BSON record types to JSON value types as follows:
|
||||
bytes of every such string at decode time and rejects ill-formed UTF-8 with a
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions`
|
||||
set to `false`, a discarded value), rather than only failing later when the resulting value is dumped. Element
|
||||
(key) names and `binary` values (type `0x05`) are unaffected and are never validated on read, since they are read
|
||||
byte-by-byte as a C string, or are not required to hold text, respectively. `to_bson()` validates both string
|
||||
values and element names and throws
|
||||
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 in either, so an
|
||||
object with such a key or value cannot be produced in the first place, even though `from_bson()` would accept it
|
||||
from another source.
|
||||
(key) names and `binary` values (type `0x05`) are unaffected and are never validated, since they are read
|
||||
byte-by-byte as a C string, or are not required to hold text, respectively.
|
||||
|
||||
??? example
|
||||
|
||||
|
||||
@@ -197,9 +197,7 @@ The library maps CBOR types to JSON value types as follows:
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value is
|
||||
dumped. Byte strings (major type 2) are unaffected and are never validated, since they are not required to hold
|
||||
text. `to_cbor()` validates string values and object keys the same way and throws
|
||||
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, so a value with
|
||||
such a string cannot be serialized in the first place.
|
||||
text.
|
||||
|
||||
!!! warning "Tagged items"
|
||||
|
||||
|
||||
@@ -153,15 +153,14 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
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 "Ill-formed UTF-8 in string values"
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain
|
||||
a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged.
|
||||
This library follows that: `from_msgpack()` reads `str` bytes (object keys included) as-is, without validating
|
||||
them, and `to_msgpack()` writes them back as-is, so such a value round-trips through `from_msgpack(to_msgpack(j))`
|
||||
byte for byte. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws
|
||||
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read this way, unless an
|
||||
error handler is passed that replaces or ignores the ill-formed bytes.
|
||||
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
||||
This library validates the bytes of every such string (object keys included) at decode time and rejects
|
||||
ill-formed UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or,
|
||||
with `allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting
|
||||
value is dumped. `bin`/`ext`/`fixext` values are unaffected and are never validated, since they are not required
|
||||
to hold text.
|
||||
|
||||
??? example
|
||||
|
||||
|
||||
@@ -47,12 +47,6 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
||||
|
||||
- strings with more than 9223372036854775807 bytes (theoretical)
|
||||
|
||||
!!! warning "UTF-8 validation of string values and object keys"
|
||||
|
||||
UBJSON's required string encoding is UTF-8. `to_ubjson()` validates the bytes of every string value and object
|
||||
key and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, so
|
||||
a value with such a string cannot be serialized in the first place.
|
||||
|
||||
!!! info "Unused UBJSON markers"
|
||||
|
||||
The following markers are not used in the conversion:
|
||||
@@ -126,13 +120,6 @@ The library maps UBJSON types to JSON value types as follows:
|
||||
|
||||
The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value.
|
||||
|
||||
!!! warning "UTF-8 validation of string values and object keys"
|
||||
|
||||
This library validates the bytes of every string value and object key at decode time and rejects ill-formed
|
||||
UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value
|
||||
is dumped.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -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).
|
||||
|
||||
## `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_AUTOMATIC_UDLS`
|
||||
|
||||
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
||||
|
||||
@@ -169,12 +169,6 @@ Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../a
|
||||
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_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`
|
||||
|
||||
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_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.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_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.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)... };
|
||||
}
|
||||
|
||||
// A one-element braced list does not reliably wrap its element: with
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
|
||||
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
|
||||
// 15 and 16) copy an element that is itself a basic_json even without it, so
|
||||
// std::tuple<json>{true} became true rather than [true]. Build what the default
|
||||
// deduction builds instead: an object if the element is a [string, value] pair,
|
||||
// a one-element array otherwise.
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. Build what the default deduction builds instead: an object
|
||||
// if the element is a [string, value] pair, a one-element array otherwise.
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
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)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
|
||||
@@ -4111,16 +4111,12 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 8949 (CBOR) §3.1 and the BSON/UBJSON specifications require
|
||||
// text strings to be valid UTF-8; reject anything else right here so
|
||||
// malformed input is caught at decode time instead of only surfacing
|
||||
// later as a type_error.316 when the value is dumped (which would
|
||||
// defeat allow_exceptions=false / strict discarding). The MessagePack
|
||||
// specification explicitly allows a str object to contain an invalid
|
||||
// byte sequence and expects deserializers to hand back the original
|
||||
// bytes, so msgpack strings (and map keys, which go through this
|
||||
// function as well) are exempt.
|
||||
if (format != input_format_t::msgpack && JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
|
||||
// RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications
|
||||
// all require text strings to be valid UTF-8; reject anything else
|
||||
// right here so malformed input is caught at decode time instead of
|
||||
// only surfacing later as a type_error.316 when the value is dumped
|
||||
// (which would defeat allow_exceptions=false / strict discarding).
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(),
|
||||
parse_error::create(113, chars_read,
|
||||
|
||||
@@ -915,7 +915,3 @@ void templated_json_throw(ExceptionType exception)
|
||||
#ifndef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||
#endif
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
|
||||
@@ -636,18 +636,6 @@ template<typename BasicJsonType, typename CompatibleType>
|
||||
struct is_compatible_type
|
||||
: 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>
|
||||
struct is_compatible_binary_type
|
||||
{
|
||||
|
||||
@@ -116,8 +116,6 @@ class binary_writer
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@pre j.type() == value_t::object
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -147,8 +145,6 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -215,8 +211,6 @@ class binary_writer
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
check_utf8(*j.m_data.m_value.string, j);
|
||||
|
||||
// step 1: write control byte and the string length
|
||||
write_cbor_head(0x60, j.m_data.m_value.string->size());
|
||||
|
||||
@@ -286,11 +280,6 @@ class binary_writer
|
||||
// step 2: write each element
|
||||
for (const auto& el : *j.m_data.m_value.object)
|
||||
{
|
||||
// el.first is checked here, against the object as
|
||||
// diagnostics context, because write_cbor(el.first)
|
||||
// converts it to a temporary basic_json that would be
|
||||
// used as the context instead
|
||||
check_utf8(el.first, j);
|
||||
write_cbor(el.first);
|
||||
write_cbor(el.second);
|
||||
}
|
||||
@@ -654,8 +643,6 @@ class binary_writer
|
||||
@param[in] add_prefix whether prefixes need to be used for this value
|
||||
@param[in] use_bjdata whether write in BJData format, default is false
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -705,8 +692,6 @@ class binary_writer
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
check_utf8(*j.m_data.m_value.string, j);
|
||||
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('S'));
|
||||
@@ -869,7 +854,6 @@ class binary_writer
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.object)
|
||||
{
|
||||
check_utf8(el.first, j);
|
||||
write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(el.first.data()),
|
||||
@@ -914,10 +898,6 @@ class binary_writer
|
||||
/*!
|
||||
@return The size of a BSON document entry header, including the id marker
|
||||
and the entry name size (and its null-terminator).
|
||||
@throw out_of_range.409 if @a name contains U+0000, before anything is
|
||||
written
|
||||
@throw type_error.316 if @a name is not valid UTF-8, before anything is
|
||||
written
|
||||
*/
|
||||
static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)
|
||||
{
|
||||
@@ -927,8 +907,7 @@ class binary_writer
|
||||
JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
|
||||
}
|
||||
|
||||
check_utf8(name, j);
|
||||
|
||||
static_cast<void>(j);
|
||||
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
|
||||
}
|
||||
|
||||
@@ -984,21 +963,9 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@return The size of the BSON-encoded string in @a value
|
||||
@throw type_error.316 if @a value is not valid UTF-8, before anything is
|
||||
written
|
||||
|
||||
@note The UTF-8 check is skipped if @a value is already too long for the
|
||||
32-bit BSON length field (@ref to_bson_length rejects it later, once
|
||||
the size of the whole document is known); this also keeps the check
|
||||
from reading past a StringType that reports a size larger than what
|
||||
it actually holds.
|
||||
*/
|
||||
static std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j)
|
||||
static std::size_t calc_bson_string_size(const string_t& value)
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(value_in_range_of<std::int32_t>(value.size())))
|
||||
{
|
||||
check_utf8(value, j);
|
||||
}
|
||||
return sizeof(std::int32_t) + value.size() + 1ul;
|
||||
}
|
||||
|
||||
@@ -1127,8 +1094,6 @@ class binary_writer
|
||||
is neither an object nor an array
|
||||
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
*/
|
||||
static std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -1150,7 +1115,7 @@ class binary_writer
|
||||
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||
|
||||
case value_t::string:
|
||||
return calc_bson_string_size(*j.m_data.m_value.string, j);
|
||||
return calc_bson_string_size(*j.m_data.m_value.string);
|
||||
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
@@ -1263,8 +1228,6 @@ class binary_writer
|
||||
written
|
||||
@throw out_of_range.415 if a binary value's subtype does not fit into a
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
*/
|
||||
static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
@@ -2289,7 +2252,7 @@ class binary_writer
|
||||
*/
|
||||
void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context)
|
||||
{
|
||||
check_utf8(s, context);
|
||||
check_bon8_utf8(s, context);
|
||||
|
||||
// a string that follows another string terminates it
|
||||
if (string_open)
|
||||
@@ -2319,7 +2282,7 @@ class binary_writer
|
||||
@throw type_error.316 if @a s is not valid UTF-8; the message names the
|
||||
first byte of the first invalid or incomplete sequence
|
||||
*/
|
||||
static void check_utf8(const string_t& s, const BasicJsonType& context)
|
||||
static void check_bon8_utf8(const string_t& s, const BasicJsonType& context)
|
||||
{
|
||||
static_cast<void>(context); // only used when exceptions are enabled
|
||||
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
|
||||
+94
-252
@@ -906,11 +906,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/*!
|
||||
@brief how many levels the operation going on in this thread has descended into
|
||||
|
||||
Copying a value, converting one from another specialization, and comparing
|
||||
two values share this count. The library never nests one of them inside
|
||||
another - none of them does either of the other two on the way - and where
|
||||
user code nests them anyway, sharing the count only ends a descent sooner
|
||||
than it had to, which costs a little speed and is never wrong.
|
||||
Copying a value and comparing two values share this count. The library never
|
||||
nests one inside the other - copying a value does not compare one, and
|
||||
comparing two values does not copy them - and where user code nests them
|
||||
anyway, sharing the count only ends a descent sooner than it had to, which
|
||||
costs a little speed and is never wrong.
|
||||
|
||||
A byte is enough: the count never exceeds the limit by more than the single
|
||||
level that notices the limit has been reached.
|
||||
@@ -1004,19 +1004,39 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
using copy_scratch_value_t = std::pair<typename object_t::key_type, basic_json>;
|
||||
using copy_scratch_t = std::vector<copy_scratch_value_t, AllocatorType<copy_scratch_value_t>>;
|
||||
|
||||
/// @brief copy everything of @a src into @a dst but its type and value
|
||||
static void copy_metadata(const basic_json& src, basic_json& dst)
|
||||
{
|
||||
// a custom base class is only required to be copy-constructible and
|
||||
// move-assignable, so the copy has to go through a temporary
|
||||
static_cast<json_base_class_t&>(dst) = json_base_class_t(static_cast<const json_base_class_t&>(src));
|
||||
/// @brief tag selecting the constructor below; used only to build the
|
||||
/// elements of a deep copy (@ref copy_array_level, @ref copy_object_level)
|
||||
struct copy_construct_tag {};
|
||||
|
||||
public:
|
||||
/*!
|
||||
@brief construct a null value whose base class - and, with @ref
|
||||
JSON_DIAGNOSTIC_POSITIONS, positions - are copied from @a src
|
||||
|
||||
Copy-constructing @ref json_base_class_t here, rather than default-
|
||||
constructing the element and assigning its base class afterwards, means
|
||||
that copying a @ref basic_json only ever requires a copy-constructible
|
||||
base class, and never a move-assignable one as well.
|
||||
|
||||
@note this constructor has to be public: @ref copy_array_level and
|
||||
@ref copy_object_level reach it through @ref array_t's or @ref
|
||||
object_t's own emplace_back(), which constructs the element from
|
||||
outside @ref basic_json and so cannot call a private constructor.
|
||||
@ref copy_construct_tag is private, though, and nothing in the
|
||||
public interface hands out a value of it, so outside code can still
|
||||
never name it to call this constructor itself.
|
||||
*/
|
||||
basic_json(copy_construct_tag /*unused*/, const basic_json& src)
|
||||
: json_base_class_t(src)
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
dst.start_position = src.start_position;
|
||||
dst.end_position = src.end_position;
|
||||
, start_position(src.start_position)
|
||||
, end_position(src.end_position)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
@brief copy the value of @a src into @a dst, which must not be structured
|
||||
|
||||
@@ -1079,8 +1099,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief copy everything of @a src into the null value @a dst but the children
|
||||
@brief finish the copy @a dst of @a src that a @ref copy_construct_tag
|
||||
constructor started, other than the children of an object or array
|
||||
|
||||
@a dst already has @a src's base class and, with @ref
|
||||
JSON_DIAGNOSTIC_POSITIONS, positions; only its value is still missing.
|
||||
Objects and arrays are not copied here; they are appended to @a worklist to
|
||||
be created later by @ref copy_iteratively. Until that happens, @a dst remains
|
||||
a null value, so that a partially built copy can be destroyed at any point
|
||||
@@ -1088,8 +1111,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
*/
|
||||
static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
|
||||
{
|
||||
copy_metadata(src, dst);
|
||||
|
||||
if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array)
|
||||
{
|
||||
// defer: dst stays a null value until its container exists
|
||||
@@ -1110,15 +1131,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
{
|
||||
const array_t& src_array = *src.m_data.m_value.array;
|
||||
|
||||
// create all elements up front: growing the array afterwards could
|
||||
// invalidate the pointers that are handed to the worklist; resize()
|
||||
// rather than the fill constructor, because not every array type
|
||||
// provides the latter (e.g., ones without a matching allocator-aware
|
||||
// fill constructor)
|
||||
dst.m_data.m_value.array = create<array_t>();
|
||||
// only now that the array exists may dst stop being a null value
|
||||
dst.m_data.m_type = value_t::array;
|
||||
dst.m_data.m_value.array->resize(src_array.size());
|
||||
|
||||
// create every element - its base class already copy-constructed from
|
||||
// its counterpart in src, via the copy_construct_tag constructor -
|
||||
// before any of their addresses are handed to worklist below: growing
|
||||
// the array while that is going on could reallocate it and invalidate
|
||||
// addresses taken from an earlier iteration
|
||||
for (const auto& src_element : src_array)
|
||||
{
|
||||
dst.m_data.m_value.array->emplace_back(copy_construct_tag{}, src_element);
|
||||
}
|
||||
|
||||
auto dst_it = dst.m_data.m_value.array->begin();
|
||||
for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it)
|
||||
@@ -1136,12 +1161,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
// build the complete key skeleton and hand it to the object's range
|
||||
// constructor: adding the keys one by one would be quadratic for object
|
||||
// types that are backed by a vector, such as nlohmann::ordered_map
|
||||
// types that are backed by a vector, such as nlohmann::ordered_map; each
|
||||
// value's base class is already copy-constructed from its counterpart
|
||||
// in src, via the copy_construct_tag constructor
|
||||
scratch.clear();
|
||||
scratch.reserve(src_object.size());
|
||||
for (const auto& element : src_object)
|
||||
{
|
||||
scratch.emplace_back(element.first, basic_json());
|
||||
scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second));
|
||||
}
|
||||
|
||||
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
|
||||
@@ -1267,222 +1294,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
copy_iteratively(src);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the value @a val of another specialization into this null
|
||||
value; @a val must be neither an object nor an array
|
||||
|
||||
Converting such a value never descends, so both ways of converting an
|
||||
object or an array (@ref convert_structured) leave their elements of this
|
||||
kind to the converting constructor, which leaves them to this.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_leaf(const BasicJsonType& val)
|
||||
{
|
||||
using other_boolean_t = typename BasicJsonType::boolean_t;
|
||||
using other_number_float_t = typename BasicJsonType::number_float_t;
|
||||
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using other_string_t = typename BasicJsonType::string_t;
|
||||
using other_binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
switch (val.type())
|
||||
{
|
||||
case value_t::boolean:
|
||||
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
||||
break;
|
||||
case value_t::number_float:
|
||||
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
||||
break;
|
||||
case value_t::string:
|
||||
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
||||
break;
|
||||
case value_t::binary:
|
||||
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
||||
break;
|
||||
case value_t::null:
|
||||
// m_data.m_type is already value_t::null
|
||||
break;
|
||||
case value_t::discarded:
|
||||
m_data.m_type = value_t::discarded;
|
||||
break;
|
||||
case value_t::object: // LCOV_EXCL_LINE
|
||||
case value_t::array: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
/// scratch space for the converted elements of the arrays that
|
||||
/// @ref convert_iteratively has yet to create
|
||||
using convert_scratch_t = std::vector<basic_json, AllocatorType<basic_json>>;
|
||||
|
||||
/*!
|
||||
@brief create the object or array @a val converted into this null value
|
||||
|
||||
Its converted elements are the last `val.size()` entries of @a elements (an
|
||||
array) or of @a members (an object); they are moved into the container in
|
||||
one go and then removed.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_level(const BasicJsonType& val, convert_scratch_t& elements, copy_scratch_t& members)
|
||||
{
|
||||
if (val.is_object())
|
||||
{
|
||||
const auto first = members.end() - static_cast<typename copy_scratch_t::difference_type>(val.size());
|
||||
m_data.m_value.object = create<object_t>(std::make_move_iterator(first),
|
||||
std::make_move_iterator(members.end()));
|
||||
// only now that the object exists may this stop being a null value
|
||||
m_data.m_type = value_t::object;
|
||||
members.erase(first, members.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto first = elements.end() - static_cast<typename convert_scratch_t::difference_type>(val.size());
|
||||
m_data.m_value.array = create<array_t>(std::make_move_iterator(first),
|
||||
std::make_move_iterator(elements.end()));
|
||||
// only now that the array exists may this stop being a null value
|
||||
m_data.m_type = value_t::array;
|
||||
elements.erase(first, elements.end());
|
||||
}
|
||||
|
||||
set_parents();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the object or array @a val of another specialization into
|
||||
this null value without recursing
|
||||
|
||||
The containers whose conversion has begun are kept on an explicit stack
|
||||
rather than on the call stack. Unlike @ref copy_iteratively, this builds
|
||||
every container from the bottom up: all its elements are converted first,
|
||||
and the container is then created from them in one go, the way the range
|
||||
constructor that converts the levels above the bound does. The two object
|
||||
types need not enumerate their members in the same order, so the members
|
||||
could not be paired up by position anyway, and building from a range keeps
|
||||
what the range constructor does with keys that become equal on conversion.
|
||||
|
||||
Every value is complete before it is handed on, and a container gets its
|
||||
type only once it exists, so whatever throws, every value left behind can
|
||||
be destroyed.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_iteratively(const BasicJsonType& val)
|
||||
{
|
||||
using other_const_iterator = typename BasicJsonType::const_iterator;
|
||||
|
||||
// the containers whose conversion has begun, innermost last, each with
|
||||
// its element to convert next
|
||||
std::vector<std::pair<const BasicJsonType*, other_const_iterator>> pending;
|
||||
|
||||
// the converted elements of the pending arrays and the converted
|
||||
// members of the pending objects, those of the innermost one last
|
||||
convert_scratch_t elements;
|
||||
copy_scratch_t members;
|
||||
|
||||
pending.emplace_back(&val, val.cbegin());
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const BasicJsonType& container = *pending.back().first;
|
||||
// a copy, as descending below can reallocate pending; the
|
||||
// iterator kept in pending is only advanced through pending.back()
|
||||
const other_const_iterator next = pending.back().second;
|
||||
|
||||
if (next != container.cend())
|
||||
{
|
||||
if (next->is_structured())
|
||||
{
|
||||
// convert its elements first; next stays where it is until
|
||||
// the converted container is handed back to this one
|
||||
pending.emplace_back(&*next, next->cbegin());
|
||||
continue;
|
||||
}
|
||||
|
||||
// the converting constructor does not descend into this value
|
||||
if (container.is_object())
|
||||
{
|
||||
members.emplace_back(next.key(), *next);
|
||||
}
|
||||
else
|
||||
{
|
||||
elements.emplace_back(*next);
|
||||
}
|
||||
++pending.back().second;
|
||||
continue;
|
||||
}
|
||||
|
||||
// all elements of the container are converted: create it
|
||||
pending.pop_back();
|
||||
|
||||
if (pending.empty())
|
||||
{
|
||||
convert_level(container, elements, members);
|
||||
return;
|
||||
}
|
||||
|
||||
basic_json converted;
|
||||
converted.convert_level(container, elements, members);
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
converted.start_position = container.start_pos();
|
||||
converted.end_position = container.end_pos();
|
||||
#endif
|
||||
|
||||
// hand it to the container it is an element of
|
||||
if (pending.back().first->is_object())
|
||||
{
|
||||
members.emplace_back(pending.back().second.key(), std::move(converted));
|
||||
}
|
||||
else
|
||||
{
|
||||
elements.push_back(std::move(converted));
|
||||
}
|
||||
++pending.back().second;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the object or array @a val of another specialization into
|
||||
this null value
|
||||
|
||||
Converting a container converts its elements, so a value nested deeply
|
||||
enough used to exhaust the call stack. The descent is bounded here as in
|
||||
@ref copy_structured: the first `detail::recursion_depth_limit()` levels
|
||||
are converted by the containers' range constructors, just as they always were,
|
||||
and anything below that is converted without the call stack by
|
||||
@ref convert_iteratively.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5650
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_structured(const BasicJsonType& val)
|
||||
{
|
||||
const nesting_depth_guard guard;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(guard.okay()))
|
||||
{
|
||||
// every element comes back to the converting constructor
|
||||
if (val.is_object())
|
||||
{
|
||||
using other_object_t = typename BasicJsonType::object_t;
|
||||
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
||||
}
|
||||
else
|
||||
{
|
||||
using other_array_t = typename BasicJsonType::array_t;
|
||||
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
convert_iteratively(val);
|
||||
}
|
||||
|
||||
|
||||
/// the result of comparing two values, including values that cannot be
|
||||
/// ordered at all, such as a discarded value or a NaN
|
||||
@@ -1812,12 +1623,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
template < typename CompatibleType,
|
||||
typename U = detail::uncvref_t<CompatibleType>,
|
||||
detail::enable_if_t <
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value
|
||||
#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 >
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
|
||||
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
||||
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
||||
std::forward<CompatibleType>(val))))
|
||||
@@ -1838,13 +1644,49 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
end_position(val.end_pos())
|
||||
#endif
|
||||
{
|
||||
if (val.is_structured())
|
||||
using other_boolean_t = typename BasicJsonType::boolean_t;
|
||||
using other_number_float_t = typename BasicJsonType::number_float_t;
|
||||
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using other_string_t = typename BasicJsonType::string_t;
|
||||
using other_object_t = typename BasicJsonType::object_t;
|
||||
using other_array_t = typename BasicJsonType::array_t;
|
||||
using other_binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
switch (val.type())
|
||||
{
|
||||
convert_structured(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
convert_leaf(val);
|
||||
case value_t::boolean:
|
||||
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
||||
break;
|
||||
case value_t::number_float:
|
||||
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
||||
break;
|
||||
case value_t::string:
|
||||
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
||||
break;
|
||||
case value_t::object:
|
||||
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
||||
break;
|
||||
case value_t::array:
|
||||
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
||||
break;
|
||||
case value_t::binary:
|
||||
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
||||
break;
|
||||
case value_t::null:
|
||||
*this = nullptr;
|
||||
break;
|
||||
case value_t::discarded:
|
||||
m_data.m_type = value_t::discarded;
|
||||
break;
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
JSON_ASSERT(m_data.m_type == val.type());
|
||||
|
||||
|
||||
+111
-328
@@ -3329,10 +3329,6 @@ void templated_json_throw(ExceptionType exception)
|
||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
#include <compare> // partial_ordering
|
||||
#endif
|
||||
@@ -4648,18 +4644,6 @@ template<typename BasicJsonType, typename CompatibleType>
|
||||
struct is_compatible_type
|
||||
: 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>
|
||||
struct is_compatible_binary_type
|
||||
{
|
||||
@@ -7018,13 +7002,11 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = { std::get<Idx>(t)... };
|
||||
}
|
||||
|
||||
// A one-element braced list does not reliably wrap its element: with
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
|
||||
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
|
||||
// 15 and 16) copy an element that is itself a basic_json even without it, so
|
||||
// std::tuple<json>{true} became true rather than [true]. Build what the default
|
||||
// deduction builds instead: an object if the element is a [string, value] pair,
|
||||
// a one-element array otherwise.
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. Build what the default deduction builds instead: an object
|
||||
// if the element is a [string, value] pair, a one-element array otherwise.
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||
{
|
||||
@@ -7042,6 +7024,7 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = BasicJsonType::array({std::move(element)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
@@ -17664,16 +17647,12 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 8949 (CBOR) §3.1 and the BSON/UBJSON specifications require
|
||||
// text strings to be valid UTF-8; reject anything else right here so
|
||||
// malformed input is caught at decode time instead of only surfacing
|
||||
// later as a type_error.316 when the value is dumped (which would
|
||||
// defeat allow_exceptions=false / strict discarding). The MessagePack
|
||||
// specification explicitly allows a str object to contain an invalid
|
||||
// byte sequence and expects deserializers to hand back the original
|
||||
// bytes, so msgpack strings (and map keys, which go through this
|
||||
// function as well) are exempt.
|
||||
if (format != input_format_t::msgpack && JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
|
||||
// RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications
|
||||
// all require text strings to be valid UTF-8; reject anything else
|
||||
// right here so malformed input is caught at decode time instead of
|
||||
// only surfacing later as a type_error.316 when the value is dumped
|
||||
// (which would defeat allow_exceptions=false / strict discarding).
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(),
|
||||
parse_error::create(113, chars_read,
|
||||
@@ -21244,8 +21223,6 @@ class binary_writer
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@pre j.type() == value_t::object
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
*/
|
||||
void write_bson(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21275,8 +21252,6 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -21343,8 +21318,6 @@ class binary_writer
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
check_utf8(*j.m_data.m_value.string, j);
|
||||
|
||||
// step 1: write control byte and the string length
|
||||
write_cbor_head(0x60, j.m_data.m_value.string->size());
|
||||
|
||||
@@ -21414,11 +21387,6 @@ class binary_writer
|
||||
// step 2: write each element
|
||||
for (const auto& el : *j.m_data.m_value.object)
|
||||
{
|
||||
// el.first is checked here, against the object as
|
||||
// diagnostics context, because write_cbor(el.first)
|
||||
// converts it to a temporary basic_json that would be
|
||||
// used as the context instead
|
||||
check_utf8(el.first, j);
|
||||
write_cbor(el.first);
|
||||
write_cbor(el.second);
|
||||
}
|
||||
@@ -21782,8 +21750,6 @@ class binary_writer
|
||||
@param[in] add_prefix whether prefixes need to be used for this value
|
||||
@param[in] use_bjdata whether write in BJData format, default is false
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -21833,8 +21799,6 @@ class binary_writer
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
check_utf8(*j.m_data.m_value.string, j);
|
||||
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('S'));
|
||||
@@ -21997,7 +21961,6 @@ class binary_writer
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.object)
|
||||
{
|
||||
check_utf8(el.first, j);
|
||||
write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);
|
||||
oa.write_characters(
|
||||
reinterpret_cast<const CharType*>(el.first.data()),
|
||||
@@ -22042,10 +22005,6 @@ class binary_writer
|
||||
/*!
|
||||
@return The size of a BSON document entry header, including the id marker
|
||||
and the entry name size (and its null-terminator).
|
||||
@throw out_of_range.409 if @a name contains U+0000, before anything is
|
||||
written
|
||||
@throw type_error.316 if @a name is not valid UTF-8, before anything is
|
||||
written
|
||||
*/
|
||||
static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)
|
||||
{
|
||||
@@ -22055,8 +22014,7 @@ class binary_writer
|
||||
JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
|
||||
}
|
||||
|
||||
check_utf8(name, j);
|
||||
|
||||
static_cast<void>(j);
|
||||
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
|
||||
}
|
||||
|
||||
@@ -22112,21 +22070,9 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@return The size of the BSON-encoded string in @a value
|
||||
@throw type_error.316 if @a value is not valid UTF-8, before anything is
|
||||
written
|
||||
|
||||
@note The UTF-8 check is skipped if @a value is already too long for the
|
||||
32-bit BSON length field (@ref to_bson_length rejects it later, once
|
||||
the size of the whole document is known); this also keeps the check
|
||||
from reading past a StringType that reports a size larger than what
|
||||
it actually holds.
|
||||
*/
|
||||
static std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j)
|
||||
static std::size_t calc_bson_string_size(const string_t& value)
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(value_in_range_of<std::int32_t>(value.size())))
|
||||
{
|
||||
check_utf8(value, j);
|
||||
}
|
||||
return sizeof(std::int32_t) + value.size() + 1ul;
|
||||
}
|
||||
|
||||
@@ -22255,8 +22201,6 @@ class binary_writer
|
||||
is neither an object nor an array
|
||||
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
*/
|
||||
static std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -22278,7 +22222,7 @@ class binary_writer
|
||||
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||
|
||||
case value_t::string:
|
||||
return calc_bson_string_size(*j.m_data.m_value.string, j);
|
||||
return calc_bson_string_size(*j.m_data.m_value.string);
|
||||
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
@@ -22391,8 +22335,6 @@ class binary_writer
|
||||
written
|
||||
@throw out_of_range.415 if a binary value's subtype does not fit into a
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
*/
|
||||
static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||
{
|
||||
@@ -23417,7 +23359,7 @@ class binary_writer
|
||||
*/
|
||||
void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context)
|
||||
{
|
||||
check_utf8(s, context);
|
||||
check_bon8_utf8(s, context);
|
||||
|
||||
// a string that follows another string terminates it
|
||||
if (string_open)
|
||||
@@ -23447,7 +23389,7 @@ class binary_writer
|
||||
@throw type_error.316 if @a s is not valid UTF-8; the message names the
|
||||
first byte of the first invalid or incomplete sequence
|
||||
*/
|
||||
static void check_utf8(const string_t& s, const BasicJsonType& context)
|
||||
static void check_bon8_utf8(const string_t& s, const BasicJsonType& context)
|
||||
{
|
||||
static_cast<void>(context); // only used when exceptions are enabled
|
||||
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
@@ -27891,11 +27833,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/*!
|
||||
@brief how many levels the operation going on in this thread has descended into
|
||||
|
||||
Copying a value, converting one from another specialization, and comparing
|
||||
two values share this count. The library never nests one of them inside
|
||||
another - none of them does either of the other two on the way - and where
|
||||
user code nests them anyway, sharing the count only ends a descent sooner
|
||||
than it had to, which costs a little speed and is never wrong.
|
||||
Copying a value and comparing two values share this count. The library never
|
||||
nests one inside the other - copying a value does not compare one, and
|
||||
comparing two values does not copy them - and where user code nests them
|
||||
anyway, sharing the count only ends a descent sooner than it had to, which
|
||||
costs a little speed and is never wrong.
|
||||
|
||||
A byte is enough: the count never exceeds the limit by more than the single
|
||||
level that notices the limit has been reached.
|
||||
@@ -27989,19 +27931,39 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
using copy_scratch_value_t = std::pair<typename object_t::key_type, basic_json>;
|
||||
using copy_scratch_t = std::vector<copy_scratch_value_t, AllocatorType<copy_scratch_value_t>>;
|
||||
|
||||
/// @brief copy everything of @a src into @a dst but its type and value
|
||||
static void copy_metadata(const basic_json& src, basic_json& dst)
|
||||
{
|
||||
// a custom base class is only required to be copy-constructible and
|
||||
// move-assignable, so the copy has to go through a temporary
|
||||
static_cast<json_base_class_t&>(dst) = json_base_class_t(static_cast<const json_base_class_t&>(src));
|
||||
/// @brief tag selecting the constructor below; used only to build the
|
||||
/// elements of a deep copy (@ref copy_array_level, @ref copy_object_level)
|
||||
struct copy_construct_tag {};
|
||||
|
||||
public:
|
||||
/*!
|
||||
@brief construct a null value whose base class - and, with @ref
|
||||
JSON_DIAGNOSTIC_POSITIONS, positions - are copied from @a src
|
||||
|
||||
Copy-constructing @ref json_base_class_t here, rather than default-
|
||||
constructing the element and assigning its base class afterwards, means
|
||||
that copying a @ref basic_json only ever requires a copy-constructible
|
||||
base class, and never a move-assignable one as well.
|
||||
|
||||
@note this constructor has to be public: @ref copy_array_level and
|
||||
@ref copy_object_level reach it through @ref array_t's or @ref
|
||||
object_t's own emplace_back(), which constructs the element from
|
||||
outside @ref basic_json and so cannot call a private constructor.
|
||||
@ref copy_construct_tag is private, though, and nothing in the
|
||||
public interface hands out a value of it, so outside code can still
|
||||
never name it to call this constructor itself.
|
||||
*/
|
||||
basic_json(copy_construct_tag /*unused*/, const basic_json& src)
|
||||
: json_base_class_t(src)
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
dst.start_position = src.start_position;
|
||||
dst.end_position = src.end_position;
|
||||
, start_position(src.start_position)
|
||||
, end_position(src.end_position)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
@brief copy the value of @a src into @a dst, which must not be structured
|
||||
|
||||
@@ -28064,8 +28026,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief copy everything of @a src into the null value @a dst but the children
|
||||
@brief finish the copy @a dst of @a src that a @ref copy_construct_tag
|
||||
constructor started, other than the children of an object or array
|
||||
|
||||
@a dst already has @a src's base class and, with @ref
|
||||
JSON_DIAGNOSTIC_POSITIONS, positions; only its value is still missing.
|
||||
Objects and arrays are not copied here; they are appended to @a worklist to
|
||||
be created later by @ref copy_iteratively. Until that happens, @a dst remains
|
||||
a null value, so that a partially built copy can be destroyed at any point
|
||||
@@ -28073,8 +28038,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
*/
|
||||
static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
|
||||
{
|
||||
copy_metadata(src, dst);
|
||||
|
||||
if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array)
|
||||
{
|
||||
// defer: dst stays a null value until its container exists
|
||||
@@ -28095,15 +28058,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
{
|
||||
const array_t& src_array = *src.m_data.m_value.array;
|
||||
|
||||
// create all elements up front: growing the array afterwards could
|
||||
// invalidate the pointers that are handed to the worklist; resize()
|
||||
// rather than the fill constructor, because not every array type
|
||||
// provides the latter (e.g., ones without a matching allocator-aware
|
||||
// fill constructor)
|
||||
dst.m_data.m_value.array = create<array_t>();
|
||||
// only now that the array exists may dst stop being a null value
|
||||
dst.m_data.m_type = value_t::array;
|
||||
dst.m_data.m_value.array->resize(src_array.size());
|
||||
|
||||
// create every element - its base class already copy-constructed from
|
||||
// its counterpart in src, via the copy_construct_tag constructor -
|
||||
// before any of their addresses are handed to worklist below: growing
|
||||
// the array while that is going on could reallocate it and invalidate
|
||||
// addresses taken from an earlier iteration
|
||||
for (const auto& src_element : src_array)
|
||||
{
|
||||
dst.m_data.m_value.array->emplace_back(copy_construct_tag{}, src_element);
|
||||
}
|
||||
|
||||
auto dst_it = dst.m_data.m_value.array->begin();
|
||||
for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it)
|
||||
@@ -28121,12 +28088,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
|
||||
// build the complete key skeleton and hand it to the object's range
|
||||
// constructor: adding the keys one by one would be quadratic for object
|
||||
// types that are backed by a vector, such as nlohmann::ordered_map
|
||||
// types that are backed by a vector, such as nlohmann::ordered_map; each
|
||||
// value's base class is already copy-constructed from its counterpart
|
||||
// in src, via the copy_construct_tag constructor
|
||||
scratch.clear();
|
||||
scratch.reserve(src_object.size());
|
||||
for (const auto& element : src_object)
|
||||
{
|
||||
scratch.emplace_back(element.first, basic_json());
|
||||
scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second));
|
||||
}
|
||||
|
||||
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
|
||||
@@ -28252,222 +28221,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
copy_iteratively(src);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the value @a val of another specialization into this null
|
||||
value; @a val must be neither an object nor an array
|
||||
|
||||
Converting such a value never descends, so both ways of converting an
|
||||
object or an array (@ref convert_structured) leave their elements of this
|
||||
kind to the converting constructor, which leaves them to this.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_leaf(const BasicJsonType& val)
|
||||
{
|
||||
using other_boolean_t = typename BasicJsonType::boolean_t;
|
||||
using other_number_float_t = typename BasicJsonType::number_float_t;
|
||||
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using other_string_t = typename BasicJsonType::string_t;
|
||||
using other_binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
switch (val.type())
|
||||
{
|
||||
case value_t::boolean:
|
||||
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
||||
break;
|
||||
case value_t::number_float:
|
||||
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
||||
break;
|
||||
case value_t::string:
|
||||
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
||||
break;
|
||||
case value_t::binary:
|
||||
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
||||
break;
|
||||
case value_t::null:
|
||||
// m_data.m_type is already value_t::null
|
||||
break;
|
||||
case value_t::discarded:
|
||||
m_data.m_type = value_t::discarded;
|
||||
break;
|
||||
case value_t::object: // LCOV_EXCL_LINE
|
||||
case value_t::array: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
/// scratch space for the converted elements of the arrays that
|
||||
/// @ref convert_iteratively has yet to create
|
||||
using convert_scratch_t = std::vector<basic_json, AllocatorType<basic_json>>;
|
||||
|
||||
/*!
|
||||
@brief create the object or array @a val converted into this null value
|
||||
|
||||
Its converted elements are the last `val.size()` entries of @a elements (an
|
||||
array) or of @a members (an object); they are moved into the container in
|
||||
one go and then removed.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_level(const BasicJsonType& val, convert_scratch_t& elements, copy_scratch_t& members)
|
||||
{
|
||||
if (val.is_object())
|
||||
{
|
||||
const auto first = members.end() - static_cast<typename copy_scratch_t::difference_type>(val.size());
|
||||
m_data.m_value.object = create<object_t>(std::make_move_iterator(first),
|
||||
std::make_move_iterator(members.end()));
|
||||
// only now that the object exists may this stop being a null value
|
||||
m_data.m_type = value_t::object;
|
||||
members.erase(first, members.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto first = elements.end() - static_cast<typename convert_scratch_t::difference_type>(val.size());
|
||||
m_data.m_value.array = create<array_t>(std::make_move_iterator(first),
|
||||
std::make_move_iterator(elements.end()));
|
||||
// only now that the array exists may this stop being a null value
|
||||
m_data.m_type = value_t::array;
|
||||
elements.erase(first, elements.end());
|
||||
}
|
||||
|
||||
set_parents();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the object or array @a val of another specialization into
|
||||
this null value without recursing
|
||||
|
||||
The containers whose conversion has begun are kept on an explicit stack
|
||||
rather than on the call stack. Unlike @ref copy_iteratively, this builds
|
||||
every container from the bottom up: all its elements are converted first,
|
||||
and the container is then created from them in one go, the way the range
|
||||
constructor that converts the levels above the bound does. The two object
|
||||
types need not enumerate their members in the same order, so the members
|
||||
could not be paired up by position anyway, and building from a range keeps
|
||||
what the range constructor does with keys that become equal on conversion.
|
||||
|
||||
Every value is complete before it is handed on, and a container gets its
|
||||
type only once it exists, so whatever throws, every value left behind can
|
||||
be destroyed.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_iteratively(const BasicJsonType& val)
|
||||
{
|
||||
using other_const_iterator = typename BasicJsonType::const_iterator;
|
||||
|
||||
// the containers whose conversion has begun, innermost last, each with
|
||||
// its element to convert next
|
||||
std::vector<std::pair<const BasicJsonType*, other_const_iterator>> pending;
|
||||
|
||||
// the converted elements of the pending arrays and the converted
|
||||
// members of the pending objects, those of the innermost one last
|
||||
convert_scratch_t elements;
|
||||
copy_scratch_t members;
|
||||
|
||||
pending.emplace_back(&val, val.cbegin());
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const BasicJsonType& container = *pending.back().first;
|
||||
// a copy, as descending below can reallocate pending; the
|
||||
// iterator kept in pending is only advanced through pending.back()
|
||||
const other_const_iterator next = pending.back().second;
|
||||
|
||||
if (next != container.cend())
|
||||
{
|
||||
if (next->is_structured())
|
||||
{
|
||||
// convert its elements first; next stays where it is until
|
||||
// the converted container is handed back to this one
|
||||
pending.emplace_back(&*next, next->cbegin());
|
||||
continue;
|
||||
}
|
||||
|
||||
// the converting constructor does not descend into this value
|
||||
if (container.is_object())
|
||||
{
|
||||
members.emplace_back(next.key(), *next);
|
||||
}
|
||||
else
|
||||
{
|
||||
elements.emplace_back(*next);
|
||||
}
|
||||
++pending.back().second;
|
||||
continue;
|
||||
}
|
||||
|
||||
// all elements of the container are converted: create it
|
||||
pending.pop_back();
|
||||
|
||||
if (pending.empty())
|
||||
{
|
||||
convert_level(container, elements, members);
|
||||
return;
|
||||
}
|
||||
|
||||
basic_json converted;
|
||||
converted.convert_level(container, elements, members);
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
converted.start_position = container.start_pos();
|
||||
converted.end_position = container.end_pos();
|
||||
#endif
|
||||
|
||||
// hand it to the container it is an element of
|
||||
if (pending.back().first->is_object())
|
||||
{
|
||||
members.emplace_back(pending.back().second.key(), std::move(converted));
|
||||
}
|
||||
else
|
||||
{
|
||||
elements.push_back(std::move(converted));
|
||||
}
|
||||
++pending.back().second;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert the object or array @a val of another specialization into
|
||||
this null value
|
||||
|
||||
Converting a container converts its elements, so a value nested deeply
|
||||
enough used to exhaust the call stack. The descent is bounded here as in
|
||||
@ref copy_structured: the first `detail::recursion_depth_limit()` levels
|
||||
are converted by the containers' range constructors, just as they always were,
|
||||
and anything below that is converted without the call stack by
|
||||
@ref convert_iteratively.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5650
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
void convert_structured(const BasicJsonType& val)
|
||||
{
|
||||
const nesting_depth_guard guard;
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(guard.okay()))
|
||||
{
|
||||
// every element comes back to the converting constructor
|
||||
if (val.is_object())
|
||||
{
|
||||
using other_object_t = typename BasicJsonType::object_t;
|
||||
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
||||
}
|
||||
else
|
||||
{
|
||||
using other_array_t = typename BasicJsonType::array_t;
|
||||
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
convert_iteratively(val);
|
||||
}
|
||||
|
||||
|
||||
/// the result of comparing two values, including values that cannot be
|
||||
/// ordered at all, such as a discarded value or a NaN
|
||||
@@ -28797,12 +28550,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
template < typename CompatibleType,
|
||||
typename U = detail::uncvref_t<CompatibleType>,
|
||||
detail::enable_if_t <
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value
|
||||
#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 >
|
||||
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
|
||||
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
|
||||
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
|
||||
std::forward<CompatibleType>(val))))
|
||||
@@ -28823,13 +28571,49 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
end_position(val.end_pos())
|
||||
#endif
|
||||
{
|
||||
if (val.is_structured())
|
||||
using other_boolean_t = typename BasicJsonType::boolean_t;
|
||||
using other_number_float_t = typename BasicJsonType::number_float_t;
|
||||
using other_number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using other_string_t = typename BasicJsonType::string_t;
|
||||
using other_object_t = typename BasicJsonType::object_t;
|
||||
using other_array_t = typename BasicJsonType::array_t;
|
||||
using other_binary_t = typename BasicJsonType::binary_t;
|
||||
|
||||
switch (val.type())
|
||||
{
|
||||
convert_structured(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
convert_leaf(val);
|
||||
case value_t::boolean:
|
||||
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
|
||||
break;
|
||||
case value_t::number_float:
|
||||
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
|
||||
break;
|
||||
case value_t::string:
|
||||
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
|
||||
break;
|
||||
case value_t::object:
|
||||
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
|
||||
break;
|
||||
case value_t::array:
|
||||
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
|
||||
break;
|
||||
case value_t::binary:
|
||||
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
|
||||
break;
|
||||
case value_t::null:
|
||||
*this = nullptr;
|
||||
break;
|
||||
case value_t::discarded:
|
||||
m_data.m_type = value_t::discarded;
|
||||
break;
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
JSON_ASSERT(m_data.m_type == val.type());
|
||||
|
||||
@@ -33815,7 +33599,6 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
|
||||
@@ -479,77 +479,6 @@ TEST_CASE("deep copy uses the provided allocator")
|
||||
CHECK(copy == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the number of constructions countdown_allocator lets happen, including the
|
||||
// one that fails; 0 means none ever fails
|
||||
std::size_t constructions_until_failure = 0;
|
||||
|
||||
template<class T>
|
||||
struct countdown_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
template<class U, class... Args>
|
||||
void construct(U* p, Args&& ... args)
|
||||
{
|
||||
if (constructions_until_failure != 0 && --constructions_until_failure == 0)
|
||||
{
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = countdown_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
|
||||
{
|
||||
using countdown_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
countdown_allocator>;
|
||||
|
||||
// deeper than the 128 levels the converting constructor descends into, so
|
||||
// that failures land on both sides of the bound - or, built with
|
||||
// JSON_NO_THREAD_LOCAL, all in the iterative conversion
|
||||
json j = {1, "two", {{"three", 3}}};
|
||||
for (std::size_t i = 0; i < 150; ++i)
|
||||
{
|
||||
j = json{{"a", json::array({j, "sibling"})}};
|
||||
}
|
||||
|
||||
// Fail every construction in turn. Each failure has to reach the caller,
|
||||
// and everything built until then has to be destroyed cleanly.
|
||||
std::size_t failures = 0;
|
||||
for (std::size_t n = 1;; ++n)
|
||||
{
|
||||
constructions_until_failure = n;
|
||||
try
|
||||
{
|
||||
const countdown_json converted = j;
|
||||
constructions_until_failure = 0;
|
||||
CHECK(converted.dump() == j.dump());
|
||||
break;
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
CHECK(failures > 0);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<class T>
|
||||
|
||||
@@ -4025,30 +4025,6 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
|
||||
CHECK(json::to_bjdata(j) == v);
|
||||
CHECK(json::from_bjdata(v) == j);
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 (see #5651)")
|
||||
{
|
||||
// a string value whose bytes are not valid UTF-8 (0xC0 0xAE is an
|
||||
// overlong encoding of '.') is rejected at decode time, matching
|
||||
// every other kind of malformed binary input, and to_bjdata()
|
||||
// rejects it as well, so a value it accepts can always be read
|
||||
// back
|
||||
const std::vector<uint8_t> v = {'S', 'i', 2, 0xc0, 0xae};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_bjdata(v, true, false).is_discarded());
|
||||
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
// a truncated multi-byte sequence
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
// an overlong encoding of '.'
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected the same way
|
||||
CHECK_THROWS_WITH_AS(json::to_bjdata(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Array Type")
|
||||
|
||||
@@ -151,44 +151,6 @@ TEST_CASE("BSON")
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 (see #5651)")
|
||||
{
|
||||
// a BSON document {"s": "\xC0\xAE"} (0xC0 0xAE is an overlong
|
||||
// encoding of '.'); the reader rejects an ill-formed string value at
|
||||
// decode time
|
||||
const std::vector<uint8_t> v =
|
||||
{
|
||||
0x0F, 0x00, 0x00, 0x00, // document length
|
||||
0x02, 's', 0x00, // type 0x02 (string), key "s"
|
||||
0x03, 0x00, 0x00, 0x00, // string length (including null)
|
||||
0xc0, 0xae, 0x00, // string content and its null terminator
|
||||
0x00 // document terminator
|
||||
};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing BSON string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||
|
||||
// to_bson() rejects the same kind of ill-formed string value, before
|
||||
// any bytes reach the output adapter (the BSON document length
|
||||
// prefix must be known up front, so nothing is written incrementally)
|
||||
std::vector<std::uint8_t> out{0x42}; // a sentinel byte the writer must not touch
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}, nlohmann::detail::output_adapter<std::uint8_t>(out)), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
CHECK(out == std::vector<std::uint8_t> {0x42});
|
||||
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
// a truncated multi-byte sequence
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC3"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
// an overlong encoding of '.'
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected as well; unlike
|
||||
// the reader (which never validates element names), the writer
|
||||
// checks both string values and object keys
|
||||
CHECK_THROWS_WITH_AS(json::to_bson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
}
|
||||
|
||||
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
|
||||
{
|
||||
// out_of_range.412 is thrown from a single shared helper
|
||||
|
||||
@@ -1896,25 +1896,6 @@ TEST_CASE("CBOR")
|
||||
CHECK(json::from_cbor(json::to_cbor(j)) == j);
|
||||
}
|
||||
|
||||
SECTION("to_cbor rejects ill-formed UTF-8 (see #5651)")
|
||||
{
|
||||
// to_cbor() must reject the same ill-formed strings from_cbor()
|
||||
// rejects, so a value it accepts can always be read back
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
// a truncated multi-byte sequence
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
// an overlong encoding of '.'
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected the same way
|
||||
CHECK_THROWS_WITH_AS(json::to_cbor(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
|
||||
// binary values are not text and are unaffected
|
||||
CHECK_NOTHROW(json::to_cbor(json::binary(std::vector<std::uint8_t>({0xFF}))));
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 in indefinite-length string")
|
||||
{
|
||||
json _;
|
||||
|
||||
@@ -405,3 +405,73 @@ TEST_CASE("JSON Visit Node")
|
||||
);
|
||||
CHECK(expected.empty());
|
||||
}
|
||||
|
||||
// A custom base class with a const member: copy-constructible (initializing a
|
||||
// const member works fine), but not copy-/move-assignable (assigning one does
|
||||
// not). Used to check that copy construction never requires more than that.
|
||||
struct const_member_base
|
||||
{
|
||||
const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes)
|
||||
};
|
||||
|
||||
using json_with_const_base = nlohmann::basic_json <
|
||||
std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
std::allocator,
|
||||
nlohmann::adl_serializer,
|
||||
std::vector<std::uint8_t>,
|
||||
const_member_base
|
||||
>;
|
||||
|
||||
// build an array nested @a depth levels deep, with the innermost value 1;
|
||||
// every level is constructed (never assigned), since const_member_base does
|
||||
// not support assignment
|
||||
static json_with_const_base make_nested_array(std::size_t depth)
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
return json_with_const_base(1);
|
||||
}
|
||||
return json_with_const_base::array({make_nested_array(depth - 1)});
|
||||
}
|
||||
|
||||
TEST_CASE("Regression test for issue #5674 - copy construction must not require an assignable base class")
|
||||
{
|
||||
SECTION("depth 0")
|
||||
{
|
||||
// as in the original bug report: copy construction only, no assignment
|
||||
const json_with_const_base j = {1, 2};
|
||||
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
|
||||
CHECK(copy.size() == 2);
|
||||
CHECK(copy.id == 7);
|
||||
}
|
||||
|
||||
SECTION("nested deeper than the copy constructor's descent bound")
|
||||
{
|
||||
// beyond nesting_depth_limit() (128) levels, the copy constructor
|
||||
// copies without the call stack (copy_iteratively / copy_array_level),
|
||||
// which used to assign the base class of every element it created
|
||||
const std::size_t depth = 300;
|
||||
|
||||
const json_with_const_base j = make_nested_array(depth);
|
||||
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||
|
||||
const json_with_const_base* c = ©
|
||||
for (std::size_t level = 0; level <= depth; ++level)
|
||||
{
|
||||
CAPTURE(level)
|
||||
REQUIRE(c->id == 7);
|
||||
if (level < depth)
|
||||
{
|
||||
c = &c->at(0);
|
||||
}
|
||||
}
|
||||
CHECK(*c == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,58 +141,6 @@ TEST_CASE("Better diagnostics with positions")
|
||||
check_objects(300);
|
||||
}
|
||||
|
||||
SECTION("converting keeps the positions of nested values (#5650)")
|
||||
{
|
||||
// Values nested deeper than the converting constructor's descent bound
|
||||
// are converted without the call stack, on a path that has to carry the
|
||||
// positions of every value over itself. Objects and arrays take turns,
|
||||
// and the innermost value is null, which used to lose its positions.
|
||||
const auto check_conversion = [](std::size_t depth)
|
||||
{
|
||||
CAPTURE(depth)
|
||||
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
text += (i % 2 == 0) ? "[12, " : R"({"b":1, "a":)";
|
||||
closing += (i % 2 == 0) ? ']' : '}';
|
||||
}
|
||||
text += "null";
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
|
||||
const json original = json::parse(text);
|
||||
const nlohmann::ordered_json converted = original;
|
||||
|
||||
const json* o = &original;
|
||||
const nlohmann::ordered_json* c = &converted;
|
||||
for (std::size_t level = 0; level <= depth; ++level)
|
||||
{
|
||||
CAPTURE(level)
|
||||
REQUIRE(c->start_pos() == o->start_pos());
|
||||
REQUIRE(c->end_pos() == o->end_pos());
|
||||
|
||||
if (level < depth)
|
||||
{
|
||||
// the number beside the value nested next
|
||||
const json& o_number = o->is_object() ? o->at("b") : o->at(0);
|
||||
const nlohmann::ordered_json& c_number = c->is_object() ? c->at("b") : c->at(0);
|
||||
REQUIRE(c_number.start_pos() == o_number.start_pos());
|
||||
REQUIRE(c_number.end_pos() == o_number.end_pos());
|
||||
|
||||
o = o->is_object() ? &o->at("a") : &o->at(1);
|
||||
c = c->is_object() ? &c->at("a") : &c->at(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check_conversion(1);
|
||||
check_conversion(127);
|
||||
check_conversion(128);
|
||||
check_conversion(129);
|
||||
check_conversion(300);
|
||||
}
|
||||
|
||||
SECTION("JSON patch add to primitive parent (#4292)")
|
||||
{
|
||||
// the JSON Patch "add" target /foo/bar/baz has a string parent
|
||||
|
||||
@@ -341,36 +341,6 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5650 - converting keeps the parents of nested values")
|
||||
{
|
||||
// A value nested deeper than the converting constructor's descent bound
|
||||
// is converted without the call stack. Every container that path creates
|
||||
// has to have the parents of its children set, or the JSON Pointer in the
|
||||
// diagnostic is cut short. Objects and arrays take turns.
|
||||
const std::size_t pairs = 150;
|
||||
|
||||
json j = "not a number";
|
||||
std::string pointer;
|
||||
for (std::size_t i = 0; i < pairs; ++i)
|
||||
{
|
||||
j = json{{"a", json::array({j})}};
|
||||
pointer += "/a/0";
|
||||
}
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
|
||||
const nlohmann::ordered_json* inner = &converted;
|
||||
for (std::size_t i = 0; i < pairs; ++i)
|
||||
{
|
||||
inner = &inner->at("a").at(0);
|
||||
}
|
||||
|
||||
std::string const expected = "[json.exception.type_error.302] (" + pointer + ") type must be number, but is string";
|
||||
int i = 0;
|
||||
CHECK_THROWS_WITH_AS(i = inner->get<int>(), expected.c_str(), nlohmann::ordered_json::type_error);
|
||||
CHECK(i == 0);
|
||||
}
|
||||
|
||||
SECTION("Regression test for issue #5668 - wrong path for std::map/unordered_map with non-string keys")
|
||||
{
|
||||
// a map with non-string keys is read from an array of [key, value] arrays;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("tests on very large JSONs")
|
||||
{
|
||||
@@ -54,24 +53,6 @@ const json* innermost_value(const json& j, std::size_t& depth)
|
||||
return current;
|
||||
}
|
||||
|
||||
// The text of a value nested depth levels deep around the number 0. Level i is
|
||||
// an array if pattern[i % pattern.size()] is '[', and otherwise an object with
|
||||
// the single member "a", which every object type enumerates in the same order.
|
||||
std::string nested_text(std::size_t depth, const std::string& pattern)
|
||||
{
|
||||
std::string text;
|
||||
std::string closing;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
const bool array = pattern[i % pattern.size()] == '[';
|
||||
text += array ? "[" : "{\"a\":";
|
||||
closing += array ? ']' : '}';
|
||||
}
|
||||
text += '0';
|
||||
text.append(closing.rbegin(), closing.rend());
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("tests on deeply nested JSONs")
|
||||
@@ -243,114 +224,5 @@ TEST_CASE("tests on deeply nested JSONs")
|
||||
CHECK(*innermost_value(j, unused) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("issue #5650 - stack overflow converting between specializations")
|
||||
{
|
||||
const std::vector<std::string> patterns = {"[", "{", "[{"};
|
||||
|
||||
SECTION("json to ordered_json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ordered_json to json")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
|
||||
|
||||
const json converted = o;
|
||||
CHECK(converted.dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("get<ordered_json>()")
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(depth, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
CHECK(j.get<nlohmann::ordered_json>().dump() == text);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("depths around the bound of the recursive descent")
|
||||
{
|
||||
for (std::size_t d = 1; d <= 300; ++d)
|
||||
{
|
||||
CAPTURE(d);
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
CAPTURE(pattern);
|
||||
const std::string text = nested_text(d, pattern);
|
||||
const json j = json::parse(text);
|
||||
|
||||
const nlohmann::ordered_json converted = j;
|
||||
CHECK(converted.dump() == text);
|
||||
const json back = converted;
|
||||
CHECK(back.dump() == text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values below the bound are converted as values above it")
|
||||
{
|
||||
// Bury a value below the bound, where it is converted without the
|
||||
// call stack, and compare it with the same value converted on its
|
||||
// own by the containers' range constructors. Its objects have
|
||||
// members that the two object types enumerate in different orders.
|
||||
const auto bury = [](nlohmann::ordered_json value)
|
||||
{
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
value = nlohmann::ordered_json::array({std::move(value)});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const auto dig = [](const json & value)
|
||||
{
|
||||
const json* current = &value;
|
||||
for (std::size_t i = 0; i < 200; ++i)
|
||||
{
|
||||
current = ¤t->at(0);
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json value = nlohmann::ordered_json::object();
|
||||
value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9),
|
||||
nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object()
|
||||
};
|
||||
value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}};
|
||||
value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)});
|
||||
|
||||
const json converted_above = value;
|
||||
const json buried = bury(value);
|
||||
const json& converted_below = *dig(buried);
|
||||
|
||||
CHECK(converted_below.dump() == converted_above.dump());
|
||||
CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9);
|
||||
CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype());
|
||||
CHECK(converted_below.at("r").at(0).is_discarded());
|
||||
|
||||
// a discarded value is never equal to anything, so compare the rest
|
||||
value.erase("r");
|
||||
const json without_discarded_above = value;
|
||||
const json without_discarded_buried = bury(value);
|
||||
CHECK(*dig(without_discarded_buried) == without_discarded_above);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1614,39 +1614,19 @@ TEST_CASE("MessagePack")
|
||||
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("ill-formed UTF-8 in string (see #5529, #5651)")
|
||||
SECTION("invalid UTF-8 in string (see #5529)")
|
||||
{
|
||||
// the MessagePack specification explicitly allows a str object to
|
||||
// contain a byte sequence that is not valid UTF-8 and expects a
|
||||
// deserializer to hand the original bytes back unchanged; this
|
||||
// library follows that, unlike CBOR/UBJSON/BJData/BSON, whose
|
||||
// specifications require text strings to be valid UTF-8
|
||||
|
||||
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
||||
// (0xC0 0xAE is an overlong encoding of '.') round-trips byte for
|
||||
// byte as a string value
|
||||
const std::vector<uint8_t> ill_formed_value = {0xa2, 0xc0, 0xae};
|
||||
json j_value;
|
||||
CHECK_NOTHROW(j_value = json::from_msgpack(ill_formed_value));
|
||||
REQUIRE(j_value.is_string());
|
||||
CHECK(j_value.get_ref<const json::string_t&>() == std::string("\xc0\xae"));
|
||||
CHECK(json::from_msgpack(json::to_msgpack(j_value)) == j_value);
|
||||
// dump() still requires valid UTF-8 and throws for such a value,
|
||||
// unless an error handler that replaces or ignores the bytes is
|
||||
// passed
|
||||
CHECK_THROWS_AS(j_value.dump(), json::type_error&);
|
||||
|
||||
// the same bytes as an object key round-trip as well
|
||||
const std::vector<uint8_t> ill_formed_key = {0x81, 0xa2, 0xc0, 0xae, 0x01};
|
||||
json j_key;
|
||||
CHECK_NOTHROW(j_key = json::from_msgpack(ill_formed_key));
|
||||
REQUIRE(j_key.is_object());
|
||||
CHECK(j_key.contains(std::string("\xc0\xae")));
|
||||
CHECK(json::from_msgpack(json::to_msgpack(j_key)) == j_key);
|
||||
// (0xC0 0xAE is an overlong encoding of '.') must be rejected at
|
||||
// decode time, matching every other kind of malformed binary
|
||||
// input, rather than only failing later when the resulting
|
||||
// value is dumped
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing MessagePack string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae}), true, false).is_discarded());
|
||||
|
||||
// a MessagePack bin8 blob with the very same bytes is NOT text
|
||||
// and must still be accepted as-is
|
||||
json _;
|
||||
CHECK_NOTHROW(_ = json::from_msgpack(std::vector<uint8_t>({0xc4, 0x02, 0xc0, 0xae})));
|
||||
CHECK(_ == json::binary(std::vector<std::uint8_t>({0xc0, 0xae})));
|
||||
|
||||
|
||||
@@ -18,19 +18,6 @@
|
||||
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||
#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
|
||||
#include <nlohmann/json.hpp>
|
||||
using json = nlohmann::json;
|
||||
@@ -41,7 +28,6 @@ using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include <cstdio>
|
||||
#include <list>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#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")
|
||||
{
|
||||
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
|
||||
|
||||
@@ -2580,30 +2580,6 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
|
||||
CHECK(json::to_ubjson(j) == v);
|
||||
CHECK(json::from_ubjson(v) == j);
|
||||
}
|
||||
|
||||
SECTION("ill-formed UTF-8 (see #5651)")
|
||||
{
|
||||
// a string value whose bytes are not valid UTF-8 (0xC0 0xAE is an
|
||||
// overlong encoding of '.') is rejected at decode time, matching
|
||||
// every other kind of malformed binary input, and to_ubjson()
|
||||
// rejects it as well, so a value it accepts can always be read
|
||||
// back
|
||||
const std::vector<uint8_t> v = {'S', 'i', 2, 0xc0, 0xae};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing UBJSON string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
CHECK(json::from_ubjson(v, true, false).is_discarded());
|
||||
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
// a truncated multi-byte sequence
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
|
||||
// an encoded surrogate half (U+D800)
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
// an overlong encoding of '.'
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
|
||||
|
||||
// an object key with ill-formed UTF-8 is rejected the same way
|
||||
CHECK_THROWS_WITH_AS(json::to_ubjson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Array Type")
|
||||
|
||||
Reference in New Issue
Block a user