mirror of
https://github.com/nlohmann/json.git
synced 2026-09-28 10:40:30 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
103b37aa7d | ||
|
|
4671f04372 | ||
|
|
99716a5ade | ||
|
|
d914e3a027 |
@@ -83,4 +83,9 @@ build_script:
|
|||||||
- cmake --build . --config "%configuration%" --parallel 2
|
- cmake --build . --config "%configuration%" --parallel 2
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- ctest -C "%configuration%" --parallel 2 --output-on-failure
|
- if "%configuration%"=="Release" ctest -C "%configuration%" --parallel 2 --output-on-failure
|
||||||
|
# On Debug builds, skip test-unicode_all
|
||||||
|
# as it is extremely slow to run and cause
|
||||||
|
# occasional timeouts on AppVeyor.
|
||||||
|
# More info: https://github.com/nlohmann/json/pull/1570
|
||||||
|
- if "%configuration%"=="Debug" ctest --exclude-regex "test-unicode" -C "%configuration%" --parallel 2 --output-on-failure
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --parallel 10
|
run: cmake --build build --parallel 10
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
run: cd build ; ctest -j 10 -C Debug --exclude-regex "test-unicode" --output-on-failure
|
||||||
|
|
||||||
clang-cl-12:
|
clang-cl-12:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --config Debug --parallel 10
|
run: cmake --build build --config Debug --parallel 10
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
run: cd build ; ctest -j 10 -C Debug --exclude-regex "test-unicode" --output-on-failure
|
||||||
|
|
||||||
ci_module_cpp20:
|
ci_module_cpp20:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|||||||
+6
-7
@@ -413,14 +413,13 @@ add_custom_target(ci_test_single_header
|
|||||||
# Valgrind.
|
# Valgrind.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# The Unicode test (~17M assertions) is too slow under Valgrind.
|
|
||||||
add_custom_target(ci_test_valgrind
|
add_custom_target(ci_test_valgrind
|
||||||
COMMAND CXX=${GCC_TOOL} ${CMAKE_COMMAND}
|
COMMAND CXX=${GCC_TOOL} ${CMAKE_COMMAND}
|
||||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||||
-DJSON_BuildTests=ON -DJSON_Valgrind=ON
|
-DJSON_BuildTests=ON -DJSON_Valgrind=ON
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_valgrind
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_valgrind
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_valgrind
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_valgrind
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_valgrind && ${CMAKE_CTEST_COMMAND} -L valgrind --exclude-regex "test-unicode" --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_valgrind && ${CMAKE_CTEST_COMMAND} -L valgrind --parallel ${N} --output-on-failure
|
||||||
COMMENT "Compile and test with Valgrind"
|
COMMENT "Compile and test with Valgrind"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -718,7 +717,7 @@ foreach(COMPILER g++-4.8 g++-4.9 g++-5 g++-6 g++-7 g++-8 g++-9 g++-10 g++-11 cla
|
|||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
||||||
${ADDITIONAL_FLAGS}
|
${ADDITIONAL_FLAGS}
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER} && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER} && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" --output-on-failure
|
||||||
COMMENT "Compile and test with ${COMPILER}"
|
COMMENT "Compile and test with ${COMPILER}"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
@@ -732,7 +731,7 @@ add_custom_target(ci_test_compiler_default
|
|||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_default
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_default
|
||||||
${ADDITIONAL_FLAGS}
|
${ADDITIONAL_FLAGS}
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_default --parallel ${N}
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_default --parallel ${N}
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_default && ${CMAKE_CTEST_COMMAND} --parallel ${N} -LE git_required --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_default && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" -LE git_required --output-on-failure
|
||||||
COMMENT "Compile and test with default C++ compiler"
|
COMMENT "Compile and test with default C++ compiler"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -770,7 +769,7 @@ add_custom_target(ci_icpc
|
|||||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpc
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpc
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpc
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpc
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" --output-on-failure
|
||||||
COMMENT "Compile and test with ICPC"
|
COMMENT "Compile and test with ICPC"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -781,7 +780,7 @@ add_custom_target(ci_icpx
|
|||||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpx
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpx
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpx
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpx
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpx && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpx && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" --output-on-failure
|
||||||
COMMENT "Compile and test with ICPX (Intel oneAPI DPC++/C++)"
|
COMMENT "Compile and test with ICPX (Intel oneAPI DPC++/C++)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -817,7 +816,7 @@ add_custom_target(ci_nvhpc
|
|||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
||||||
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
||||||
# instead of treating them as shell pipe operators
|
# instead of treating them as shell pipe operators
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison_cpp20\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode\\|test-comparison_cpp20\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
|
||||||
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -589,6 +589,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 +778,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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 {};
|
||||||
|
|||||||
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)
|
||||||
{
|
{
|
||||||
@@ -32549,6 +32650,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) \
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ json_test_set_test_options(test-disabled_exceptions
|
|||||||
#$<$<CXX_COMPILER_ID:MSVC>:/EH>
|
#$<$<CXX_COMPILER_ID:MSVC>:/EH>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# raise timeout of expensive Unicode test
|
||||||
|
json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# add unit tests
|
# add unit tests
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array> // array
|
|
||||||
#include <cstdint> // uint8_t
|
#include <cstdint> // uint8_t
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <fstream> // ifstream, istreambuf_iterator, ios
|
#include <fstream> // ifstream, istreambuf_iterator, ios
|
||||||
@@ -43,33 +42,6 @@ T next_integer_sample(T i, T last, T stride)
|
|||||||
return n < last ? n : last;
|
return n < last ? n : last;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UTF-8 continuation bytes in [lo, hi] that stand in for all of them in the
|
|
||||||
// ill-formed UTF-8 tests. Both the lexer's range checks and the serializer's
|
|
||||||
// decoder (detail::decode) only distinguish the classes 0x80..0x8F, 0x90..0x9F,
|
|
||||||
// and 0xA0..0xBF, so the first and last byte of each class within [lo, hi]
|
|
||||||
// exercise every behavior while a test sweeps another byte position through
|
|
||||||
// all 256 values (#5418). Define JSON_TEST_UTF8_EXHAUSTIVE to get every byte.
|
|
||||||
inline std::vector<int> utf8_continuation_bytes(int lo, int hi)
|
|
||||||
{
|
|
||||||
std::vector<int> result;
|
|
||||||
#ifdef JSON_TEST_UTF8_EXHAUSTIVE
|
|
||||||
for (int byte = lo; byte <= hi; ++byte)
|
|
||||||
{
|
|
||||||
result.push_back(byte);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static const std::array<int, 6> class_ends = {{0x80, 0x8F, 0x90, 0x9F, 0xA0, 0xBF}};
|
|
||||||
for (const int byte : class_ends)
|
|
||||||
{
|
|
||||||
if (lo <= byte && byte <= hi)
|
|
||||||
{
|
|
||||||
result.push_back(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
||||||
{
|
{
|
||||||
std::ifstream file(filename, std::ios::binary);
|
std::ifstream file(filename, std::ios::binary);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using nlohmann::json;
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
|
||||||
TEST_CASE("Binary Formats")
|
TEST_CASE("Binary Formats" * doctest::skip())
|
||||||
{
|
{
|
||||||
SECTION("canada.json")
|
SECTION("canada.json")
|
||||||
{
|
{
|
||||||
@@ -142,6 +142,48 @@ TEST_CASE("Binary Formats")
|
|||||||
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(84.963));
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(84.963));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("jeopardy.json")
|
||||||
|
{
|
||||||
|
const auto* filename = TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json";
|
||||||
|
json j = json::parse(std::ifstream(filename));
|
||||||
|
|
||||||
|
const auto json_size = j.dump().size();
|
||||||
|
const auto bjdata_1_size = json::to_bjdata(j).size();
|
||||||
|
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
||||||
|
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
||||||
|
const auto bon8_size = json::to_bon8(j).size();
|
||||||
|
const auto bson_size = json::to_bson({{"", j}}).size(); // wrap array in object for BSON
|
||||||
|
const auto cbor_size = json::to_cbor(j).size();
|
||||||
|
const auto msgpack_size = json::to_msgpack(j).size();
|
||||||
|
const auto ubjson_1_size = json::to_ubjson(j).size();
|
||||||
|
const auto ubjson_2_size = json::to_ubjson(j, true).size();
|
||||||
|
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
|
||||||
|
|
||||||
|
CHECK(json_size == 52508728);
|
||||||
|
CHECK(bjdata_1_size == 50710965);
|
||||||
|
CHECK(bjdata_2_size == 51144830);
|
||||||
|
CHECK(bjdata_3_size == 51144830);
|
||||||
|
CHECK(bon8_size == 45942080);
|
||||||
|
CHECK(bson_size == 56008520);
|
||||||
|
CHECK(cbor_size == 46187320);
|
||||||
|
CHECK(msgpack_size == 46158575);
|
||||||
|
CHECK(ubjson_1_size == 50710965);
|
||||||
|
CHECK(ubjson_2_size == 51144830);
|
||||||
|
CHECK(ubjson_3_size == 49861422);
|
||||||
|
|
||||||
|
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
|
||||||
|
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(96.576));
|
||||||
|
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(97.402));
|
||||||
|
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(97.402));
|
||||||
|
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(87.494));
|
||||||
|
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(106.665));
|
||||||
|
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.961));
|
||||||
|
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.906));
|
||||||
|
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(96.576));
|
||||||
|
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(97.402));
|
||||||
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(94.958));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("sample.json")
|
SECTION("sample.json")
|
||||||
{
|
{
|
||||||
const auto* filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
|
const auto* filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
|
||||||
@@ -182,47 +224,3 @@ TEST_CASE("Binary Formats")
|
|||||||
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
|
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// jeopardy.json is 52 MB and produces ~500 MB of serialization output, so it
|
|
||||||
// is kept apart from the cheap corpus files above (#5418)
|
|
||||||
TEST_CASE("Binary Formats (jeopardy.json)" * doctest::skip())
|
|
||||||
{
|
|
||||||
const auto* filename = TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json";
|
|
||||||
json j = json::parse(std::ifstream(filename));
|
|
||||||
|
|
||||||
const auto json_size = j.dump().size();
|
|
||||||
const auto bjdata_1_size = json::to_bjdata(j).size();
|
|
||||||
const auto bjdata_2_size = json::to_bjdata(j, true).size();
|
|
||||||
const auto bjdata_3_size = json::to_bjdata(j, true, true).size();
|
|
||||||
const auto bon8_size = json::to_bon8(j).size();
|
|
||||||
const auto bson_size = json::to_bson({{"", j}}).size(); // wrap array in object for BSON
|
|
||||||
const auto cbor_size = json::to_cbor(j).size();
|
|
||||||
const auto msgpack_size = json::to_msgpack(j).size();
|
|
||||||
const auto ubjson_1_size = json::to_ubjson(j).size();
|
|
||||||
const auto ubjson_2_size = json::to_ubjson(j, true).size();
|
|
||||||
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
|
|
||||||
|
|
||||||
CHECK(json_size == 52508728);
|
|
||||||
CHECK(bjdata_1_size == 50710965);
|
|
||||||
CHECK(bjdata_2_size == 51144830);
|
|
||||||
CHECK(bjdata_3_size == 51144830);
|
|
||||||
CHECK(bon8_size == 45942080);
|
|
||||||
CHECK(bson_size == 56008520);
|
|
||||||
CHECK(cbor_size == 46187320);
|
|
||||||
CHECK(msgpack_size == 46158575);
|
|
||||||
CHECK(ubjson_1_size == 50710965);
|
|
||||||
CHECK(ubjson_2_size == 51144830);
|
|
||||||
CHECK(ubjson_3_size == 49861422);
|
|
||||||
|
|
||||||
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
|
|
||||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(96.576));
|
|
||||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(97.402));
|
|
||||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(97.402));
|
|
||||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(87.494));
|
|
||||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(106.665));
|
|
||||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.961));
|
|
||||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.906));
|
|
||||||
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(96.576));
|
|
||||||
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(97.402));
|
|
||||||
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(94.958));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,141 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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")
|
||||||
|
{
|
||||||
|
int_map im;
|
||||||
|
int_umap ium;
|
||||||
|
CHECK_THROWS_WITH_AS(json::parse(R"({"1":2})").get_to(im),
|
||||||
|
"[json.exception.type_error.302] type must be array, but is object", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json::parse(R"({"1":2})").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
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++17-only part of unit-msgpack.cpp (std::byte
|
|
||||||
// input). It is kept in a separate translation unit so the (much larger)
|
|
||||||
// unit-msgpack.cpp does not need to be compiled and run a second time just
|
|
||||||
// for this one test case (#5418).
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <cstddef>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// Test suite for verifying MessagePack handling with std::byte input
|
|
||||||
TEST_CASE("MessagePack with std::byte")
|
|
||||||
{
|
|
||||||
|
|
||||||
SECTION("std::byte compatibility")
|
|
||||||
{
|
|
||||||
SECTION("vector roundtrip")
|
|
||||||
{
|
|
||||||
json original =
|
|
||||||
{
|
|
||||||
{"name", "test"},
|
|
||||||
{"value", 42},
|
|
||||||
{"array", {1, 2, 3}}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<uint8_t> temp = json::to_msgpack(original);
|
|
||||||
// Convert the uint8_t vector to std::byte vector
|
|
||||||
std::vector<std::byte> msgpack_data(temp.size());
|
|
||||||
for (size_t i = 0; i < temp.size(); ++i)
|
|
||||||
{
|
|
||||||
msgpack_data[i] = std::byte(temp[i]);
|
|
||||||
}
|
|
||||||
// Deserialize from std::byte vector back to JSON
|
|
||||||
json from_bytes;
|
|
||||||
CHECK_NOTHROW(from_bytes = json::from_msgpack(msgpack_data));
|
|
||||||
|
|
||||||
CHECK(from_bytes == original);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("empty vector")
|
|
||||||
{
|
|
||||||
const std::vector<std::byte> empty_data;
|
|
||||||
CHECK_THROWS_WITH_AS([&]()
|
|
||||||
{
|
|
||||||
[[maybe_unused]] auto result = json::from_msgpack(empty_data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
(),
|
|
||||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing MessagePack value: unexpected end of input",
|
|
||||||
json::parse_error&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("comparison with workaround")
|
|
||||||
{
|
|
||||||
json original =
|
|
||||||
{
|
|
||||||
{"string", "hello"},
|
|
||||||
{"integer", 42},
|
|
||||||
{"float", 3.14},
|
|
||||||
{"boolean", true},
|
|
||||||
{"null", nullptr},
|
|
||||||
{"array", {1, 2, 3}},
|
|
||||||
{"object", {{"key", "value"}}}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<uint8_t> temp = json::to_msgpack(original);
|
|
||||||
|
|
||||||
std::vector<std::byte> msgpack_data(temp.size());
|
|
||||||
for (size_t i = 0; i < temp.size(); ++i)
|
|
||||||
{
|
|
||||||
msgpack_data[i] = std::byte(temp[i]);
|
|
||||||
}
|
|
||||||
// Attempt direct deserialization using std::byte input
|
|
||||||
const json direct_result = json::from_msgpack(msgpack_data);
|
|
||||||
|
|
||||||
// Test the workaround approach: reinterpret as unsigned char* and use iterator range
|
|
||||||
const auto* const char_start = reinterpret_cast<unsigned char const*>(msgpack_data.data());
|
|
||||||
const auto* const char_end = char_start + msgpack_data.size();
|
|
||||||
json workaround_result = json::from_msgpack(char_start, char_end);
|
|
||||||
|
|
||||||
// Verify that the final deserialized JSON matches the original JSON
|
|
||||||
CHECK(direct_result == workaround_result);
|
|
||||||
CHECK(direct_result == original);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -2085,6 +2085,85 @@ TEST_CASE("MessagePack roundtrips" * doctest::skip())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
// Test suite for verifying MessagePack handling with std::byte input
|
||||||
|
TEST_CASE("MessagePack with std::byte")
|
||||||
|
{
|
||||||
|
|
||||||
|
SECTION("std::byte compatibility")
|
||||||
|
{
|
||||||
|
SECTION("vector roundtrip")
|
||||||
|
{
|
||||||
|
json original =
|
||||||
|
{
|
||||||
|
{"name", "test"},
|
||||||
|
{"value", 42},
|
||||||
|
{"array", {1, 2, 3}}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint8_t> temp = json::to_msgpack(original);
|
||||||
|
// Convert the uint8_t vector to std::byte vector
|
||||||
|
std::vector<std::byte> msgpack_data(temp.size());
|
||||||
|
for (size_t i = 0; i < temp.size(); ++i)
|
||||||
|
{
|
||||||
|
msgpack_data[i] = std::byte(temp[i]);
|
||||||
|
}
|
||||||
|
// Deserialize from std::byte vector back to JSON
|
||||||
|
json from_bytes;
|
||||||
|
CHECK_NOTHROW(from_bytes = json::from_msgpack(msgpack_data));
|
||||||
|
|
||||||
|
CHECK(from_bytes == original);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("empty vector")
|
||||||
|
{
|
||||||
|
const std::vector<std::byte> empty_data;
|
||||||
|
CHECK_THROWS_WITH_AS([&]()
|
||||||
|
{
|
||||||
|
[[maybe_unused]] auto result = json::from_msgpack(empty_data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(),
|
||||||
|
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing MessagePack value: unexpected end of input",
|
||||||
|
json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comparison with workaround")
|
||||||
|
{
|
||||||
|
json original =
|
||||||
|
{
|
||||||
|
{"string", "hello"},
|
||||||
|
{"integer", 42},
|
||||||
|
{"float", 3.14},
|
||||||
|
{"boolean", true},
|
||||||
|
{"null", nullptr},
|
||||||
|
{"array", {1, 2, 3}},
|
||||||
|
{"object", {{"key", "value"}}}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint8_t> temp = json::to_msgpack(original);
|
||||||
|
|
||||||
|
std::vector<std::byte> msgpack_data(temp.size());
|
||||||
|
for (size_t i = 0; i < temp.size(); ++i)
|
||||||
|
{
|
||||||
|
msgpack_data[i] = std::byte(temp[i]);
|
||||||
|
}
|
||||||
|
// Attempt direct deserialization using std::byte input
|
||||||
|
const json direct_result = json::from_msgpack(msgpack_data);
|
||||||
|
|
||||||
|
// Test the workaround approach: reinterpret as unsigned char* and use iterator range
|
||||||
|
const auto* const char_start = reinterpret_cast<unsigned char const*>(msgpack_data.data());
|
||||||
|
const auto* const char_end = char_start + msgpack_data.size();
|
||||||
|
json workaround_result = json::from_msgpack(char_start, char_end);
|
||||||
|
|
||||||
|
// Verify that the final deserialized JSON matches the original JSON
|
||||||
|
CHECK(direct_result == workaround_result);
|
||||||
|
CHECK(direct_result == original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// the fake sizes below do not fit into a 32-bit std::size_t
|
// the fake sizes below do not fit into a 32-bit std::size_t
|
||||||
// with clang and libstdc++ 10, the std::filesystem::path conversion that
|
// with clang and libstdc++ 10, the std::filesystem::path conversion that
|
||||||
// C++17 builds consider for every string type is ambiguous for a class
|
// C++17 builds consider for every string type is ambiguous for a class
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,623 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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"
|
||||||
|
|
||||||
|
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||||
|
#include <locale>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("Unicode (1/5)" * doctest::skip())
|
||||||
|
{
|
||||||
|
SECTION("\\uxxxx sequences")
|
||||||
|
{
|
||||||
|
// create an escaped string from a code point
|
||||||
|
const auto codepoint_to_unicode = [](std::size_t cp)
|
||||||
|
{
|
||||||
|
// code points are represented as a six-character sequence: a
|
||||||
|
// reverse solidus, followed by the lowercase letter u, followed
|
||||||
|
// by four hexadecimal digits that encode the character's code
|
||||||
|
// point
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "\\u" << std::setw(4) << std::setfill('0') << std::hex << cp;
|
||||||
|
return ss.str();
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("correct sequences")
|
||||||
|
{
|
||||||
|
// generate all UTF-8 code points; in total, 1112064 code points are
|
||||||
|
// generated: 0x1FFFFF code points - 2048 invalid values between
|
||||||
|
// 0xD800 and 0xDFFF.
|
||||||
|
for (std::size_t cp = 0; cp <= 0x10FFFFu; ++cp)
|
||||||
|
{
|
||||||
|
// string to store the code point as in \uxxxx format
|
||||||
|
std::string json_text = "\"";
|
||||||
|
|
||||||
|
// decide whether to use one or two \uxxxx sequences
|
||||||
|
if (cp < 0x10000u)
|
||||||
|
{
|
||||||
|
// The Unicode standard permanently reserves these code point
|
||||||
|
// values for UTF-16 encoding of the high and low surrogates, and
|
||||||
|
// they will never be assigned a character, so there should be no
|
||||||
|
// reason to encode them. The official Unicode standard says that
|
||||||
|
// no UTF forms, including UTF-16, can encode these code points.
|
||||||
|
if (cp >= 0xD800u && cp <= 0xDFFFu)
|
||||||
|
{
|
||||||
|
// if we would not skip these code points, we would get a
|
||||||
|
// "missing low surrogate" exception
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// code points in the Basic Multilingual Plane can be
|
||||||
|
// represented with one \uxxxx sequence
|
||||||
|
json_text += codepoint_to_unicode(cp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// To escape an extended character that is not in the Basic
|
||||||
|
// Multilingual Plane, the character is represented as a
|
||||||
|
// 12-character sequence, encoding the UTF-16 surrogate pair
|
||||||
|
const auto codepoint1 = 0xd800u + (((cp - 0x10000u) >> 10) & 0x3ffu);
|
||||||
|
const auto codepoint2 = 0xdc00u + ((cp - 0x10000u) & 0x3ffu);
|
||||||
|
json_text += codepoint_to_unicode(codepoint1) + codepoint_to_unicode(codepoint2);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_text += "\"";
|
||||||
|
CAPTURE(json_text)
|
||||||
|
json _;
|
||||||
|
CHECK_NOTHROW(_ = json::parse(json_text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("incorrect sequences")
|
||||||
|
{
|
||||||
|
SECTION("incorrect surrogate values")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uDC00\\uDC00\""), "[json.exception.parse_error.101] parse error at line 1, column 7: syntax error while parsing value - invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF; last read: '\"\\uDC00'", json::parse_error&);
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD7FF\\uDC00\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF; last read: '\"\\uD7FF\\uDC00'", json::parse_error&);
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800]\""), "[json.exception.parse_error.101] parse error at line 1, column 8: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800]'", json::parse_error&);
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\v\""), "[json.exception.parse_error.101] parse error at line 1, column 9: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800\\v'", json::parse_error&);
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\u123\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: '\\u' must be followed by 4 hex digits; last read: '\"\\uD800\\u123\"'", json::parse_error&);
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\uDBFF\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800\\uDBFF'", json::parse_error&);
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse("\"\\uD800\\uE000\""), "[json.exception.parse_error.101] parse error at line 1, column 13: syntax error while parsing value - invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF; last read: '\"\\uD800\\uE000'", json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if)
|
||||||
|
SECTION("incorrect sequences")
|
||||||
|
{
|
||||||
|
SECTION("high surrogate without low surrogate")
|
||||||
|
{
|
||||||
|
// D800..DBFF are high surrogates and must be followed by low
|
||||||
|
// surrogates DC00..DFFF; here, nothing follows
|
||||||
|
for (std::size_t cp = 0xD800u; cp <= 0xDBFFu; ++cp)
|
||||||
|
{
|
||||||
|
std::string json_text = "\"" + codepoint_to_unicode(cp) + "\"";
|
||||||
|
CAPTURE(json_text)
|
||||||
|
CHECK_THROWS_AS(json::parse(json_text), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("high surrogate with wrong low surrogate")
|
||||||
|
{
|
||||||
|
// D800..DBFF are high surrogates and must be followed by low
|
||||||
|
// surrogates DC00..DFFF; here a different sequence follows
|
||||||
|
for (std::size_t cp1 = 0xD800u; cp1 <= 0xDBFFu; ++cp1)
|
||||||
|
{
|
||||||
|
for (std::size_t cp2 = 0x0000u; cp2 <= 0xFFFFu; ++cp2)
|
||||||
|
{
|
||||||
|
if (0xDC00u <= cp2 && cp2 <= 0xDFFFu)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string json_text = "\"" + codepoint_to_unicode(cp1) + codepoint_to_unicode(cp2) + "\"";
|
||||||
|
CAPTURE(json_text)
|
||||||
|
CHECK_THROWS_AS(json::parse(json_text), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("low surrogate without high surrogate")
|
||||||
|
{
|
||||||
|
// low surrogates DC00..DFFF must follow high surrogates; here,
|
||||||
|
// they occur alone
|
||||||
|
for (std::size_t cp = 0xDC00u; cp <= 0xDFFFu; ++cp)
|
||||||
|
{
|
||||||
|
std::string json_text = "\"" + codepoint_to_unicode(cp) + "\"";
|
||||||
|
CAPTURE(json_text)
|
||||||
|
CHECK_THROWS_AS(json::parse(json_text), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("read all unicode characters")
|
||||||
|
{
|
||||||
|
// read a file with all Unicode characters stored as single-character
|
||||||
|
// strings in a JSON array
|
||||||
|
std::ifstream f(TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json");
|
||||||
|
json j;
|
||||||
|
CHECK_NOTHROW(f >> j);
|
||||||
|
|
||||||
|
// the array has 1112064 + 1 elements (a terminating "null" value)
|
||||||
|
// Note: 1112064 = 0x1FFFFF code points - 2048 invalid values between
|
||||||
|
// 0xD800 and 0xDFFF.
|
||||||
|
CHECK(j.size() == 1112065);
|
||||||
|
|
||||||
|
SECTION("check JSON Pointers")
|
||||||
|
{
|
||||||
|
for (const auto& s : j)
|
||||||
|
{
|
||||||
|
// skip non-string JSON values
|
||||||
|
if (!s.is_string())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ptr = s.get<std::string>();
|
||||||
|
|
||||||
|
// tilde must be followed by 0 or 1
|
||||||
|
if (ptr == "~")
|
||||||
|
{
|
||||||
|
ptr += "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON Pointers must begin with "/"
|
||||||
|
ptr.insert(0, "/");
|
||||||
|
|
||||||
|
CHECK_NOTHROW(json::json_pointer("/" + ptr));
|
||||||
|
|
||||||
|
// check escape/unescape roundtrip
|
||||||
|
auto escaped = nlohmann::detail::escape(ptr);
|
||||||
|
nlohmann::detail::unescape(escaped);
|
||||||
|
CHECK(escaped == ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ignore byte-order-mark")
|
||||||
|
{
|
||||||
|
SECTION("in a stream")
|
||||||
|
{
|
||||||
|
// read a file with a UTF-8 BOM
|
||||||
|
std::ifstream f(TEST_DATA_DIRECTORY "/json_nlohmann_tests/bom.json");
|
||||||
|
json j;
|
||||||
|
CHECK_NOTHROW(f >> j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("with an iterator")
|
||||||
|
{
|
||||||
|
std::string i = "\xef\xbb\xbf{\n \"foo\": true\n}";
|
||||||
|
json _;
|
||||||
|
CHECK_NOTHROW(_ = json::parse(i.begin(), i.end()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("error for incomplete/wrong BOM")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::parse("\xef\xbb"), json::parse_error&);
|
||||||
|
CHECK_THROWS_AS(_ = json::parse("\xef\xbb\xbb"), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void roundtrip(bool success_expected, const std::string& s);
|
||||||
|
|
||||||
|
void roundtrip(bool success_expected, const std::string& s)
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
json _;
|
||||||
|
|
||||||
|
// create JSON string value
|
||||||
|
const json j = s;
|
||||||
|
// create JSON text
|
||||||
|
const std::string ps = std::string("\"") + s + "\"";
|
||||||
|
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
// serialization succeeds
|
||||||
|
// dump() is nodiscard; this only checks that dumping does not throw
|
||||||
|
CHECK_NOTHROW(utils::ignore_return_value(j.dump()));
|
||||||
|
|
||||||
|
// exclude parse test for U+0000
|
||||||
|
if (s[0] != '\0')
|
||||||
|
{
|
||||||
|
// parsing JSON text succeeds
|
||||||
|
CHECK_NOTHROW(_ = json::parse(ps));
|
||||||
|
}
|
||||||
|
|
||||||
|
// roundtrip succeeds
|
||||||
|
CHECK_NOTHROW(_ = json::parse(j.dump()));
|
||||||
|
|
||||||
|
// after roundtrip, the same string is stored
|
||||||
|
const json jr = json::parse(j.dump());
|
||||||
|
CHECK(jr.get<std::string>() == s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// serialization fails
|
||||||
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
|
|
||||||
|
// parsing JSON text fails
|
||||||
|
CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Markus Kuhn's UTF-8 decoder capability and stress test")
|
||||||
|
{
|
||||||
|
// Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> - 2015-08-28 - CC BY 4.0
|
||||||
|
// http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
|
||||||
|
|
||||||
|
SECTION("1 Some correct UTF-8 text")
|
||||||
|
{
|
||||||
|
roundtrip(true, "κόσμε");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("2 Boundary condition test cases")
|
||||||
|
{
|
||||||
|
SECTION("2.1 First possible sequence of a certain length")
|
||||||
|
{
|
||||||
|
// 2.1.1 1 byte (U-00000000)
|
||||||
|
roundtrip(true, std::string("\0", 1));
|
||||||
|
// 2.1.2 2 bytes (U-00000080)
|
||||||
|
roundtrip(true, "\xc2\x80");
|
||||||
|
// 2.1.3 3 bytes (U-00000800)
|
||||||
|
roundtrip(true, "\xe0\xa0\x80");
|
||||||
|
// 2.1.4 4 bytes (U-00010000)
|
||||||
|
roundtrip(true, "\xf0\x90\x80\x80");
|
||||||
|
|
||||||
|
// 2.1.5 5 bytes (U-00200000)
|
||||||
|
roundtrip(false, "\xF8\x88\x80\x80\x80");
|
||||||
|
// 2.1.6 6 bytes (U-04000000)
|
||||||
|
roundtrip(false, "\xFC\x84\x80\x80\x80\x80");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("2.2 Last possible sequence of a certain length")
|
||||||
|
{
|
||||||
|
// 2.2.1 1 byte (U-0000007F)
|
||||||
|
roundtrip(true, "\x7f");
|
||||||
|
// 2.2.2 2 bytes (U-000007FF)
|
||||||
|
roundtrip(true, "\xdf\xbf");
|
||||||
|
// 2.2.3 3 bytes (U-0000FFFF)
|
||||||
|
roundtrip(true, "\xef\xbf\xbf");
|
||||||
|
|
||||||
|
// 2.2.4 4 bytes (U-001FFFFF)
|
||||||
|
roundtrip(false, "\xF7\xBF\xBF\xBF");
|
||||||
|
// 2.2.5 5 bytes (U-03FFFFFF)
|
||||||
|
roundtrip(false, "\xFB\xBF\xBF\xBF\xBF");
|
||||||
|
// 2.2.6 6 bytes (U-7FFFFFFF)
|
||||||
|
roundtrip(false, "\xFD\xBF\xBF\xBF\xBF\xBF");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("2.3 Other boundary conditions")
|
||||||
|
{
|
||||||
|
// 2.3.1 U-0000D7FF = ed 9f bf
|
||||||
|
roundtrip(true, "\xed\x9f\xbf");
|
||||||
|
// 2.3.2 U-0000E000 = ee 80 80
|
||||||
|
roundtrip(true, "\xee\x80\x80");
|
||||||
|
// 2.3.3 U-0000FFFD = ef bf bd
|
||||||
|
roundtrip(true, "\xef\xbf\xbd");
|
||||||
|
// 2.3.4 U-0010FFFF = f4 8f bf bf
|
||||||
|
roundtrip(true, "\xf4\x8f\xbf\xbf");
|
||||||
|
|
||||||
|
// 2.3.5 U-00110000 = f4 90 80 80
|
||||||
|
roundtrip(false, "\xf4\x90\x80\x80");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("3 Malformed sequences")
|
||||||
|
{
|
||||||
|
SECTION("3.1 Unexpected continuation bytes")
|
||||||
|
{
|
||||||
|
// Each unexpected continuation byte should be separately signalled as a
|
||||||
|
// malformed sequence of its own.
|
||||||
|
|
||||||
|
// 3.1.1 First continuation byte 0x80
|
||||||
|
roundtrip(false, "\x80");
|
||||||
|
// 3.1.2 Last continuation byte 0xbf
|
||||||
|
roundtrip(false, "\xbf");
|
||||||
|
|
||||||
|
// 3.1.3 2 continuation bytes
|
||||||
|
roundtrip(false, "\x80\xbf");
|
||||||
|
// 3.1.4 3 continuation bytes
|
||||||
|
roundtrip(false, "\x80\xbf\x80");
|
||||||
|
// 3.1.5 4 continuation bytes
|
||||||
|
roundtrip(false, "\x80\xbf\x80\xbf");
|
||||||
|
// 3.1.6 5 continuation bytes
|
||||||
|
roundtrip(false, "\x80\xbf\x80\xbf\x80");
|
||||||
|
// 3.1.7 6 continuation bytes
|
||||||
|
roundtrip(false, "\x80\xbf\x80\xbf\x80\xbf");
|
||||||
|
// 3.1.8 7 continuation bytes
|
||||||
|
roundtrip(false, "\x80\xbf\x80\xbf\x80\xbf\x80");
|
||||||
|
|
||||||
|
// 3.1.9 Sequence of all 64 possible continuation bytes (0x80-0xbf)
|
||||||
|
roundtrip(false, "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("3.2 Lonely start characters")
|
||||||
|
{
|
||||||
|
// 3.2.1 All 32 first bytes of 2-byte sequences (0xc0-0xdf)
|
||||||
|
roundtrip(false, "\xc0 \xc1 \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 \xc8 \xc9 \xca \xcb \xcc \xcd \xce \xcf \xd0 \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 \xd8 \xd9 \xda \xdb \xdc \xdd \xde \xdf");
|
||||||
|
// 3.2.2 All 16 first bytes of 3-byte sequences (0xe0-0xef)
|
||||||
|
roundtrip(false, "\xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef");
|
||||||
|
// 3.2.3 All 8 first bytes of 4-byte sequences (0xf0-0xf7)
|
||||||
|
roundtrip(false, "\xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7");
|
||||||
|
// 3.2.4 All 4 first bytes of 5-byte sequences (0xf8-0xfb)
|
||||||
|
roundtrip(false, "\xf8 \xf9 \xfa \xfb");
|
||||||
|
// 3.2.5 All 2 first bytes of 6-byte sequences (0xfc-0xfd)
|
||||||
|
roundtrip(false, "\xfc \xfd");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("3.3 Sequences with last continuation byte missing")
|
||||||
|
{
|
||||||
|
// All bytes of an incomplete sequence should be signalled as a single
|
||||||
|
// malformed sequence, i.e., you should see only a single replacement
|
||||||
|
// character in each of the next 10 tests. (Characters as in section 2)
|
||||||
|
|
||||||
|
// 3.3.1 2-byte sequence with last byte missing (U+0000)
|
||||||
|
roundtrip(false, "\xc0");
|
||||||
|
// 3.3.2 3-byte sequence with last byte missing (U+0000)
|
||||||
|
roundtrip(false, "\xe0\x80");
|
||||||
|
// 3.3.3 4-byte sequence with last byte missing (U+0000)
|
||||||
|
roundtrip(false, "\xf0\x80\x80");
|
||||||
|
// 3.3.4 5-byte sequence with last byte missing (U+0000)
|
||||||
|
roundtrip(false, "\xf8\x80\x80\x80");
|
||||||
|
// 3.3.5 6-byte sequence with last byte missing (U+0000)
|
||||||
|
roundtrip(false, "\xfc\x80\x80\x80\x80");
|
||||||
|
// 3.3.6 2-byte sequence with last byte missing (U-000007FF)
|
||||||
|
roundtrip(false, "\xdf");
|
||||||
|
// 3.3.7 3-byte sequence with last byte missing (U-0000FFFF)
|
||||||
|
roundtrip(false, "\xef\xbf");
|
||||||
|
// 3.3.8 4-byte sequence with last byte missing (U-001FFFFF)
|
||||||
|
roundtrip(false, "\xf7\xbf\xbf");
|
||||||
|
// 3.3.9 5-byte sequence with last byte missing (U-03FFFFFF)
|
||||||
|
roundtrip(false, "\xfb\xbf\xbf\xbf");
|
||||||
|
// 3.3.10 6-byte sequence with last byte missing (U-7FFFFFFF)
|
||||||
|
roundtrip(false, "\xfd\xbf\xbf\xbf\xbf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("3.4 Concatenation of incomplete sequences")
|
||||||
|
{
|
||||||
|
// All the 10 sequences of 3.3 concatenated, you should see 10 malformed
|
||||||
|
// sequences being signalled:
|
||||||
|
roundtrip(false, "\xc0\xe0\x80\xf0\x80\x80\xf8\x80\x80\x80\xfc\x80\x80\x80\x80\xdf\xef\xbf\xf7\xbf\xbf\xfb\xbf\xbf\xbf\xfd\xbf\xbf\xbf\xbf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("3.5 Impossible bytes")
|
||||||
|
{
|
||||||
|
// The following two bytes cannot appear in a correct UTF-8 string
|
||||||
|
|
||||||
|
// 3.5.1 fe
|
||||||
|
roundtrip(false, "\xfe");
|
||||||
|
// 3.5.2 ff
|
||||||
|
roundtrip(false, "\xff");
|
||||||
|
// 3.5.3 fe fe ff ff
|
||||||
|
roundtrip(false, "\xfe\xfe\xff\xff");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("4 Overlong sequences")
|
||||||
|
{
|
||||||
|
// The following sequences are not malformed according to the letter of
|
||||||
|
// the Unicode 2.0 standard. However, they are longer then necessary and
|
||||||
|
// a correct UTF-8 encoder is not allowed to produce them. A "safe UTF-8
|
||||||
|
// decoder" should reject them just like malformed sequences for two
|
||||||
|
// reasons: (1) It helps to debug applications if overlong sequences are
|
||||||
|
// not treated as valid representations of characters, because this helps
|
||||||
|
// to spot problems more quickly. (2) Overlong sequences provide
|
||||||
|
// alternative representations of characters, that could maliciously be
|
||||||
|
// used to bypass filters that check only for ASCII characters. For
|
||||||
|
// instance, a 2-byte encoded line feed (LF) would not be caught by a
|
||||||
|
// line counter that counts only 0x0a bytes, but it would still be
|
||||||
|
// processed as a line feed by an unsafe UTF-8 decoder later in the
|
||||||
|
// pipeline. From a security point of view, ASCII compatibility of UTF-8
|
||||||
|
// sequences means also, that ASCII characters are *only* allowed to be
|
||||||
|
// represented by ASCII bytes in the range 0x00-0x7f. To ensure this
|
||||||
|
// aspect of ASCII compatibility, use only "safe UTF-8 decoders" that
|
||||||
|
// reject overlong UTF-8 sequences for which a shorter encoding exists.
|
||||||
|
|
||||||
|
SECTION("4.1 Examples of an overlong ASCII character")
|
||||||
|
{
|
||||||
|
// With a safe UTF-8 decoder, all the following five overlong
|
||||||
|
// representations of the ASCII character slash ("/") should be rejected
|
||||||
|
// like a malformed UTF-8 sequence, for instance by substituting it with
|
||||||
|
// a replacement character. If you see a slash below, you do not have a
|
||||||
|
// safe UTF-8 decoder!
|
||||||
|
|
||||||
|
// 4.1.1 U+002F = c0 af
|
||||||
|
roundtrip(false, "\xc0\xaf");
|
||||||
|
// 4.1.2 U+002F = e0 80 af
|
||||||
|
roundtrip(false, "\xe0\x80\xaf");
|
||||||
|
// 4.1.3 U+002F = f0 80 80 af
|
||||||
|
roundtrip(false, "\xf0\x80\x80\xaf");
|
||||||
|
// 4.1.4 U+002F = f8 80 80 80 af
|
||||||
|
roundtrip(false, "\xf8\x80\x80\x80\xaf");
|
||||||
|
// 4.1.5 U+002F = fc 80 80 80 80 af
|
||||||
|
roundtrip(false, "\xfc\x80\x80\x80\x80\xaf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("4.2 Maximum overlong sequences")
|
||||||
|
{
|
||||||
|
// Below you see the highest Unicode value that is still resulting in an
|
||||||
|
// overlong sequence if represented with the given number of bytes. This
|
||||||
|
// is a boundary test for safe UTF-8 decoders. All five characters should
|
||||||
|
// be rejected like malformed UTF-8 sequences.
|
||||||
|
|
||||||
|
// 4.2.1 U-0000007F = c1 bf
|
||||||
|
roundtrip(false, "\xc1\xbf");
|
||||||
|
// 4.2.2 U-000007FF = e0 9f bf
|
||||||
|
roundtrip(false, "\xe0\x9f\xbf");
|
||||||
|
// 4.2.3 U-0000FFFF = f0 8f bf bf
|
||||||
|
roundtrip(false, "\xf0\x8f\xbf\xbf");
|
||||||
|
// 4.2.4 U-001FFFFF = f8 87 bf bf bf
|
||||||
|
roundtrip(false, "\xf8\x87\xbf\xbf\xbf");
|
||||||
|
// 4.2.5 U-03FFFFFF = fc 83 bf bf bf bf
|
||||||
|
roundtrip(false, "\xfc\x83\xbf\xbf\xbf\xbf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("4.3 Overlong representation of the NUL character")
|
||||||
|
{
|
||||||
|
// The following five sequences should also be rejected like malformed
|
||||||
|
// UTF-8 sequences and should not be treated like the ASCII NUL
|
||||||
|
// character.
|
||||||
|
|
||||||
|
// 4.3.1 U+0000 = c0 80
|
||||||
|
roundtrip(false, "\xc0\x80");
|
||||||
|
// 4.3.2 U+0000 = e0 80 80
|
||||||
|
roundtrip(false, "\xe0\x80\x80");
|
||||||
|
// 4.3.3 U+0000 = f0 80 80 80
|
||||||
|
roundtrip(false, "\xf0\x80\x80\x80");
|
||||||
|
// 4.3.4 U+0000 = f8 80 80 80 80
|
||||||
|
roundtrip(false, "\xf8\x80\x80\x80\x80");
|
||||||
|
// 4.3.5 U+0000 = fc 80 80 80 80 80
|
||||||
|
roundtrip(false, "\xfc\x80\x80\x80\x80\x80");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("5 Illegal code positions")
|
||||||
|
{
|
||||||
|
// The following UTF-8 sequences should be rejected like malformed
|
||||||
|
// sequences, because they never represent valid ISO 10646 characters and
|
||||||
|
// a UTF-8 decoder that accepts them might introduce security problems
|
||||||
|
// comparable to overlong UTF-8 sequences.
|
||||||
|
|
||||||
|
SECTION("5.1 Single UTF-16 surrogates")
|
||||||
|
{
|
||||||
|
// 5.1.1 U+D800 = ed a0 80
|
||||||
|
roundtrip(false, "\xed\xa0\x80");
|
||||||
|
// 5.1.2 U+DB7F = ed ad bf
|
||||||
|
roundtrip(false, "\xed\xad\xbf");
|
||||||
|
// 5.1.3 U+DB80 = ed ae 80
|
||||||
|
roundtrip(false, "\xed\xae\x80");
|
||||||
|
// 5.1.4 U+DBFF = ed af bf
|
||||||
|
roundtrip(false, "\xed\xaf\xbf");
|
||||||
|
// 5.1.5 U+DC00 = ed b0 80
|
||||||
|
roundtrip(false, "\xed\xb0\x80");
|
||||||
|
// 5.1.6 U+DF80 = ed be 80
|
||||||
|
roundtrip(false, "\xed\xbe\x80");
|
||||||
|
// 5.1.7 U+DFFF = ed bf bf
|
||||||
|
roundtrip(false, "\xed\xbf\xbf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("5.2 Paired UTF-16 surrogates")
|
||||||
|
{
|
||||||
|
// 5.2.1 U+D800 U+DC00 = ed a0 80 ed b0 80
|
||||||
|
roundtrip(false, "\xed\xa0\x80\xed\xb0\x80");
|
||||||
|
// 5.2.2 U+D800 U+DFFF = ed a0 80 ed bf bf
|
||||||
|
roundtrip(false, "\xed\xa0\x80\xed\xbf\xbf");
|
||||||
|
// 5.2.3 U+DB7F U+DC00 = ed ad bf ed b0 80
|
||||||
|
roundtrip(false, "\xed\xad\xbf\xed\xb0\x80");
|
||||||
|
// 5.2.4 U+DB7F U+DFFF = ed ad bf ed bf bf
|
||||||
|
roundtrip(false, "\xed\xad\xbf\xed\xbf\xbf");
|
||||||
|
// 5.2.5 U+DB80 U+DC00 = ed ae 80 ed b0 80
|
||||||
|
roundtrip(false, "\xed\xae\x80\xed\xb0\x80");
|
||||||
|
// 5.2.6 U+DB80 U+DFFF = ed ae 80 ed bf bf
|
||||||
|
roundtrip(false, "\xed\xae\x80\xed\xbf\xbf");
|
||||||
|
// 5.2.7 U+DBFF U+DC00 = ed af bf ed b0 80
|
||||||
|
roundtrip(false, "\xed\xaf\xbf\xed\xb0\x80");
|
||||||
|
// 5.2.8 U+DBFF U+DFFF = ed af bf ed bf bf
|
||||||
|
roundtrip(false, "\xed\xaf\xbf\xed\xbf\xbf");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("5.3 Noncharacter code positions")
|
||||||
|
{
|
||||||
|
// The following "noncharacters" are "reserved for internal use" by
|
||||||
|
// applications, and according to older versions of the Unicode Standard
|
||||||
|
// "should never be interchanged". Unicode Corrigendum #9 dropped the
|
||||||
|
// latter restriction. Nevertheless, their presence in incoming UTF-8 data
|
||||||
|
// can remain a potential security risk, depending on what use is made of
|
||||||
|
// these codes subsequently. Examples of such internal use:
|
||||||
|
//
|
||||||
|
// - Some file APIs with 16-bit characters may use the integer value -1
|
||||||
|
// = U+FFFF to signal an end-of-file (EOF) or error condition.
|
||||||
|
//
|
||||||
|
// - In some UTF-16 receivers, code point U+FFFE might trigger a
|
||||||
|
// byte-swap operation (to convert between UTF-16LE and UTF-16BE).
|
||||||
|
//
|
||||||
|
// With such internal use of noncharacters, it may be desirable and safer
|
||||||
|
// to block those code points in UTF-8 decoders, as they should never
|
||||||
|
// occur legitimately in incoming UTF-8 data, and could trigger unsafe
|
||||||
|
// behaviour in subsequent processing.
|
||||||
|
|
||||||
|
// Particularly problematic noncharacters in 16-bit applications:
|
||||||
|
|
||||||
|
// 5.3.1 U+FFFE = ef bf be
|
||||||
|
roundtrip(true, "\xef\xbf\xbe");
|
||||||
|
// 5.3.2 U+FFFF = ef bf bf
|
||||||
|
roundtrip(true, "\xef\xbf\xbf");
|
||||||
|
|
||||||
|
// 5.3.3 U+FDD0 .. U+FDEF
|
||||||
|
roundtrip(true, "\xEF\xB7\x90");
|
||||||
|
roundtrip(true, "\xEF\xB7\x91");
|
||||||
|
roundtrip(true, "\xEF\xB7\x92");
|
||||||
|
roundtrip(true, "\xEF\xB7\x93");
|
||||||
|
roundtrip(true, "\xEF\xB7\x94");
|
||||||
|
roundtrip(true, "\xEF\xB7\x95");
|
||||||
|
roundtrip(true, "\xEF\xB7\x96");
|
||||||
|
roundtrip(true, "\xEF\xB7\x97");
|
||||||
|
roundtrip(true, "\xEF\xB7\x98");
|
||||||
|
roundtrip(true, "\xEF\xB7\x99");
|
||||||
|
roundtrip(true, "\xEF\xB7\x9A");
|
||||||
|
roundtrip(true, "\xEF\xB7\x9B");
|
||||||
|
roundtrip(true, "\xEF\xB7\x9C");
|
||||||
|
roundtrip(true, "\xEF\xB7\x9D");
|
||||||
|
roundtrip(true, "\xEF\xB7\x9E");
|
||||||
|
roundtrip(true, "\xEF\xB7\x9F");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA0");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA1");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA2");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA3");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA4");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA5");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA6");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA7");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA8");
|
||||||
|
roundtrip(true, "\xEF\xB7\xA9");
|
||||||
|
roundtrip(true, "\xEF\xB7\xAA");
|
||||||
|
roundtrip(true, "\xEF\xB7\xAB");
|
||||||
|
roundtrip(true, "\xEF\xB7\xAC");
|
||||||
|
roundtrip(true, "\xEF\xB7\xAD");
|
||||||
|
roundtrip(true, "\xEF\xB7\xAE");
|
||||||
|
roundtrip(true, "\xEF\xB7\xAF");
|
||||||
|
|
||||||
|
// 5.3.4 U+nFFFE U+nFFFF (for n = 1..10)
|
||||||
|
roundtrip(true, "\xF0\x9F\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF0\xAF\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF0\xBF\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF1\x8F\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF1\x9F\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF1\xAF\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF1\xBF\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF2\x8F\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF2\x9F\xBF\xBF");
|
||||||
|
roundtrip(true, "\xF2\xAF\xBF\xBF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,612 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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"
|
||||||
|
|
||||||
|
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
// this test suite uses static variables with non-trivial destructors
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
extern size_t calls;
|
||||||
|
size_t calls = 0;
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
static std::string json_string;
|
||||||
|
json_string.clear();
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
CAPTURE(byte2)
|
||||||
|
CAPTURE(byte3)
|
||||||
|
CAPTURE(byte4)
|
||||||
|
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
// store the string in a JSON value
|
||||||
|
static json j;
|
||||||
|
static json j2;
|
||||||
|
j = json_string;
|
||||||
|
j2 = "abc" + json_string + "xyz";
|
||||||
|
|
||||||
|
static std::string s_ignored;
|
||||||
|
static std::string s_ignored2;
|
||||||
|
static std::string s_ignored_ascii;
|
||||||
|
static std::string s_ignored2_ascii;
|
||||||
|
static std::string s_replaced;
|
||||||
|
static std::string s_replaced2;
|
||||||
|
static std::string s_replaced_ascii;
|
||||||
|
static std::string s_replaced2_ascii;
|
||||||
|
|
||||||
|
// dumping with ignore/replace must not throw in any case
|
||||||
|
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
static std::string s_strict;
|
||||||
|
// strict mode must not throw if success is expected
|
||||||
|
s_strict = j.dump();
|
||||||
|
// all dumps should agree on the string
|
||||||
|
CHECK(s_strict == s_ignored);
|
||||||
|
CHECK(s_strict == s_replaced);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// strict mode must throw if success is not expected
|
||||||
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
|
// ignore and replace must create different dumps
|
||||||
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
// check that replace string contains a replacement character
|
||||||
|
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that prefix and suffix are preserved
|
||||||
|
CHECK(s_ignored2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
// create and check a JSON string with up to four UTF-8 bytes
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
if (++calls % 100000 == 0)
|
||||||
|
{
|
||||||
|
std::cout << calls << " of 455355 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string json_string;
|
||||||
|
json_string = "\"";
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte2)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte3)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte4)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_string += "\"";
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
json _;
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
CHECK_NOTHROW(_ = json::parse(json_string));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Unicode (2/5)" * doctest::skip())
|
||||||
|
{
|
||||||
|
SECTION("RFC 3629")
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
A UTF-8 string is a sequence of octets representing a sequence of UCS
|
||||||
|
characters. An octet sequence is valid UTF-8 only if it matches the
|
||||||
|
following syntax, which is derived from the rules for encoding UTF-8
|
||||||
|
and is expressed in the ABNF of [RFC2234].
|
||||||
|
|
||||||
|
UTF8-octets = *( UTF8-char )
|
||||||
|
UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
|
||||||
|
UTF8-1 = %x00-7F
|
||||||
|
UTF8-2 = %xC2-DF UTF8-tail
|
||||||
|
UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
|
||||||
|
%xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
|
||||||
|
UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
|
||||||
|
%xF4 %x80-8F 2( UTF8-tail )
|
||||||
|
UTF8-tail = %x80-BF
|
||||||
|
*/
|
||||||
|
|
||||||
|
SECTION("ill-formed first byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0x80; byte1 <= 0xC1; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte1 = 0xF5; byte1 <= 0xFF; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("UTF8-1 (x00-x7F)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0x00; byte1 <= 0x7F; ++byte1)
|
||||||
|
{
|
||||||
|
// unescaped control characters are parse errors in JSON
|
||||||
|
if (0x00 <= byte1 && byte1 <= 0x1F)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a single quote is a parse error in JSON
|
||||||
|
if (byte1 == 0x22)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a single backslash is a parse error in JSON
|
||||||
|
if (byte1 == 0x5C)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all other characters are OK
|
||||||
|
check_utf8string(true, byte1);
|
||||||
|
check_utf8dump(true, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("UTF8-2 (xC2-xDF UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2);
|
||||||
|
check_utf8dump(true, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xC2; byte1 <= 0xDF; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0x80 <= byte2 && byte2 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("UTF8-3 (xE0 xA0-BF UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(true, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0xA0 <= byte2 && byte2 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE0; byte1 <= 0xE0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0xA0; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
|
||||||
|
{
|
||||||
|
// skip correct third byte
|
||||||
|
if (0x80 <= byte3 && byte3 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("UTF8-3 (xE1-xEC UTF8-tail UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(true, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0x80 <= byte2 && byte2 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xE1; byte1 <= 0xEC; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
|
||||||
|
{
|
||||||
|
// skip correct third byte
|
||||||
|
if (0x80 <= byte3 && byte3 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("UTF8-3 (xED x80-9F UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(true, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0x80 <= byte2 && byte2 <= 0x9F)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xED; byte1 <= 0xED; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x9F; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
|
||||||
|
{
|
||||||
|
// skip correct third byte
|
||||||
|
if (0x80 <= byte3 && byte3 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("UTF8-3 (xEE-xEF UTF8-tail UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(true, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0x80 <= byte2 && byte2 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xEE; byte1 <= 0xEF; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
|
||||||
|
{
|
||||||
|
// skip correct third byte
|
||||||
|
if (0x80 <= byte3 && byte3 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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"
|
||||||
|
|
||||||
|
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
// this test suite uses static variables with non-trivial destructors
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
extern size_t calls;
|
||||||
|
size_t calls = 0;
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
static std::string json_string;
|
||||||
|
json_string.clear();
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
CAPTURE(byte2)
|
||||||
|
CAPTURE(byte3)
|
||||||
|
CAPTURE(byte4)
|
||||||
|
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
// store the string in a JSON value
|
||||||
|
static json j;
|
||||||
|
static json j2;
|
||||||
|
j = json_string;
|
||||||
|
j2 = "abc" + json_string + "xyz";
|
||||||
|
|
||||||
|
static std::string s_ignored;
|
||||||
|
static std::string s_ignored2;
|
||||||
|
static std::string s_ignored_ascii;
|
||||||
|
static std::string s_ignored2_ascii;
|
||||||
|
static std::string s_replaced;
|
||||||
|
static std::string s_replaced2;
|
||||||
|
static std::string s_replaced_ascii;
|
||||||
|
static std::string s_replaced2_ascii;
|
||||||
|
|
||||||
|
// dumping with ignore/replace must not throw in any case
|
||||||
|
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
static std::string s_strict;
|
||||||
|
// strict mode must not throw if success is expected
|
||||||
|
s_strict = j.dump();
|
||||||
|
// all dumps should agree on the string
|
||||||
|
CHECK(s_strict == s_ignored);
|
||||||
|
CHECK(s_strict == s_replaced);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// strict mode must throw if success is not expected
|
||||||
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
|
// ignore and replace must create different dumps
|
||||||
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
// check that replace string contains a replacement character
|
||||||
|
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that prefix and suffix are preserved
|
||||||
|
CHECK(s_ignored2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
// create and check a JSON string with up to four UTF-8 bytes
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
if (++calls % 100000 == 0)
|
||||||
|
{
|
||||||
|
std::cout << calls << " of 1641521 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string json_string;
|
||||||
|
json_string = "\"";
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte2)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte3)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte4)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_string += "\"";
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
json _;
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
CHECK_NOTHROW(_ = json::parse(json_string));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Unicode (3/5)" * doctest::skip())
|
||||||
|
{
|
||||||
|
SECTION("RFC 3629")
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
A UTF-8 string is a sequence of octets representing a sequence of UCS
|
||||||
|
characters. An octet sequence is valid UTF-8 only if it matches the
|
||||||
|
following syntax, which is derived from the rules for encoding UTF-8
|
||||||
|
and is expressed in the ABNF of [RFC2234].
|
||||||
|
|
||||||
|
UTF8-octets = *( UTF8-char )
|
||||||
|
UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
|
||||||
|
UTF8-1 = %x00-7F
|
||||||
|
UTF8-2 = %xC2-DF UTF8-tail
|
||||||
|
UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
|
||||||
|
%xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
|
||||||
|
UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
|
||||||
|
%xF4 %x80-8F 2( UTF8-tail )
|
||||||
|
UTF8-tail = %x80-BF
|
||||||
|
*/
|
||||||
|
|
||||||
|
SECTION("UTF8-4 (xF0 x90-BF UTF8-tail UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(true, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing fourth byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0x90 <= byte2 && byte2 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
|
||||||
|
{
|
||||||
|
// skip correct third byte
|
||||||
|
if (0x80 <= byte3 && byte3 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong fourth byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF0; byte1 <= 0xF0; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x90; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
|
||||||
|
{
|
||||||
|
// skip correct fourth byte
|
||||||
|
if (0x80 <= byte4 && byte4 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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"
|
||||||
|
|
||||||
|
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
// this test suite uses static variables with non-trivial destructors
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
extern size_t calls;
|
||||||
|
size_t calls = 0;
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
static std::string json_string;
|
||||||
|
json_string.clear();
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
CAPTURE(byte2)
|
||||||
|
CAPTURE(byte3)
|
||||||
|
CAPTURE(byte4)
|
||||||
|
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
// store the string in a JSON value
|
||||||
|
static json j;
|
||||||
|
static json j2;
|
||||||
|
j = json_string;
|
||||||
|
j2 = "abc" + json_string + "xyz";
|
||||||
|
|
||||||
|
static std::string s_ignored;
|
||||||
|
static std::string s_ignored2;
|
||||||
|
static std::string s_ignored_ascii;
|
||||||
|
static std::string s_ignored2_ascii;
|
||||||
|
static std::string s_replaced;
|
||||||
|
static std::string s_replaced2;
|
||||||
|
static std::string s_replaced_ascii;
|
||||||
|
static std::string s_replaced2_ascii;
|
||||||
|
|
||||||
|
// dumping with ignore/replace must not throw in any case
|
||||||
|
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
static std::string s_strict;
|
||||||
|
// strict mode must not throw if success is expected
|
||||||
|
s_strict = j.dump();
|
||||||
|
// all dumps should agree on the string
|
||||||
|
CHECK(s_strict == s_ignored);
|
||||||
|
CHECK(s_strict == s_replaced);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// strict mode must throw if success is not expected
|
||||||
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
|
// ignore and replace must create different dumps
|
||||||
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
// check that replace string contains a replacement character
|
||||||
|
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that prefix and suffix are preserved
|
||||||
|
CHECK(s_ignored2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
// create and check a JSON string with up to four UTF-8 bytes
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
if (++calls % 100000 == 0)
|
||||||
|
{
|
||||||
|
std::cout << calls << " of 5517507 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string json_string;
|
||||||
|
json_string = "\"";
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte2)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte3)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte4)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_string += "\"";
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
json _;
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
CHECK_NOTHROW(_ = json::parse(json_string));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Unicode (4/5)" * doctest::skip())
|
||||||
|
{
|
||||||
|
SECTION("RFC 3629")
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
A UTF-8 string is a sequence of octets representing a sequence of UCS
|
||||||
|
characters. An octet sequence is valid UTF-8 only if it matches the
|
||||||
|
following syntax, which is derived from the rules for encoding UTF-8
|
||||||
|
and is expressed in the ABNF of [RFC2234].
|
||||||
|
|
||||||
|
UTF8-octets = *( UTF8-char )
|
||||||
|
UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
|
||||||
|
UTF8-1 = %x00-7F
|
||||||
|
UTF8-2 = %xC2-DF UTF8-tail
|
||||||
|
UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
|
||||||
|
%xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
|
||||||
|
UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
|
||||||
|
%xF4 %x80-8F 2( UTF8-tail )
|
||||||
|
UTF8-tail = %x80-BF
|
||||||
|
*/
|
||||||
|
|
||||||
|
SECTION("UTF8-4 (xF1-F3 UTF8-tail UTF8-tail UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(true, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing fourth byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0x80 <= byte2 && byte2 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
|
||||||
|
{
|
||||||
|
// skip correct third byte
|
||||||
|
if (0x80 <= byte3 && byte3 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong fourth byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF1; byte1 <= 0xF3; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0xBF; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
|
||||||
|
{
|
||||||
|
// skip correct fourth byte
|
||||||
|
if (0x80 <= byte4 && byte4 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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"
|
||||||
|
|
||||||
|
// for some reason including this after the json header leads to linker errors with VS 2017...
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
// this test suite uses static variables with non-trivial destructors
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
extern size_t calls;
|
||||||
|
size_t calls = 0;
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
static std::string json_string;
|
||||||
|
json_string.clear();
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
CAPTURE(byte2)
|
||||||
|
CAPTURE(byte3)
|
||||||
|
CAPTURE(byte4)
|
||||||
|
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
// store the string in a JSON value
|
||||||
|
static json j;
|
||||||
|
static json j2;
|
||||||
|
j = json_string;
|
||||||
|
j2 = "abc" + json_string + "xyz";
|
||||||
|
|
||||||
|
static std::string s_ignored;
|
||||||
|
static std::string s_ignored2;
|
||||||
|
static std::string s_ignored_ascii;
|
||||||
|
static std::string s_ignored2_ascii;
|
||||||
|
static std::string s_replaced;
|
||||||
|
static std::string s_replaced2;
|
||||||
|
static std::string s_replaced_ascii;
|
||||||
|
static std::string s_replaced2_ascii;
|
||||||
|
|
||||||
|
// dumping with ignore/replace must not throw in any case
|
||||||
|
s_ignored = j.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored2 = j2.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
s_ignored_ascii = j.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_ignored2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::ignore);
|
||||||
|
s_replaced = j.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced2 = j2.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
s_replaced_ascii = j.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
s_replaced2_ascii = j2.dump(-1, ' ', true, json::error_handler_t::replace);
|
||||||
|
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
static std::string s_strict;
|
||||||
|
// strict mode must not throw if success is expected
|
||||||
|
s_strict = j.dump();
|
||||||
|
// all dumps should agree on the string
|
||||||
|
CHECK(s_strict == s_ignored);
|
||||||
|
CHECK(s_strict == s_replaced);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// strict mode must throw if success is not expected
|
||||||
|
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
|
||||||
|
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
|
||||||
|
// ignore and replace must create different dumps
|
||||||
|
CHECK(s_ignored != s_replaced);
|
||||||
|
|
||||||
|
// check that replace string contains a replacement character
|
||||||
|
CHECK(s_replaced.find("\xEF\xBF\xBD") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that prefix and suffix are preserved
|
||||||
|
CHECK(s_ignored2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2.substr(s_ignored2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_ignored2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_ignored2_ascii.substr(s_ignored2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2.substr(s_replaced2.size() - 4, 3) == "xyz");
|
||||||
|
CHECK(s_replaced2_ascii.substr(1, 3) == "abc");
|
||||||
|
CHECK(s_replaced2_ascii.substr(s_replaced2_ascii.size() - 4, 3) == "xyz");
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2, int byte3, int byte4);
|
||||||
|
|
||||||
|
// create and check a JSON string with up to four UTF-8 bytes
|
||||||
|
void check_utf8string(bool success_expected, int byte1, int byte2 = -1, int byte3 = -1, int byte4 = -1)
|
||||||
|
{
|
||||||
|
if (++calls % 100000 == 0)
|
||||||
|
{
|
||||||
|
std::cout << calls << " of 1246225 UTF-8 strings checked" << std::endl; // NOLINT(performance-avoid-endl)
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string json_string;
|
||||||
|
json_string = "\"";
|
||||||
|
|
||||||
|
CAPTURE(byte1)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte1));
|
||||||
|
|
||||||
|
if (byte2 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte2)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte3 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte3)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byte4 != -1)
|
||||||
|
{
|
||||||
|
CAPTURE(byte4)
|
||||||
|
json_string += std::string(1, static_cast<char>(byte4));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_string += "\"";
|
||||||
|
|
||||||
|
CAPTURE(json_string)
|
||||||
|
|
||||||
|
json _;
|
||||||
|
if (success_expected)
|
||||||
|
{
|
||||||
|
CHECK_NOTHROW(_ = json::parse(json_string));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(_ = json::parse(json_string), json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Unicode (5/5)" * doctest::skip())
|
||||||
|
{
|
||||||
|
SECTION("RFC 3629")
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
RFC 3629 describes in Sect. 4 the syntax of UTF-8 byte sequences as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
A UTF-8 string is a sequence of octets representing a sequence of UCS
|
||||||
|
characters. An octet sequence is valid UTF-8 only if it matches the
|
||||||
|
following syntax, which is derived from the rules for encoding UTF-8
|
||||||
|
and is expressed in the ABNF of [RFC2234].
|
||||||
|
|
||||||
|
UTF8-octets = *( UTF8-char )
|
||||||
|
UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
|
||||||
|
UTF8-1 = %x00-7F
|
||||||
|
UTF8-2 = %xC2-DF UTF8-tail
|
||||||
|
UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
|
||||||
|
%xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
|
||||||
|
UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
|
||||||
|
%xF4 %x80-8F 2( UTF8-tail )
|
||||||
|
UTF8-tail = %x80-BF
|
||||||
|
*/
|
||||||
|
|
||||||
|
SECTION("UTF8-4 (xF4 x80-8F UTF8-tail UTF8-tail)")
|
||||||
|
{
|
||||||
|
SECTION("well-formed")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(true, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(true, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1);
|
||||||
|
check_utf8dump(false, byte1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2);
|
||||||
|
check_utf8dump(false, byte1, byte2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: missing fourth byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong second byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x00; byte2 <= 0xFF; ++byte2)
|
||||||
|
{
|
||||||
|
// skip correct second byte
|
||||||
|
if (0x80 <= byte2 && byte2 <= 0x8F)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong third byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x00; byte3 <= 0xFF; ++byte3)
|
||||||
|
{
|
||||||
|
// skip correct third byte
|
||||||
|
if (0x80 <= byte3 && byte3 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int byte4 = 0x80; byte4 <= 0xBF; ++byte4)
|
||||||
|
{
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ill-formed: wrong fourth byte")
|
||||||
|
{
|
||||||
|
for (int byte1 = 0xF4; byte1 <= 0xF4; ++byte1)
|
||||||
|
{
|
||||||
|
for (int byte2 = 0x80; byte2 <= 0x8F; ++byte2)
|
||||||
|
{
|
||||||
|
for (int byte3 = 0x80; byte3 <= 0xBF; ++byte3)
|
||||||
|
{
|
||||||
|
for (int byte4 = 0x00; byte4 <= 0xFF; ++byte4)
|
||||||
|
{
|
||||||
|
// skip correct fourth byte
|
||||||
|
if (0x80 <= byte4 && byte4 <= 0xBF)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_utf8string(false, byte1, byte2, byte3, byte4);
|
||||||
|
check_utf8dump(false, byte1, byte2, byte3, byte4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -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 = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user