mirror of
https://github.com/nlohmann/json.git
synced 2026-10-05 14:10:31 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
994b83cd17 | ||
|
|
dd2dd45abd | ||
|
|
362a902170 | ||
|
|
ba6bf32f6d | ||
|
|
056d187986 | ||
|
|
90078597c7 | ||
|
|
fa28fff7d6 | ||
|
|
e9c84befa1 | ||
|
|
8a26f2dc8f | ||
|
|
2c108d0b56 |
@@ -55,10 +55,6 @@ This implementation does exactly follow this approach, as it uses double precisi
|
|||||||
smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally
|
smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally
|
||||||
and be serialized to `null`.
|
and be serialized to `null`.
|
||||||
|
|
||||||
During deserialization (from JSON text or any of the binary formats), a finite number that does not fit into
|
|
||||||
`number_float_t` is rejected with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406), for
|
|
||||||
example a double-precision number in a binary format when `number_float_t` is `#!cpp float`.
|
|
||||||
|
|
||||||
### Storage
|
### Storage
|
||||||
|
|
||||||
Floating-point number values are stored directly inside a `basic_json` type.
|
Floating-point number values are stored directly inside a `basic_json` type.
|
||||||
|
|||||||
@@ -47,9 +47,8 @@ With the default values for `NumberIntegerType` (`std::int64_t`), the default va
|
|||||||
|
|
||||||
When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and
|
When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and
|
||||||
the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of
|
the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of
|
||||||
range will yield over/underflow when used in a constructor. During deserialization (from JSON text or any of the binary
|
range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers
|
||||||
formats), too large or small integer numbers will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md)
|
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
|
||||||
or [`number_float_t`](number_float_t.md).
|
|
||||||
|
|
||||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||||
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||||
|
|||||||
@@ -48,9 +48,8 @@ With the default values for `NumberUnsignedType` (`std::uint64_t`), the default
|
|||||||
|
|
||||||
When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and
|
When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and
|
||||||
the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow
|
the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow
|
||||||
when used in a constructor. During deserialization (from JSON text or any of the binary formats), too large or small
|
when used in a constructor. During deserialization, too large or small integer numbers will automatically be stored
|
||||||
integer numbers will automatically be stored as [`number_integer_t`](number_integer_t.md) or
|
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
|
||||||
[`number_float_t`](number_float_t.md).
|
|
||||||
|
|
||||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||||
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||||
|
|||||||
@@ -58,8 +58,6 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
||||||
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
|
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
|
||||||
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
||||||
- [**JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS**](json_use_objects_for_enum_keyed_maps.md) - opt in to storing maps with enum
|
|
||||||
keys as objects
|
|
||||||
|
|
||||||
## Comparison behavior
|
## Comparison behavior
|
||||||
|
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
# JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS /* value */
|
|
||||||
```
|
|
||||||
|
|
||||||
When defined to `1`, maps whose keys are enums (such as `std::map<E, T>` or `std::unordered_map<E, T>`) are stored as
|
|
||||||
JSON objects, using the enum's own conversion for the keys. By default, they are stored as arrays of `[key, value]`
|
|
||||||
pairs.
|
|
||||||
|
|
||||||
## Default definition
|
|
||||||
|
|
||||||
The default value is `0` (disabled — existing behavior is preserved).
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
!!! note "Background"
|
|
||||||
|
|
||||||
JSON object keys are strings, so a map is only stored as an object if its keys can be converted to a string type.
|
|
||||||
Enums are not, even if [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps them to strings, so a
|
|
||||||
map with enum keys becomes an array of `[key, value]` pairs:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[["stopped", "aa"], ["completed", "bb"]]
|
|
||||||
```
|
|
||||||
|
|
||||||
With this macro, the same map becomes an object
|
|
||||||
(see [#4378](https://github.com/nlohmann/json/issues/4378)):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"completed": "bb", "stopped": "aa"}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note "Maps with non-unique keys"
|
|
||||||
|
|
||||||
Maps that allow duplicate keys, such as `std::multimap<E, T>` or `std::unordered_multimap<E, T>`, are not affected
|
|
||||||
by the macro and are still stored as arrays of `[key, value]` pairs, as an object cannot hold duplicate keys.
|
|
||||||
|
|
||||||
!!! note "Reading"
|
|
||||||
|
|
||||||
Reading is not affected by the macro: a map with enum keys can always be read from both an array of pairs and an
|
|
||||||
object. For the latter, each key is converted to the enum with its `from_json` function, e.g., the one defined by
|
|
||||||
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md). Data written without the macro can therefore
|
|
||||||
still be read after enabling it.
|
|
||||||
|
|
||||||
!!! warning "Keys must serialize to distinct strings"
|
|
||||||
|
|
||||||
Each key is converted with the enum's `to_json` function. If a key is not converted to a string (for instance, an
|
|
||||||
enum without [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md), which is stored as an integer, or an
|
|
||||||
enumerator mapped to `nullptr`), [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) is thrown.
|
|
||||||
If two keys are converted to the same string (for instance, because
|
|
||||||
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps an unlisted enumerator to the first entry),
|
|
||||||
[`type_error.318`](../../home/exceptions.md#jsonexceptiontype_error318) is thrown. In both cases, the target value
|
|
||||||
is not changed.
|
|
||||||
|
|
||||||
!!! warning "Opt-in only"
|
|
||||||
|
|
||||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
|
|
||||||
|
|
||||||
!!! note "ABI compatibility"
|
|
||||||
|
|
||||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_ekmo`), resulting in
|
|
||||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
|
||||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
??? example "Default behavior (macro not defined)"
|
|
||||||
|
|
||||||
Without the macro, a map with enum keys is stored as an array of pairs:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <map>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
|
|
||||||
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
|
|
||||||
{TS_STOPPED, "stopped"},
|
|
||||||
{TS_RUNNING, "running"},
|
|
||||||
{TS_COMPLETED, "completed"},
|
|
||||||
})
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
|
|
||||||
json j = m;
|
|
||||||
// j is [["stopped","aa"],["completed","bb"]]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
??? example "Objects for enum-keyed maps (macro defined to 1)"
|
|
||||||
|
|
||||||
With the macro, the same map is stored as an object:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
|
|
||||||
#include <map>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
|
|
||||||
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
|
|
||||||
{TS_STOPPED, "stopped"},
|
|
||||||
{TS_RUNNING, "running"},
|
|
||||||
{TS_COMPLETED, "completed"},
|
|
||||||
})
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
|
|
||||||
json j = m;
|
|
||||||
// j is {"completed":"bb","stopped":"aa"}
|
|
||||||
|
|
||||||
auto m2 = j.get<std::map<TaskState, std::string>>();
|
|
||||||
// m2 == m
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
|
||||||
- [**NLOHMANN_JSON_SERIALIZE_ENUM**](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum
|
|
||||||
- [**NLOHMANN_JSON_SERIALIZE_ENUM_STRICT**](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with
|
|
||||||
exceptions
|
|
||||||
|
|
||||||
## Version history
|
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
|
||||||
@@ -41,9 +41,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
conversion. Select this default pair carefully. See example 1 below.
|
conversion. Select this default pair carefully. See example 1 below.
|
||||||
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
||||||
list will be returned when converting to or from JSON. See example 2 below.
|
list will be returned when converting to or from JSON. See example 2 below.
|
||||||
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
|
|
||||||
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
|
|
||||||
with the converted keys. Such maps can be read from both forms.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -83,7 +80,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
- [Specializing enum conversion](../../features/enum_conversion.md)
|
||||||
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md)
|
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md)
|
||||||
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
||||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
|
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
`"enum value out of range for <type>"`.
|
`"enum value out of range for <type>"`.
|
||||||
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
||||||
list will be returned when converting to or from JSON. See example 2 below.
|
list will be returned when converting to or from JSON. See example 2 below.
|
||||||
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
|
|
||||||
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
|
|
||||||
with the converted keys. Such maps can be read from both forms.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -102,7 +99,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
- [Specializing enum conversion](../../features/enum_conversion.md)
|
||||||
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md)
|
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md)
|
||||||
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
||||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
|
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -168,9 +168,9 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
!!! warning "Negative integer overflow"
|
!!! warning "Negative integer overflow"
|
||||||
|
|
||||||
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
|
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
|
||||||
result to fit into `number_integer_t` (`std::int64_t` by default), the result is stored as `number_float_t`, like
|
result to fit into `number_integer_t` (`std::int64_t` by default), parsing fails with a
|
||||||
a too small integer in JSON text. For example, `-18446744073709551616` (`0x3B` followed by eight `0xFF` bytes) is
|
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) exception rather than overflowing
|
||||||
stored as `-1.8446744073709552e+19`.
|
silently.
|
||||||
|
|
||||||
!!! warning "Object keys"
|
!!! warning "Object keys"
|
||||||
|
|
||||||
|
|||||||
@@ -58,23 +58,6 @@ assert(jPi.get<TaskState>() == TS_INVALID );
|
|||||||
--8<-- "examples/nlohmann_json_serialize_enum.output"
|
--8<-- "examples/nlohmann_json_serialize_enum.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Maps with enum keys
|
|
||||||
|
|
||||||
By default, maps with enum keys, such as `std::map<TaskState, std::string>`, are stored as arrays of `[key, value]`
|
|
||||||
pairs, because JSON object keys must be strings. Define
|
|
||||||
[`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) before including the
|
|
||||||
library to store them as objects, with the keys converted by the enum's `to_json()` function:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
|
|
||||||
json j = m;
|
|
||||||
// default: [["stopped","aa"],["completed","bb"]]
|
|
||||||
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS: {"completed":"bb","stopped":"aa"}
|
|
||||||
```
|
|
||||||
|
|
||||||
Either form can be read back, with or without the macro.
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
|
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
|
||||||
|
|||||||
@@ -198,13 +198,6 @@ default.
|
|||||||
|
|
||||||
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
|
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
|
||||||
|
|
||||||
## `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`
|
|
||||||
|
|
||||||
When defined to `1`, maps with enum keys (e.g., `std::map<E, T>`) are stored as objects, using the enum's conversion for
|
|
||||||
the keys, instead of arrays of `[key, value]` pairs. It is switched off (`0`) by default.
|
|
||||||
|
|
||||||
See [full documentation of `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md).
|
|
||||||
|
|
||||||
## `JSON_USE_SIMDUTF`
|
## `JSON_USE_SIMDUTF`
|
||||||
|
|
||||||
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
|
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ The complete default namespace name is derived as follows:
|
|||||||
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
|
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
|
||||||
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
|
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
|
||||||
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
|
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
|
||||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
|
|
||||||
appends `_ekmo`.
|
|
||||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||||
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
||||||
below.
|
below.
|
||||||
|
|||||||
@@ -331,6 +331,9 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
|
|||||||
[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1
|
[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
|
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
|
||||||
|
```
|
||||||
|
```
|
||||||
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -596,9 +599,6 @@ During implicit or explicit value conversion, the JSON type must be compatible w
|
|||||||
[json.exception.type_error.302] type must be string, but is object
|
[json.exception.type_error.302] type must be string, but is object
|
||||||
```
|
```
|
||||||
|
|
||||||
This exception is also thrown with [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)
|
|
||||||
if a key of a map with enum keys is not converted to a string, for instance, because the enum is stored as an integer.
|
|
||||||
|
|
||||||
### json.exception.type_error.303
|
### json.exception.type_error.303
|
||||||
|
|
||||||
To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.
|
To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.
|
||||||
@@ -791,19 +791,6 @@ The dynamic type of the object cannot be represented in the requested serializat
|
|||||||
|
|
||||||
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
|
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
|
||||||
|
|
||||||
### json.exception.type_error.318
|
|
||||||
|
|
||||||
With [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md), a map with enum
|
|
||||||
keys is stored as an object. This exception is thrown if two of its keys are converted to the same string, so one of the
|
|
||||||
entries would be lost. This happens, for instance, if [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md)
|
|
||||||
does not list an enumerator and it is therefore converted like the first listed one.
|
|
||||||
|
|
||||||
!!! failure "Example message"
|
|
||||||
|
|
||||||
```
|
|
||||||
[json.exception.type_error.318] duplicate object key 'red'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Out of range
|
## Out of range
|
||||||
|
|
||||||
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
||||||
@@ -876,18 +863,13 @@ The JSON Patch operations 'remove' and 'add' cannot be applied to the root eleme
|
|||||||
|
|
||||||
### json.exception.out_of_range.406
|
### json.exception.out_of_range.406
|
||||||
|
|
||||||
A parsed number could not be stored without changing it to NaN or INF. For the binary formats, this happens when a
|
A parsed number could not be stored as without changing it to NaN or INF.
|
||||||
finite floating-point number does not fit into [`number_float_t`](../api/basic_json/number_float_t.md), for example a
|
|
||||||
double-precision number when `number_float_t` is `#!cpp float`.
|
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example message"
|
||||||
|
|
||||||
```
|
```
|
||||||
number overflow parsing '10E1000'
|
number overflow parsing '10E1000'
|
||||||
```
|
```
|
||||||
```
|
|
||||||
[json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow
|
|
||||||
```
|
|
||||||
|
|
||||||
### json.exception.out_of_range.407
|
### json.exception.out_of_range.407
|
||||||
|
|
||||||
|
|||||||
@@ -313,7 +313,6 @@ nav:
|
|||||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
||||||
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
||||||
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
||||||
- 'JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS': api/macros/json_use_objects_for_enum_keyed_maps.md
|
|
||||||
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
||||||
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
||||||
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
||||||
|
|||||||
@@ -50,10 +50,6 @@
|
|||||||
#define JSON_STRICT_BINARY_UTF8 0
|
#define JSON_STRICT_BINARY_UTF8 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
#else
|
#else
|
||||||
@@ -96,20 +92,14 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
|
|
||||||
#else
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -119,8 +109,7 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
|
||||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
|
|
||||||
|
|
||||||
// Construct the namespace version component
|
// Construct the namespace version component
|
||||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
|||||||
@@ -530,40 +530,11 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read a map with enum keys from an object, using the enum's own from_json for
|
|
||||||
// the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written
|
|
||||||
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
template<typename BasicJsonType, typename Map>
|
|
||||||
inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/)
|
|
||||||
{
|
|
||||||
if (!j.is_object())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m.clear();
|
|
||||||
for (const auto& p : *j.template get_ptr<const typename BasicJsonType::object_t*>())
|
|
||||||
{
|
|
||||||
m.emplace(BasicJsonType(p.first).template get<typename Map::key_type>(), p.second.template get<typename Map::mapped_type>());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename Map>
|
|
||||||
inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
|
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
|
||||||
typename = enable_if_t < !std::is_constructible <
|
typename = enable_if_t < !std::is_constructible <
|
||||||
typename BasicJsonType::string_t, Key >::value >>
|
typename BasicJsonType::string_t, Key >::value >>
|
||||||
void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
|
void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
|
|
||||||
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
from_json_pair_array_to_map(j, m);
|
from_json_pair_array_to_map(j, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,11 +543,6 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
|
|||||||
typename BasicJsonType::string_t, Key >::value >>
|
typename BasicJsonType::string_t, Key >::value >>
|
||||||
void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
|
void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
|
|
||||||
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
from_json_pair_array_to_map(j, m);
|
from_json_pair_array_to_map(j, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <valarray> // valarray
|
#include <valarray> // valarray
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
|
||||||
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
|
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
|
||||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||||
#include <nlohmann/detail/meta/std_fs.hpp>
|
#include <nlohmann/detail/meta/std_fs.hpp>
|
||||||
@@ -43,6 +42,10 @@ namespace detail
|
|||||||
* j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an
|
* j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an
|
||||||
* allocated value (e.g., a string). See bug issue
|
* allocated value (e.g., a string). See bug issue
|
||||||
* https://github.com/nlohmann/json/issues/2865 for more information.
|
* https://github.com/nlohmann/json/issues/2865 for more information.
|
||||||
|
*
|
||||||
|
* A value that has to be allocated is created before the old one is destroyed:
|
||||||
|
* were it the other way around, an exception while creating the new value would
|
||||||
|
* leave j with the type of the new value, but the pointer to the destroyed old one.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
template<value_t> struct external_constructor;
|
template<value_t> struct external_constructor;
|
||||||
@@ -66,18 +69,20 @@ struct external_constructor<value_t::string>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(s);
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::string;
|
j.m_data.m_type = value_t::string;
|
||||||
j.m_data.m_value = s;
|
j.m_data.m_value = value;
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
|
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(std::move(s));
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::string;
|
j.m_data.m_type = value_t::string;
|
||||||
j.m_data.m_value = std::move(s);
|
j.m_data.m_value = value;
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +91,10 @@ struct external_constructor<value_t::string>
|
|||||||
int > = 0 >
|
int > = 0 >
|
||||||
static void construct(BasicJsonType& j, const CompatibleStringType& str)
|
static void construct(BasicJsonType& j, const CompatibleStringType& str)
|
||||||
{
|
{
|
||||||
|
auto* created = j.template create<typename BasicJsonType::string_t>(str);
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::string;
|
j.m_data.m_type = value_t::string;
|
||||||
j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
|
j.m_data.m_value.string = created;
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -99,18 +105,20 @@ struct external_constructor<value_t::binary>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(b);
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::binary;
|
j.m_data.m_type = value_t::binary;
|
||||||
j.m_data.m_value = typename BasicJsonType::binary_t(b);
|
j.m_data.m_value = value;
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
|
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(std::move(b));
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::binary;
|
j.m_data.m_type = value_t::binary;
|
||||||
j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));
|
j.m_data.m_value = value;
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -160,9 +168,10 @@ struct external_constructor<value_t::array>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(arr);
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::array;
|
j.m_data.m_type = value_t::array;
|
||||||
j.m_data.m_value = arr;
|
j.m_data.m_value = value;
|
||||||
j.set_parents();
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
@@ -170,9 +179,10 @@ struct external_constructor<value_t::array>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
|
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(std::move(arr));
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::array;
|
j.m_data.m_type = value_t::array;
|
||||||
j.m_data.m_value = std::move(arr);
|
j.m_data.m_value = value;
|
||||||
j.set_parents();
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
@@ -188,9 +198,10 @@ struct external_constructor<value_t::array>
|
|||||||
using std::begin;
|
using std::begin;
|
||||||
using std::end;
|
using std::end;
|
||||||
|
|
||||||
|
auto* created = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::array;
|
j.m_data.m_type = value_t::array;
|
||||||
j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
|
j.m_data.m_value.array = created;
|
||||||
j.set_parents();
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
@@ -198,15 +209,17 @@ struct external_constructor<value_t::array>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
||||||
{
|
{
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
typename BasicJsonType::array_t elements;
|
||||||
j.m_data.m_type = value_t::array;
|
elements.reserve(arr.size());
|
||||||
j.m_data.m_value = value_t::array;
|
|
||||||
j.m_data.m_value.array->reserve(arr.size());
|
|
||||||
for (const bool x : arr)
|
for (const bool x : arr)
|
||||||
{
|
{
|
||||||
j.m_data.m_value.array->push_back(x);
|
elements.push_back(x);
|
||||||
j.set_parent(j.m_data.m_value.array->back());
|
|
||||||
}
|
}
|
||||||
|
const typename BasicJsonType::json_value value(std::move(elements));
|
||||||
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
|
j.m_data.m_type = value_t::array;
|
||||||
|
j.m_data.m_value = value;
|
||||||
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,11 +227,12 @@ struct external_constructor<value_t::array>
|
|||||||
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
||||||
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
|
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
|
||||||
{
|
{
|
||||||
|
typename BasicJsonType::array_t elements(arr.size());
|
||||||
|
std::copy(std::begin(arr), std::end(arr), elements.begin());
|
||||||
|
const typename BasicJsonType::json_value value(std::move(elements));
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::array;
|
j.m_data.m_type = value_t::array;
|
||||||
j.m_data.m_value = value_t::array;
|
j.m_data.m_value = value;
|
||||||
j.m_data.m_value.array->resize(arr.size());
|
|
||||||
std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());
|
|
||||||
j.set_parents();
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
@@ -228,13 +242,15 @@ struct external_constructor<value_t::array>
|
|||||||
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
|
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
|
||||||
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
|
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
|
||||||
{
|
{
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
typename BasicJsonType::array_t elements;
|
||||||
j.m_data.m_type = value_t::array;
|
|
||||||
j.m_data.m_value = value_t::array;
|
|
||||||
for (auto&& x : std::forward<CompatibleArrayType>(arr))
|
for (auto&& x : std::forward<CompatibleArrayType>(arr))
|
||||||
{
|
{
|
||||||
j.m_data.m_value.array->push_back(x);
|
elements.push_back(x);
|
||||||
}
|
}
|
||||||
|
const typename BasicJsonType::json_value value(std::move(elements));
|
||||||
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
|
j.m_data.m_type = value_t::array;
|
||||||
|
j.m_data.m_value = value;
|
||||||
// set the parents only once all elements are in place: a push_back
|
// set the parents only once all elements are in place: a push_back
|
||||||
// that reallocates moves the earlier elements, which does not keep
|
// that reallocates moves the earlier elements, which does not keep
|
||||||
// their parent pointers
|
// their parent pointers
|
||||||
@@ -250,9 +266,10 @@ struct external_constructor<value_t::object>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(obj);
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::object;
|
j.m_data.m_type = value_t::object;
|
||||||
j.m_data.m_value = obj;
|
j.m_data.m_value = value;
|
||||||
j.set_parents();
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
@@ -260,9 +277,10 @@ struct external_constructor<value_t::object>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
||||||
{
|
{
|
||||||
|
const typename BasicJsonType::json_value value(std::move(obj));
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::object;
|
j.m_data.m_type = value_t::object;
|
||||||
j.m_data.m_value = std::move(obj);
|
j.m_data.m_value = value;
|
||||||
j.set_parents();
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
@@ -274,9 +292,10 @@ struct external_constructor<value_t::object>
|
|||||||
using std::begin;
|
using std::begin;
|
||||||
using std::end;
|
using std::end;
|
||||||
|
|
||||||
|
auto* created = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
|
||||||
j.m_data.m_value.destroy(j.m_data.m_type);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.m_data.m_type = value_t::object;
|
j.m_data.m_type = value_t::object;
|
||||||
j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
|
j.m_data.m_value.object = created;
|
||||||
j.set_parents();
|
j.set_parents();
|
||||||
j.assert_invariant();
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
@@ -385,9 +404,6 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
|||||||
!is_basic_json<CompatibleArrayType>::value
|
!is_basic_json<CompatibleArrayType>::value
|
||||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||||
&& !is_compatible_range_view<CompatibleArrayType>::value
|
&& !is_compatible_range_view<CompatibleArrayType>::value
|
||||||
#endif
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
&& !is_enum_keyed_map<CompatibleArrayType>::value
|
|
||||||
#endif
|
#endif
|
||||||
,
|
,
|
||||||
int > = 0 >
|
int > = 0 >
|
||||||
@@ -442,33 +458,6 @@ inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
|
|||||||
external_constructor<value_t::object>::construct(j, obj);
|
external_constructor<value_t::object>::construct(j, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
// store a map with enum keys as an object, using the enum's own to_json for the
|
|
||||||
// keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); without the macro, such maps
|
|
||||||
// are stored as arrays of [key, value] pairs
|
|
||||||
template < typename BasicJsonType, typename EnumKeyedMap,
|
|
||||||
enable_if_t < is_enum_keyed_map<EnumKeyedMap>::value&& !is_basic_json<EnumKeyedMap>::value, int > = 0 >
|
|
||||||
inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
|
||||||
{
|
|
||||||
typename BasicJsonType::object_t obj;
|
|
||||||
for (const auto& p : map)
|
|
||||||
{
|
|
||||||
BasicJsonType key = p.first;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!key.is_string()))
|
|
||||||
{
|
|
||||||
JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>();
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second))
|
|
||||||
{
|
|
||||||
JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
external_constructor<value_t::object>::construct(j, std::move(obj));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,6 @@
|
|||||||
#undef JSON_PRECISE_STREAM_POSITION
|
#undef JSON_PRECISE_STREAM_POSITION
|
||||||
#undef JSON_STRICT_NUL_HANDLING
|
#undef JSON_STRICT_NUL_HANDLING
|
||||||
#undef JSON_STRICT_BINARY_UTF8
|
#undef JSON_STRICT_BINARY_UTF8
|
||||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||||
|
|||||||
@@ -438,30 +438,6 @@ template<typename BasicJsonType, typename CompatibleObjectType>
|
|||||||
struct is_compatible_object_type
|
struct is_compatible_object_type
|
||||||
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
|
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
using insert_result_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()));
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
using insert_result_second_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()).second);
|
|
||||||
|
|
||||||
// a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see
|
|
||||||
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
template<typename T, typename = void>
|
|
||||||
struct is_enum_keyed_map : std::false_type {};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct is_enum_keyed_map <
|
|
||||||
T, enable_if_t < is_detected<mapped_type_t, T>::value&&
|
|
||||||
is_detected<key_type_t, T>::value >>
|
|
||||||
{
|
|
||||||
// maps with non-unique keys (std::multimap, std::unordered_multimap, ...)
|
|
||||||
// are excluded, because an object cannot hold duplicate keys; they are
|
|
||||||
// detected by insert() returning an iterator instead of a pair<iterator, bool>
|
|
||||||
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
|
|
||||||
static constexpr bool value = std::is_enum<typename T::key_type>::value &&
|
|
||||||
!(is_detected<insert_result_t, T>::value && !is_detected<insert_result_second_t, T>::value);
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||||
typename = void>
|
typename = void>
|
||||||
struct is_constructible_object_type_impl : std::false_type {};
|
struct is_constructible_object_type_impl : std::false_type {};
|
||||||
|
|||||||
+80
-186
@@ -612,208 +612,100 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// constructor for rvalue binary arrays (internal type)
|
/// constructor for rvalue binary arrays (internal type)
|
||||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||||
|
|
||||||
// raw, allocation-free transfer of m_data from src to dst: no
|
void destroy(value_t t)
|
||||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
|
||||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
|
||||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
|
||||||
static void take(basic_json& dst, basic_json& src) noexcept
|
|
||||||
{
|
|
||||||
dst.m_data.m_type = src.m_data.m_type;
|
|
||||||
dst.m_data.m_value = src.m_data.m_value;
|
|
||||||
src.m_data.m_type = value_t::null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// true if v is not an array/object, or is an already-empty one
|
|
||||||
static bool has_no_children(const basic_json& v) noexcept
|
|
||||||
{
|
|
||||||
switch (v.m_data.m_type)
|
|
||||||
{
|
|
||||||
case value_t::array:
|
|
||||||
return v.m_data.m_value.array->empty();
|
|
||||||
case value_t::object:
|
|
||||||
return v.m_data.m_value.object->empty();
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static basic_json& last_child(basic_json& v)
|
|
||||||
{
|
|
||||||
if (v.m_data.m_type == value_t::array)
|
|
||||||
{
|
|
||||||
return v.m_data.m_value.array->back();
|
|
||||||
}
|
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
|
||||||
return v.m_data.m_value.object->rbegin()->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// removes the last child of a non-empty array/object v; this never
|
|
||||||
// allocates, and since it is only ever called when that child is a
|
|
||||||
// scalar or an already-empty array/object, destroying it never
|
|
||||||
// recurses more than one level deep (see destroy() below)
|
|
||||||
static void pop_last_child(basic_json& v)
|
|
||||||
{
|
|
||||||
if (v.m_data.m_type == value_t::array)
|
|
||||||
{
|
|
||||||
v.m_data.m_value.array->pop_back();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
|
||||||
// erase() needs a forward iterator, so std::prev(end()) is
|
|
||||||
// used here rather than rbegin() (see last_child() above)
|
|
||||||
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deallocates the (already empty) array/object held by v; this is
|
|
||||||
// the same allocator-based free the old recursive implementation
|
|
||||||
// used, just factored out so every level of the walk in destroy()
|
|
||||||
// can share it
|
|
||||||
static void free_container(basic_json& v) noexcept
|
|
||||||
{
|
|
||||||
if (v.m_data.m_type == value_t::array)
|
|
||||||
{
|
|
||||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
|
||||||
AllocatorType<array_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
|
||||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
|
||||||
AllocatorType<object_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
|
||||||
}
|
|
||||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy_string() noexcept
|
|
||||||
{
|
|
||||||
if (string == nullptr)
|
|
||||||
{
|
|
||||||
// not initialized (e.g., due to exception in the ctor)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AllocatorType<string_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy_binary() noexcept
|
|
||||||
{
|
|
||||||
if (binary == nullptr)
|
|
||||||
{
|
|
||||||
// not initialized (e.g., due to exception in the ctor)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AllocatorType<binary_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// t must be value_t::array or value_t::object
|
|
||||||
void destroy_container(value_t t) noexcept
|
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
(t == value_t::object && object == nullptr) ||
|
(t == value_t::object && object == nullptr) ||
|
||||||
(t == value_t::array && array == nullptr)
|
(t == value_t::array && array == nullptr) ||
|
||||||
|
(t == value_t::string && string == nullptr) ||
|
||||||
|
(t == value_t::binary && binary == nullptr)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// not initialized (e.g., due to exception in the ctor)
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (t == value_t::array || t == value_t::object)
|
||||||
// Destroy the tree without recursing per nesting level and
|
|
||||||
// without any heap allocation: a heap-allocated flattening
|
|
||||||
// stack (the previous implementation) can itself throw
|
|
||||||
// bad_alloc, which would escape this noexcept destructor and
|
|
||||||
// terminate the program (#5135).
|
|
||||||
//
|
|
||||||
// Instead, walk down the "last child" chain, reversing links
|
|
||||||
// as we go: cur is the container currently being emptied,
|
|
||||||
// and prev is its parent (value_t::null when there is none).
|
|
||||||
// Each parent's last child slot doubles as storage for that
|
|
||||||
// parent's own parent link while we are below it, so no
|
|
||||||
// extra memory is needed. We only ever remove a child once
|
|
||||||
// it is a scalar or an empty array/object, which neither
|
|
||||||
// allocates nor recurses more than one level deep.
|
|
||||||
//
|
|
||||||
// This json_value is not itself a basic_json, so the
|
|
||||||
// top-level container is first moved into a local stand-in
|
|
||||||
// ("cur"); a default-constructed basic_json has a null
|
|
||||||
// pointer in its m_value (see data::m_value's initializer),
|
|
||||||
// so swapping it with *this leaves this union's own pointer
|
|
||||||
// null, and it is never looked at or freed a second time.
|
|
||||||
basic_json cur;
|
|
||||||
cur.m_data.m_type = t;
|
|
||||||
using std::swap;
|
|
||||||
swap(cur.m_data.m_value, *this);
|
|
||||||
|
|
||||||
basic_json prev; // value_t::null: no parent
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
if (has_no_children(cur))
|
// flatten the current json_value to a heap-allocated stack
|
||||||
|
std::vector<basic_json> stack;
|
||||||
|
|
||||||
|
// move the top-level items to stack
|
||||||
|
if (t == value_t::array)
|
||||||
{
|
{
|
||||||
if (prev.m_data.m_type == value_t::null)
|
stack.reserve(array->size());
|
||||||
{
|
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||||
free_container(cur);
|
|
||||||
return; // back at the top with nothing left to do
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// ascend: detach the grandparent link from prev's
|
|
||||||
// last slot, drop that (now null) slot, free cur
|
|
||||||
// (it is empty), then move up one level
|
|
||||||
basic_json gp;
|
|
||||||
take(gp, last_child(prev));
|
|
||||||
pop_last_child(prev);
|
|
||||||
|
|
||||||
free_container(cur);
|
|
||||||
|
|
||||||
take(cur, prev);
|
|
||||||
take(prev, gp);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
basic_json& cur_last_ref = last_child(cur);
|
|
||||||
|
|
||||||
if (has_no_children(cur_last_ref))
|
|
||||||
{
|
{
|
||||||
// scalar, or already-empty array/object
|
stack.reserve(object->size());
|
||||||
pop_last_child(cur);
|
for (auto&& it : *object)
|
||||||
continue;
|
{
|
||||||
}
|
stack.push_back(std::move(it.second));
|
||||||
|
|
||||||
// descend into the non-empty last child, reversing the
|
|
||||||
// link: its slot takes over prev, and the child becomes
|
|
||||||
// the new cur
|
|
||||||
basic_json tmp;
|
|
||||||
take(tmp, cur_last_ref);
|
|
||||||
take(cur_last_ref, prev);
|
|
||||||
take(prev, cur);
|
|
||||||
take(cur, tmp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy(value_t t)
|
while (!stack.empty())
|
||||||
{
|
{
|
||||||
|
// move the last item to a local variable to be processed
|
||||||
|
basic_json current_item(std::move(stack.back()));
|
||||||
|
stack.pop_back();
|
||||||
|
|
||||||
|
// if current_item is array/object, move
|
||||||
|
// its children to the stack to be processed later
|
||||||
|
if (current_item.is_array())
|
||||||
|
{
|
||||||
|
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||||
|
|
||||||
|
current_item.m_data.m_value.array->clear();
|
||||||
|
}
|
||||||
|
else if (current_item.is_object())
|
||||||
|
{
|
||||||
|
for (auto&& it : *current_item.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
stack.push_back(std::move(it.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
current_item.m_data.m_value.object->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's now safe that current_item gets destructed
|
||||||
|
// since it doesn't have any children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::object:
|
||||||
destroy_string();
|
{
|
||||||
|
AllocatorType<object_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
AllocatorType<array_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::string:
|
||||||
|
{
|
||||||
|
AllocatorType<string_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
destroy_binary();
|
{
|
||||||
break;
|
AllocatorType<binary_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||||
case value_t::object:
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||||
case value_t::array:
|
|
||||||
destroy_container(t);
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
@@ -822,9 +714,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -2162,8 +2056,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
if (is_an_object)
|
if (is_an_object)
|
||||||
{
|
{
|
||||||
// the initializer list is a list of pairs -> create an object
|
// the initializer list is a list of pairs -> create an object
|
||||||
m_data.m_type = value_t::object;
|
|
||||||
m_data.m_value = value_t::object;
|
m_data.m_value = value_t::object;
|
||||||
|
m_data.m_type = value_t::object;
|
||||||
|
|
||||||
for (auto& element_ref : init)
|
for (auto& element_ref : init)
|
||||||
{
|
{
|
||||||
@@ -2185,8 +2079,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// the initializer list describes an array -> create an array
|
// the initializer list describes an array -> create an array
|
||||||
m_data.m_type = value_t::array;
|
|
||||||
m_data.m_value.array = create<array_t>(init.begin(), init.end());
|
m_data.m_value.array = create<array_t>(init.begin(), init.end());
|
||||||
|
m_data.m_type = value_t::array;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_parents();
|
set_parents();
|
||||||
@@ -2199,8 +2093,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
static basic_json binary(const typename binary_t::container_type& init)
|
static basic_json binary(const typename binary_t::container_type& init)
|
||||||
{
|
{
|
||||||
auto res = basic_json();
|
auto res = basic_json();
|
||||||
res.m_data.m_type = value_t::binary;
|
|
||||||
res.m_data.m_value = init;
|
res.m_data.m_value = init;
|
||||||
|
res.m_data.m_type = value_t::binary;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2210,8 +2104,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
|
static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)
|
||||||
{
|
{
|
||||||
auto res = basic_json();
|
auto res = basic_json();
|
||||||
res.m_data.m_type = value_t::binary;
|
|
||||||
res.m_data.m_value = binary_t(init, subtype);
|
res.m_data.m_value = binary_t(init, subtype);
|
||||||
|
res.m_data.m_type = value_t::binary;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2221,8 +2115,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
static basic_json binary(typename binary_t::container_type&& init)
|
static basic_json binary(typename binary_t::container_type&& init)
|
||||||
{
|
{
|
||||||
auto res = basic_json();
|
auto res = basic_json();
|
||||||
res.m_data.m_type = value_t::binary;
|
|
||||||
res.m_data.m_value = std::move(init);
|
res.m_data.m_value = std::move(init);
|
||||||
|
res.m_data.m_type = value_t::binary;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2232,8 +2126,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
|
static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)
|
||||||
{
|
{
|
||||||
auto res = basic_json();
|
auto res = basic_json();
|
||||||
res.m_data.m_type = value_t::binary;
|
|
||||||
res.m_data.m_value = binary_t(std::move(init), subtype);
|
res.m_data.m_value = binary_t(std::move(init), subtype);
|
||||||
|
res.m_data.m_type = value_t::binary;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+339
-599
File diff suppressed because it is too large
Load Diff
@@ -67,10 +67,6 @@
|
|||||||
#define JSON_STRICT_BINARY_UTF8 0
|
#define JSON_STRICT_BINARY_UTF8 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
#else
|
#else
|
||||||
@@ -113,20 +109,14 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
|
|
||||||
#else
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -136,8 +126,7 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
|
||||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
|
|
||||||
|
|
||||||
// Construct the namespace version component
|
// Construct the namespace version component
|
||||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
|||||||
@@ -140,6 +140,11 @@ json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
|
|||||||
# only the #972 regression test needs thirdparty/fifo_map on its include path
|
# only the #972 regression test needs thirdparty/fifo_map on its include path
|
||||||
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
|
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
|
||||||
|
|
||||||
|
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742)
|
||||||
|
json_test_set_test_options(test-diagnostics-optimized
|
||||||
|
COMPILE_OPTIONS $<$<CXX_COMPILER_ID:GNU>:-O3 -Werror=array-bounds>
|
||||||
|
)
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# add unit tests
|
# add unit tests
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ TEST_CASE("default namespace")
|
|||||||
expected += "_sbu8";
|
expected += "_sbu8";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
expected += "_ekmo";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
||||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
||||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ TEST_CASE("default namespace without version component")
|
|||||||
expected += "_sbu8";
|
expected += "_sbu8";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
expected += "_ekmo";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
expected += "::basic_json";
|
expected += "::basic_json";
|
||||||
|
|
||||||
// fallback for Clang
|
// fallback for Clang
|
||||||
|
|||||||
+156
-66
@@ -12,6 +12,11 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <valarray>
|
||||||
|
#if JSON_HAS_RANGES
|
||||||
|
#include <ranges>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// special test case to check if memory is leaked if constructor throws
|
// special test case to check if memory is leaked if constructor throws
|
||||||
@@ -603,87 +608,172 @@ TEST_CASE("bad my_allocator::construct")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
||||||
|
// next_construct_fails set for the next allocation outside a check
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
TEST_CASE("a failed allocation leaves the value unchanged")
|
||||||
{
|
{
|
||||||
std::size_t counting_allocator_allocations = 0;
|
// create JSON type using the throwing allocator
|
||||||
std::size_t counting_allocator_deallocations = 0;
|
using my_json = nlohmann::basic_json<std::map,
|
||||||
|
|
||||||
template<class T>
|
|
||||||
struct counting_allocator : std::allocator<T>
|
|
||||||
{
|
|
||||||
using std::allocator<T>::allocator;
|
|
||||||
|
|
||||||
T* allocate(std::size_t n)
|
|
||||||
{
|
|
||||||
++counting_allocator_allocations;
|
|
||||||
return std::allocator<T>::allocate(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deallocate(T* p, std::size_t n)
|
|
||||||
{
|
|
||||||
++counting_allocator_deallocations;
|
|
||||||
std::allocator<T>::deallocate(p, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class U>
|
|
||||||
struct rebind
|
|
||||||
{
|
|
||||||
using other = counting_allocator<U>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("destructor performs no allocation, only deallocation")
|
|
||||||
{
|
|
||||||
// see https://github.com/nlohmann/json/issues/4842 and
|
|
||||||
// https://github.com/nlohmann/json/issues/5135: destroying nested
|
|
||||||
// arrays/objects used to allocate a temporary stack (first with
|
|
||||||
// std::allocator, later - after #4842 - with the provided allocator).
|
|
||||||
// Since that stack could itself throw bad_alloc from inside the
|
|
||||||
// noexcept destructor (#5135), destroy() no longer allocates anything:
|
|
||||||
// it only ever frees what is already there.
|
|
||||||
using counting_json = nlohmann::basic_json<std::map,
|
|
||||||
std::vector,
|
std::vector,
|
||||||
std::string,
|
std::string,
|
||||||
bool,
|
bool,
|
||||||
std::int64_t,
|
std::int64_t,
|
||||||
std::uint64_t,
|
std::uint64_t,
|
||||||
double,
|
double,
|
||||||
counting_allocator>;
|
my_allocator>;
|
||||||
|
|
||||||
SECTION("array")
|
// Each of these creates a string, array, object, or binary value. The
|
||||||
|
// value must be created before the type is changed: otherwise, a failed
|
||||||
|
// creation left a value of the new type without anything behind it (an
|
||||||
|
// assertion in its destructor, a null pointer everywhere else) or, when
|
||||||
|
// an old value was destroyed first, with a pointer to that destroyed one.
|
||||||
|
|
||||||
|
SECTION("creating a binary value")
|
||||||
{
|
{
|
||||||
auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory)
|
const std::vector<std::uint8_t> bytes = {1, 2, 3};
|
||||||
const auto allocations_before = counting_allocator_allocations;
|
my_json _;
|
||||||
const auto deallocations_before = counting_allocator_deallocations;
|
|
||||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
next_construct_fails = true;
|
||||||
CHECK(counting_allocator_allocations == allocations_before);
|
CHECK_THROWS_AS(_ = my_json::binary(bytes), std::bad_alloc&);
|
||||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(_ = my_json::binary(bytes, 42), std::bad_alloc&);
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes)), std::bad_alloc&);
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(_ = my_json::binary(std::vector<std::uint8_t>(bytes), 42), std::bad_alloc&);
|
||||||
|
next_construct_fails = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("object")
|
SECTION("turning a null value into an array or object")
|
||||||
{
|
{
|
||||||
auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory)
|
my_json j;
|
||||||
const auto allocations_before = counting_allocator_allocations;
|
|
||||||
const auto deallocations_before = counting_allocator_deallocations;
|
next_construct_fails = true;
|
||||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
CHECK_THROWS_AS(j[0], std::bad_alloc&);
|
||||||
CHECK(counting_allocator_allocations == allocations_before);
|
CHECK(j.is_null());
|
||||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
const my_json one = 1;
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j.push_back(one), std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j.push_back(my_json::object_t::value_type("key", 1)), std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j.emplace_back(1), std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j.emplace("key", 1), std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
const my_json object = {{"key", 1}};
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j.update(object), std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
next_construct_fails = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("mixed tree of empty/non-empty arrays and objects")
|
// With iterator debugging, VS 2015's containers construct a proxy with the
|
||||||
|
// allocator in constructors that cannot report its failure, so a failing
|
||||||
|
// allocator crashes this section there (SIGSEGV with VS 2015 Debug x86).
|
||||||
|
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||||
|
SECTION("converting into an existing value")
|
||||||
{
|
{
|
||||||
auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory)
|
// to_json replaces the value it is given; the old one must survive a
|
||||||
|
// failed creation of the new one
|
||||||
|
my_json j = "old";
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::string("new")), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<int> {1, 2}), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::vector<bool> {true, false}), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::map<std::string, int> {{"a", 1}}), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, my_json::binary_t({1, 2})), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
// the overloads for lvalues of the value types, for the value types
|
||||||
|
// themselves, and for the remaining compatible types
|
||||||
|
const std::string string = "new";
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, string), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, "new"), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
// to_json only moves a binary value that it converted from another
|
||||||
|
// container type, which my_json's std::vector<std::uint8_t> is not
|
||||||
|
using binary_constructor = nlohmann::detail::external_constructor<nlohmann::detail::value_t::binary>;
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(binary_constructor::construct(j, my_json::binary_t({1, 2})), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
my_json::array_t array = {1, 2};
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, array), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(array)), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
my_json::object_t object = {{"a", 1}};
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, object), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::move(object)), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
|
const std::vector<int> numbers = {1, 2};
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||||
{
|
{
|
||||||
{"empty_obj", counting_json::object()},
|
return true;
|
||||||
{"empty_arr", counting_json::array()},
|
})), std::bad_alloc&);
|
||||||
{"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}},
|
CHECK(j == "old");
|
||||||
{"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})}
|
#endif
|
||||||
});
|
|
||||||
const auto allocations_before = counting_allocator_allocations;
|
next_construct_fails = false;
|
||||||
const auto deallocations_before = counting_allocator_deallocations;
|
nlohmann::to_json(j, std::vector<int> {1, 2});
|
||||||
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
CHECK(j == my_json({1, 2}));
|
||||||
CHECK(counting_allocator_allocations == allocations_before);
|
|
||||||
CHECK(counting_allocator_deallocations > deallocations_before);
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -11,12 +11,7 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
|
||||||
TEST_CASE("Binary Formats" * doctest::skip())
|
TEST_CASE("Binary Formats" * doctest::skip())
|
||||||
@@ -229,139 +224,3 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
|||||||
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// the binary formats as function pointers for "Binary formats with narrow number types";
|
|
||||||
// named functions rather than lambdas, because clang 3.5 cannot convert a lambda
|
|
||||||
// to a function pointer in the braced initializer of the format table
|
|
||||||
using narrow_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
|
|
||||||
using bytes = std::vector<std::uint8_t>;
|
|
||||||
|
|
||||||
bytes encode_cbor(const json& j)
|
|
||||||
{
|
|
||||||
return json::to_cbor(j);
|
|
||||||
}
|
|
||||||
narrow_json decode_cbor(const bytes& v, bool allow_exceptions)
|
|
||||||
{
|
|
||||||
return narrow_json::from_cbor(v, true, allow_exceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes encode_msgpack(const json& j)
|
|
||||||
{
|
|
||||||
return json::to_msgpack(j);
|
|
||||||
}
|
|
||||||
narrow_json decode_msgpack(const bytes& v, bool allow_exceptions)
|
|
||||||
{
|
|
||||||
return narrow_json::from_msgpack(v, true, allow_exceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes encode_ubjson(const json& j)
|
|
||||||
{
|
|
||||||
return json::to_ubjson(j);
|
|
||||||
}
|
|
||||||
narrow_json decode_ubjson(const bytes& v, bool allow_exceptions)
|
|
||||||
{
|
|
||||||
return narrow_json::from_ubjson(v, true, allow_exceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes encode_bjdata(const json& j)
|
|
||||||
{
|
|
||||||
return json::to_bjdata(j);
|
|
||||||
}
|
|
||||||
narrow_json decode_bjdata(const bytes& v, bool allow_exceptions)
|
|
||||||
{
|
|
||||||
return narrow_json::from_bjdata(v, true, allow_exceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BSON can only store numbers as object members
|
|
||||||
bytes encode_bson(const json& j)
|
|
||||||
{
|
|
||||||
return json::to_bson(json{{"a", j}});
|
|
||||||
}
|
|
||||||
narrow_json decode_bson(const bytes& v, bool allow_exceptions)
|
|
||||||
{
|
|
||||||
const auto result = narrow_json::from_bson(v, true, allow_exceptions);
|
|
||||||
return result.is_discarded() ? result : result.at("a");
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes encode_bon8(const json& j)
|
|
||||||
{
|
|
||||||
return json::to_bon8(j);
|
|
||||||
}
|
|
||||||
narrow_json decode_bon8(const bytes& v, bool allow_exceptions)
|
|
||||||
{
|
|
||||||
return narrow_json::from_bon8(v, true, allow_exceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("Binary formats with narrow number types")
|
|
||||||
{
|
|
||||||
// Numbers that do not fit the number types are handled like the lexer
|
|
||||||
// handles them in JSON text: an integer that fits neither integer type is
|
|
||||||
// stored as a floating-point number, and a finite floating-point number
|
|
||||||
// that overflows number_float_t is rejected with out_of_range.406.
|
|
||||||
struct binary_format
|
|
||||||
{
|
|
||||||
const char* name;
|
|
||||||
bytes (*encode)(const json&);
|
|
||||||
narrow_json (*decode)(const bytes&, bool);
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<binary_format> formats =
|
|
||||||
{
|
|
||||||
{"CBOR", encode_cbor, decode_cbor},
|
|
||||||
{"MessagePack", encode_msgpack, decode_msgpack},
|
|
||||||
{"UBJSON", encode_ubjson, decode_ubjson},
|
|
||||||
{"BJData", encode_bjdata, decode_bjdata},
|
|
||||||
{"BSON", encode_bson, decode_bson},
|
|
||||||
{"BON8", encode_bon8, decode_bon8},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& format : formats)
|
|
||||||
{
|
|
||||||
const std::string name = format.name;
|
|
||||||
INFO("format := ", name);
|
|
||||||
const auto roundtrip = [&format](const json & j)
|
|
||||||
{
|
|
||||||
return format.decode(format.encode(j), true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// integers that fit keep their type
|
|
||||||
CHECK(roundtrip(json(-5)).is_number_integer());
|
|
||||||
CHECK(roundtrip(json(-5)).get<std::int32_t>() == -5);
|
|
||||||
CHECK(roundtrip(json(3000000000u)).is_number_unsigned());
|
|
||||||
CHECK(roundtrip(json(3000000000u)).get<std::uint32_t>() == 3000000000u);
|
|
||||||
|
|
||||||
// integers that fit neither integer type are stored as float
|
|
||||||
CHECK(roundtrip(json(5000000000u)).is_number_float());
|
|
||||||
CHECK(roundtrip(json(5000000000u)).get<float>() == 5000000000.0f);
|
|
||||||
if (name != "BON8") // BON8 cannot encode integers above INT64_MAX
|
|
||||||
{
|
|
||||||
CHECK(roundtrip(json(10000000000000000000u)).is_number_float());
|
|
||||||
CHECK(roundtrip(json(10000000000000000000u)).get<float>() == 10000000000000000000.0f);
|
|
||||||
}
|
|
||||||
CHECK(roundtrip(json(-3000000000LL)).is_number_float());
|
|
||||||
CHECK(roundtrip(json(-3000000000LL)).get<float>() == -3000000000.0f);
|
|
||||||
CHECK(roundtrip(json(-5000000000LL)).is_number_float());
|
|
||||||
CHECK(roundtrip(json(-5000000000LL)).get<float>() == -5000000000.0f);
|
|
||||||
|
|
||||||
// floating-point numbers that fit
|
|
||||||
CHECK(roundtrip(json(1.5)).get<float>() == 1.5f);
|
|
||||||
const auto just_above_max = std::nextafter(static_cast<double>((std::numeric_limits<float>::max)()),
|
|
||||||
std::numeric_limits<double>::infinity());
|
|
||||||
CHECK(roundtrip(json(just_above_max)).get<float>() == (std::numeric_limits<float>::max)());
|
|
||||||
|
|
||||||
// infinity and NaN are passed on
|
|
||||||
CHECK(std::isinf(roundtrip(json(std::numeric_limits<double>::infinity())).get<float>()));
|
|
||||||
CHECK(std::isnan(roundtrip(json(std::numeric_limits<double>::quiet_NaN())).get<float>()));
|
|
||||||
|
|
||||||
// finite floating-point numbers that overflow number_float_t are rejected
|
|
||||||
const std::string message = "[json.exception.out_of_range.406] syntax error while parsing " + name
|
|
||||||
+ " value: number overflow";
|
|
||||||
CHECK_THROWS_WITH_AS(roundtrip(json(1e300)), message.c_str(), narrow_json::out_of_range&);
|
|
||||||
CHECK_THROWS_WITH_AS(roundtrip(json(-1e300)), message.c_str(), narrow_json::out_of_range&);
|
|
||||||
CHECK(format.decode(format.encode(json(1e300)), false).is_discarded());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+14
-16
@@ -3185,8 +3185,7 @@ TEST_CASE("Tagged values")
|
|||||||
// CBOR encodes negative integers as: result = -1 - n
|
// CBOR encodes negative integers as: result = -1 - n
|
||||||
// For type 0x3B, n is an 8-byte uint64_t. Valid range for n with
|
// For type 0x3B, n is an 8-byte uint64_t. Valid range for n with
|
||||||
// the default int64_t is [0, INT64_MAX], producing results in [INT64_MIN, -1].
|
// the default int64_t is [0, INT64_MAX], producing results in [INT64_MIN, -1].
|
||||||
// When n > INT64_MAX, the result exceeds int64_t range and is stored
|
// When n > INT64_MAX, the result exceeds int64_t range and is rejected.
|
||||||
// as a floating-point number, as the lexer does for JSON text.
|
|
||||||
|
|
||||||
SECTION("n = 0 is valid (result = -1)")
|
SECTION("n = 0 is valid (result = -1)")
|
||||||
{
|
{
|
||||||
@@ -3207,34 +3206,33 @@ TEST_CASE("Tagged values")
|
|||||||
CHECK(result.get<int64_t>() == (std::numeric_limits<int64_t>::min)());
|
CHECK(result.get<int64_t>() == (std::numeric_limits<int64_t>::min)());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("n = INT64_MAX + 1 is stored as float")
|
SECTION("n = INT64_MAX + 1 is rejected (overflow)")
|
||||||
{
|
{
|
||||||
// n = INT64_MAX + 1 (0x8000000000000000)
|
// n = INT64_MAX + 1 (0x8000000000000000)
|
||||||
// result = -1 - n = -9223372036854775809, which exceeds int64_t range;
|
// result = -1 - n = -9223372036854775809, which exceeds int64_t range
|
||||||
// the nearest double is -9223372036854775808.0
|
|
||||||
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
const auto result = json::from_cbor(input);
|
json _;
|
||||||
CHECK(result.is_number_float());
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
|
||||||
CHECK(result.get<double>() == -9223372036854775808.0);
|
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
|
||||||
CHECK(result == json::parse("-9223372036854775809"));
|
json::parse_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("n = UINT64_MAX is stored as float")
|
SECTION("n = UINT64_MAX is rejected (overflow)")
|
||||||
{
|
{
|
||||||
// n = UINT64_MAX (0xFFFFFFFFFFFFFFFF)
|
// n = UINT64_MAX (0xFFFFFFFFFFFFFFFF)
|
||||||
// result = -1 - n = -18446744073709551616, which exceeds int64_t range
|
// result = -1 - n = -18446744073709551616, which exceeds int64_t range
|
||||||
const std::vector<uint8_t> input = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
const std::vector<uint8_t> input = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
const auto result = json::from_cbor(input);
|
json _;
|
||||||
CHECK(result.is_number_float());
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
|
||||||
CHECK(result.get<double>() == -18446744073709551616.0);
|
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
|
||||||
CHECK(result == json::parse("-18446744073709551616"));
|
json::parse_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("overflow with allow_exceptions=false is not an error")
|
SECTION("overflow with allow_exceptions=false returns discarded")
|
||||||
{
|
{
|
||||||
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
const auto result = json::from_cbor(input, true, false);
|
const auto result = json::from_cbor(input, true, false);
|
||||||
CHECK(result.is_number_float());
|
CHECK(result.is_discarded());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// Regression test for https://github.com/nlohmann/json/issues/5742: with
|
||||||
|
// JSON_DIAGNOSTICS, GCC (12 to at least 16) reported a false -Warray-bounds
|
||||||
|
// error in the inlined set_parents() at -O3. The type of a new string was set
|
||||||
|
// before the string was allocated, so GCC had to assume that operator new
|
||||||
|
// could change it again and checked the object branch of set_parents()
|
||||||
|
// against the string's allocation. Setting the type after creating the value
|
||||||
|
// avoids this. The warning depends on GCC's inlining decisions, so the
|
||||||
|
// sections cover two patterns that trigger it on different GCC versions
|
||||||
|
// (#4819 and #5742).
|
||||||
|
// On GCC, this file is compiled with -O3 -Werror=array-bounds (see
|
||||||
|
// tests/CMakeLists.txt), so the test fails to build if the warning returns.
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#ifdef JSON_DIAGNOSTICS
|
||||||
|
#undef JSON_DIAGNOSTICS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define JSON_DIAGNOSTICS 1
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
enum class diag_color
|
||||||
|
{
|
||||||
|
red,
|
||||||
|
green,
|
||||||
|
blue
|
||||||
|
};
|
||||||
|
|
||||||
|
void to_json(json& j, const diag_color& c)
|
||||||
|
{
|
||||||
|
static const std::pair<diag_color, json> m[] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
{
|
||||||
|
{diag_color::red, "r"},
|
||||||
|
{diag_color::green, "g"},
|
||||||
|
{diag_color::blue, "b"},
|
||||||
|
};
|
||||||
|
const auto* it = std::find_if(std::begin(m), std::end(m), [c](const std::pair<diag_color, json>& p)
|
||||||
|
{
|
||||||
|
return p.first == c;
|
||||||
|
});
|
||||||
|
j = it->second;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("diagnostics with optimization")
|
||||||
|
{
|
||||||
|
SECTION("issue #4819 - object in vector")
|
||||||
|
{
|
||||||
|
std::vector<json> jsons{};
|
||||||
|
jsons.emplace_back(json({{"key", "value"}}));
|
||||||
|
CHECK(jsons.back()["key"] == "value");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("issue #5742 - string values from a static table")
|
||||||
|
{
|
||||||
|
json j = json::array();
|
||||||
|
j.push_back(diag_color::red);
|
||||||
|
j.push_back(diag_color::green);
|
||||||
|
j.push_back(diag_color::blue);
|
||||||
|
CHECK(j.dump() == R"(["r","g","b"])");
|
||||||
|
CHECK_THROWS_WITH_AS(j[1].get<int>(), "[json.exception.type_error.302] (/1) type must be number, but is string", json::type_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,246 +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"
|
|
||||||
|
|
||||||
// skip tests if JSON_DisableEnumSerialization=ON (#4384)
|
|
||||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
|
||||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This file tests the opt-in JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS, so it defines
|
|
||||||
// the macro itself rather than relying on a -D flag, and runs in every build.
|
|
||||||
// The default behavior is tested in unit-enum_keyed_maps_default.cpp.
|
|
||||||
#ifdef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
using nlohmann::ordered_json;
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#define STRINGIZE_EX(x) #x
|
|
||||||
#define STRINGIZE(x) STRINGIZE_EX(x)
|
|
||||||
|
|
||||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// std::hash is only required for enums since C++14
|
|
||||||
struct enum_hash
|
|
||||||
{
|
|
||||||
template<typename T>
|
|
||||||
std::size_t operator()(T t) const noexcept
|
|
||||||
{
|
|
||||||
return static_cast<std::size_t>(t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// the example from #4378
|
|
||||||
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
|
|
||||||
{
|
|
||||||
TS_STOPPED,
|
|
||||||
TS_RUNNING,
|
|
||||||
TS_COMPLETED,
|
|
||||||
TS_INVALID = -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
|
|
||||||
{
|
|
||||||
{TS_INVALID, nullptr},
|
|
||||||
{TS_STOPPED, "stopped"},
|
|
||||||
{TS_RUNNING, "running"},
|
|
||||||
{TS_COMPLETED, "completed"},
|
|
||||||
})
|
|
||||||
|
|
||||||
enum class color {red, green, blue}; // blue is not mapped and falls back to "red"
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(color,
|
|
||||||
{
|
|
||||||
{color::red, "red"},
|
|
||||||
{color::green, "green"},
|
|
||||||
})
|
|
||||||
|
|
||||||
enum class strict_color {red, green, blue}; // blue is not mapped
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_color,
|
|
||||||
{
|
|
||||||
{strict_color::red, "red"},
|
|
||||||
{strict_color::green, "green"},
|
|
||||||
})
|
|
||||||
|
|
||||||
enum class digit {zero, one};
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(digit,
|
|
||||||
{
|
|
||||||
{digit::zero, 0},
|
|
||||||
{digit::one, 1},
|
|
||||||
})
|
|
||||||
|
|
||||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
|
||||||
enum class plain {zero, one}; // serialized as integer
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS")
|
|
||||||
{
|
|
||||||
SECTION("the macro is part of the ABI tag")
|
|
||||||
{
|
|
||||||
const std::string ns = STRINGIZE(NLOHMANN_JSON_NAMESPACE);
|
|
||||||
CHECK(ns.find("_ekmo") != std::string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("std::map (#4378)")
|
|
||||||
{
|
|
||||||
using task_map = std::map<TaskState, std::string>;
|
|
||||||
const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
const json j = m;
|
|
||||||
CHECK(j == json::parse(R"({"stopped":"aa","completed":"bb"})"));
|
|
||||||
CHECK(j.get<task_map>() == m);
|
|
||||||
|
|
||||||
json j2;
|
|
||||||
j2["x"] = m;
|
|
||||||
CHECK(j2.dump() == R"({"x":{"completed":"bb","stopped":"aa"}})");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("std::map with custom comparator")
|
|
||||||
{
|
|
||||||
using task_map = std::map<TaskState, int, std::greater<TaskState>>;
|
|
||||||
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}};
|
|
||||||
const json j = m;
|
|
||||||
CHECK(j == json::parse(R"({"stopped":1,"running":2})"));
|
|
||||||
CHECK(j.get<task_map>() == m);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("std::unordered_map")
|
|
||||||
{
|
|
||||||
using task_map = std::unordered_map<TaskState, int, enum_hash>;
|
|
||||||
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}};
|
|
||||||
const json j = m;
|
|
||||||
CHECK(j == json::parse(R"({"stopped":1,"running":2})"));
|
|
||||||
CHECK(j.get<task_map>() == m);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("nested maps")
|
|
||||||
{
|
|
||||||
using nested_map = std::map<color, std::map<TaskState, int>>;
|
|
||||||
const nested_map m = {{color::green, {{TS_RUNNING, 1}}}, {color::red, {}}};
|
|
||||||
const json j = m;
|
|
||||||
CHECK(j == json::parse(R"({"green":{"running":1},"red":{}})"));
|
|
||||||
CHECK(j.get<nested_map>() == m);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("ordered_json keeps the order of the map")
|
|
||||||
{
|
|
||||||
using task_map = std::map<TaskState, int>;
|
|
||||||
const task_map m = {{TS_STOPPED, 1}, {TS_RUNNING, 2}, {TS_COMPLETED, 3}};
|
|
||||||
const ordered_json j = m;
|
|
||||||
CHECK(j.dump() == R"({"stopped":1,"running":2,"completed":3})");
|
|
||||||
CHECK(j.get<task_map>() == m);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("empty map")
|
|
||||||
{
|
|
||||||
const json j = std::map<TaskState, int>();
|
|
||||||
CHECK(j.is_object());
|
|
||||||
CHECK(j.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT")
|
|
||||||
{
|
|
||||||
using color_map = std::map<strict_color, int>;
|
|
||||||
const color_map m = {{strict_color::red, 1}, {strict_color::green, 2}};
|
|
||||||
const json j = m;
|
|
||||||
CHECK(j == json::parse(R"({"red":1,"green":2})"));
|
|
||||||
CHECK(j.get<color_map>() == m);
|
|
||||||
|
|
||||||
const color_map unmapped = {{strict_color::blue, 1}};
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_WITH_AS(_ = unmapped,
|
|
||||||
"[json.exception.out_of_range.410] enum value out of range for strict_color", json::out_of_range&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("arrays of [key, value] pairs are still read")
|
|
||||||
{
|
|
||||||
using task_map = std::map<TaskState, int>;
|
|
||||||
const task_map m = {{TS_STOPPED, 1}};
|
|
||||||
CHECK(json::parse(R"([["stopped",1]])").get<task_map>() == m);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("other containers are not affected")
|
|
||||||
{
|
|
||||||
const std::vector<std::pair<TaskState, int>> pairs = {{TS_STOPPED, 1}};
|
|
||||||
const std::map<std::string, TaskState> string_keys = {{"a", TS_STOPPED}};
|
|
||||||
const std::map<int, int> int_keys = {{1, 2}};
|
|
||||||
CHECK(json(pairs) == json::parse(R"([["stopped",1]])"));
|
|
||||||
CHECK(json(string_keys) == json::parse(R"({"a":"stopped"})"));
|
|
||||||
CHECK(json(int_keys) == json::parse("[[1,2]]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("maps with non-unique keys are still stored as arrays of pairs")
|
|
||||||
{
|
|
||||||
const std::multimap<TaskState, int> mm = {{TS_STOPPED, 1}, {TS_STOPPED, 2}};
|
|
||||||
const std::unordered_multimap<TaskState, int, enum_hash> umm = {{TS_RUNNING, 3}, {TS_RUNNING, 3}};
|
|
||||||
CHECK(json(mm) == json::parse(R"([["stopped",1],["stopped",2]])"));
|
|
||||||
CHECK(json(umm) == json::parse(R"([["running",3],["running",3]])"));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("keys that do not serialize to strings")
|
|
||||||
{
|
|
||||||
const std::map<TaskState, int> null_key = {{TS_INVALID, 1}};
|
|
||||||
const std::map<digit, int> number_key = {{digit::zero, 1}};
|
|
||||||
json j = "unchanged";
|
|
||||||
|
|
||||||
// mapped to null
|
|
||||||
CHECK_THROWS_WITH_AS(j = null_key,
|
|
||||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
|
||||||
|
|
||||||
// mapped to a number
|
|
||||||
CHECK_THROWS_WITH_AS(j = number_key,
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
|
|
||||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
|
||||||
// enum without NLOHMANN_JSON_SERIALIZE_ENUM
|
|
||||||
const std::map<plain, int> plain_key = {{plain::zero, 1}};
|
|
||||||
CHECK_THROWS_WITH_AS(j = plain_key,
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CHECK(j == "unchanged");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("keys that serialize to the same string")
|
|
||||||
{
|
|
||||||
const std::map<color, int> m = {{color::red, 1}, {color::blue, 2}};
|
|
||||||
json j = "unchanged";
|
|
||||||
|
|
||||||
// color::blue is not mapped and falls back to "red"
|
|
||||||
CHECK_THROWS_WITH_AS(j = m,
|
|
||||||
"[json.exception.type_error.318] duplicate object key 'red'", json::type_error&);
|
|
||||||
|
|
||||||
CHECK(j == "unchanged");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
|
||||||
@@ -1,144 +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 maps with enum keys with the default setting of
|
|
||||||
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS (or whatever a -D flag sets it to).
|
|
||||||
// unit-enum_keyed_maps.cpp tests JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1.
|
|
||||||
// These tests are not part of unit-conversions.cpp, because that object file
|
|
||||||
// is already too big for the MinGW linker of some compilers.
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
|
||||||
|
|
||||||
enum class cards {kreuz, pik, herz, karo};
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(cards,
|
|
||||||
{
|
|
||||||
{cards::kreuz, "kreuz"},
|
|
||||||
{cards::pik, "pik"},
|
|
||||||
{cards::herz, "herz"},
|
|
||||||
{cards::karo, "karo"}
|
|
||||||
})
|
|
||||||
|
|
||||||
enum TaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
|
|
||||||
{
|
|
||||||
TS_STOPPED,
|
|
||||||
TS_RUNNING,
|
|
||||||
TS_COMPLETED,
|
|
||||||
TS_INVALID = -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState,
|
|
||||||
{
|
|
||||||
{TS_INVALID, nullptr},
|
|
||||||
{TS_STOPPED, "stopped"},
|
|
||||||
{TS_RUNNING, "running"},
|
|
||||||
{TS_COMPLETED, "completed"},
|
|
||||||
})
|
|
||||||
|
|
||||||
enum class strict_cards {kreuz, pik, herz, karo, andere}; // andere not included in mapping
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
|
|
||||||
{
|
|
||||||
{strict_cards::kreuz, "kreuz"},
|
|
||||||
{strict_cards::pik, "pik"},
|
|
||||||
{strict_cards::herz, "herz"},
|
|
||||||
{strict_cards::karo, "karo"}
|
|
||||||
})
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// std::hash is only required for enums since C++14
|
|
||||||
struct enum_hash
|
|
||||||
{
|
|
||||||
template<typename T>
|
|
||||||
std::size_t operator()(T t) const noexcept
|
|
||||||
{
|
|
||||||
return static_cast<std::size_t>(t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// see unit-enum_keyed_maps.cpp for JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS=1
|
|
||||||
TEST_CASE("maps with enum keys")
|
|
||||||
{
|
|
||||||
using task_map = std::map<TaskState, std::string>;
|
|
||||||
using task_umap = std::unordered_map<TaskState, std::string, enum_hash>;
|
|
||||||
using task_gmap = std::map<TaskState, std::string, std::greater<TaskState>>;
|
|
||||||
using nested_map = std::map<cards, std::map<TaskState, int>>;
|
|
||||||
using strict_map = std::map<strict_cards, int>;
|
|
||||||
using int_map = std::map<int, int>;
|
|
||||||
using int_umap = std::unordered_map<int, int>;
|
|
||||||
|
|
||||||
const task_map m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
|
|
||||||
#if !JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
SECTION("stored as array of pairs")
|
|
||||||
{
|
|
||||||
CHECK(json(m) == json::parse(R"([["stopped","aa"],["completed","bb"]])"));
|
|
||||||
CHECK(json(task_umap {{TS_RUNNING, "cc"}}) == json::parse(R"([["running","cc"]])"));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("read from array of pairs")
|
|
||||||
{
|
|
||||||
CHECK(json::parse(R"([["stopped","aa"],["completed","bb"]])").get<task_map>() == m);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("read from object (#4378)")
|
|
||||||
{
|
|
||||||
const json j = json::parse(R"({"stopped":"aa","completed":"bb"})");
|
|
||||||
CHECK(j.get<task_map>() == m);
|
|
||||||
CHECK(j.get<task_umap>() == task_umap(m.begin(), m.end()));
|
|
||||||
CHECK(j.get<task_gmap>() == task_gmap(m.begin(), m.end()));
|
|
||||||
CHECK(json::parse(R"({"kreuz":{"stopped":1}})").get<nested_map>() == nested_map {{cards::kreuz, {{TS_STOPPED, 1}}}});
|
|
||||||
CHECK(nlohmann::ordered_json::parse(R"({"stopped":"aa","completed":"bb"})").get<task_map>() == m);
|
|
||||||
|
|
||||||
// object keys go through the enum's from_json
|
|
||||||
strict_map sm;
|
|
||||||
CHECK_THROWS_WITH_AS(json::parse(R"({"what?":1})").get_to(sm),
|
|
||||||
"[json.exception.out_of_range.410] enum value out of range for strict_cards: \"what?\"", json::out_of_range&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("objects are only read for enum keys")
|
|
||||||
{
|
|
||||||
// built rather than parsed, so that the messages do not gain a byte
|
|
||||||
// range with JSON_DIAGNOSTIC_POSITIONS
|
|
||||||
const json j = {{"1", 2}};
|
|
||||||
int_map im;
|
|
||||||
int_umap ium;
|
|
||||||
CHECK_THROWS_WITH_AS(j.get_to(im),
|
|
||||||
"[json.exception.type_error.302] type must be array, but is object", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(j.get_to(ium),
|
|
||||||
"[json.exception.type_error.302] type must be array, but is object", json::type_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("other types are rejected")
|
|
||||||
{
|
|
||||||
task_map tm;
|
|
||||||
CHECK_THROWS_WITH_AS(json("stopped").get_to(tm),
|
|
||||||
"[json.exception.type_error.302] type must be array, but is string", json::type_error&);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
|
||||||
@@ -40,9 +40,7 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <new>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -109,84 +107,6 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
|||||||
|
|
||||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
|
||||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// An allocator whose allocate() can be told to fail on demand, so tests can
|
|
||||||
// check that ~basic_json() tolerates - in fact, after #5135, never even
|
|
||||||
// triggers - an allocation failure. This replaces an earlier version of
|
|
||||||
// this test that overrode the process-wide ::operator new/::operator
|
|
||||||
// delete, which affected every allocation in the whole unit-regression2
|
|
||||||
// binary rather than just the values under test.
|
|
||||||
std::size_t failing_allocator_allocations = 0;
|
|
||||||
std::size_t failing_allocator_deallocations = 0;
|
|
||||||
bool fail_next_allocation = false;
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
struct failing_allocator : std::allocator<T>
|
|
||||||
{
|
|
||||||
using std::allocator<T>::allocator;
|
|
||||||
|
|
||||||
failing_allocator() noexcept = default;
|
|
||||||
template<class U>
|
|
||||||
failing_allocator(const failing_allocator<U>& /*unused*/) noexcept {} // NOLINT(google-explicit-constructor)
|
|
||||||
|
|
||||||
T* allocate(std::size_t n)
|
|
||||||
{
|
|
||||||
if (fail_next_allocation)
|
|
||||||
{
|
|
||||||
fail_next_allocation = false;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
++failing_allocator_allocations;
|
|
||||||
return std::allocator<T>::allocate(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deallocate(T* p, std::size_t n)
|
|
||||||
{
|
|
||||||
++failing_allocator_deallocations;
|
|
||||||
std::allocator<T>::deallocate(p, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class U>
|
|
||||||
struct rebind
|
|
||||||
{
|
|
||||||
using other = failing_allocator<U>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
|
||||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
|
||||||
using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
|
|
||||||
std::int64_t, std::uint64_t, double, failing_allocator>;
|
|
||||||
|
|
||||||
// builds `depth` levels of nesting around a scalar, iteratively (never
|
|
||||||
// recursing: each wrap only moves the previous, already-built value, which
|
|
||||||
// is O(1)), each level an array or an object depending on `nest_objects`
|
|
||||||
template<class BasicJsonType>
|
|
||||||
BasicJsonType make_deep_nest(std::size_t depth, bool nest_objects)
|
|
||||||
{
|
|
||||||
BasicJsonType v = 0;
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
if (nest_objects)
|
|
||||||
{
|
|
||||||
BasicJsonType wrapper = BasicJsonType::object();
|
|
||||||
wrapper["x"] = std::move(v);
|
|
||||||
v = std::move(wrapper);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BasicJsonType wrapper = BasicJsonType::array();
|
|
||||||
wrapper.push_back(std::move(v));
|
|
||||||
v = std::move(wrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
// for #1647
|
// for #1647
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
@@ -1020,211 +940,4 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
|||||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
|
||||||
TEST_CASE("regression test #5135 - destructor never allocates, even under memory pressure")
|
|
||||||
{
|
|
||||||
// Before the fix, ~basic_json() flattened a nested array/object into a
|
|
||||||
// heap-allocated std::vector to avoid recursing; that allocation could
|
|
||||||
// itself throw bad_alloc, which escapes a noexcept destructor and
|
|
||||||
// terminates the program. destroy() no longer allocates anything, so
|
|
||||||
// none of the sections below ever observe fail_next_allocation being
|
|
||||||
// consumed: CHECK(fail_next_allocation) confirms it was never touched.
|
|
||||||
|
|
||||||
SECTION("the original report: a small, mixed array/object nest")
|
|
||||||
{
|
|
||||||
failing_allocator_allocations = 0;
|
|
||||||
failing_allocator_deallocations = 0;
|
|
||||||
{
|
|
||||||
failing_json j = failing_json::array(
|
|
||||||
{
|
|
||||||
failing_json::array({1, 2}),
|
|
||||||
failing_json::object({{"key", failing_json::array({3})}})
|
|
||||||
});
|
|
||||||
fail_next_allocation = true;
|
|
||||||
} // j is destroyed here, with every further allocation set to fail
|
|
||||||
|
|
||||||
CHECK(fail_next_allocation);
|
|
||||||
fail_next_allocation = false;
|
|
||||||
CHECK(failing_allocator_deallocations > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("100000-deep nested array")
|
|
||||||
{
|
|
||||||
std::size_t allocations_before = 0;
|
|
||||||
{
|
|
||||||
failing_json j = make_deep_nest<failing_json>(100000, false);
|
|
||||||
allocations_before = failing_allocator_allocations;
|
|
||||||
fail_next_allocation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(fail_next_allocation);
|
|
||||||
fail_next_allocation = false;
|
|
||||||
CHECK(failing_allocator_allocations == allocations_before);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("100000-deep nested object")
|
|
||||||
{
|
|
||||||
std::size_t allocations_before = 0;
|
|
||||||
{
|
|
||||||
failing_json j = make_deep_nest<failing_json>(100000, true);
|
|
||||||
allocations_before = failing_allocator_allocations;
|
|
||||||
fail_next_allocation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(fail_next_allocation);
|
|
||||||
fail_next_allocation = false;
|
|
||||||
CHECK(failing_allocator_allocations == allocations_before);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("100000-deep nested ordered_json")
|
|
||||||
{
|
|
||||||
std::size_t allocations_before = 0;
|
|
||||||
{
|
|
||||||
failing_ordered_json j = make_deep_nest<failing_ordered_json>(100000, true);
|
|
||||||
allocations_before = failing_allocator_allocations;
|
|
||||||
fail_next_allocation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(fail_next_allocation);
|
|
||||||
fail_next_allocation = false;
|
|
||||||
CHECK(failing_allocator_allocations == allocations_before);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("wide and deep: 1000 arrays of 1000 elements, each a small nested object")
|
|
||||||
{
|
|
||||||
std::size_t allocations_before = 0;
|
|
||||||
{
|
|
||||||
failing_json wide = failing_json::array();
|
|
||||||
for (std::size_t i = 0; i < 1000; ++i)
|
|
||||||
{
|
|
||||||
failing_json inner = failing_json::array();
|
|
||||||
for (std::size_t k = 0; k < 1000; ++k)
|
|
||||||
{
|
|
||||||
inner.push_back(failing_json::object({{"a", 1}, {"b", failing_json::array({1, 2, 3})}}));
|
|
||||||
}
|
|
||||||
wide.push_back(std::move(inner));
|
|
||||||
}
|
|
||||||
|
|
||||||
allocations_before = failing_allocator_allocations;
|
|
||||||
fail_next_allocation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(fail_next_allocation);
|
|
||||||
fail_next_allocation = false;
|
|
||||||
CHECK(failing_allocator_allocations == allocations_before);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a single-element chain of `depth` arrays, built iteratively (never
|
|
||||||
// recursing: each wrap only moves the previous, already-built value)
|
|
||||||
template<class BasicJsonType>
|
|
||||||
BasicJsonType make_single_chain(std::size_t depth)
|
|
||||||
{
|
|
||||||
BasicJsonType v = 1;
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
BasicJsonType wrapper = BasicJsonType::array();
|
|
||||||
wrapper.push_back(std::move(v));
|
|
||||||
v = std::move(wrapper);
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// copies value first, to make sure nothing was corrupted by building it,
|
|
||||||
// then lets both the copy and the original destruct via normal scope exit
|
|
||||||
template<class BasicJsonType>
|
|
||||||
void check_destroy_edge_case(const BasicJsonType& value)
|
|
||||||
{
|
|
||||||
const BasicJsonType copy = value;
|
|
||||||
CHECK(copy == value);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType, json, ordered_json)
|
|
||||||
{
|
|
||||||
using binary_t = typename BasicJsonType::binary_t;
|
|
||||||
|
|
||||||
SECTION("mix of empty objects, empty arrays, non-empty containers, and scalars")
|
|
||||||
{
|
|
||||||
BasicJsonType root = BasicJsonType::array();
|
|
||||||
root.push_back(BasicJsonType::object());
|
|
||||||
root.push_back(BasicJsonType::array());
|
|
||||||
root.push_back(BasicJsonType::object({{"k", 1}}));
|
|
||||||
root.push_back(BasicJsonType::array({1, 2, 3}));
|
|
||||||
root.push_back(nullptr);
|
|
||||||
root.push_back(true);
|
|
||||||
root.push_back(42);
|
|
||||||
root.push_back(3.14);
|
|
||||||
root.push_back("a string");
|
|
||||||
root.push_back(BasicJsonType(binary_t({1, 2, 3})));
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("container child in first position only")
|
|
||||||
{
|
|
||||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1, 2}), 3, 4, 5});
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("container child in last position only")
|
|
||||||
{
|
|
||||||
BasicJsonType root = BasicJsonType::array({1, 2, 3, BasicJsonType::array({4, 5})});
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("container children in first and last position")
|
|
||||||
{
|
|
||||||
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1}), 2, 3, BasicJsonType::array({4})});
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("single-element chain, 1000 levels deep")
|
|
||||||
{
|
|
||||||
BasicJsonType root = make_single_chain<BasicJsonType>(1000);
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("top-level empty array")
|
|
||||||
{
|
|
||||||
BasicJsonType root = BasicJsonType::array();
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("top-level empty object")
|
|
||||||
{
|
|
||||||
BasicJsonType root = BasicJsonType::object();
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("object whose last child is a non-empty array whose last child is an empty object")
|
|
||||||
{
|
|
||||||
BasicJsonType inner_array = BasicJsonType::array({1, 2, BasicJsonType::object()});
|
|
||||||
BasicJsonType root = BasicJsonType::object({{"a", 1}, {"b", inner_array}});
|
|
||||||
check_destroy_edge_case(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("destruction via erase() on a deeply nested child")
|
|
||||||
{
|
|
||||||
BasicJsonType root = BasicJsonType::array();
|
|
||||||
root.push_back(make_single_chain<BasicJsonType>(500));
|
|
||||||
root.push_back(BasicJsonType::object({{"k", BasicJsonType::array({1, 2, 3})}}));
|
|
||||||
// erase() must destroy the removed subtree without recursing or
|
|
||||||
// allocating beyond what erase() itself needs
|
|
||||||
root.erase(0);
|
|
||||||
CAPTURE(root.size())
|
|
||||||
CHECK(root.size() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("destruction via assignment on a deep tree")
|
|
||||||
{
|
|
||||||
BasicJsonType root = make_single_chain<BasicJsonType>(2000);
|
|
||||||
// assigning a new value destroys the old one in place
|
|
||||||
root = nullptr;
|
|
||||||
CHECK(root.is_null());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
|
|||||||
Reference in New Issue
Block a user