mirror of
https://github.com/nlohmann/json.git
synced 2026-09-29 11:10:30 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10cbcd9562 | ||
|
|
633de8e44b | ||
|
|
93fd4aa6f6 | ||
|
|
fc03b9912e | ||
|
|
9e1a09eec0 | ||
|
|
103b37aa7d | ||
|
|
4671f04372 | ||
|
|
99716a5ade | ||
|
|
d914e3a027 |
@@ -80,8 +80,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
the end of the file was not reached when `strict` was set to true
|
the end of the file was not reached when `strict` was set to true
|
||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
|
||||||
used in the given input or if the input is not valid CBOR
|
used in the given input or if the input is not valid CBOR
|
||||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
|
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
|
||||||
but not found
|
types are not supported, as JSON object keys are always strings) or a string is malformed
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
the end of the file was not reached when `strict` was set to true
|
the end of the file was not reached when `strict` was set to true
|
||||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
|
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
|
||||||
MessagePack were used in the given input or if the input is not valid MessagePack
|
MessagePack were used in the given input or if the input is not valid MessagePack
|
||||||
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
|
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other
|
||||||
but not found
|
types are not supported, as JSON object keys are always strings) or a string is malformed
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
|
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
|
||||||
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
||||||
- [**JSON_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
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# 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,6 +41,9 @@ 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
|
||||||
|
|
||||||
@@ -80,6 +83,7 @@ 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,6 +44,9 @@ 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
|
||||||
|
|
||||||
@@ -99,6 +102,7 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,20 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
|
|
||||||
!!! warning "Object keys"
|
!!! warning "Object keys"
|
||||||
|
|
||||||
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected.
|
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps
|
||||||
|
with keys other than text strings (major type 3) are rejected with a
|
||||||
|
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions` set
|
||||||
|
to `false`, a discarded value) naming the type of the key that was found, for instance:
|
||||||
|
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
|
||||||
|
on. This is a deliberate restriction of the library's JSON value model, not an oversight: formats built on CBOR
|
||||||
|
maps with integer keys, such as COSE ([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052.html)) or CWT
|
||||||
|
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
|
||||||
|
general-purpose CBOR library instead.
|
||||||
|
|
||||||
!!! warning "UTF-8 validation of text strings"
|
!!! warning "UTF-8 validation of text strings"
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,21 @@ The library maps MessagePack types to JSON value types as follows:
|
|||||||
|
|
||||||
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
||||||
|
|
||||||
|
!!! warning "Object keys"
|
||||||
|
|
||||||
|
MessagePack allows map keys of any type, whereas JSON only allows strings as keys in object values. Like the
|
||||||
|
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the
|
||||||
|
MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are
|
||||||
|
rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||||
|
`allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance:
|
||||||
|
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
|
||||||
|
on. Such input needs a general-purpose MessagePack library instead.
|
||||||
|
|
||||||
!!! warning "UTF-8 validation of string values"
|
!!! warning "UTF-8 validation of string values"
|
||||||
|
|
||||||
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
||||||
|
|||||||
@@ -43,6 +43,23 @@ json jPi = 3.14;
|
|||||||
assert(jPi.get<TaskState>() == TS_INVALID );
|
assert(jPi.get<TaskState>() == TS_INVALID );
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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,
|
||||||
|
|||||||
@@ -167,6 +167,13 @@ behavior is deprecated and switched off (`0`) by 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
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ The complete default namespace name is derived as follows:
|
|||||||
`_bics`.
|
`_bics`.
|
||||||
- [`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_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.
|
||||||
|
|||||||
@@ -343,13 +343,20 @@ A string could not be read from a [binary format](../features/binary_formats/ind
|
|||||||
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
||||||
the string's bytes are not valid UTF-8.
|
the string's bytes are not valid UTF-8.
|
||||||
|
|
||||||
|
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
|
||||||
|
type (for instance integers or `null`) are therefore not supported; see the notes on
|
||||||
|
[CBOR](../features/binary_formats/cbor.md) and [MessagePack](../features/binary_formats/messagepack.md).
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
|
||||||
|
```
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
||||||
@@ -589,6 +596,9 @@ 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 &`.
|
||||||
@@ -775,6 +785,19 @@ 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.
|
||||||
|
|||||||
@@ -303,6 +303,7 @@ 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
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
#define JSON_STRICT_NUL_HANDLING 0
|
#define JSON_STRICT_NUL_HANDLING 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
|
||||||
@@ -82,14 +86,20 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
||||||
#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) json_abi ## a ## b ## c ## d ## e ## f
|
#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) \
|
#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)
|
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( \
|
||||||
@@ -98,7 +108,8 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||||
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_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) \
|
||||||
|
|||||||
@@ -550,11 +550,40 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t)
|
|||||||
return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});
|
return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 >>
|
||||||
inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
|
inline 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;
|
||||||
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||||
{
|
{
|
||||||
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
||||||
@@ -575,6 +604,11 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
|
|||||||
typename BasicJsonType::string_t, Key >::value >>
|
typename BasicJsonType::string_t, Key >::value >>
|
||||||
inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
|
inline 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;
|
||||||
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||||
{
|
{
|
||||||
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#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>
|
||||||
@@ -381,6 +382,9 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
|||||||
!is_basic_json<CompatibleArrayType>::value
|
!is_basic_json<CompatibleArrayType>::value
|
||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
&& !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 >
|
||||||
@@ -435,6 +439,33 @@ 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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1324,6 +1324,80 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief reads a CBOR object key
|
||||||
|
|
||||||
|
RFC 8949 allows any data item as a map key, but only strings have a
|
||||||
|
counterpart in JSON. A key of any other type is rejected with a message
|
||||||
|
naming that type, rather than the one @ref get_cbor_string gives for a
|
||||||
|
malformed string.
|
||||||
|
|
||||||
|
@param[out] result created key
|
||||||
|
|
||||||
|
@return whether key creation completed
|
||||||
|
*/
|
||||||
|
bool get_cbor_object_key(string_t& result)
|
||||||
|
{
|
||||||
|
// EOF and major type 3 (text string) are left to get_cbor_string
|
||||||
|
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
|
||||||
|
{
|
||||||
|
return get_cbor_string(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* found = nullptr;
|
||||||
|
switch (static_cast<unsigned int>(current) >> 5u)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
found = "an unsigned integer";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
found = "a negative integer";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
found = "a byte string";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
found = "an array";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
found = "a map";
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
found = "a tag";
|
||||||
|
break;
|
||||||
|
default: // major type 7
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case 0xF4:
|
||||||
|
case 0xF5:
|
||||||
|
found = "a boolean";
|
||||||
|
break;
|
||||||
|
case 0xF6:
|
||||||
|
found = "null";
|
||||||
|
break;
|
||||||
|
case 0xF7:
|
||||||
|
found = "undefined";
|
||||||
|
break;
|
||||||
|
case 0xF9:
|
||||||
|
case 0xFA:
|
||||||
|
case 0xFB:
|
||||||
|
found = "a floating-point number";
|
||||||
|
break;
|
||||||
|
case 0xFF:
|
||||||
|
found = "a break stop code";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
found = "a simple value";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
|
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR byte array
|
@brief reads a definite-length CBOR byte array
|
||||||
|
|
||||||
@@ -1568,7 +1642,7 @@ class binary_reader
|
|||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2069,6 +2143,98 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief reads a MessagePack object key
|
||||||
|
|
||||||
|
The MessagePack specification allows any type as a map key, but only
|
||||||
|
strings have a counterpart in JSON. A key of any other type is rejected
|
||||||
|
with a message naming that type, rather than the one @ref
|
||||||
|
get_msgpack_string gives for a malformed string.
|
||||||
|
|
||||||
|
@param[out] result created key
|
||||||
|
|
||||||
|
@return whether key creation completed
|
||||||
|
*/
|
||||||
|
bool get_msgpack_object_key(string_t& result)
|
||||||
|
{
|
||||||
|
const char* found = nullptr;
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case 0xC0:
|
||||||
|
found = "nil";
|
||||||
|
break;
|
||||||
|
case 0xC2:
|
||||||
|
case 0xC3:
|
||||||
|
found = "a boolean";
|
||||||
|
break;
|
||||||
|
case 0xCA:
|
||||||
|
case 0xCB:
|
||||||
|
found = "a float";
|
||||||
|
break;
|
||||||
|
case 0xC4:
|
||||||
|
case 0xC5:
|
||||||
|
case 0xC6:
|
||||||
|
found = "a bin";
|
||||||
|
break;
|
||||||
|
case 0xC7:
|
||||||
|
case 0xC8:
|
||||||
|
case 0xC9:
|
||||||
|
case 0xD4:
|
||||||
|
case 0xD5:
|
||||||
|
case 0xD6:
|
||||||
|
case 0xD7:
|
||||||
|
case 0xD8:
|
||||||
|
found = "an ext";
|
||||||
|
break;
|
||||||
|
case 0xCC:
|
||||||
|
case 0xCD:
|
||||||
|
case 0xCE:
|
||||||
|
case 0xCF:
|
||||||
|
case 0xD0:
|
||||||
|
case 0xD1:
|
||||||
|
case 0xD2:
|
||||||
|
case 0xD3:
|
||||||
|
found = "an integer";
|
||||||
|
break;
|
||||||
|
case 0xDC:
|
||||||
|
case 0xDD:
|
||||||
|
found = "an array";
|
||||||
|
break;
|
||||||
|
case 0xDE:
|
||||||
|
case 0xDF:
|
||||||
|
found = "a map";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// fixint, fixmap, and fixarray; strings, EOF, and the unused
|
||||||
|
// byte 0xC1 are left to get_msgpack_string
|
||||||
|
if (current == char_traits<char_type>::eof())
|
||||||
|
{
|
||||||
|
return get_msgpack_string(result);
|
||||||
|
}
|
||||||
|
if (current <= 0x7F || current >= 0xE0)
|
||||||
|
{
|
||||||
|
found = "an integer";
|
||||||
|
}
|
||||||
|
else if (current <= 0x8F)
|
||||||
|
{
|
||||||
|
found = "a map";
|
||||||
|
}
|
||||||
|
else if (current <= 0x9F)
|
||||||
|
{
|
||||||
|
found = "an array";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return get_msgpack_string(result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
|
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a MessagePack byte array
|
@brief reads a MessagePack byte array
|
||||||
|
|
||||||
@@ -2231,7 +2397,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
|
|
||||||
, discard_number_values(discard_number_values_)
|
, discard_number_values(discard_number_values_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -222,8 +221,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// locales
|
// locales
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
/// return the locale-dependent decimal point
|
/// return the decimal point of the current locale
|
||||||
JSON_HEDLEY_PURE
|
|
||||||
static char get_decimal_point() noexcept
|
static char get_decimal_point() noexcept
|
||||||
{
|
{
|
||||||
const auto* loc = localeconv();
|
const auto* loc = localeconv();
|
||||||
@@ -1092,9 +1090,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
token_type::value_float if number could be successfully scanned,
|
token_type::value_float if number could be successfully scanned,
|
||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note The scanner is independent of the current locale. Internally, the
|
@note The scanner is independent of the current locale: token_buffer
|
||||||
locale's decimal point is used instead of `.` to work with the
|
always holds `.`. Only the std::strtod fallback of convert_number()
|
||||||
locale-dependent converters.
|
depends on the locale, and it looks up the decimal point right
|
||||||
|
before converting (see convert_float_locale_aware()).
|
||||||
*/
|
*/
|
||||||
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
||||||
{
|
{
|
||||||
@@ -1183,7 +1182,7 @@ scan_number_zero:
|
|||||||
{
|
{
|
||||||
case '.':
|
case '.':
|
||||||
{
|
{
|
||||||
add(decimal_point_char);
|
add(current);
|
||||||
decimal_point_position = token_buffer.size() - 1;
|
decimal_point_position = token_buffer.size() - 1;
|
||||||
goto scan_number_decimal1;
|
goto scan_number_decimal1;
|
||||||
}
|
}
|
||||||
@@ -1220,7 +1219,7 @@ scan_number_any1:
|
|||||||
|
|
||||||
case '.':
|
case '.':
|
||||||
{
|
{
|
||||||
add(decimal_point_char);
|
add(current);
|
||||||
decimal_point_position = token_buffer.size() - 1;
|
decimal_point_position = token_buffer.size() - 1;
|
||||||
goto scan_number_decimal1;
|
goto scan_number_decimal1;
|
||||||
}
|
}
|
||||||
@@ -1462,9 +1461,9 @@ scan_number_done:
|
|||||||
|
|
||||||
// Only a number below 1 can carry further insignificant zeros, and only
|
// Only a number below 1 can carry further insignificant zeros, and only
|
||||||
// while the count stays at the limit does removing them change the
|
// while the count stays at the limit does removing them change the
|
||||||
// answer - so this loop is skipped for all but a few tokens. Note
|
// answer - so this loop is skipped for all but a few tokens. The
|
||||||
// token_buffer holds the locale's decimal point, so the fraction is
|
// fraction is located through decimal_point_position rather than by
|
||||||
// located through decimal_point_position rather than by searching '.'.
|
// searching '.'.
|
||||||
if (lead_zero != 0)
|
if (lead_zero != 0)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||||
@@ -1482,8 +1481,8 @@ scan_number_done:
|
|||||||
@brief convert the number text in token_buffer to its value and token type
|
@brief convert the number text in token_buffer to its value and token type
|
||||||
|
|
||||||
The digit sequence in token_buffer has already been validated (by the
|
The digit sequence in token_buffer has already been validated (by the
|
||||||
scan_number() state machine or by the contiguous fast path) and holds the
|
scan_number() state machine or by the contiguous fast path) and holds '.'
|
||||||
locale decimal point in place of '.'. Integers are parsed first and fall
|
as decimal point, independent of the locale. Integers are parsed first and fall
|
||||||
back to floating point on overflow. This is shared so both scanners produce
|
back to floating point on overflow. This is shared so both scanners produce
|
||||||
identical results.
|
identical results.
|
||||||
|
|
||||||
@@ -1563,7 +1562,7 @@ scan_number_done:
|
|||||||
// integer conversion above overflowed. Prefer std::from_chars
|
// integer conversion above overflowed. Prefer std::from_chars
|
||||||
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||||
// otherwise the exact Clinger fast path (double only); otherwise the
|
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||||
// locale-aware strtof/strtod.
|
// locale-aware strtof/strtod/strtold.
|
||||||
if (parse_float_from_chars(num_begin, num_end, value_float))
|
if (parse_float_from_chars(num_begin, num_end, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
@@ -1572,18 +1571,67 @@ scan_number_done:
|
|||||||
// extra pass over the token's bytes, which otherwise shows up on
|
// extra pass over the token's bytes, which otherwise shows up on
|
||||||
// high-precision inputs such as canada.json
|
// high-precision inputs such as canada.json
|
||||||
if (mantissa_fits_clinger(mantissa_end)
|
if (mantissa_fits_clinger(mantissa_end)
|
||||||
&& parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
|
&& parse_float_fast(num_begin, num_end, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convert_float_locale_aware();
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the float in token_buffer with strtof/strtod/strtold
|
||||||
|
|
||||||
|
These functions expect the decimal point of the *current* locale, so it is
|
||||||
|
looked up right before the conversion instead of once when the lexer is
|
||||||
|
constructed: a locale change in between (by a parser callback, a SAX
|
||||||
|
handler, or another thread) must not truncate the value (#5198). The
|
||||||
|
token has been validated before, so if the conversion stops early and the
|
||||||
|
decimal point changed in the meantime, the locale changed between the
|
||||||
|
lookup and the call, and the conversion is repeated with the new decimal
|
||||||
|
point. If the decimal point did not change, a retry cannot succeed: the
|
||||||
|
locale's decimal point is not a single character (e.g., the two-byte
|
||||||
|
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
|
||||||
|
The value strtod parsed up to that point is kept, as before this change.
|
||||||
|
|
||||||
|
Note that changing the locale in another thread *while* strtod runs is
|
||||||
|
undefined behavior of the C library, which this function cannot prevent.
|
||||||
|
*/
|
||||||
|
void convert_float_locale_aware()
|
||||||
|
{
|
||||||
|
const bool has_dot = decimal_point_position != std::string::npos;
|
||||||
|
char decimal_point = get_decimal_point();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const bool substitute = has_dot && decimal_point != '.';
|
||||||
|
if (substitute)
|
||||||
|
{
|
||||||
|
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
|
||||||
|
}
|
||||||
|
|
||||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||||
strtof(value_float, token_buffer.data(), &endptr);
|
strtof(value_float, token_buffer.data(), &endptr);
|
||||||
|
|
||||||
// we checked the number format before
|
if (substitute)
|
||||||
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
|
{
|
||||||
|
// get_string() hands the token to the SAX interface with '.'
|
||||||
|
token_buffer[decimal_point_position] = '.';
|
||||||
|
}
|
||||||
|
|
||||||
return token_type::value_float;
|
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// retry only if the locale changed; otherwise, this would loop forever
|
||||||
|
const char current_decimal_point = get_decimal_point();
|
||||||
|
if (current_decimal_point == decimal_point)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
decimal_point = current_decimal_point;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1591,7 +1639,7 @@ scan_number_done:
|
|||||||
|
|
||||||
Parses the whole number token straight from the input buffer, avoiding the
|
Parses the whole number token straight from the input buffer, avoiding the
|
||||||
per-character get()/add() of scan_number(). On success it fills token_buffer
|
per-character get()/add() of scan_number(). On success it fills token_buffer
|
||||||
(with the locale decimal point substituted, as scan_number() does) and
|
(as scan_number() does) and
|
||||||
returns the token type. On anything it does not fully recognize as a
|
returns the token type. On anything it does not fully recognize as a
|
||||||
well-formed number it makes no state change and returns
|
well-formed number it makes no state change and returns
|
||||||
token_type::uninitialized, so the caller falls back to scan_number(), which
|
token_type::uninitialized, so the caller falls back to scan_number(), which
|
||||||
@@ -1707,16 +1755,11 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// materialize the token exactly as scan_number() would, substituting the
|
// materialize the token exactly as scan_number() would. reset() already
|
||||||
// locale decimal point so convert_number()'s strtof fallback stays valid.
|
// cleared token_buffer, so append() fills it (assign() is avoided
|
||||||
// reset() already cleared token_buffer, so append() fills it (assign() is
|
// because custom string_t types need not provide it)
|
||||||
// avoided because custom string_t types need not provide it)
|
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
if (dot_index != std::string::npos)
|
|
||||||
{
|
|
||||||
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
|
|
||||||
decimal_point_position = dot_index;
|
decimal_point_position = dot_index;
|
||||||
}
|
|
||||||
|
|
||||||
ia.bulk_skip(len - 1);
|
ia.bulk_skip(len - 1);
|
||||||
position.chars_read_total += (len - 1);
|
position.chars_read_total += (len - 1);
|
||||||
@@ -1983,11 +2026,7 @@ scan_number_done:
|
|||||||
/// return current string value (implicitly resets the token; useful only once)
|
/// return current string value (implicitly resets the token; useful only once)
|
||||||
string_t& get_string()
|
string_t& get_string()
|
||||||
{
|
{
|
||||||
// translate decimal points from locale back to '.' (#4084)
|
// a number token holds '.' regardless of the locale (#4084)
|
||||||
if (decimal_point_char != '.' && decimal_point_position != std::string::npos)
|
|
||||||
{
|
|
||||||
token_buffer[decimal_point_position] = '.';
|
|
||||||
}
|
|
||||||
return token_buffer;
|
return token_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2283,9 +2322,7 @@ scan_number_done:
|
|||||||
number_unsigned_t value_unsigned = 0;
|
number_unsigned_t value_unsigned = 0;
|
||||||
number_float_t value_float = 0;
|
number_float_t value_float = 0;
|
||||||
|
|
||||||
/// the decimal point
|
/// the position of the decimal point in token_buffer
|
||||||
const char_int_type decimal_point_char = '.';
|
|
||||||
/// the position of the decimal point in the input
|
|
||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
||||||
|
|||||||
@@ -120,12 +120,10 @@ below).
|
|||||||
|
|
||||||
@param[in] first pointer to the first character of the number
|
@param[in] first pointer to the first character of the number
|
||||||
@param[in] last pointer past the last character
|
@param[in] last pointer past the last character
|
||||||
@param[in] decimal_point the (locale-dependent) decimal point character
|
|
||||||
@param[out] out the parsed value on success
|
@param[out] out the parsed value on success
|
||||||
@return true if the value was parsed exactly; false to fall back to strtod
|
@return true if the value was parsed exactly; false to fall back to strtod
|
||||||
*/
|
*/
|
||||||
template<typename DecimalPointType>
|
inline bool parse_float_fast(const char* first, const char* last, double& out) noexcept
|
||||||
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
|
|
||||||
{
|
{
|
||||||
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||||
// Clinger's fast path is only exact when double operations are evaluated in
|
// Clinger's fast path is only exact when double operations are evaluated in
|
||||||
@@ -136,7 +134,6 @@ bool parse_float_fast(const char* first, const char* last, DecimalPointType deci
|
|||||||
// std::from_chars / std::strtod path.
|
// std::from_chars / std::strtod path.
|
||||||
static_cast<void>(first);
|
static_cast<void>(first);
|
||||||
static_cast<void>(last);
|
static_cast<void>(last);
|
||||||
static_cast<void>(decimal_point);
|
|
||||||
static_cast<void>(out);
|
static_cast<void>(out);
|
||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
@@ -175,7 +172,7 @@ bool parse_float_fast(const char* first, const char* last, DecimalPointType deci
|
|||||||
++num_digits;
|
++num_digits;
|
||||||
fractional_digits += static_cast<int>(seen_dot);
|
fractional_digits += static_cast<int>(seen_dot);
|
||||||
}
|
}
|
||||||
else if (static_cast<DecimalPointType>(c) == decimal_point)
|
else if (c == '.')
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
||||||
{
|
{
|
||||||
@@ -260,8 +257,8 @@ bool parse_float_fast(const char* first, const char* last, DecimalPointType deci
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// fast float path is only exact for `double`; decline for float/long double
|
/// fast float path is only exact for `double`; decline for float/long double
|
||||||
template<typename DecimalPointType, typename FloatType>
|
template<typename FloatType>
|
||||||
bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
|
bool parse_float_fast(const char* /*first*/, const char* /*last*/, FloatType& /*out*/) noexcept
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -273,9 +270,7 @@ std::from_chars is locale-independent, correctly rounded, and - via the
|
|||||||
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
||||||
over the whole value range (not just the Clinger subset). It is used only when
|
over the whole value range (not just the Clinger subset). It is used only when
|
||||||
__cpp_lib_to_chars indicates full floating-point support and only when it
|
__cpp_lib_to_chars indicates full floating-point support and only when it
|
||||||
consumes the entire token ([first, last)); a partial parse means the buffer
|
consumes the entire token ([first, last)). An under-/overflow (result_out_of_range) also declines, so
|
||||||
uses a non-'.' locale decimal point, in which case the caller falls back to the
|
|
||||||
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
|
|
||||||
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
||||||
expects (side-stepping the P4168 divergence between implementations).
|
expects (side-stepping the P4168 divergence between implementations).
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||||
#undef JSON_PRECISE_STREAM_POSITION
|
#undef JSON_PRECISE_STREAM_POSITION
|
||||||
#undef JSON_STRICT_NUL_HANDLING
|
#undef JSON_STRICT_NUL_HANDLING
|
||||||
|
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||||
|
|||||||
@@ -400,6 +400,30 @@ 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 {};
|
||||||
|
|||||||
+3328
-5164
File diff suppressed because it is too large
Load Diff
@@ -103,6 +103,10 @@
|
|||||||
#define JSON_STRICT_NUL_HANDLING 0
|
#define JSON_STRICT_NUL_HANDLING 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
|
||||||
@@ -139,14 +143,20 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
||||||
#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) json_abi ## a ## b ## c ## d ## e ## f
|
#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) \
|
#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)
|
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( \
|
||||||
@@ -155,7 +165,8 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||||
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_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) \
|
||||||
@@ -4410,6 +4421,30 @@ 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 {};
|
||||||
@@ -6047,11 +6082,40 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t)
|
|||||||
return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});
|
return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 >>
|
||||||
inline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
|
inline 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;
|
||||||
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||||
{
|
{
|
||||||
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
||||||
@@ -6072,6 +6136,11 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
|
|||||||
typename BasicJsonType::string_t, Key >::value >>
|
typename BasicJsonType::string_t, Key >::value >>
|
||||||
inline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
|
inline 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;
|
||||||
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
if (JSON_HEDLEY_UNLIKELY(!j.is_array()))
|
||||||
{
|
{
|
||||||
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
JSON_THROW(type_error::create(302, concat("type must be array, but is ", j.type_name()), &j));
|
||||||
@@ -6167,6 +6236,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#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>
|
||||||
// __ _____ _____ _____
|
// __ _____ _____ _____
|
||||||
// __| | __| | | | JSON for Modern C++
|
// __| | __| | | | JSON for Modern C++
|
||||||
@@ -6910,6 +6981,9 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
|||||||
!is_basic_json<CompatibleArrayType>::value
|
!is_basic_json<CompatibleArrayType>::value
|
||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
&& !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 >
|
||||||
@@ -6964,6 +7038,33 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -8607,12 +8708,10 @@ below).
|
|||||||
|
|
||||||
@param[in] first pointer to the first character of the number
|
@param[in] first pointer to the first character of the number
|
||||||
@param[in] last pointer past the last character
|
@param[in] last pointer past the last character
|
||||||
@param[in] decimal_point the (locale-dependent) decimal point character
|
|
||||||
@param[out] out the parsed value on success
|
@param[out] out the parsed value on success
|
||||||
@return true if the value was parsed exactly; false to fall back to strtod
|
@return true if the value was parsed exactly; false to fall back to strtod
|
||||||
*/
|
*/
|
||||||
template<typename DecimalPointType>
|
inline bool parse_float_fast(const char* first, const char* last, double& out) noexcept
|
||||||
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
|
|
||||||
{
|
{
|
||||||
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||||
// Clinger's fast path is only exact when double operations are evaluated in
|
// Clinger's fast path is only exact when double operations are evaluated in
|
||||||
@@ -8623,7 +8722,6 @@ bool parse_float_fast(const char* first, const char* last, DecimalPointType deci
|
|||||||
// std::from_chars / std::strtod path.
|
// std::from_chars / std::strtod path.
|
||||||
static_cast<void>(first);
|
static_cast<void>(first);
|
||||||
static_cast<void>(last);
|
static_cast<void>(last);
|
||||||
static_cast<void>(decimal_point);
|
|
||||||
static_cast<void>(out);
|
static_cast<void>(out);
|
||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
@@ -8662,7 +8760,7 @@ bool parse_float_fast(const char* first, const char* last, DecimalPointType deci
|
|||||||
++num_digits;
|
++num_digits;
|
||||||
fractional_digits += static_cast<int>(seen_dot);
|
fractional_digits += static_cast<int>(seen_dot);
|
||||||
}
|
}
|
||||||
else if (static_cast<DecimalPointType>(c) == decimal_point)
|
else if (c == '.')
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
||||||
{
|
{
|
||||||
@@ -8747,8 +8845,8 @@ bool parse_float_fast(const char* first, const char* last, DecimalPointType deci
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// fast float path is only exact for `double`; decline for float/long double
|
/// fast float path is only exact for `double`; decline for float/long double
|
||||||
template<typename DecimalPointType, typename FloatType>
|
template<typename FloatType>
|
||||||
bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
|
bool parse_float_fast(const char* /*first*/, const char* /*last*/, FloatType& /*out*/) noexcept
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -8760,9 +8858,7 @@ std::from_chars is locale-independent, correctly rounded, and - via the
|
|||||||
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
||||||
over the whole value range (not just the Clinger subset). It is used only when
|
over the whole value range (not just the Clinger subset). It is used only when
|
||||||
__cpp_lib_to_chars indicates full floating-point support and only when it
|
__cpp_lib_to_chars indicates full floating-point support and only when it
|
||||||
consumes the entire token ([first, last)); a partial parse means the buffer
|
consumes the entire token ([first, last)). An under-/overflow (result_out_of_range) also declines, so
|
||||||
uses a non-'.' locale decimal point, in which case the caller falls back to the
|
|
||||||
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
|
|
||||||
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
||||||
expects (side-stepping the P4168 divergence between implementations).
|
expects (side-stepping the P4168 divergence between implementations).
|
||||||
|
|
||||||
@@ -9303,7 +9399,6 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
|
|
||||||
, discard_number_values(discard_number_values_)
|
, discard_number_values(discard_number_values_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -9319,8 +9414,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// locales
|
// locales
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
/// return the locale-dependent decimal point
|
/// return the decimal point of the current locale
|
||||||
JSON_HEDLEY_PURE
|
|
||||||
static char get_decimal_point() noexcept
|
static char get_decimal_point() noexcept
|
||||||
{
|
{
|
||||||
const auto* loc = localeconv();
|
const auto* loc = localeconv();
|
||||||
@@ -10189,9 +10283,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
token_type::value_float if number could be successfully scanned,
|
token_type::value_float if number could be successfully scanned,
|
||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note The scanner is independent of the current locale. Internally, the
|
@note The scanner is independent of the current locale: token_buffer
|
||||||
locale's decimal point is used instead of `.` to work with the
|
always holds `.`. Only the std::strtod fallback of convert_number()
|
||||||
locale-dependent converters.
|
depends on the locale, and it looks up the decimal point right
|
||||||
|
before converting (see convert_float_locale_aware()).
|
||||||
*/
|
*/
|
||||||
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
|
||||||
{
|
{
|
||||||
@@ -10280,7 +10375,7 @@ scan_number_zero:
|
|||||||
{
|
{
|
||||||
case '.':
|
case '.':
|
||||||
{
|
{
|
||||||
add(decimal_point_char);
|
add(current);
|
||||||
decimal_point_position = token_buffer.size() - 1;
|
decimal_point_position = token_buffer.size() - 1;
|
||||||
goto scan_number_decimal1;
|
goto scan_number_decimal1;
|
||||||
}
|
}
|
||||||
@@ -10317,7 +10412,7 @@ scan_number_any1:
|
|||||||
|
|
||||||
case '.':
|
case '.':
|
||||||
{
|
{
|
||||||
add(decimal_point_char);
|
add(current);
|
||||||
decimal_point_position = token_buffer.size() - 1;
|
decimal_point_position = token_buffer.size() - 1;
|
||||||
goto scan_number_decimal1;
|
goto scan_number_decimal1;
|
||||||
}
|
}
|
||||||
@@ -10559,9 +10654,9 @@ scan_number_done:
|
|||||||
|
|
||||||
// Only a number below 1 can carry further insignificant zeros, and only
|
// Only a number below 1 can carry further insignificant zeros, and only
|
||||||
// while the count stays at the limit does removing them change the
|
// while the count stays at the limit does removing them change the
|
||||||
// answer - so this loop is skipped for all but a few tokens. Note
|
// answer - so this loop is skipped for all but a few tokens. The
|
||||||
// token_buffer holds the locale's decimal point, so the fraction is
|
// fraction is located through decimal_point_position rather than by
|
||||||
// located through decimal_point_position rather than by searching '.'.
|
// searching '.'.
|
||||||
if (lead_zero != 0)
|
if (lead_zero != 0)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||||
@@ -10579,8 +10674,8 @@ scan_number_done:
|
|||||||
@brief convert the number text in token_buffer to its value and token type
|
@brief convert the number text in token_buffer to its value and token type
|
||||||
|
|
||||||
The digit sequence in token_buffer has already been validated (by the
|
The digit sequence in token_buffer has already been validated (by the
|
||||||
scan_number() state machine or by the contiguous fast path) and holds the
|
scan_number() state machine or by the contiguous fast path) and holds '.'
|
||||||
locale decimal point in place of '.'. Integers are parsed first and fall
|
as decimal point, independent of the locale. Integers are parsed first and fall
|
||||||
back to floating point on overflow. This is shared so both scanners produce
|
back to floating point on overflow. This is shared so both scanners produce
|
||||||
identical results.
|
identical results.
|
||||||
|
|
||||||
@@ -10660,7 +10755,7 @@ scan_number_done:
|
|||||||
// integer conversion above overflowed. Prefer std::from_chars
|
// integer conversion above overflowed. Prefer std::from_chars
|
||||||
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||||
// otherwise the exact Clinger fast path (double only); otherwise the
|
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||||
// locale-aware strtof/strtod.
|
// locale-aware strtof/strtod/strtold.
|
||||||
if (parse_float_from_chars(num_begin, num_end, value_float))
|
if (parse_float_from_chars(num_begin, num_end, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
@@ -10669,18 +10764,67 @@ scan_number_done:
|
|||||||
// extra pass over the token's bytes, which otherwise shows up on
|
// extra pass over the token's bytes, which otherwise shows up on
|
||||||
// high-precision inputs such as canada.json
|
// high-precision inputs such as canada.json
|
||||||
if (mantissa_fits_clinger(mantissa_end)
|
if (mantissa_fits_clinger(mantissa_end)
|
||||||
&& parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
|
&& parse_float_fast(num_begin, num_end, value_float))
|
||||||
{
|
{
|
||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convert_float_locale_aware();
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the float in token_buffer with strtof/strtod/strtold
|
||||||
|
|
||||||
|
These functions expect the decimal point of the *current* locale, so it is
|
||||||
|
looked up right before the conversion instead of once when the lexer is
|
||||||
|
constructed: a locale change in between (by a parser callback, a SAX
|
||||||
|
handler, or another thread) must not truncate the value (#5198). The
|
||||||
|
token has been validated before, so if the conversion stops early and the
|
||||||
|
decimal point changed in the meantime, the locale changed between the
|
||||||
|
lookup and the call, and the conversion is repeated with the new decimal
|
||||||
|
point. If the decimal point did not change, a retry cannot succeed: the
|
||||||
|
locale's decimal point is not a single character (e.g., the two-byte
|
||||||
|
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
|
||||||
|
The value strtod parsed up to that point is kept, as before this change.
|
||||||
|
|
||||||
|
Note that changing the locale in another thread *while* strtod runs is
|
||||||
|
undefined behavior of the C library, which this function cannot prevent.
|
||||||
|
*/
|
||||||
|
void convert_float_locale_aware()
|
||||||
|
{
|
||||||
|
const bool has_dot = decimal_point_position != std::string::npos;
|
||||||
|
char decimal_point = get_decimal_point();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const bool substitute = has_dot && decimal_point != '.';
|
||||||
|
if (substitute)
|
||||||
|
{
|
||||||
|
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
|
||||||
|
}
|
||||||
|
|
||||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||||
strtof(value_float, token_buffer.data(), &endptr);
|
strtof(value_float, token_buffer.data(), &endptr);
|
||||||
|
|
||||||
// we checked the number format before
|
if (substitute)
|
||||||
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
|
{
|
||||||
|
// get_string() hands the token to the SAX interface with '.'
|
||||||
|
token_buffer[decimal_point_position] = '.';
|
||||||
|
}
|
||||||
|
|
||||||
return token_type::value_float;
|
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// retry only if the locale changed; otherwise, this would loop forever
|
||||||
|
const char current_decimal_point = get_decimal_point();
|
||||||
|
if (current_decimal_point == decimal_point)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
decimal_point = current_decimal_point;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -10688,7 +10832,7 @@ scan_number_done:
|
|||||||
|
|
||||||
Parses the whole number token straight from the input buffer, avoiding the
|
Parses the whole number token straight from the input buffer, avoiding the
|
||||||
per-character get()/add() of scan_number(). On success it fills token_buffer
|
per-character get()/add() of scan_number(). On success it fills token_buffer
|
||||||
(with the locale decimal point substituted, as scan_number() does) and
|
(as scan_number() does) and
|
||||||
returns the token type. On anything it does not fully recognize as a
|
returns the token type. On anything it does not fully recognize as a
|
||||||
well-formed number it makes no state change and returns
|
well-formed number it makes no state change and returns
|
||||||
token_type::uninitialized, so the caller falls back to scan_number(), which
|
token_type::uninitialized, so the caller falls back to scan_number(), which
|
||||||
@@ -10804,16 +10948,11 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// materialize the token exactly as scan_number() would, substituting the
|
// materialize the token exactly as scan_number() would. reset() already
|
||||||
// locale decimal point so convert_number()'s strtof fallback stays valid.
|
// cleared token_buffer, so append() fills it (assign() is avoided
|
||||||
// reset() already cleared token_buffer, so append() fills it (assign() is
|
// because custom string_t types need not provide it)
|
||||||
// avoided because custom string_t types need not provide it)
|
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
if (dot_index != std::string::npos)
|
|
||||||
{
|
|
||||||
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
|
|
||||||
decimal_point_position = dot_index;
|
decimal_point_position = dot_index;
|
||||||
}
|
|
||||||
|
|
||||||
ia.bulk_skip(len - 1);
|
ia.bulk_skip(len - 1);
|
||||||
position.chars_read_total += (len - 1);
|
position.chars_read_total += (len - 1);
|
||||||
@@ -11080,11 +11219,7 @@ scan_number_done:
|
|||||||
/// return current string value (implicitly resets the token; useful only once)
|
/// return current string value (implicitly resets the token; useful only once)
|
||||||
string_t& get_string()
|
string_t& get_string()
|
||||||
{
|
{
|
||||||
// translate decimal points from locale back to '.' (#4084)
|
// a number token holds '.' regardless of the locale (#4084)
|
||||||
if (decimal_point_char != '.' && decimal_point_position != std::string::npos)
|
|
||||||
{
|
|
||||||
token_buffer[decimal_point_position] = '.';
|
|
||||||
}
|
|
||||||
return token_buffer;
|
return token_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11380,9 +11515,7 @@ scan_number_done:
|
|||||||
number_unsigned_t value_unsigned = 0;
|
number_unsigned_t value_unsigned = 0;
|
||||||
number_float_t value_float = 0;
|
number_float_t value_float = 0;
|
||||||
|
|
||||||
/// the decimal point
|
/// the position of the decimal point in token_buffer
|
||||||
const char_int_type decimal_point_char = '.';
|
|
||||||
/// the position of the decimal point in the input
|
|
||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
||||||
@@ -14059,6 +14192,80 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief reads a CBOR object key
|
||||||
|
|
||||||
|
RFC 8949 allows any data item as a map key, but only strings have a
|
||||||
|
counterpart in JSON. A key of any other type is rejected with a message
|
||||||
|
naming that type, rather than the one @ref get_cbor_string gives for a
|
||||||
|
malformed string.
|
||||||
|
|
||||||
|
@param[out] result created key
|
||||||
|
|
||||||
|
@return whether key creation completed
|
||||||
|
*/
|
||||||
|
bool get_cbor_object_key(string_t& result)
|
||||||
|
{
|
||||||
|
// EOF and major type 3 (text string) are left to get_cbor_string
|
||||||
|
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
|
||||||
|
{
|
||||||
|
return get_cbor_string(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* found = nullptr;
|
||||||
|
switch (static_cast<unsigned int>(current) >> 5u)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
found = "an unsigned integer";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
found = "a negative integer";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
found = "a byte string";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
found = "an array";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
found = "a map";
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
found = "a tag";
|
||||||
|
break;
|
||||||
|
default: // major type 7
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case 0xF4:
|
||||||
|
case 0xF5:
|
||||||
|
found = "a boolean";
|
||||||
|
break;
|
||||||
|
case 0xF6:
|
||||||
|
found = "null";
|
||||||
|
break;
|
||||||
|
case 0xF7:
|
||||||
|
found = "undefined";
|
||||||
|
break;
|
||||||
|
case 0xF9:
|
||||||
|
case 0xFA:
|
||||||
|
case 0xFB:
|
||||||
|
found = "a floating-point number";
|
||||||
|
break;
|
||||||
|
case 0xFF:
|
||||||
|
found = "a break stop code";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
found = "a simple value";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
|
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR byte array
|
@brief reads a definite-length CBOR byte array
|
||||||
|
|
||||||
@@ -14303,7 +14510,7 @@ class binary_reader
|
|||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -14804,6 +15011,98 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief reads a MessagePack object key
|
||||||
|
|
||||||
|
The MessagePack specification allows any type as a map key, but only
|
||||||
|
strings have a counterpart in JSON. A key of any other type is rejected
|
||||||
|
with a message naming that type, rather than the one @ref
|
||||||
|
get_msgpack_string gives for a malformed string.
|
||||||
|
|
||||||
|
@param[out] result created key
|
||||||
|
|
||||||
|
@return whether key creation completed
|
||||||
|
*/
|
||||||
|
bool get_msgpack_object_key(string_t& result)
|
||||||
|
{
|
||||||
|
const char* found = nullptr;
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case 0xC0:
|
||||||
|
found = "nil";
|
||||||
|
break;
|
||||||
|
case 0xC2:
|
||||||
|
case 0xC3:
|
||||||
|
found = "a boolean";
|
||||||
|
break;
|
||||||
|
case 0xCA:
|
||||||
|
case 0xCB:
|
||||||
|
found = "a float";
|
||||||
|
break;
|
||||||
|
case 0xC4:
|
||||||
|
case 0xC5:
|
||||||
|
case 0xC6:
|
||||||
|
found = "a bin";
|
||||||
|
break;
|
||||||
|
case 0xC7:
|
||||||
|
case 0xC8:
|
||||||
|
case 0xC9:
|
||||||
|
case 0xD4:
|
||||||
|
case 0xD5:
|
||||||
|
case 0xD6:
|
||||||
|
case 0xD7:
|
||||||
|
case 0xD8:
|
||||||
|
found = "an ext";
|
||||||
|
break;
|
||||||
|
case 0xCC:
|
||||||
|
case 0xCD:
|
||||||
|
case 0xCE:
|
||||||
|
case 0xCF:
|
||||||
|
case 0xD0:
|
||||||
|
case 0xD1:
|
||||||
|
case 0xD2:
|
||||||
|
case 0xD3:
|
||||||
|
found = "an integer";
|
||||||
|
break;
|
||||||
|
case 0xDC:
|
||||||
|
case 0xDD:
|
||||||
|
found = "an array";
|
||||||
|
break;
|
||||||
|
case 0xDE:
|
||||||
|
case 0xDF:
|
||||||
|
found = "a map";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// fixint, fixmap, and fixarray; strings, EOF, and the unused
|
||||||
|
// byte 0xC1 are left to get_msgpack_string
|
||||||
|
if (current == char_traits<char_type>::eof())
|
||||||
|
{
|
||||||
|
return get_msgpack_string(result);
|
||||||
|
}
|
||||||
|
if (current <= 0x7F || current >= 0xE0)
|
||||||
|
{
|
||||||
|
found = "an integer";
|
||||||
|
}
|
||||||
|
else if (current <= 0x8F)
|
||||||
|
{
|
||||||
|
found = "a map";
|
||||||
|
}
|
||||||
|
else if (current <= 0x9F)
|
||||||
|
{
|
||||||
|
found = "an array";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return get_msgpack_string(result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
|
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a MessagePack byte array
|
@brief reads a MessagePack byte array
|
||||||
|
|
||||||
@@ -14966,7 +15265,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
|
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -32549,6 +32848,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
|||||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||||
#undef JSON_PRECISE_STREAM_POSITION
|
#undef JSON_PRECISE_STREAM_POSITION
|
||||||
#undef JSON_STRICT_NUL_HANDLING
|
#undef JSON_STRICT_NUL_HANDLING
|
||||||
|
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
// #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||||
|
|||||||
@@ -64,6 +64,10 @@
|
|||||||
#define JSON_STRICT_NUL_HANDLING 0
|
#define JSON_STRICT_NUL_HANDLING 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
|
||||||
@@ -100,14 +104,20 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
||||||
#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) json_abi ## a ## b ## c ## d ## e ## f
|
#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) \
|
#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)
|
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( \
|
||||||
@@ -116,7 +126,8 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||||
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_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) \
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ TEST_CASE("default namespace")
|
|||||||
expected += "_snul";
|
expected += "_snul";
|
||||||
#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";
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ TEST_CASE("default namespace without version component")
|
|||||||
expected += "_snul";
|
expected += "_snul";
|
||||||
#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
|
||||||
|
|||||||
+43
-2
@@ -1830,10 +1830,51 @@ TEST_CASE("CBOR")
|
|||||||
SECTION("invalid string in map")
|
SECTION("invalid string in map")
|
||||||
{
|
{
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("non-string key (see #2766 and #3381)")
|
||||||
|
{
|
||||||
|
// only text strings map to JSON object keys; any other key is
|
||||||
|
// rejected with a message naming its type
|
||||||
|
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
|
||||||
|
{
|
||||||
|
{{0xA1, 0x01, 0x01}, "an unsigned integer; last byte: 0x01"},
|
||||||
|
{{0xA1, 0x20, 0x01}, "a negative integer; last byte: 0x20"},
|
||||||
|
{{0xA1, 0x41, 0x61, 0x01}, "a byte string; last byte: 0x41"},
|
||||||
|
{{0xA1, 0x80, 0x01}, "an array; last byte: 0x80"},
|
||||||
|
{{0xA1, 0xA0, 0x01}, "a map; last byte: 0xA0"},
|
||||||
|
{{0xA1, 0xC0, 0x61, 0x61, 0x01}, "a tag; last byte: 0xC0"},
|
||||||
|
{{0xA1, 0xF4, 0x01}, "a boolean; last byte: 0xF4"},
|
||||||
|
{{0xA1, 0xF5, 0x01}, "a boolean; last byte: 0xF5"},
|
||||||
|
{{0xA1, 0xF6, 0x01}, "null; last byte: 0xF6"},
|
||||||
|
{{0xA1, 0xF7, 0x01}, "undefined; last byte: 0xF7"},
|
||||||
|
{{0xA1, 0xF9, 0x3C, 0x00, 0x01}, "a floating-point number; last byte: 0xF9"},
|
||||||
|
{{0xA1, 0xFA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFA"},
|
||||||
|
{{0xA1, 0xFB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFB"},
|
||||||
|
{{0xA1, 0xE0, 0x01}, "a simple value; last byte: 0xE0"},
|
||||||
|
{{0xA1, 0xF8, 0x20, 0x01}, "a simple value; last byte: 0xF8"},
|
||||||
|
// indefinite-length map
|
||||||
|
{{0xBF, 0x01, 0x01, 0xFF}, "an unsigned integer; last byte: 0x01"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& c : cases)
|
||||||
|
{
|
||||||
|
CAPTURE(c.first)
|
||||||
|
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found " + c.second;
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(c.first), expected.c_str(), json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(c.first, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
// a key of major type 3 with a reserved length is still reported as
|
||||||
|
// a malformed string, and a missing key as the end of input
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("invalid UTF-8 in string (see #5529)")
|
SECTION("invalid UTF-8 in string (see #5529)")
|
||||||
{
|
{
|
||||||
// a two-character text string (major type 3) whose bytes are not
|
// a two-character text string (major type 3) whose bytes are not
|
||||||
@@ -2284,7 +2325,7 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
|||||||
SECTION("a break marker outside an indefinite-length string is not a string")
|
SECTION("a break marker outside an indefinite-length string is not a string")
|
||||||
{
|
{
|
||||||
// 0xFF only closes a string that was opened; on its own it is not one
|
// 0xFF only closes a string that was opened; on its own it is not one
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -666,7 +666,7 @@ TEST_CASE("parse_float_fast declines what it cannot convert exactly")
|
|||||||
// always safe: the caller then falls back to a slower, exact conversion.
|
// always safe: the caller then falls back to a slower, exact conversion.
|
||||||
const auto fast = [](const std::string & s, double & out)
|
const auto fast = [](const std::string & s, double & out)
|
||||||
{
|
{
|
||||||
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out);
|
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), out);
|
||||||
};
|
};
|
||||||
double out = 0;
|
double out = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
@@ -12,7 +12,12 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <clocale>
|
#include <clocale>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct ParserImpl final: public nlohmann::json_sax<json>
|
struct ParserImpl final: public nlohmann::json_sax<json>
|
||||||
{
|
{
|
||||||
@@ -175,3 +180,208 @@ TEST_CASE("locale-dependent test (LC_NUMERIC=de_DE)")
|
|||||||
MESSAGE("locale de_DE is not usable");
|
MESSAGE("locale de_DE is not usable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// records the numbers of a flat array and switches LC_NUMERIC to the given
|
||||||
|
// locale once the array opens - after the lexer was constructed, but before
|
||||||
|
// any number in the array is lexed
|
||||||
|
struct LocaleSwitchingSax final: public nlohmann::json_sax<json>
|
||||||
|
{
|
||||||
|
explicit LocaleSwitchingSax(const char* switch_to)
|
||||||
|
: locale_after_open(switch_to)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool null() override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool boolean(bool /*val*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool number_integer(json::number_integer_t /*val*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool number_unsigned(json::number_unsigned_t /*val*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool number_float(json::number_float_t val, const json::string_t& s) override
|
||||||
|
{
|
||||||
|
values.push_back(val);
|
||||||
|
strings.push_back(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool string(json::string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool binary(json::binary_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool start_object(std::size_t /*val*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool key(json::string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool end_object() override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool start_array(std::size_t /*val*/) override
|
||||||
|
{
|
||||||
|
switched = std::setlocale(LC_NUMERIC, locale_after_open.c_str()) != nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool end_array() override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool parse_error(std::size_t /*val*/, const std::string& /*val*/, const nlohmann::detail::exception& /*val*/) override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string locale_after_open;
|
||||||
|
bool switched = false;
|
||||||
|
std::vector<json::number_float_t> values {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
std::vector<json::string_t> strings {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("locale changes between lexer construction and number conversion (#5198)")
|
||||||
|
{
|
||||||
|
// The numbers are chosen so that the conversion also takes the strtod
|
||||||
|
// fallback, which honors the locale that is current at conversion time:
|
||||||
|
// too many significant digits for Clinger's fast path, an underflow that
|
||||||
|
// std::from_chars rejects, and a plain value.
|
||||||
|
const std::vector<std::string> numbers = {"3.14159265358979323846", "1.5e-400", "12.34", "-0.000123456789012345678"};
|
||||||
|
std::string text = "[";
|
||||||
|
for (const auto& n : numbers)
|
||||||
|
{
|
||||||
|
text += (text.size() == 1 ? "" : ",") + n;
|
||||||
|
}
|
||||||
|
text += "]";
|
||||||
|
|
||||||
|
using long_double_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, long double>;
|
||||||
|
|
||||||
|
// reference values, parsed without a locale switch
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
const json expected = json::parse(text);
|
||||||
|
const long_double_json expected_ld = long_double_json::parse(text);
|
||||||
|
|
||||||
|
const std::array<std::pair<const char*, const char*>, 2> transitions =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{"C", "de_DE"},
|
||||||
|
{"de_DE", "C"}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& transition : transitions)
|
||||||
|
{
|
||||||
|
CAPTURE(transition.first);
|
||||||
|
CAPTURE(transition.second);
|
||||||
|
|
||||||
|
if (std::setlocale(LC_NUMERIC, transition.first) == nullptr)
|
||||||
|
{
|
||||||
|
MESSAGE("locale is not usable");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAX parsing
|
||||||
|
{
|
||||||
|
LocaleSwitchingSax sax(transition.second);
|
||||||
|
CHECK(json::sax_parse(text, &sax));
|
||||||
|
if (sax.switched)
|
||||||
|
{
|
||||||
|
CHECK(sax.values == expected.get<std::vector<json::number_float_t>>());
|
||||||
|
CHECK(sax.strings == numbers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOM parsing with a callback
|
||||||
|
{
|
||||||
|
bool switched = false;
|
||||||
|
const auto cb = [&](int /*depth*/, json::parse_event_t event, json& /*parsed*/) noexcept
|
||||||
|
{
|
||||||
|
if (event == json::parse_event_t::array_start)
|
||||||
|
{
|
||||||
|
switched = std::setlocale(LC_NUMERIC, transition.second) != nullptr;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const json j = json::parse(text, cb);
|
||||||
|
if (switched)
|
||||||
|
{
|
||||||
|
CHECK(j == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a long double goes through std::strtold unless std::from_chars supports it
|
||||||
|
{
|
||||||
|
bool switched = false;
|
||||||
|
const auto cb = [&](int /*depth*/, long_double_json::parse_event_t event, long_double_json& /*parsed*/) noexcept
|
||||||
|
{
|
||||||
|
if (event == long_double_json::parse_event_t::array_start)
|
||||||
|
{
|
||||||
|
switched = std::setlocale(LC_NUMERIC, transition.second) != nullptr;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const long_double_json j = long_double_json::parse(text, cb);
|
||||||
|
if (switched)
|
||||||
|
{
|
||||||
|
CHECK(j == expected_ld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("locale with a multi-byte decimal point")
|
||||||
|
{
|
||||||
|
// Some locales use a decimal point that is not a single character, e.g.
|
||||||
|
// U+066B ARABIC DECIMAL SEPARATOR (two bytes in UTF-8). It cannot be
|
||||||
|
// substituted in place for '.', so the strtod fallback stops early. The
|
||||||
|
// conversion must still terminate rather than retry forever.
|
||||||
|
const std::array<const char*, 6> names = {{"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"}};
|
||||||
|
bool tested = false;
|
||||||
|
for (const char* name : names)
|
||||||
|
{
|
||||||
|
if (std::setlocale(LC_NUMERIC, name) == nullptr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string decimal_point = std::localeconv()->decimal_point;
|
||||||
|
if (decimal_point.size() < 2)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CAPTURE(name);
|
||||||
|
tested = true;
|
||||||
|
|
||||||
|
// too many significant digits for Clinger's fast path, and an underflow
|
||||||
|
// that std::from_chars rejects: both reach the strtod fallback
|
||||||
|
json j;
|
||||||
|
CHECK_NOTHROW(j = json::parse("[3.14159265358979323846, 1.5e-400, -0.000123456789012345678]"));
|
||||||
|
CHECK(j.is_array());
|
||||||
|
CHECK(json::accept("3.14159265358979323846"));
|
||||||
|
|
||||||
|
// a value the locale-independent paths convert is not affected
|
||||||
|
CHECK(json::parse("12.5") == 12.5);
|
||||||
|
}
|
||||||
|
if (!tested)
|
||||||
|
{
|
||||||
|
MESSAGE("no locale with a multi-byte decimal point is usable");
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1551,10 +1551,69 @@ TEST_CASE("MessagePack")
|
|||||||
SECTION("invalid string in map")
|
SECTION("invalid string in map")
|
||||||
{
|
{
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found an integer; last byte: 0xFF", json::parse_error&);
|
||||||
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("non-string key (see #3381)")
|
||||||
|
{
|
||||||
|
// only strings map to JSON object keys; any other key is rejected
|
||||||
|
// with a message naming its type
|
||||||
|
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
|
||||||
|
{
|
||||||
|
{{0x81, 0xC0, 0x01}, "nil; last byte: 0xC0"},
|
||||||
|
{{0x81, 0xC2, 0x01}, "a boolean; last byte: 0xC2"},
|
||||||
|
{{0x81, 0xC3, 0x01}, "a boolean; last byte: 0xC3"},
|
||||||
|
{{0x81, 0xCA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a float; last byte: 0xCA"},
|
||||||
|
{{0x81, 0xCB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a float; last byte: 0xCB"},
|
||||||
|
{{0x81, 0xC4, 0x00, 0x01}, "a bin; last byte: 0xC4"},
|
||||||
|
{{0x81, 0xC5, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC5"},
|
||||||
|
{{0x81, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x01}, "a bin; last byte: 0xC6"},
|
||||||
|
{{0x81, 0xC7, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC7"},
|
||||||
|
{{0x81, 0xC8, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC8"},
|
||||||
|
{{0x81, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an ext; last byte: 0xC9"},
|
||||||
|
{{0x81, 0xD4, 0x01, 0x00, 0x01}, "an ext; last byte: 0xD4"},
|
||||||
|
{{0x81, 0xD5, 0x01, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD5"},
|
||||||
|
{{0x81, 0xD6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD6"},
|
||||||
|
{{0x81, 0xD7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD7"},
|
||||||
|
{{0x81, 0xD8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "an ext; last byte: 0xD8"},
|
||||||
|
{{0x81, 0xCC, 0x01, 0x01}, "an integer; last byte: 0xCC"},
|
||||||
|
{{0x81, 0xCD, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCD"},
|
||||||
|
{{0x81, 0xCE, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCE"},
|
||||||
|
{{0x81, 0xCF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xCF"},
|
||||||
|
{{0x81, 0xD0, 0x01, 0x01}, "an integer; last byte: 0xD0"},
|
||||||
|
{{0x81, 0xD1, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD1"},
|
||||||
|
{{0x81, 0xD2, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD2"},
|
||||||
|
{{0x81, 0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}, "an integer; last byte: 0xD3"},
|
||||||
|
{{0x81, 0x00, 0x01}, "an integer; last byte: 0x00"},
|
||||||
|
{{0x81, 0x7F, 0x01}, "an integer; last byte: 0x7F"},
|
||||||
|
{{0x81, 0xE0, 0x01}, "an integer; last byte: 0xE0"},
|
||||||
|
{{0x81, 0x80, 0x01}, "a map; last byte: 0x80"},
|
||||||
|
{{0x81, 0x8F, 0x01}, "a map; last byte: 0x8F"},
|
||||||
|
{{0x81, 0xDE, 0x00, 0x00, 0x01}, "a map; last byte: 0xDE"},
|
||||||
|
{{0x81, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x01}, "a map; last byte: 0xDF"},
|
||||||
|
{{0x81, 0x90, 0x01}, "an array; last byte: 0x90"},
|
||||||
|
{{0x81, 0x9F, 0x01}, "an array; last byte: 0x9F"},
|
||||||
|
{{0x81, 0xDC, 0x00, 0x00, 0x01}, "an array; last byte: 0xDC"},
|
||||||
|
{{0x81, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x01}, "an array; last byte: 0xDD"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& c : cases)
|
||||||
|
{
|
||||||
|
CAPTURE(c.first)
|
||||||
|
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found " + c.second;
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(c.first), expected.c_str(), json::parse_error&);
|
||||||
|
CHECK(json::from_msgpack(c.first, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
json _;
|
||||||
|
// the unused byte 0xC1 is still reported as a malformed string
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81, 0xC1, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xC1", json::parse_error&);
|
||||||
|
// a missing key is still reported as the end of input
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("invalid UTF-8 in string (see #5529)")
|
SECTION("invalid UTF-8 in string (see #5529)")
|
||||||
{
|
{
|
||||||
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
||||||
|
|||||||
@@ -1018,7 +1018,7 @@ TEST_CASE("regression tests 1")
|
|||||||
};
|
};
|
||||||
|
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x98", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an array; last byte: 0x98", json::parse_error&);
|
||||||
|
|
||||||
// related test case: nonempty UTF-8 string (indefinite length)
|
// related test case: nonempty UTF-8 string (indefinite length)
|
||||||
std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
|
std::vector<uint8_t> const vec1 {0x7f, 0x61, 0x61};
|
||||||
@@ -1065,7 +1065,7 @@ TEST_CASE("regression tests 1")
|
|||||||
};
|
};
|
||||||
|
|
||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec1), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
|
||||||
|
|
||||||
// related test case: double-precision
|
// related test case: double-precision
|
||||||
std::vector<uint8_t> const vec2
|
std::vector<uint8_t> const vec2
|
||||||
@@ -1077,7 +1077,7 @@ TEST_CASE("regression tests 1")
|
|||||||
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
|
0x96, 0x96, 0xb4, 0xb4, 0xfa, 0x94, 0x94, 0x61,
|
||||||
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
|
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0xfb
|
||||||
};
|
};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xB4", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec2), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing CBOR object key: only string keys are supported, but found a map; last byte: 0xB4", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
|
SECTION("issue #452 - Heap-buffer-overflow (OSS-Fuzz issue 585)")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
namespaces = ['nlohmann']
|
namespaces = ['nlohmann']
|
||||||
abi_prefix = 'json_abi'
|
abi_prefix = 'json_abi'
|
||||||
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics', '_psp', '_snul']
|
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics', '_psp', '_snul', '_ekmo']
|
||||||
version = '_v' + args.version.replace('.', '_')
|
version = '_v' + args.version.replace('.', '_')
|
||||||
inline_namespaces = []
|
inline_namespaces = []
|
||||||
|
|
||||||
|
|||||||
@@ -36,31 +36,5 @@
|
|||||||
</Expand>
|
</Expand>
|
||||||
</Type>
|
</Type>
|
||||||
|
|
||||||
<!-- Fallback for when the basic_json entry above does not match: json_default_base is the (empty) default
|
|
||||||
base class of basic_json, and base class visualizers are inherited by derived types and evaluated
|
|
||||||
against the derived object, so m_data is accessible here. The class lives in {{ ns }} after
|
|
||||||
3.12.0 (#5238) and in {{ ns }}::detail up to 3.12.0, so both names are listed. -->
|
|
||||||
{% for default_base in ['json_default_base', 'detail::json_default_base'] %}
|
|
||||||
<Type Name="{{ ns }}::{{ default_base }}">
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::null">null</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::discarded">discarded</DisplayString>
|
|
||||||
<Expand>
|
|
||||||
<ExpandedItem Condition="m_data.m_type == {{ ns }}::detail::value_t::object">
|
|
||||||
*(m_data.m_value.object),view(simple)
|
|
||||||
</ExpandedItem>
|
|
||||||
<ExpandedItem Condition="m_data.m_type == {{ ns }}::detail::value_t::array">
|
|
||||||
*(m_data.m_value.array),view(simple)
|
|
||||||
</ExpandedItem>
|
|
||||||
</Expand>
|
|
||||||
</Type>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</AutoVisualizer>
|
</AutoVisualizer>
|
||||||
|
|||||||
Reference in New Issue
Block a user