mirror of
https://github.com/nlohmann/json.git
synced 2026-09-25 09:20:32 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f7c0f3ea7 | ||
|
|
f290b36ad2 | ||
|
|
5f673b0eae | ||
|
|
e94e164b07 | ||
|
|
4daca40d7b | ||
|
|
7c90ec2323 |
+4
-4
@@ -37,13 +37,13 @@ labels:
|
||||
files:
|
||||
- "include/nlohmann/detail/input/binary_reader\\.hpp"
|
||||
- "include/nlohmann/detail/output/binary_writer\\.hpp"
|
||||
- "tests/src/unit-(bson|cbor|msgpack|ubjson|bjdata|binary_formats)"
|
||||
- "tests/src/fuzzer-parse_(bson|cbor|msgpack|ubjson|bjdata)"
|
||||
- "tests/src/unit-(bson|cbor|msgpack|ubjson|bjdata|bon8|binary_formats)"
|
||||
- "tests/src/fuzzer-parse_(bson|cbor|msgpack|ubjson|bjdata|bon8)"
|
||||
- "docs/mkdocs/docs/features/binary_formats/"
|
||||
- "docs/mkdocs/docs/(api/basic_json|examples)/(to|from)_(bson|cbor|msgpack|ubjson|bjdata)"
|
||||
- "docs/mkdocs/docs/(api/basic_json|examples)/(to|from)_(bson|cbor|msgpack|ubjson|bjdata|bon8)"
|
||||
|
||||
- label: "aspect: binary formats"
|
||||
title: "(?i)(bson|cbor|msgpack|messagepack|ubjson|bjdata|binary format)"
|
||||
title: "(?i)(bson|cbor|msgpack|messagepack|ubjson|bjdata|bon8|binary format)"
|
||||
|
||||
- label: "python"
|
||||
files:
|
||||
|
||||
@@ -124,11 +124,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Run CMake (Release)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
if: matrix.build_type == 'Release'
|
||||
shell: pwsh
|
||||
- name: Run CMake (Debug)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
if: matrix.build_type == 'Debug'
|
||||
shell: pwsh
|
||||
- name: Build
|
||||
|
||||
@@ -58,6 +58,7 @@ cc_library(
|
||||
"include/nlohmann/detail/output/binary_writer.hpp",
|
||||
"include/nlohmann/detail/output/output_adapters.hpp",
|
||||
"include/nlohmann/detail/output/serializer.hpp",
|
||||
"include/nlohmann/detail/recursion_depth_limit.hpp",
|
||||
"include/nlohmann/detail/string_concat.hpp",
|
||||
"include/nlohmann/detail/string_escape.hpp",
|
||||
"include/nlohmann/detail/string_utils.hpp",
|
||||
|
||||
@@ -36,6 +36,7 @@ all:
|
||||
@echo "clean - remove built files"
|
||||
@echo "doctest - compile example files and check their output"
|
||||
@echo "fuzz_testing - prepare fuzz testing of the JSON parser"
|
||||
@echo "fuzz_testing_bon8 - prepare fuzz testing of the BON8 parser"
|
||||
@echo "fuzz_testing_bson - prepare fuzz testing of the BSON parser"
|
||||
@echo "fuzz_testing_cbor - prepare fuzz testing of the CBOR parser"
|
||||
@echo "fuzz_testing_msgpack - prepare fuzz testing of the MessagePack parser"
|
||||
@@ -71,6 +72,14 @@ fuzz_testing:
|
||||
find tests/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_bon8:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_bon8_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_bon8_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data -size -5k -name *.bon8 | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_bson:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
- [Implicit conversions](#implicit-conversions)
|
||||
- [Conversions to/from arbitrary types](#arbitrary-types-conversions)
|
||||
- [Specializing enum conversion](#specializing-enum-conversion)
|
||||
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)
|
||||
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, BJData, and BON8)](#binary-formats-bson-cbor-messagepack-ubjson-bjdata-and-bon8)
|
||||
- [Customers](#customers)
|
||||
- [Ecosystem](#ecosystem)
|
||||
- [Supported compilers](#supported-compilers)
|
||||
@@ -128,7 +128,7 @@ There is also a [**docset**](https://github.com/Kapeli/Dash-User-Contributions/t
|
||||
- **JSON Pointer functions**: [flatten](https://json.nlohmann.me/api/basic_json/flatten), [unflatten](https://json.nlohmann.me/api/basic_json/unflatten)
|
||||
- **JSON Patch functions**: [patch](https://json.nlohmann.me/api/basic_json/patch), [patch_inplace](https://json.nlohmann.me/api/basic_json/patch_inplace), [diff](https://json.nlohmann.me/api/basic_json/diff), [merge_patch](https://json.nlohmann.me/api/basic_json/merge_patch)
|
||||
- **Static functions**: [meta](https://json.nlohmann.me/api/basic_json/meta), [get_allocator](https://json.nlohmann.me/api/basic_json/get_allocator)
|
||||
- **Binary formats**: [from_bjdata](https://json.nlohmann.me/api/basic_json/from_bjdata), [from_bson](https://json.nlohmann.me/api/basic_json/from_bson), [from_cbor](https://json.nlohmann.me/api/basic_json/from_cbor), [from_msgpack](https://json.nlohmann.me/api/basic_json/from_msgpack), [from_ubjson](https://json.nlohmann.me/api/basic_json/from_ubjson), [to_bjdata](https://json.nlohmann.me/api/basic_json/to_bjdata), [to_bson](https://json.nlohmann.me/api/basic_json/to_bson), [to_cbor](https://json.nlohmann.me/api/basic_json/to_cbor), [to_msgpack](https://json.nlohmann.me/api/basic_json/to_msgpack), [to_ubjson](https://json.nlohmann.me/api/basic_json/to_ubjson)
|
||||
- **Binary formats**: [from_bjdata](https://json.nlohmann.me/api/basic_json/from_bjdata), [from_bon8](https://json.nlohmann.me/api/basic_json/from_bon8), [from_bson](https://json.nlohmann.me/api/basic_json/from_bson), [from_cbor](https://json.nlohmann.me/api/basic_json/from_cbor), [from_msgpack](https://json.nlohmann.me/api/basic_json/from_msgpack), [from_ubjson](https://json.nlohmann.me/api/basic_json/from_ubjson), [to_bjdata](https://json.nlohmann.me/api/basic_json/to_bjdata), [to_bon8](https://json.nlohmann.me/api/basic_json/to_bon8), [to_bson](https://json.nlohmann.me/api/basic_json/to_bson), [to_cbor](https://json.nlohmann.me/api/basic_json/to_cbor), [to_msgpack](https://json.nlohmann.me/api/basic_json/to_msgpack), [to_ubjson](https://json.nlohmann.me/api/basic_json/to_ubjson)
|
||||
- **Non-member functions**: [operator<<](https://json.nlohmann.me/api/operator_ltlt/), [operator>>](https://json.nlohmann.me/api/operator_gtgt/), [to_string](https://json.nlohmann.me/api/basic_json/to_string)
|
||||
- **Literals**: [operator""_json](https://json.nlohmann.me/api/operator_literal_json)
|
||||
- **Helper classes**: [std::hash<basic_json>](https://json.nlohmann.me/api/basic_json/std_hash), [std::swap<basic_json>](https://json.nlohmann.me/api/basic_json/std_swap)
|
||||
@@ -1110,9 +1110,9 @@ Other Important points:
|
||||
- When using `get<ENUM_TYPE>()`, undefined JSON values will default to the first pair specified in your map. Select this default pair carefully. If you desire an exception in this circumstance use `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT()` which behaves identically except for throwing an exception on unrecognized values.
|
||||
- If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the map will be returned when converting to or from JSON.
|
||||
|
||||
### Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)
|
||||
### Binary formats (BSON, CBOR, MessagePack, UBJSON, BJData, and BON8)
|
||||
|
||||
Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [BSON](https://bsonspec.org) (Binary JSON), [CBOR](https://cbor.io) (Concise Binary Object Representation), [MessagePack](https://msgpack.org), [UBJSON](https://ubjson.org) (Universal Binary JSON Specification) and [BJData](https://neurojson.org/bjdata) (Binary JData) to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
Though JSON is a ubiquitous data format, it is not a very compact format suitable for data exchange, for instance over a network. Hence, the library supports [BSON](https://bsonspec.org) (Binary JSON), [CBOR](https://cbor.io) (Concise Binary Object Representation), [MessagePack](https://msgpack.org), [UBJSON](https://ubjson.org) (Universal Binary JSON Specification), [BJData](https://neurojson.org/bjdata) (Binary JData), and [BON8](https://github.com/hikoworks/hikogui/blob/main/docs/BON8.md) (Binary Object Notation 8) to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
|
||||
```cpp
|
||||
// create a JSON value
|
||||
@@ -1149,6 +1149,14 @@ std::vector<std::uint8_t> v_ubjson = json::to_ubjson(j);
|
||||
|
||||
// roundtrip
|
||||
json j_from_ubjson = json::from_ubjson(v_ubjson);
|
||||
|
||||
// serialize to BON8
|
||||
std::vector<std::uint8_t> v_bon8 = json::to_bon8(j);
|
||||
|
||||
// 0x88, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0xF9, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x90
|
||||
|
||||
// roundtrip
|
||||
json j_from_bon8 = json::from_bon8(v_bon8);
|
||||
```
|
||||
|
||||
The library also supports binary types from BSON, CBOR (byte strings), and MessagePack (bin, ext, fixext). They are stored by default as `std::vector<std::uint8_t>` to be processed outside the library.
|
||||
|
||||
+1
-1
@@ -542,7 +542,7 @@ add_custom_target(ci_infer
|
||||
|
||||
add_custom_target(ci_offline_testdata
|
||||
COMMAND mkdir -p ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data && ${GIT_TOOL} clone -c advice.detachedHead=false --branch v3.1.0 https://github.com/nlohmann/json_test_data.git --quiet --depth 1
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_offline_testdata/test_data && ${GIT_TOOL} clone -c advice.detachedHead=false --branch v3.2.0 https://github.com/nlohmann/json_test_data.git --quiet --depth 1
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_TestDataDirectory=${PROJECT_BINARY_DIR}/build_offline_testdata/test_data/json_test_data
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
set(JSON_TEST_DATA_URL https://github.com/nlohmann/json_test_data)
|
||||
set(JSON_TEST_DATA_VERSION 3.1.0)
|
||||
set(JSON_TEST_DATA_VERSION 3.2.0)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bjdata', 'Fu
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bson', 'Function', 'api/basic_json/from_bson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_cbor', 'Function', 'api/basic_json/from_cbor/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_msgpack', 'Function', 'api/basic_json/from_msgpack/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_bon8', 'Function', 'api/basic_json/from_bon8/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::from_ubjson', 'Function', 'api/basic_json/from_ubjson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::front', 'Method', 'api/basic_json/front/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::get', 'Method', 'api/basic_json/get/index.html');
|
||||
@@ -121,6 +122,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bjdata', 'Func
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bson', 'Function', 'api/basic_json/to_bson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_cbor', 'Function', 'api/basic_json/to_cbor/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_msgpack', 'Function', 'api/basic_json/to_msgpack/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_bon8', 'Function', 'api/basic_json/to_bon8/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_string', 'Method', 'api/basic_json/to_string/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html');
|
||||
@@ -171,6 +173,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BJData', 'Gui
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BSON', 'Guide', 'features/binary_formats/bson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: CBOR', 'Guide', 'features/binary_formats/cbor/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: MessagePack', 'Guide', 'features/binary_formats/messagepack/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: BON8', 'Guide', 'features/binary_formats/bon8/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Formats: UBJSON', 'Guide', 'features/binary_formats/ubjson/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Binary Values', 'Guide', 'features/binary_values/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('Comments', 'Guide', 'features/comments/index.html');
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# <small>nlohmann::basic_json::</small>from_bon8
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
template<typename InputType>
|
||||
static basic_json from_bon8(InputType&& i,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true);
|
||||
// (2)
|
||||
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||
static basic_json from_bon8(IteratorType first, SentinelType last,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true);
|
||||
```
|
||||
|
||||
Deserializes a given input to a JSON value using the BON8 (Binary Object Notation 8) serialization format.
|
||||
|
||||
1. Reads from a compatible input.
|
||||
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support).
|
||||
|
||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bon8.md).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`InputType`
|
||||
: A compatible input, for instance:
|
||||
|
||||
- an `std::istream` object
|
||||
- a `FILE` pointer
|
||||
- a C-style array of characters
|
||||
- a pointer to a null-terminated string of single byte characters
|
||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
||||
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
||||
|
||||
`IteratorType`
|
||||
: a compatible iterator type
|
||||
|
||||
`SentinelType`
|
||||
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||
|
||||
- a custom sentinel type for C++20 ranges
|
||||
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||
|
||||
## Parameters
|
||||
|
||||
`i` (in)
|
||||
: an input in BON8 format convertible to an input adapter
|
||||
|
||||
`first` (in)
|
||||
: iterator to the start of the input
|
||||
|
||||
`last` (in)
|
||||
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||
|
||||
`strict` (in)
|
||||
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
||||
|
||||
`allow_exceptions` (in)
|
||||
: whether to throw exceptions in case of a parse error (optional, `#!cpp true` by default)
|
||||
|
||||
## Return value
|
||||
|
||||
deserialized JSON value; in case of a parse error and `allow_exceptions` set to `#!cpp false`, the return value will be
|
||||
`value_t::discarded`. The latter can be checked with [`is_discarded`](is_discarded.md).
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or
|
||||
the end of the file was not reached when `strict` was set to true
|
||||
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs, for instance
|
||||
an invalid byte, a string that is not valid UTF-8, or an object key that is not a string
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example shows the deserialization of a byte vector in BON8 format to a JSON value.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/from_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/from_bon8.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [to_bon8](to_bon8.md) create a BON8 serialization of a JSON value
|
||||
- [from_cbor](from_cbor.md) create a JSON value from an input in CBOR format
|
||||
- [from_msgpack](from_msgpack.md) create a JSON value from an input in MessagePack format
|
||||
- [from_bson](from_bson.md) create a JSON value from an input in BSON format
|
||||
- [from_ubjson](from_ubjson.md) create a JSON value from an input in UBJSON format
|
||||
- [from_bjdata](from_bjdata.md) create a JSON value from an input in BJData format
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -290,11 +290,13 @@ Access to the JSON value
|
||||
### Binary formats
|
||||
|
||||
- [**from_bjdata**](from_bjdata.md) (_static_) - create a JSON value from an input in BJData format
|
||||
- [**from_bon8**](from_bon8.md) (_static_) - create a JSON value from an input in BON8 format
|
||||
- [**from_bson**](from_bson.md) (_static_) - create a JSON value from an input in BSON format
|
||||
- [**from_cbor**](from_cbor.md) (_static_) - create a JSON value from an input in CBOR format
|
||||
- [**from_msgpack**](from_msgpack.md) (_static_) - create a JSON value from an input in MessagePack format
|
||||
- [**from_ubjson**](from_ubjson.md) (_static_) - create a JSON value from an input in UBJSON format
|
||||
- [**to_bjdata**](to_bjdata.md) (_static_) - create a BJData serialization of a given JSON value
|
||||
- [**to_bon8**](to_bon8.md) (_static_) - create a BON8 serialization of a given JSON value
|
||||
- [**to_bson**](to_bson.md) (_static_) - create a BSON serialization of a given JSON value
|
||||
- [**to_cbor**](to_cbor.md) (_static_) - create a CBOR serialization of a given JSON value
|
||||
- [**to_msgpack**](to_msgpack.md) (_static_) - create a MessagePack serialization of a given JSON value
|
||||
|
||||
@@ -7,7 +7,8 @@ enum class input_format_t {
|
||||
msgpack,
|
||||
ubjson,
|
||||
bson,
|
||||
bjdata
|
||||
bjdata,
|
||||
bon8
|
||||
};
|
||||
```
|
||||
|
||||
@@ -31,6 +32,9 @@ bson
|
||||
bjdata
|
||||
: BJData (Binary JData)
|
||||
|
||||
bon8
|
||||
: BON8 (Binary Object Notation 8)
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
@@ -5,7 +5,7 @@ class parse_error : public exception;
|
||||
```
|
||||
|
||||
The library throws this exception when a parse error occurs. Parse errors can occur during the deserialization of
|
||||
JSON text, BSON, CBOR, MessagePack, UBJSON, as well as when using JSON Patch.
|
||||
JSON text, BJData, BON8, BSON, CBOR, MessagePack, UBJSON, as well as when using JSON Patch.
|
||||
|
||||
Member `byte` holds the byte index of the last read character in the input file (see note below).
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
: SAX event listener (must not be null)
|
||||
|
||||
`format` (in)
|
||||
: the format to parse (JSON, CBOR, MessagePack, or UBJSON) (optional, `input_format_t::json` by default), see
|
||||
: the format to parse (JSON, BJData, BON8, BSON, CBOR, MessagePack, or UBJSON) (optional, `input_format_t::json` by
|
||||
default), see
|
||||
[`input_format_t`](input_format_t.md) for more information
|
||||
|
||||
`strict` (in)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# <small>nlohmann::basic_json::</small>to_bon8
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j);
|
||||
|
||||
// (2)
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o);
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o);
|
||||
```
|
||||
|
||||
Serializes a given JSON value `j` to a byte vector using the BON8 (Binary Object Notation 8) serialization format. BON8
|
||||
is a compact binary serialization format that stores strings as UTF-8 without a length prefix.
|
||||
|
||||
1. Returns a byte vector containing the BON8 serialization.
|
||||
2. Writes the BON8 serialization to an output adapter.
|
||||
|
||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bon8.md).
|
||||
|
||||
## Parameters
|
||||
|
||||
`j` (in)
|
||||
: JSON value to serialize
|
||||
|
||||
`o` (in)
|
||||
: output adapter to write serialization to
|
||||
|
||||
## Return value
|
||||
|
||||
1. BON8 serialization as a byte vector
|
||||
2. (none)
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value `j`, which is never modified.
|
||||
With (2), the bytes written before the exception remain in the output adapter.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an unsigned integer
|
||||
above 9223372036854775807, which BON8 cannot represent
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
|
||||
valid UTF-8
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the JSON value `j`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example shows the serialization of a JSON value to a byte vector in BON8 format.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/to_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/to_bon8.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [from_bon8](from_bon8.md) create a JSON value from an input in BON8 format
|
||||
- [to_cbor](to_cbor.md) create a CBOR serialization of a JSON value
|
||||
- [to_msgpack](to_msgpack.md) create a MessagePack serialization of a JSON value
|
||||
- [to_bson](to_bson.md) create a BSON serialization of a JSON value
|
||||
- [to_ubjson](to_ubjson.md) create a UBJSON serialization of a JSON value
|
||||
- [to_bjdata](to_bjdata.md) create a BJData serialization of a JSON value
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -11,9 +11,9 @@ The macro only affects the JSON text parser ([`parse`](../basic_json/parse.md),
|
||||
[`sax_parse`](../basic_json/sax_parse.md), and [`operator>>`](../operator_gtgt.md)). There are three cases where a NUL
|
||||
byte is still not rejected:
|
||||
|
||||
- The binary formats ([`from_bjdata`](../basic_json/from_bjdata.md), [`from_bson`](../basic_json/from_bson.md),
|
||||
[`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md),
|
||||
[`from_ubjson`](../basic_json/from_ubjson.md)) are never affected: there, `0x00` is ordinary data.
|
||||
- The binary formats ([`from_bjdata`](../basic_json/from_bjdata.md), [`from_bon8`](../basic_json/from_bon8.md),
|
||||
[`from_bson`](../basic_json/from_bson.md), [`from_cbor`](../basic_json/from_cbor.md),
|
||||
[`from_msgpack`](../basic_json/from_msgpack.md), [`from_ubjson`](../basic_json/from_ubjson.md)) are never affected: there, `0x00` is ordinary data.
|
||||
- A bare `const char*` pointer has no length of its own, so its length is still determined with `strlen()`. The first
|
||||
NUL byte therefore still marks the end of the input, and nothing after it is read.
|
||||
- One trailing `'\0'` at the end of a `char` array (e.g., a string literal) is trimmed; see the warning below.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create byte vector
|
||||
std::vector<std::uint8_t> v = {0x89, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74,
|
||||
0xf9, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0xff,
|
||||
0x42, 0x4f, 0x4e, 0x38, 0xff, 0x73, 0x63, 0x68,
|
||||
0x65, 0x6d, 0x61, 0x90
|
||||
};
|
||||
|
||||
// deserialize it with BON8
|
||||
json j = json::from_bon8(v);
|
||||
|
||||
// print the deserialized JSON value
|
||||
std::cout << std::setw(2) << j << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compact": true,
|
||||
"format": "BON8",
|
||||
"schema": 0
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using namespace nlohmann::literals;
|
||||
|
||||
int main()
|
||||
{
|
||||
// create a JSON value
|
||||
json j = R"({"compact": true, "format": "BON8", "schema": 0})"_json;
|
||||
|
||||
// serialize it to BON8
|
||||
std::vector<std::uint8_t> v = json::to_bon8(j);
|
||||
|
||||
// print the vector content
|
||||
for (auto& byte : v)
|
||||
{
|
||||
std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0x89 0x63 0x6f 0x6d 0x70 0x61 0x63 0x74 0xf9 0x66 0x6f 0x72 0x6d 0x61 0x74 0xff 0x42 0x4f 0x4e 0x38 0xff 0x73 0x63 0x68 0x65 0x6d 0x61 0x90
|
||||
@@ -0,0 +1,159 @@
|
||||
# BON8
|
||||
|
||||
BON8 (Binary Object Notation 8) is a compact binary serialization format for JSON values. It uses the byte values that
|
||||
cannot begin a UTF-8 character as type markers, so strings are stored as plain UTF-8 without a length prefix: a string
|
||||
ends at the first byte that cannot continue it. Integers from -10 to 39, `true`, `false`, `null`, and the floating-point
|
||||
values -1.0, 0.0, and 1.0 take a single byte, and arrays and objects with up to four elements need no terminator.
|
||||
|
||||
!!! abstract "References"
|
||||
|
||||
- [BON8 specification](https://github.com/hikoworks/hikogui/blob/main/docs/BON8.md)
|
||||
- [Reference implementation](https://github.com/hikoworks/hikogui/blob/main/src/hikogui/codec/BON8.hpp) in HikoGUI
|
||||
|
||||
## Serialization
|
||||
|
||||
The library uses the following mapping from JSON values types to BON8 types according to the BON8 specification:
|
||||
|
||||
| JSON value type | value/range | BON8 type | first byte |
|
||||
|-----------------|----------------------------------------------|-------------------------------|------------|
|
||||
| null | `null` | null | 0xFA |
|
||||
| boolean | `true` | true | 0xF9 |
|
||||
| boolean | `false` | false | 0xF8 |
|
||||
| number_integer | -9223372036854775808..-2147483649 | int64 | 0x8D |
|
||||
| number_integer | -2147483648..-33818507 | int32 | 0x8C |
|
||||
| number_integer | -33818506..-264075 | 4-byte negative integer | 0xF0..0xF7 |
|
||||
| number_integer | -264074..-1931 | 3-byte negative integer | 0xE0..0xEF |
|
||||
| number_integer | -1930..-11 | 2-byte negative integer | 0xC2..0xDF |
|
||||
| number_integer | -10..-1 | 1-byte negative integer | 0xB8..0xC1 |
|
||||
| number_integer | 0..39 | 1-byte positive integer | 0x90..0xB7 |
|
||||
| number_integer | 40..3879 | 2-byte positive integer | 0xC2..0xDF |
|
||||
| number_integer | 3880..528167 | 3-byte positive integer | 0xE0..0xEF |
|
||||
| number_integer | 528168..67637031 | 4-byte positive integer | 0xF0..0xF7 |
|
||||
| number_integer | 67637032..2147483647 | int32 | 0x8C |
|
||||
| number_integer | 2147483648..9223372036854775807 | int64 | 0x8D |
|
||||
| number_unsigned | 0..39 | 1-byte positive integer | 0x90..0xB7 |
|
||||
| number_unsigned | 40..3879 | 2-byte positive integer | 0xC2..0xDF |
|
||||
| number_unsigned | 3880..528167 | 3-byte positive integer | 0xE0..0xEF |
|
||||
| number_unsigned | 528168..67637031 | 4-byte positive integer | 0xF0..0xF7 |
|
||||
| number_unsigned | 67637032..2147483647 | int32 | 0x8C |
|
||||
| number_unsigned | 2147483648..9223372036854775807 | int64 | 0x8D |
|
||||
| number_float | `-1.0` | -1.0 | 0xFB |
|
||||
| number_float | `0.0` | 0.0 | 0xFC |
|
||||
| number_float | `1.0` | 1.0 | 0xFD |
|
||||
| number_float | *any other value representable by a float* | binary32 | 0x8E |
|
||||
| number_float | *any value NOT representable by a float* | binary64 | 0x8F |
|
||||
| string | *empty* | end of string | 0xFF |
|
||||
| string | *non-empty* | UTF-8 string | 0x00..0x7F, 0xC2..0xF4 |
|
||||
| array | *size*: 0..4 | array with count | 0x80..0x84 |
|
||||
| array | *size*: 5 or more | array (terminated by 0xFE) | 0x85 |
|
||||
| object | *size*: 0..4 | object with count | 0x86..0x8A |
|
||||
| object | *size*: 5 or more | object (terminated by 0xFE) | 0x8B |
|
||||
| binary | *size*: 0..4 | array with count | 0x80..0x84 |
|
||||
| binary | *size*: 5 or more | array (terminated by 0xFE) | 0x85 |
|
||||
|
||||
An integer that takes 2 to 4 bytes starts with a UTF-8 lead byte (0xC2..0xF7) that is followed by a byte that cannot
|
||||
continue a UTF-8 character: 0x00..0x7F for positive and 0xC0..0xFF for negative integers. A string is terminated by
|
||||
0xFF only if it is empty, if another string follows it, or if it is the last value of the message; otherwise, the first
|
||||
byte of the next value ends it.
|
||||
|
||||
!!! success "Complete mapping"
|
||||
|
||||
Except for the values listed below, any JSON value can be converted to a BON8 value.
|
||||
|
||||
Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`.
|
||||
|
||||
!!! warning "Unsupported values"
|
||||
|
||||
The following values can **not** be converted to a BON8 value:
|
||||
|
||||
- unsigned integers above 9223372036854775807, because BON8 has no unsigned 64-bit integer type
|
||||
([out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407))
|
||||
- strings that are not valid UTF-8, because the end of a string is determined from its encoding
|
||||
([type_error.316](../../home/exceptions.md#jsonexceptiontype_error316))
|
||||
|
||||
!!! info "NaN/infinity handling"
|
||||
|
||||
`-0.0`, `Infinity`, and `-Infinity` are serialized as binary32 (type 0x8E, 5 bytes total). `NaN` is serialized as
|
||||
the binary32 value 0x7F800001 that the specification recommends. This is in contrast to the
|
||||
[dump](../../api/basic_json/dump.md) function which serializes NaN or Infinity to `null`.
|
||||
|
||||
!!! warning "Binary values"
|
||||
|
||||
BON8 has no binary type. Binary values are serialized as arrays of integers (0..255), so they are read back as
|
||||
arrays. The subtype is not serialized.
|
||||
|
||||
!!! info "Canonical representation"
|
||||
|
||||
The output follows the specification's canonical representation rules: every value uses the shortest encoding,
|
||||
floating-point numbers use binary32 whenever that loses no precision, and object keys are sorted by their UTF-8
|
||||
code units. There are two exceptions:
|
||||
|
||||
- Strings are not normalized to Unicode Normalization Form C (NFC).
|
||||
- Object keys are written in the order of the object type, which is sorted for `json`, but not for
|
||||
[`ordered_json`](../../api/ordered_json.md).
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/to_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```c
|
||||
--8<-- "examples/to_bon8.output"
|
||||
```
|
||||
|
||||
## Deserialization
|
||||
|
||||
The library maps BON8 types to JSON value types as follows:
|
||||
|
||||
| BON8 type | JSON value type | first byte |
|
||||
|-------------------------------|-----------------|------------------------|
|
||||
| UTF-8 string | string | 0x00..0x7F |
|
||||
| array with count | array | 0x80..0x84 |
|
||||
| array (terminated by 0xFE) | array | 0x85 |
|
||||
| object with count | object | 0x86..0x8A |
|
||||
| object (terminated by 0xFE) | object | 0x8B |
|
||||
| int32 | number_unsigned or number_integer | 0x8C |
|
||||
| int64 | number_unsigned or number_integer | 0x8D |
|
||||
| binary32 | number_float | 0x8E |
|
||||
| binary64 | number_float | 0x8F |
|
||||
| 1-byte positive integer | number_unsigned | 0x90..0xB7 |
|
||||
| 1-byte negative integer | number_integer | 0xB8..0xC1 |
|
||||
| UTF-8 string | string | 0xC2..0xF4, followed by 0x80..0xBF |
|
||||
| 2- to 4-byte positive integer | number_unsigned | 0xC2..0xF7, followed by 0x00..0x7F |
|
||||
| 2- to 4-byte negative integer | number_integer | 0xC2..0xF7, followed by 0xC0..0xFF |
|
||||
| false | `false` | 0xF8 |
|
||||
| true | `true` | 0xF9 |
|
||||
| null | `null` | 0xFA |
|
||||
| -1.0 | number_float | 0xFB |
|
||||
| 0.0 | number_float | 0xFC |
|
||||
| 1.0 | number_float | 0xFD |
|
||||
| empty string | string | 0xFF |
|
||||
|
||||
Non-negative integers are read as number_unsigned, negative integers as number_integer.
|
||||
|
||||
!!! info
|
||||
|
||||
Values that do not use the canonical representation, such as integers with a longer encoding than necessary,
|
||||
arrays and objects with up to four elements that are terminated by 0xFE, unsorted object keys, or unneeded 0xFF
|
||||
bytes after a string, are accepted.
|
||||
|
||||
Strings must be valid UTF-8, and the last string of a message must be terminated by 0xFF.
|
||||
|
||||
!!! info
|
||||
|
||||
Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/from_bon8.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/from_bon8.output"
|
||||
```
|
||||
@@ -4,6 +4,7 @@ Though JSON is a ubiquitous data format, it is not a very compact format suitabl
|
||||
a network. Hence, the library supports
|
||||
|
||||
- [BJData](bjdata.md) (Binary JData),
|
||||
- [BON8](bon8.md) (Binary Object Notation 8),
|
||||
- [BSON](bson.md) (Binary JSON),
|
||||
- [CBOR](cbor.md) (Concise Binary Object Representation),
|
||||
- [MessagePack](messagepack.md), and
|
||||
@@ -18,6 +19,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
| Format | Serialization | Deserialization |
|
||||
|-------------|-----------------------------------------------|----------------------------------------------|
|
||||
| BJData | complete | complete |
|
||||
| BON8 | incomplete: no unsigned integers above int64 | complete |
|
||||
| BSON | incomplete: top-level value must be an object | incomplete, but all JSON types are supported |
|
||||
| CBOR | complete | incomplete, but all JSON types are supported |
|
||||
| MessagePack | complete | complete |
|
||||
@@ -28,6 +30,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors.
|
||||
| Format | Binary values | Binary subtypes |
|
||||
|-------------|---------------|-----------------|
|
||||
| BJData | not supported | not supported |
|
||||
| BON8 | not supported | not supported |
|
||||
| BSON | supported | supported |
|
||||
| CBOR | supported | supported |
|
||||
| MessagePack | supported | supported |
|
||||
@@ -42,6 +45,7 @@ See [binary values](../binary_values.md) for more information.
|
||||
| BJData | 53.2 % | 91.1 % | 78.1 % | 96.6 % |
|
||||
| BJData (size) | 58.6 % | 92.1 % | 86.7 % | 97.4 % |
|
||||
| BJData (size+type) | 58.6 % | 92.1 % | 86.5 % | 97.4 % |
|
||||
| BON8 | 50.5 % | 83.8 % | 63.5 % | 87.5 % |
|
||||
| BSON | 85.8 % | 95.2 % | 95.8 % | 106.7 % |
|
||||
| CBOR | 50.5 % | 86.3 % | 68.4 % | 88.0 % |
|
||||
| MessagePack | 50.5 % | 86.0 % | 68.5 % | 87.9 % |
|
||||
|
||||
@@ -187,6 +187,41 @@ as an array of uint8 values. The library implements this translation.
|
||||
}
|
||||
```
|
||||
|
||||
### BON8
|
||||
|
||||
[BON8](binary_formats/bon8.md) neither supports binary values nor subtypes. The library serializes binary values as an
|
||||
array of integers.
|
||||
|
||||
??? example
|
||||
|
||||
Code:
|
||||
|
||||
```cpp
|
||||
// create a binary value of subtype 42 (will be ignored in BON8)
|
||||
json j;
|
||||
j["binary"] = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 42);
|
||||
|
||||
// convert to BON8
|
||||
auto v = json::to_bon8(j);
|
||||
```
|
||||
|
||||
`v` is a `std::vector<std::uint8_t>` with the following 16 elements:
|
||||
|
||||
```c
|
||||
0x87 // object with 1 member
|
||||
0x62 0x69 0x6E 0x61 0x72 0x79 // "binary"
|
||||
0x84 // array with 4 elements
|
||||
0xC3 0x22 0xC3 0x56 0xC3 0x12 0xC3 0x16 // content (each byte as a 2-byte integer)
|
||||
```
|
||||
|
||||
Note that the subtype is lost, and deserializing `v` would yield the following value:
|
||||
|
||||
```json
|
||||
{
|
||||
"binary": [202, 254, 186, 190]
|
||||
}
|
||||
```
|
||||
|
||||
### BSON
|
||||
|
||||
[BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as an
|
||||
|
||||
@@ -35,8 +35,8 @@ C++ types, and finally serialize it again.
|
||||
- [Serialization](serialization.md) — turn a value back into JSON text with [`dump`](../api/basic_json/dump.md),
|
||||
including pretty-printing and handling of non-ASCII and invalid UTF-8.
|
||||
- [Binary formats](binary_formats/index.md) — encode values more compactly as
|
||||
[BJData](binary_formats/bjdata.md), [BSON](binary_formats/bson.md), [CBOR](binary_formats/cbor.md),
|
||||
[MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
|
||||
[BJData](binary_formats/bjdata.md), [BON8](binary_formats/bon8.md), [BSON](binary_formats/bson.md),
|
||||
[CBOR](binary_formats/cbor.md), [MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
|
||||
- [Binary values](binary_values.md) — store and exchange raw byte sequences.
|
||||
|
||||
## How values are stored and configured
|
||||
|
||||
@@ -117,7 +117,7 @@ For the [{fmt}](https://github.com/fmtlib/fmt) library, the library ships a
|
||||
## Serializing to other formats
|
||||
|
||||
Besides JSON text, a value can also be serialized to the more compact [binary formats](binary_formats/index.md)
|
||||
(BJData, BSON, CBOR, MessagePack, UBJSON).
|
||||
(BJData, BON8, BSON, CBOR, MessagePack, UBJSON).
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -547,7 +547,7 @@ Grisu2 algorithm, which produces the shortest representation that round-trips. O
|
||||
### Required for the binary formats
|
||||
|
||||
`NumberFloatType` must be `#!cpp float` or `#!cpp double`. The writers for
|
||||
[CBOR, MessagePack, UBJSON, BJData, and BSON](../binary_formats/index.md) map a floating-point value onto an IEEE 754
|
||||
[CBOR, MessagePack, UBJSON, BJData, BON8, and BSON](../binary_formats/index.md) map a floating-point value onto an IEEE 754
|
||||
binary32 or binary64 field and have no encoding for `#!cpp long double`.
|
||||
|
||||
### Compatible types
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*[ASCII]: American Standard Code for Information Interchange
|
||||
*[BDFL]: Benevolent Dictator for Life
|
||||
*[BJData]: Binary JData
|
||||
*[BON8]: Binary Object Notation 8
|
||||
*[BSON]: Binary JSON
|
||||
*[CBOR]: Concise Binary Object Representation
|
||||
*[CC0]: Creative Commons Zero
|
||||
|
||||
@@ -63,6 +63,7 @@ nav:
|
||||
- Binary Formats:
|
||||
- features/binary_formats/index.md
|
||||
- features/binary_formats/bjdata.md
|
||||
- features/binary_formats/bon8.md
|
||||
- features/binary_formats/bson.md
|
||||
- features/binary_formats/cbor.md
|
||||
- features/binary_formats/messagepack.md
|
||||
@@ -142,6 +143,7 @@ nav:
|
||||
- 'flatten': api/basic_json/flatten.md
|
||||
- 'format_as': api/basic_json/format_as.md
|
||||
- 'from_bjdata': api/basic_json/from_bjdata.md
|
||||
- 'from_bon8': api/basic_json/from_bon8.md
|
||||
- 'from_bson': api/basic_json/from_bson.md
|
||||
- 'from_cbor': api/basic_json/from_cbor.md
|
||||
- 'from_msgpack': api/basic_json/from_msgpack.md
|
||||
@@ -213,6 +215,7 @@ nav:
|
||||
- 'swap': api/basic_json/swap.md
|
||||
- 'std::swap<basic_json>': api/basic_json/std_swap.md
|
||||
- 'to_bjdata': api/basic_json/to_bjdata.md
|
||||
- 'to_bon8': api/basic_json/to_bon8.md
|
||||
- 'to_bson': api/basic_json/to_bson.md
|
||||
- 'to_cbor': api/basic_json/to_cbor.md
|
||||
- 'to_msgpack': api/basic_json/to_msgpack.md
|
||||
@@ -409,7 +412,7 @@ plugins:
|
||||
markdown_description: >
|
||||
JSON for Modern C++ is a C++11 header-only library implementing a JSON
|
||||
value type with an STL-like API, JSON Pointer/Patch, CBOR/MessagePack/
|
||||
BSON/UBJSON/BJData binary format support, and a SAX-style parser interface.
|
||||
BSON/UBJSON/BJData/BON8 binary format support, and a SAX-style parser interface.
|
||||
sections:
|
||||
Home:
|
||||
- index.md
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <functional> // hash
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -26,6 +28,9 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
|
||||
return seed;
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j);
|
||||
|
||||
/*!
|
||||
@brief hash a JSON value
|
||||
|
||||
@@ -33,12 +38,21 @@ The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||
type of the JSON value is taken into account to have different hash values for
|
||||
null, 0, 0U, and false, etc.
|
||||
|
||||
Hashing an array or an object hashes its elements, which used to call this
|
||||
function again once per nesting level, so a value nested deeply enough
|
||||
exhausted the call stack and terminated the process. The descent is bounded
|
||||
here: once @ref recursion_depth_limit levels have been entered, @ref
|
||||
hash_iteratively hashes what is left without the call stack. A value nested
|
||||
less deeply than that - all but a vanishing minority - is hashed exactly as
|
||||
before, without allocating.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j JSON value to hash
|
||||
@param depth nesting level of @a j, counted from the value passed by the caller
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash(const BasicJsonType& j)
|
||||
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -56,22 +70,32 @@ std::size_t hash(const BasicJsonType& j)
|
||||
|
||||
case BasicJsonType::value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j.items())
|
||||
{
|
||||
const auto h = std::hash<string_t> {}(element.key());
|
||||
seed = combine(seed, h);
|
||||
seed = combine(seed, hash(element.value()));
|
||||
seed = combine(seed, hash(element.value(), depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j)
|
||||
{
|
||||
seed = combine(seed, hash(element));
|
||||
seed = combine(seed, hash(element, depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
@@ -127,5 +151,77 @@ std::size_t hash(const BasicJsonType& j)
|
||||
}
|
||||
}
|
||||
|
||||
/// an array or object whose elements @ref hash_iteratively is hashing
|
||||
template<typename BasicJsonType>
|
||||
struct hash_frame
|
||||
{
|
||||
hash_frame(const BasicJsonType* value_, std::size_t seed_) noexcept
|
||||
: value(value_), position(value_->cbegin()), seed(seed_)
|
||||
{}
|
||||
|
||||
const BasicJsonType* value;
|
||||
typename BasicJsonType::const_iterator position;
|
||||
std::size_t seed;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief hash the array or object @a j without the call stack
|
||||
|
||||
Computes the same value as @ref hash, keeping the arrays and objects it has
|
||||
entered on an explicit stack instead of descending into them. Only reached for
|
||||
values nested deeper than @ref recursion_depth_limit.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j array or object to hash
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
|
||||
std::vector<hash_frame<BasicJsonType>> stack;
|
||||
stack.emplace_back(&j, combine(static_cast<std::size_t>(j.type()), j.size()));
|
||||
|
||||
while (true)
|
||||
{
|
||||
// a copy, as entering an element below can reallocate the stack; the
|
||||
// frame itself is only changed through stack.back()
|
||||
const hash_frame<BasicJsonType> frame = stack.back();
|
||||
|
||||
if (frame.position == frame.value->cend())
|
||||
{
|
||||
// all elements are hashed: fold this value's hash into its parent's
|
||||
// seed, exactly where the recursive version returns it
|
||||
const std::size_t h = frame.seed;
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
return h;
|
||||
}
|
||||
stack.back().seed = combine(stack.back().seed, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.value->is_object())
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, std::hash<string_t> {}(frame.position.key()));
|
||||
}
|
||||
|
||||
// advance before entering the element, which pushes onto the stack
|
||||
const BasicJsonType& element = *frame.position;
|
||||
++stack.back().position;
|
||||
|
||||
if (element.is_structured())
|
||||
{
|
||||
stack.emplace_back(&element, combine(static_cast<std::size_t>(element.type()), element.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, hash(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -83,7 +83,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
|
||||
///////////////////
|
||||
|
||||
/*!
|
||||
@brief deserialization of CBOR, MessagePack, and UBJSON values
|
||||
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
|
||||
class binary_reader
|
||||
@@ -131,6 +131,7 @@ class binary_reader
|
||||
{
|
||||
sax = sax_;
|
||||
container_stack.clear();
|
||||
bon8_pushback_size = 0;
|
||||
bool result = false;
|
||||
|
||||
switch (format)
|
||||
@@ -152,6 +153,10 @@ class binary_reader
|
||||
result = parse_ubjson_internal();
|
||||
break;
|
||||
|
||||
case input_format_t::bon8:
|
||||
result = parse_bon8_internal();
|
||||
break;
|
||||
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
@@ -164,6 +169,11 @@ class binary_reader
|
||||
{
|
||||
get_ignore_noop();
|
||||
}
|
||||
else if (input_format == input_format_t::bon8)
|
||||
{
|
||||
// a string that ends a container hands back the byte after it
|
||||
get_bon8();
|
||||
}
|
||||
else
|
||||
{
|
||||
get();
|
||||
@@ -3171,6 +3181,511 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
//////////
|
||||
// BON8 //
|
||||
//////////
|
||||
|
||||
/*!
|
||||
@brief get the next byte of a BON8 value
|
||||
|
||||
A BON8 string has no length prefix and no mandatory terminator: it ends at
|
||||
the first byte that cannot continue it, which is already the first byte (or,
|
||||
for an integer that begins with a UTF-8 lead byte, the first two bytes) of
|
||||
whatever follows. The string reader hands those bytes back with
|
||||
@ref unget_bon8, and every BON8 read goes through this function so that
|
||||
they are seen again.
|
||||
|
||||
@return character read from the input
|
||||
*/
|
||||
char_int_type get_bon8()
|
||||
{
|
||||
if (bon8_pushback_size != 0)
|
||||
{
|
||||
++chars_read;
|
||||
return current = bon8_pushback[--bon8_pushback_size];
|
||||
}
|
||||
return get();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief hand a byte back so that the next @ref get_bon8 returns it again
|
||||
|
||||
@param[in] c the byte to hand back; bytes handed back are returned in
|
||||
reverse order
|
||||
*/
|
||||
void unget_bon8(const char_int_type c)
|
||||
{
|
||||
// At most two bytes are ever handed back: a byte is only handed back
|
||||
// right after it was read with get_bon8(), and the only place that
|
||||
// hands back two bytes (a lead byte and the byte after it) read both
|
||||
// of them in a row, which emptied the buffer first. This is an
|
||||
// invariant of the reader rather than a property of the input, so
|
||||
// an assertion suffices (the fuzzers are built with assertions).
|
||||
JSON_ASSERT(bon8_pushback_size < bon8_pushback.size());
|
||||
bon8_pushback[bon8_pushback_size++] = c;
|
||||
--chars_read;
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] c a byte
|
||||
@return whether @a c is a UTF-8 continuation byte (0x80..0xBF)
|
||||
*/
|
||||
static constexpr bool is_bon8_continuation(const char_int_type c) noexcept
|
||||
{
|
||||
return 0x80 <= c && c <= 0xBF;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a parse error at the last read byte
|
||||
|
||||
@param[in] detail a detailed error message
|
||||
@param[in] context further context information
|
||||
@return false
|
||||
*/
|
||||
bool bon8_error(const std::string& detail, const char* context)
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format_t::bon8, concat(detail, ": 0x", last_token), context), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a BON8 value and everything nested inside it
|
||||
|
||||
Reads values until the one that was begun here is complete, resuming the
|
||||
enclosing container after each element, so that the nesting depth of the
|
||||
input costs heap rather than native stack (see #5104).
|
||||
|
||||
@return whether reading the value succeeded
|
||||
*/
|
||||
bool parse_bon8_internal()
|
||||
{
|
||||
// the key currently being read; hoisted out of the loop so that its
|
||||
// capacity is reused across elements and across nesting levels
|
||||
string_t key;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!container_stack.empty())
|
||||
{
|
||||
// a copy, not a reference: it must stay valid across the
|
||||
// pop_back() below, which destroys the container_stack element
|
||||
// it would otherwise alias
|
||||
const container_frame top = container_stack.back();
|
||||
bool at_end = false;
|
||||
|
||||
if (top.remaining != npos)
|
||||
{
|
||||
// counted container (0x80..0x84, 0x86..0x8A): it ends once
|
||||
// its elements have been read
|
||||
at_end = (top.remaining == 0);
|
||||
if (!at_end)
|
||||
{
|
||||
// claim the element about to be read
|
||||
--container_stack.back().remaining;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// container 0x85 or 0x8B: it ends at an end-of-container
|
||||
// marker (0xFE); any other byte begins the next element
|
||||
at_end = (get_bon8() == 0xFE);
|
||||
if (!at_end)
|
||||
{
|
||||
unget_bon8(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (at_end)
|
||||
{
|
||||
container_stack.pop_back();
|
||||
if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the value begun here is complete once its container is
|
||||
if (container_stack.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (top.is_object)
|
||||
{
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_bon8_value()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// a value that opened a container left it on the stack; one that
|
||||
// did not, and that was not inside a container, was the whole value
|
||||
if (container_stack.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read one BON8 value
|
||||
|
||||
Reads a single value and passes it to the SAX parser. A value that begins
|
||||
a container is not read to its end: the container is opened with
|
||||
@ref enter_container and its elements are read by
|
||||
@ref parse_bon8_internal, so that nesting does not consume native stack.
|
||||
|
||||
@return whether reading the value succeeded
|
||||
*/
|
||||
bool parse_bon8_value()
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "value");
|
||||
}
|
||||
|
||||
// string: ASCII character
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
string_t s;
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(s) && sax->string(s);
|
||||
}
|
||||
|
||||
// array with 0..4 elements
|
||||
if (byte <= 0x84)
|
||||
{
|
||||
return enter_array(static_cast<std::size_t>(byte - 0x80));
|
||||
}
|
||||
|
||||
// array terminated by 0xFE
|
||||
if (byte == 0x85)
|
||||
{
|
||||
return enter_array(npos);
|
||||
}
|
||||
|
||||
// object with 0..4 members
|
||||
if (byte <= 0x8A)
|
||||
{
|
||||
return enter_object(static_cast<std::size_t>(byte - 0x86));
|
||||
}
|
||||
|
||||
switch (byte)
|
||||
{
|
||||
case 0x8B: // object terminated by 0xFE
|
||||
return enter_object(npos);
|
||||
|
||||
case 0x8C: // int32
|
||||
{
|
||||
std::int32_t number{};
|
||||
return get_number(input_format_t::bon8, number) && emit_bon8_integer(number);
|
||||
}
|
||||
|
||||
case 0x8D: // int64
|
||||
{
|
||||
std::int64_t number{};
|
||||
return get_number(input_format_t::bon8, number) && emit_bon8_integer(number);
|
||||
}
|
||||
|
||||
case 0x8E: // binary32
|
||||
{
|
||||
float number{};
|
||||
return get_number(input_format_t::bon8, number) && sax->number_float(static_cast<number_float_t>(number), "");
|
||||
}
|
||||
|
||||
case 0x8F: // binary64
|
||||
{
|
||||
double number{};
|
||||
return get_number(input_format_t::bon8, number) && sax->number_float(static_cast<number_float_t>(number), "");
|
||||
}
|
||||
|
||||
case 0xF8:
|
||||
return sax->boolean(false);
|
||||
|
||||
case 0xF9:
|
||||
return sax->boolean(true);
|
||||
|
||||
case 0xFA:
|
||||
return sax->null();
|
||||
|
||||
case 0xFB:
|
||||
return sax->number_float(static_cast<number_float_t>(-1.0), "");
|
||||
|
||||
case 0xFC:
|
||||
return sax->number_float(static_cast<number_float_t>(0.0), "");
|
||||
|
||||
case 0xFD:
|
||||
return sax->number_float(static_cast<number_float_t>(1.0), "");
|
||||
|
||||
case 0xFF: // empty string
|
||||
{
|
||||
string_t s;
|
||||
return sax->string(s);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// integer 0..39
|
||||
if (byte <= 0xB7)
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(byte - 0x90));
|
||||
}
|
||||
|
||||
// integer -1..-10
|
||||
if (byte <= 0xC1)
|
||||
{
|
||||
return sax->number_integer(-1 - static_cast<number_integer_t>(byte - 0xB8));
|
||||
}
|
||||
|
||||
// 0xC2..0xF7: a UTF-8 lead byte begins a string if a continuation
|
||||
// byte follows and an integer otherwise
|
||||
if (byte <= 0xF7)
|
||||
{
|
||||
const auto second = get_bon8();
|
||||
if (is_bon8_continuation(second))
|
||||
{
|
||||
string_t s;
|
||||
unget_bon8(second);
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(s) && sax->string(s);
|
||||
}
|
||||
return get_bon8_integer(byte, second);
|
||||
}
|
||||
|
||||
// 0xFE: end of container where a value is expected
|
||||
return bon8_error("invalid byte", "value");
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass an integer to the SAX parser
|
||||
|
||||
Non-negative integers are passed as unsigned, negative integers as signed
|
||||
numbers, like the other binary formats do.
|
||||
|
||||
@param[in] number the integer
|
||||
@return whether the SAX parser accepted the value
|
||||
*/
|
||||
bool emit_bon8_integer(const std::int64_t number)
|
||||
{
|
||||
if (number >= 0)
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(number));
|
||||
}
|
||||
return sax->number_integer(static_cast<number_integer_t>(number));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read an integer encoded in 2..4 bytes
|
||||
|
||||
The first byte is a UTF-8 lead byte (0xC2..0xF7) that is followed by a
|
||||
byte that is not a continuation byte: 0x00..0x7F for positive and
|
||||
0xC0..0xFF for negative integers. The lead byte's low bits and the second
|
||||
byte's low 7 (positive) or 6 (negative) bits are the most significant bits
|
||||
of the value; 3- and 4-byte integers add one or two full bytes. Each range
|
||||
starts where the shorter one ends, so no value has two encodings of the
|
||||
same length.
|
||||
|
||||
@param[in] lead the first byte (0xC2..0xF7)
|
||||
@param[in] second the second byte
|
||||
@return whether reading the integer succeeded
|
||||
*/
|
||||
bool get_bon8_integer(const char_int_type lead, const char_int_type second)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bon8, "number")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool negative = second >= 0xC0;
|
||||
std::int64_t value = static_cast<std::int64_t>(negative ? (second & 0x3F) : second);
|
||||
std::int64_t offset = 0;
|
||||
int extra_bytes = 0;
|
||||
|
||||
if (lead <= 0xDF)
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead - 0xC2) << (negative ? 6 : 7);
|
||||
offset = negative ? 11 : 40;
|
||||
}
|
||||
else if (lead <= 0xEF)
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead & 0x0F) << (negative ? 6 : 7);
|
||||
offset = negative ? 1931 : 3880;
|
||||
extra_bytes = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead & 0x07) << (negative ? 6 : 7);
|
||||
offset = negative ? 264075 : 528168;
|
||||
extra_bytes = 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < extra_bytes; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(get_bon8() == char_traits<char_type>::eof()))
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "number");
|
||||
}
|
||||
value = (value << 8) | static_cast<std::int64_t>(current);
|
||||
}
|
||||
|
||||
return negative ? sax->number_integer(static_cast<number_integer_t>(-(value + offset)))
|
||||
: sax->number_unsigned(static_cast<number_unsigned_t>(value + offset));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read an object key
|
||||
|
||||
A key must be a string, so its first byte must be an ASCII character, a
|
||||
UTF-8 lead byte followed by a continuation byte, or 0xFF (empty string).
|
||||
|
||||
@param[out] result the key
|
||||
@return whether reading the key succeeded
|
||||
*/
|
||||
bool get_bon8_key(string_t& result)
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "key");
|
||||
}
|
||||
|
||||
if (byte == 0xFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(result);
|
||||
}
|
||||
|
||||
if (0xC2 <= byte && byte <= 0xF7)
|
||||
{
|
||||
const auto second = get_bon8();
|
||||
unget_bon8(second);
|
||||
if (is_bon8_continuation(second))
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(result);
|
||||
}
|
||||
// an integer: report its first byte rather than the one after it
|
||||
current = byte;
|
||||
}
|
||||
|
||||
return bon8_error("expected a string; last byte", "key");
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a string
|
||||
|
||||
Reads UTF-8 characters until an end-of-string marker (0xFF), which is
|
||||
consumed, or a byte that cannot continue the string, which is handed back
|
||||
to be read as the start of the next value. The string must be valid UTF-8,
|
||||
and it must not end at the end of the input: the last string of a message
|
||||
is always terminated by 0xFF.
|
||||
|
||||
@param[out] result the string
|
||||
@return whether reading the string succeeded
|
||||
*/
|
||||
bool get_bon8_string(string_t& result)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "string");
|
||||
}
|
||||
|
||||
// end of string
|
||||
if (byte == 0xFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// ASCII character
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
result.push_back(static_cast<typename string_t::value_type>(byte));
|
||||
continue;
|
||||
}
|
||||
|
||||
// a byte that cannot begin a character ends the string and begins
|
||||
// the next value
|
||||
if (byte < 0xC2 || byte > 0xF7)
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return true;
|
||||
}
|
||||
|
||||
// a lead byte ends the string if no continuation byte follows: it
|
||||
// is then the first byte of an integer
|
||||
const auto second = get_bon8();
|
||||
if (!is_bon8_continuation(second))
|
||||
{
|
||||
unget_bon8(second);
|
||||
unget_bon8(byte);
|
||||
return true;
|
||||
}
|
||||
|
||||
// the valid range of the second byte excludes overlong forms,
|
||||
// surrogates, and code points above U+10FFFF
|
||||
// (RFC 3629, section 4)
|
||||
int continuation_bytes = 0;
|
||||
bool valid_second = true;
|
||||
if (byte <= 0xDF)
|
||||
{
|
||||
continuation_bytes = 1;
|
||||
}
|
||||
else if (byte <= 0xEF)
|
||||
{
|
||||
continuation_bytes = 2;
|
||||
valid_second = (byte != 0xE0 || second >= 0xA0) && (byte != 0xED || second <= 0x9F);
|
||||
}
|
||||
else
|
||||
{
|
||||
continuation_bytes = 3;
|
||||
valid_second = byte <= 0xF4 && (byte != 0xF0 || second >= 0x90) && (byte != 0xF4 || second <= 0x8F);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!valid_second))
|
||||
{
|
||||
return bon8_error("invalid UTF-8 byte", "string");
|
||||
}
|
||||
|
||||
result.push_back(static_cast<typename string_t::value_type>(byte));
|
||||
result.push_back(static_cast<typename string_t::value_type>(second));
|
||||
|
||||
for (int i = 1; i < continuation_bytes; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(get_bon8() == char_traits<char_type>::eof()))
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "string");
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current)))
|
||||
{
|
||||
return bon8_error("invalid UTF-8 byte", "string");
|
||||
}
|
||||
result.push_back(static_cast<typename string_t::value_type>(current));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// Utility functions //
|
||||
///////////////////////
|
||||
@@ -3448,6 +3963,10 @@ class binary_reader
|
||||
error_msg += "BJData";
|
||||
break;
|
||||
|
||||
case input_format_t::bon8:
|
||||
error_msg += "BON8";
|
||||
break;
|
||||
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
@@ -3480,6 +3999,11 @@ class binary_reader
|
||||
/// the containers that have been opened and not closed yet; see @ref container_frame
|
||||
std::vector<container_frame> container_stack{};
|
||||
|
||||
/// BON8: bytes read past the end of a string, returned again by @ref get_bon8
|
||||
std::array<char_int_type, 2> bon8_pushback{{}};
|
||||
/// BON8: number of bytes in @ref bon8_pushback
|
||||
std::size_t bon8_pushback_size = 0;
|
||||
|
||||
// excluded markers in bjdata optimized type
|
||||
#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \
|
||||
make_array<char_int_type>('F', 'H', 'N', 'S', 'T', 'Z', '[', '{')
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace detail
|
||||
{
|
||||
|
||||
/// the supported input formats
|
||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };
|
||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||
|
||||
////////////////////
|
||||
// input adapters //
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
@@ -76,7 +77,7 @@ std::size_t binary_reserve_hint(const BasicJsonType& j)
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief serialization to CBOR and MessagePack values
|
||||
@brief serialization to BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
||||
*/
|
||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType = output_adapter_sink<CharType>>
|
||||
class binary_writer
|
||||
@@ -1032,6 +1033,21 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
*/
|
||||
void write_bon8(const BasicJsonType& j)
|
||||
{
|
||||
bool string_open = false;
|
||||
write_bon8_value(j, string_open);
|
||||
|
||||
// the last string of a message must be terminated
|
||||
if (string_open)
|
||||
{
|
||||
oa.write_character(to_char_type(0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//////////
|
||||
// BSON //
|
||||
@@ -1431,6 +1447,16 @@ class binary_writer
|
||||
return to_char_type(0xCB); // float 64
|
||||
}
|
||||
|
||||
static constexpr CharType get_bon8_float_prefix(float /*unused*/)
|
||||
{
|
||||
return to_char_type(0x8E); // binary32
|
||||
}
|
||||
|
||||
static constexpr CharType get_bon8_float_prefix(double /*unused*/)
|
||||
{
|
||||
return to_char_type(0x8F); // binary64
|
||||
}
|
||||
|
||||
////////////
|
||||
// UBJSON //
|
||||
////////////
|
||||
@@ -2023,6 +2049,331 @@ class binary_writer
|
||||
return false;
|
||||
}
|
||||
|
||||
//////////
|
||||
// BON8 //
|
||||
//////////
|
||||
|
||||
/*!
|
||||
@brief write a BON8 value
|
||||
|
||||
A string is written without length or terminator: it ends at the first
|
||||
byte that cannot continue it, which is the first byte of any non-string
|
||||
value and of the end-of-container marker 0xFE. It only needs an explicit
|
||||
end-of-string marker (0xFF) when it is empty, when another string follows,
|
||||
or when it is the last thing in the message.
|
||||
|
||||
@param[in] j JSON value to serialize
|
||||
@param[in,out] string_open whether the output ends with a non-empty
|
||||
string that has not been terminated with 0xFF
|
||||
*/
|
||||
void write_bon8_value(const BasicJsonType& j, bool& string_open)
|
||||
{
|
||||
switch (j.type())
|
||||
{
|
||||
case value_t::null:
|
||||
{
|
||||
write_bon8_marker(0xFA, string_open);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::boolean:
|
||||
{
|
||||
write_bon8_marker(j.m_data.m_value.boolean ? 0xF9 : 0xF8, string_open);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j));
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
write_bon8_float(j.m_data.m_value.number_float);
|
||||
string_open = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
write_bon8_string(*j.m_data.m_value.string, string_open, j);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
const auto N = j.m_data.m_value.array->size();
|
||||
// 0x80..0x84: array with 0..4 elements; 0x85: array ended by 0xFE
|
||||
write_bon8_marker(static_cast<std::uint8_t>(N <= 4 ? 0x80 + N : 0x85), string_open);
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_bon8_value(el, string_open);
|
||||
}
|
||||
|
||||
if (N > 4)
|
||||
{
|
||||
write_bon8_marker(0xFE, string_open);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
const auto N = j.m_data.m_value.object->size();
|
||||
// 0x86..0x8A: object with 0..4 members; 0x8B: object ended by 0xFE
|
||||
write_bon8_marker(static_cast<std::uint8_t>(N <= 4 ? 0x86 + N : 0x8B), string_open);
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.object)
|
||||
{
|
||||
write_bon8_string(el.first, string_open, j);
|
||||
write_bon8_value(el.second, string_open);
|
||||
}
|
||||
|
||||
if (N > 4)
|
||||
{
|
||||
write_bon8_marker(0xFE, string_open);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
// BON8 has no binary type: write the bytes as an array of
|
||||
// integers, like UBJSON and BJData do
|
||||
const auto N = j.m_data.m_value.binary->size();
|
||||
write_bon8_marker(static_cast<std::uint8_t>(N <= 4 ? 0x80 + N : 0x85), string_open);
|
||||
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
// the cast is needed for binary types whose value type
|
||||
// is not an integer (e.g., std::byte)
|
||||
write_bon8_integer(static_cast<std::uint8_t>(j.m_data.m_value.binary->data()[i]));
|
||||
}
|
||||
|
||||
if (N > 4)
|
||||
{
|
||||
oa.write_character(to_char_type(0xFE));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a single byte that is not part of a string
|
||||
|
||||
@param[in] marker the byte to write
|
||||
@param[out] string_open set to false, because the output no longer ends
|
||||
with a string; see @ref write_bon8_value
|
||||
*/
|
||||
void write_bon8_marker(const std::uint8_t marker, bool& string_open)
|
||||
{
|
||||
oa.write_character(to_char_type(marker));
|
||||
string_open = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a string
|
||||
|
||||
@param[in] s the string to write
|
||||
@param[in,out] string_open see @ref write_bon8_value
|
||||
@param[in] context the value the string belongs to (for diagnostics)
|
||||
|
||||
@throw type_error.316 if @a s is not valid UTF-8, because the end of a
|
||||
string is determined from its encoding
|
||||
*/
|
||||
void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context)
|
||||
{
|
||||
check_bon8_utf8(s, context);
|
||||
|
||||
// a string that follows another string terminates it
|
||||
if (string_open)
|
||||
{
|
||||
oa.write_character(to_char_type(0xFF));
|
||||
}
|
||||
|
||||
if (s.empty())
|
||||
{
|
||||
// the empty string is just the end-of-string marker
|
||||
oa.write_character(to_char_type(0xFF));
|
||||
string_open = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
oa.write_characters(reinterpret_cast<const CharType*>(s.data()), s.size());
|
||||
string_open = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check that a string is valid UTF-8 (RFC 3629)
|
||||
|
||||
@param[in] s the string to check
|
||||
@param[in] context the value the string belongs to (for diagnostics)
|
||||
|
||||
@throw type_error.316 if @a s is not valid UTF-8; the message names the
|
||||
first byte of the first invalid or incomplete sequence
|
||||
*/
|
||||
static void check_bon8_utf8(const string_t& s, const BasicJsonType& context)
|
||||
{
|
||||
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
for (std::size_t i = 0; i < s.size();)
|
||||
{
|
||||
if (data[i] < 0x80)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::size_t length = validate_one_utf8(data + i, s.size() - i);
|
||||
if (JSON_HEDLEY_UNLIKELY(length == 0))
|
||||
{
|
||||
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(data[i])), &context));
|
||||
}
|
||||
i += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// @return a byte as two uppercase hexadecimal digits
|
||||
static std::string hex_byte(const std::uint8_t byte)
|
||||
{
|
||||
std::string result = "00";
|
||||
constexpr const char* nibble_to_hex = "0123456789ABCDEF";
|
||||
result[0] = nibble_to_hex[byte / 16];
|
||||
result[1] = nibble_to_hex[byte % 16];
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an integer in the shortest encoding
|
||||
|
||||
Integers from -10 to 39 take one byte. Up to -33818506 and 67637031, an
|
||||
integer takes 2 to 4 bytes that begin with a UTF-8 lead byte (0xC2..0xF7)
|
||||
followed by a byte that is not a continuation byte: 0x00..0x7F for
|
||||
positive and 0xC0..0xFF for negative integers. Each range starts where the
|
||||
shorter one ends. Larger integers are written as int32 (0x8C) or int64
|
||||
(0x8D) in big-endian byte order.
|
||||
|
||||
@param[in] value the integer to write
|
||||
*/
|
||||
void write_bon8_integer(std::int64_t value)
|
||||
{
|
||||
if (value < (std::numeric_limits<std::int32_t>::min)() || value > (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
oa.write_character(to_char_type(0x8D));
|
||||
write_number(value);
|
||||
}
|
||||
else if (value < -33818506 || value > 67637031)
|
||||
{
|
||||
oa.write_character(to_char_type(0x8C));
|
||||
write_number(static_cast<std::int32_t>(value));
|
||||
}
|
||||
else if (value <= -264075)
|
||||
{
|
||||
value = -(value + 264075);
|
||||
write_bon8_bytes(0xF0 + ((value >> 22) & 0x07), 0xC0 + ((value >> 16) & 0x3F), value >> 8, value);
|
||||
}
|
||||
else if (value <= -1931)
|
||||
{
|
||||
value = -(value + 1931);
|
||||
write_bon8_bytes(0xE0 + ((value >> 14) & 0x0F), 0xC0 + ((value >> 8) & 0x3F), value);
|
||||
}
|
||||
else if (value <= -11)
|
||||
{
|
||||
value = -(value + 11);
|
||||
write_bon8_bytes(0xC2 + ((value >> 6) & 0x1F), 0xC0 + (value & 0x3F));
|
||||
}
|
||||
else if (value <= -1)
|
||||
{
|
||||
write_bon8_bytes(0xB8 - (value + 1));
|
||||
}
|
||||
else if (value <= 39)
|
||||
{
|
||||
write_bon8_bytes(0x90 + value);
|
||||
}
|
||||
else if (value <= 3879)
|
||||
{
|
||||
value -= 40;
|
||||
write_bon8_bytes(0xC2 + ((value >> 7) & 0x1F), value & 0x7F);
|
||||
}
|
||||
else if (value <= 528167)
|
||||
{
|
||||
value -= 3880;
|
||||
write_bon8_bytes(0xE0 + ((value >> 15) & 0x0F), (value >> 8) & 0x7F, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
value -= 528168;
|
||||
write_bon8_bytes(0xF0 + ((value >> 23) & 0x07), (value >> 16) & 0x7F, value >> 8, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// write the low byte of each argument
|
||||
template<typename... Bytes>
|
||||
void write_bon8_bytes(const Bytes... bytes)
|
||||
{
|
||||
const std::array<CharType, sizeof...(Bytes)> buffer{{to_char_type(static_cast<std::uint8_t>(bytes & 0xFF))...}};
|
||||
oa.write_characters(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a floating-point number
|
||||
|
||||
-1.0, +0.0, and 1.0 take one byte. Other numbers are written as binary32
|
||||
(0x8E) if that loses no precision, and as binary64 (0x8F) otherwise; -0.0,
|
||||
infinities, and NaN are always written as binary32, NaN as 0x7F800001.
|
||||
|
||||
@param[in] n the number to write
|
||||
*/
|
||||
void write_bon8_float(const number_float_t n)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
#endif
|
||||
if (n == -1.0)
|
||||
{
|
||||
oa.write_character(to_char_type(0xFB));
|
||||
}
|
||||
else if (n == 0.0 && !std::signbit(n))
|
||||
{
|
||||
oa.write_character(to_char_type(0xFC));
|
||||
}
|
||||
else if (n == 1.0)
|
||||
{
|
||||
oa.write_character(to_char_type(0xFD));
|
||||
}
|
||||
else if (std::isnan(n))
|
||||
{
|
||||
write_bon8_bytes(0x8E, 0x7F, 0x80, 0x00, 0x01);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_compact_float(n, detail::input_format_t::bon8);
|
||||
}
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
#endif
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// Utility functions //
|
||||
///////////////////////
|
||||
@@ -2159,6 +2510,8 @@ class binary_writer
|
||||
{
|
||||
oa.write_character(format == detail::input_format_t::cbor
|
||||
? get_cbor_float_prefix(static_cast<float>(n))
|
||||
: format == detail::input_format_t::bon8
|
||||
? get_bon8_float_prefix(static_cast<float>(n))
|
||||
: get_msgpack_float_prefix(static_cast<float>(n)));
|
||||
write_number(static_cast<float>(n));
|
||||
}
|
||||
@@ -2166,6 +2519,8 @@ class binary_writer
|
||||
{
|
||||
oa.write_character(format == detail::input_format_t::cbor
|
||||
? get_cbor_float_prefix(n)
|
||||
: format == detail::input_format_t::bon8
|
||||
? get_bon8_float_prefix(n)
|
||||
: get_msgpack_float_prefix(n));
|
||||
write_number(n);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
@@ -133,7 +134,7 @@ class serializer
|
||||
|
||||
Serializing a container descends into its elements, so a value nested deeply
|
||||
enough used to exhaust the call stack and terminate the process with no
|
||||
exception to catch. The descent is bounded here: once @ref dump_depth_limit
|
||||
exception to catch. The descent is bounded here: once @ref recursion_depth_limit
|
||||
levels have been entered, @ref dump_iteratively writes out what is left
|
||||
without the call stack. A value nested less deeply than that - all but a
|
||||
vanishing minority - is written by exactly the code that always wrote it.
|
||||
@@ -148,7 +149,7 @@ class serializer
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -223,7 +224,7 @@ class serializer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -408,19 +409,12 @@ class serializer
|
||||
}
|
||||
|
||||
private:
|
||||
/// the number of levels @ref dump_internal descends into before it hands
|
||||
/// over to @ref dump_iteratively
|
||||
static constexpr std::size_t dump_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a val and everything below it without the call stack
|
||||
|
||||
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
||||
entered on an explicit stack instead of descending into them. Only reached
|
||||
for values nested deeper than @ref dump_depth_limit, which is why it is not
|
||||
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
||||
written for speed: walking every value this way measured up to 20% slower on
|
||||
object-heavy documents than letting the compiler drive the descent.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief the number of nesting levels an operation recurses into
|
||||
|
||||
Operations that walk a value (serializing, hashing, merging, ...) recurse once
|
||||
per nesting level, which is fastest, but a value nested deeply enough would
|
||||
exhaust the call stack. So they recurse only this many levels deep and finish
|
||||
whatever lies below with an explicit stack. All of them share this limit.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
constexpr std::size_t recursion_depth_limit() noexcept
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
+237
-3
@@ -68,6 +68,7 @@
|
||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/output/serializer.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <nlohmann/ordered_map.hpp>
|
||||
@@ -3912,17 +3913,54 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object));
|
||||
}
|
||||
|
||||
update_members(first, last, merge_objects, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief an object @ref update_members_iteratively or @ref
|
||||
/// merge_patch_iteratively is merging into, and the members still to merge
|
||||
struct merge_frame
|
||||
{
|
||||
merge_frame(basic_json* target_, const_iterator position_, const_iterator last_) noexcept
|
||||
: target(target_), position(std::move(position_)), last(std::move(last_))
|
||||
{}
|
||||
|
||||
basic_json* target;
|
||||
const_iterator position;
|
||||
const_iterator last;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief the members loop of @ref update, for this object and range
|
||||
|
||||
Merging a nested object calls this function again, once per nesting
|
||||
level, so a value nested deeply enough used to exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
update_members_iteratively merges what is left without the call stack.
|
||||
|
||||
@param[in] depth nesting level of this object, counted from the object
|
||||
@ref update was called on
|
||||
*/
|
||||
void update_members(const const_iterator& first, const const_iterator& last, const bool merge_objects, const std::size_t depth)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
update_members_iteratively(first, last);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = first; it != last; ++it)
|
||||
{
|
||||
if (merge_objects && it.value().is_object())
|
||||
{
|
||||
auto it2 = m_data.m_value.object->find(it.key());
|
||||
const auto it2 = m_data.m_value.object->find(it.key());
|
||||
// Only recurse when the existing value is itself an object.
|
||||
// Otherwise overwrite, matching the documented "all other values
|
||||
// are overwritten as usual" behavior (see #5402).
|
||||
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
it2->second.update(it.value(), true);
|
||||
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
|
||||
#if JSON_DIAGNOSTICS
|
||||
it2->second.set_parents();
|
||||
#endif
|
||||
@@ -3936,6 +3974,64 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief merge @a first to @a last into this object without the call stack
|
||||
|
||||
Does the same as @ref update_members with `merge_objects` set, keeping the
|
||||
objects whose merge was interrupted by a nested one on an explicit stack
|
||||
instead of descending into them. A nested object is still merged
|
||||
completely before the next member, in the same order as the recursive
|
||||
version. Only reached for values nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void update_members_iteratively(const_iterator first, const_iterator last)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
basic_json* target = this;
|
||||
while (true)
|
||||
{
|
||||
if (first == last)
|
||||
{
|
||||
if (stack.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// a nested object is merged: continue with its parent
|
||||
#if JSON_DIAGNOSTICS
|
||||
target->set_parents();
|
||||
#endif
|
||||
target = stack.back().target;
|
||||
first = stack.back().position;
|
||||
last = stack.back().last;
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first.value().is_object())
|
||||
{
|
||||
const auto it2 = target->m_data.m_value.object->find(first.key());
|
||||
if (it2 != target->m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
const basic_json& source = first.value();
|
||||
++first;
|
||||
stack.emplace_back(target, first, last);
|
||||
target = &it2->second;
|
||||
first = source.cbegin();
|
||||
last = source.cend();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
target->m_data.m_value.object->operator[](first.key()) = first.value();
|
||||
#if JSON_DIAGNOSTICS
|
||||
target->m_data.m_value.object->operator[](first.key()).m_parent = target;
|
||||
#endif
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief exchanges the values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/swap/
|
||||
void swap(reference other) noexcept (
|
||||
@@ -4879,6 +4975,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
binary_writer<char>(o).write_bson(j);
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
result.reserve(detail::binary_reserve_hint(j));
|
||||
vector_writer(result).write_bon8(j);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
|
||||
{
|
||||
binary_writer<std::uint8_t>(o).write_bon8(j);
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
||||
{
|
||||
binary_writer<char>(o).write_bon8(j);
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in CBOR format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
|
||||
template<typename InputType>
|
||||
@@ -5112,6 +5232,43 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in BON8 format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bon8/
|
||||
template<typename InputType>
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
static basic_json from_bon8(InputType&& i,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
{
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in BON8 format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bon8/
|
||||
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
static basic_json from_bon8(IteratorType first, SentinelType last,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
{
|
||||
basic_json result;
|
||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bon8).sax_parse(input_format_t::bon8, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||
{
|
||||
result = value_t::discarded;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in BSON format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
|
||||
template<typename InputType>
|
||||
@@ -5830,9 +5987,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @brief applies a JSON Merge Patch
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/merge_patch/
|
||||
void merge_patch(const basic_json& apply_patch)
|
||||
{
|
||||
apply_merge_patch(apply_patch, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief @ref merge_patch, for a patch at nesting level @a depth
|
||||
|
||||
Applying a nested object calls this function again, once per nesting
|
||||
level, so a patch nested deeply enough used to exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
merge_patch_iteratively applies what is left without the call stack.
|
||||
*/
|
||||
void apply_merge_patch(const basic_json& apply_patch, const std::size_t depth)
|
||||
{
|
||||
if (apply_patch.is_object())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
merge_patch_iteratively(apply_patch);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_object())
|
||||
{
|
||||
*this = object();
|
||||
@@ -5845,7 +6023,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
else
|
||||
{
|
||||
operator[](it.key()).merge_patch(it.value());
|
||||
operator[](it.key()).apply_merge_patch(it.value(), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5855,6 +6033,62 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief apply @a apply_patch to this value without the call stack
|
||||
|
||||
Does the same as @ref merge_patch, keeping the objects being patched on an
|
||||
explicit stack instead of descending into them. A nested object is still
|
||||
patched completely before the next member, in the same order as the
|
||||
recursive version. Only reached for patches nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void merge_patch_iteratively(const basic_json& apply_patch)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
// patch `target` with `patch`, or start patching it member by member
|
||||
const auto apply = [&stack](basic_json & target, const basic_json & patch)
|
||||
{
|
||||
if (patch.is_object())
|
||||
{
|
||||
if (!target.is_object())
|
||||
{
|
||||
target = basic_json::object();
|
||||
}
|
||||
stack.emplace_back(&target, patch.cbegin(), patch.cend());
|
||||
}
|
||||
else
|
||||
{
|
||||
target = patch;
|
||||
}
|
||||
};
|
||||
|
||||
apply(*this, apply_patch);
|
||||
while (!stack.empty())
|
||||
{
|
||||
// a copy, as applying a member below can reallocate the stack;
|
||||
// the frame itself is only changed through stack.back()
|
||||
const merge_frame frame = stack.back();
|
||||
if (frame.position == frame.last)
|
||||
{
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const const_iterator member = frame.position;
|
||||
++stack.back().position;
|
||||
if (member.value().is_null())
|
||||
{
|
||||
frame.target->erase(member.key());
|
||||
}
|
||||
else
|
||||
{
|
||||
apply(frame.target->operator[](member.key()), member.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
+1263
-20
File diff suppressed because it is too large
Load Diff
@@ -109,7 +109,7 @@ endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
# avoid stack overflow, see https://github.com/nlohmann/json/issues/2955
|
||||
json_test_set_test_options("test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
json_test_set_test_options("test-bon8;test-cbor;test-msgpack;test-ubjson;test-bjdata;test-binary_formats" LINK_OPTIONS /STACK:4000000)
|
||||
endif()
|
||||
|
||||
# disable exceptions for test-disabled_exceptions
|
||||
|
||||
+4
-1
@@ -10,7 +10,7 @@ CXXFLAGS += -std=c++11
|
||||
CPPFLAGS += -I ../single_include
|
||||
|
||||
FUZZER_ENGINE = src/fuzzer-driver_afl.cpp
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer parse_bon8_fuzzer
|
||||
fuzzers: $(FUZZERS)
|
||||
|
||||
parse_afl_fuzzer:
|
||||
@@ -30,3 +30,6 @@ parse_ubjson_fuzzer:
|
||||
|
||||
parse_bjdata_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bjdata.cpp -o $@
|
||||
|
||||
parse_bon8_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bon8.cpp -o $@
|
||||
|
||||
@@ -273,7 +273,8 @@ enum class binary_format
|
||||
ubjson_optimized,
|
||||
bjdata,
|
||||
bjdata_optimized,
|
||||
bson
|
||||
bson,
|
||||
bon8
|
||||
};
|
||||
|
||||
static std::vector<std::uint8_t> to_binary(const json& j, const binary_format format)
|
||||
@@ -292,6 +293,8 @@ static std::vector<std::uint8_t> to_binary(const json& j, const binary_format fo
|
||||
return json::to_bjdata(j);
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::to_bjdata(j, true, true);
|
||||
case binary_format::bon8:
|
||||
return json::to_bon8(j);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::to_bson(j);
|
||||
@@ -312,6 +315,8 @@ static json from_binary(const std::vector<std::uint8_t>& bytes, const binary_for
|
||||
case binary_format::bjdata:
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::from_bjdata(bytes);
|
||||
case binary_format::bon8:
|
||||
return json::from_bon8(bytes);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::from_bson(bytes);
|
||||
@@ -332,6 +337,8 @@ static json from_binary(std::FILE* file, const binary_format format)
|
||||
case binary_format::bjdata:
|
||||
case binary_format::bjdata_optimized:
|
||||
return json::from_bjdata(file);
|
||||
case binary_format::bon8:
|
||||
return json::from_bon8(file);
|
||||
case binary_format::bson:
|
||||
default:
|
||||
return json::from_bson(file);
|
||||
@@ -406,6 +413,10 @@ BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / canada, TEST_DATA_DIRECTORY "/nativ
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8);
|
||||
// BSON requires an object at the top level, so the array-rooted test files
|
||||
// (jeopardy and the regression files) cannot be captured here
|
||||
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bson);
|
||||
@@ -449,6 +460,8 @@ BENCHMARK_CAPTURE(FromBinaryFile, cbor / twitter, TEST_DATA_DIRECTORY "/nativejs
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bon8 / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bon8 / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryFile, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -529,18 +542,21 @@ BENCHMARK_CAPTURE(FromBinaryShape, nested / msgpack, make_nested, binary_format:
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / ubjson, make_nested, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bjdata, make_nested, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bson, make_nested, binary_format::bson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, nested / bon8, make_nested, binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / cbor, make_containers, binary_format::cbor);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / msgpack, make_containers, binary_format::msgpack);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson, make_containers, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_containers, binary_format::ubjson_optimized);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bon8, make_containers, binary_format::bon8);
|
||||
// BSON names every array element, so a large array measures key generation
|
||||
// rather than scalar decoding and is left out here
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / msgpack, make_scalars, binary_format::msgpack);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / ubjson, make_scalars, binary_format::ubjson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bjdata, make_scalars, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bon8, make_scalars, binary_format::bon8);
|
||||
|
||||
/*!
|
||||
@brief parse an indefinite-length CBOR string
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# Fuzz testing
|
||||
|
||||
Each parser of the library (JSON, BJData, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently,
|
||||
Each parser of the library (JSON, BJData, BON8, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently,
|
||||
[libFuzzer](https://llvm.org/docs/LibFuzzer.html) and [afl++](https://github.com/AFLplusplus/AFLplusplus) are supported.
|
||||
|
||||
## Corpus creation
|
||||
@@ -10,11 +10,11 @@ directory with some simple input files that cover several features of the parser
|
||||
for mutations.
|
||||
|
||||
```shell
|
||||
TEST_DATA_VERSION=3.1.0
|
||||
TEST_DATA_VERSION=3.2.0
|
||||
wget https://github.com/nlohmann/json_test_data/archive/refs/tags/v$TEST_DATA_VERSION.zip
|
||||
unzip v$TEST_DATA_VERSION.zip
|
||||
rm v$TEST_DATA_VERSION.zip
|
||||
for FORMAT in json bjdata bson cbor msgpack ubjson
|
||||
for FORMAT in json bjdata bon8 bson cbor msgpack ubjson
|
||||
do
|
||||
rm -fr corpus_$FORMAT
|
||||
mkdir corpus_$FORMAT
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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 implements a parser test suitable for fuzz testing. Given a byte
|
||||
array data, it performs the following steps:
|
||||
|
||||
- j1 = from_bon8(data)
|
||||
- vec = to_bon8(j1)
|
||||
- j2 = from_bon8(vec)
|
||||
- assert(j1 == j2)
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
// step 1: parse input
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
json const j1 = json::from_bon8(vec1);
|
||||
|
||||
try
|
||||
{
|
||||
// step 2: round trip
|
||||
std::vector<uint8_t> const vec2 = json::to_bon8(j1);
|
||||
|
||||
// parse serialization
|
||||
json const j2 = json::from_bon8(vec2);
|
||||
|
||||
// serializations must match
|
||||
assert(json::to_bon8(j2) == vec2);
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parsing a BON8 serialization must not fail
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
// parse errors are ok, because input may be random bytes
|
||||
}
|
||||
catch (const json::type_error&)
|
||||
{
|
||||
// type errors can occur during parsing, too
|
||||
}
|
||||
catch (const json::out_of_range&)
|
||||
{
|
||||
// out of range errors may happen if provided sizes are excessive
|
||||
}
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -185,6 +185,7 @@ TEST_CASE("alternative string type")
|
||||
|
||||
CHECK(alt_json::from_cbor(alt_json::to_cbor(doc)) == doc);
|
||||
CHECK(alt_json::from_msgpack(alt_json::to_msgpack(doc)) == doc);
|
||||
CHECK(alt_json::from_bon8(alt_json::to_bon8(doc)) == doc);
|
||||
// BSON is not covered: it additionally needs string_t::find(value_type),
|
||||
// which alt_string does not provide
|
||||
CHECK(alt_json::from_ubjson(alt_json::to_ubjson(doc)) == doc);
|
||||
|
||||
@@ -25,6 +25,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
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();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -36,6 +37,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 1112030);
|
||||
CHECK(bjdata_2_size == 1224148);
|
||||
CHECK(bjdata_3_size == 1224148);
|
||||
CHECK(bon8_size == 1055792);
|
||||
CHECK(bson_size == 1794522);
|
||||
CHECK(cbor_size == 1055552);
|
||||
CHECK(msgpack_size == 1056145);
|
||||
@@ -47,6 +49,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.199));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.563));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.563));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(50.509));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.849));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.497));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.526));
|
||||
@@ -64,6 +67,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
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();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -75,6 +79,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 425342);
|
||||
CHECK(bjdata_2_size == 429970);
|
||||
CHECK(bjdata_3_size == 429970);
|
||||
CHECK(bon8_size == 391396);
|
||||
CHECK(bson_size == 444568);
|
||||
CHECK(cbor_size == 402814);
|
||||
CHECK(msgpack_size == 401510);
|
||||
@@ -86,6 +91,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(91.097));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(92.089));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(92.089));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(83.828));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.215));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(86.273));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(85.993));
|
||||
@@ -103,6 +109,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
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();
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -114,6 +121,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 390781);
|
||||
CHECK(bjdata_2_size == 433557);
|
||||
CHECK(bjdata_3_size == 432964);
|
||||
CHECK(bon8_size == 317879);
|
||||
CHECK(bson_size == 479430);
|
||||
CHECK(cbor_size == 342373);
|
||||
CHECK(msgpack_size == 342473);
|
||||
@@ -125,6 +133,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(78.109));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(86.659));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(86.541));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(63.538));
|
||||
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(95.828));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(68.433));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(68.453));
|
||||
@@ -142,6 +151,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
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();
|
||||
@@ -153,6 +163,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
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);
|
||||
@@ -164,6 +175,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
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));
|
||||
@@ -181,6 +193,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
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();
|
||||
// BSON cannot process the file as it contains code point U+0000
|
||||
const auto cbor_size = json::to_cbor(j).size();
|
||||
const auto msgpack_size = json::to_msgpack(j).size();
|
||||
@@ -192,6 +205,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK(bjdata_1_size == 148695);
|
||||
CHECK(bjdata_2_size == 150569);
|
||||
CHECK(bjdata_3_size == 150569);
|
||||
CHECK(bon8_size == 144477);
|
||||
CHECK(cbor_size == 147095);
|
||||
CHECK(msgpack_size == 147017);
|
||||
CHECK(ubjson_1_size == 148695);
|
||||
@@ -202,6 +216,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
|
||||
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(88.153));
|
||||
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(89.264));
|
||||
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(89.264));
|
||||
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(85.653));
|
||||
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(87.205));
|
||||
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(87.158));
|
||||
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(88.153));
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -49,6 +50,12 @@ std::vector<json> test_values()
|
||||
};
|
||||
}
|
||||
|
||||
// BON8 has no integers above the int64 range, so to_bon8() rejects them
|
||||
bool bon8_representable(const json& j)
|
||||
{
|
||||
return !j.is_number_unsigned() || j.get<std::uint64_t>() <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)());
|
||||
}
|
||||
|
||||
// values to_bson() accepts: the document must be an object
|
||||
std::vector<json> bson_values()
|
||||
{
|
||||
@@ -92,6 +99,13 @@ TEST_CASE("binary writer output sinks")
|
||||
json::to_msgpack(j, msgpack);
|
||||
CHECK(json::to_msgpack(j) == msgpack);
|
||||
|
||||
if (bon8_representable(j))
|
||||
{
|
||||
std::vector<std::uint8_t> bon8;
|
||||
json::to_bon8(j, bon8);
|
||||
CHECK(json::to_bon8(j) == bon8);
|
||||
}
|
||||
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
@@ -172,6 +186,10 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
CHECK(hint <= json::to_ubjson(j).size());
|
||||
CHECK(hint <= json::to_ubjson(j, true, true).size());
|
||||
CHECK(hint <= json::to_bjdata(j).size());
|
||||
if (bon8_representable(j))
|
||||
{
|
||||
CHECK(hint <= json::to_bon8(j).size());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& j : bson_values())
|
||||
|
||||
@@ -0,0 +1,976 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
#ifdef JSON_TEST_NO_GLOBAL_UDLS
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class SaxCountdown
|
||||
{
|
||||
public:
|
||||
explicit SaxCountdown(const int count) : events_left(count)
|
||||
{}
|
||||
|
||||
bool null()
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool boolean(bool /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_integer(json::number_integer_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_unsigned(json::number_unsigned_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool number_float(json::number_float_t /*unused*/, const std::string& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool string(std::string& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool binary(std::vector<std::uint8_t>& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool start_object(std::size_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool key(std::string& /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool end_object()
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool start_array(std::size_t /*unused*/)
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool end_array()
|
||||
{
|
||||
return events_left-- > 0;
|
||||
}
|
||||
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& /*unused*/) // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
int events_left = 0;
|
||||
};
|
||||
|
||||
using bytes = std::vector<std::uint8_t>;
|
||||
|
||||
/// check that @a j is serialized to @a expected and that @a expected is read back as @a j
|
||||
void check_bon8(const json& j, const bytes& expected)
|
||||
{
|
||||
CAPTURE(j)
|
||||
CHECK(json::to_bon8(j) == expected);
|
||||
|
||||
const json read = json::from_bon8(expected);
|
||||
CHECK(read == j);
|
||||
// integers are not read back as floats and vice versa
|
||||
CHECK(read.type() == j.type());
|
||||
}
|
||||
|
||||
/// @return the string with the given bytes
|
||||
std::string str(const bytes& b)
|
||||
{
|
||||
return {b.begin(), b.end()};
|
||||
}
|
||||
|
||||
/// @return @a b followed by @a tail
|
||||
bytes concat(bytes b, const bytes& tail)
|
||||
{
|
||||
b.insert(b.end(), tail.begin(), tail.end());
|
||||
return b;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("BON8")
|
||||
{
|
||||
SECTION("individual values")
|
||||
{
|
||||
SECTION("discarded")
|
||||
{
|
||||
// discarded values are not serialized
|
||||
const json j = json::value_t::discarded;
|
||||
CHECK(json::to_bon8(j).empty());
|
||||
}
|
||||
|
||||
SECTION("null")
|
||||
{
|
||||
check_bon8(nullptr, {0xFA});
|
||||
}
|
||||
|
||||
SECTION("boolean")
|
||||
{
|
||||
check_bon8(true, {0xF9});
|
||||
check_bon8(false, {0xF8});
|
||||
}
|
||||
|
||||
SECTION("integers")
|
||||
{
|
||||
// test vectors from HikoGUI (src/hikogui/codec/BON8_tests.cpp,
|
||||
// Copyright Take Vos 2021, Boost Software License 1.0), which
|
||||
// cover the first and last value of each encoding
|
||||
SECTION("positive")
|
||||
{
|
||||
check_bon8(0u, {0x90});
|
||||
check_bon8(39u, {0xB7});
|
||||
check_bon8(40u, {0xC2, 0x00});
|
||||
check_bon8(3879u, {0xDF, 0x7F});
|
||||
check_bon8(3880u, {0xE0, 0x00, 0x00});
|
||||
check_bon8(528167u, {0xEF, 0x7F, 0xFF});
|
||||
check_bon8(528168u, {0xF0, 0x00, 0x00, 0x00});
|
||||
check_bon8(67637031u, {0xF7, 0x7F, 0xFF, 0xFF});
|
||||
check_bon8(67637032u, {0x8C, 0x04, 0x08, 0x0F, 0x28});
|
||||
check_bon8(2147483647u, {0x8C, 0x7F, 0xFF, 0xFF, 0xFF});
|
||||
check_bon8(2147483648u, {0x8D, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8(9223372036854775807u, {0x8D, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("negative")
|
||||
{
|
||||
check_bon8(-1, {0xB8});
|
||||
check_bon8(-10, {0xC1});
|
||||
check_bon8(-11, {0xC2, 0xC0});
|
||||
check_bon8(-1930, {0xDF, 0xFF});
|
||||
check_bon8(-1931, {0xE0, 0xC0, 0x00});
|
||||
check_bon8(-264074, {0xEF, 0xFF, 0xFF});
|
||||
check_bon8(-264075, {0xF0, 0xC0, 0x00, 0x00});
|
||||
check_bon8(-33818506, {0xF7, 0xFF, 0xFF, 0xFF});
|
||||
check_bon8(-33818507, {0x8C, 0xFD, 0xFB, 0xF8, 0x75});
|
||||
check_bon8(-2147483648, {0x8C, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8(-2147483649, {0x8D, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF});
|
||||
check_bon8((std::numeric_limits<std::int64_t>::min)(), {0x8D, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
}
|
||||
|
||||
SECTION("values inside the ranges")
|
||||
{
|
||||
check_bon8(1u, {0x91});
|
||||
check_bon8(100u, {0xC2, 0x3C});
|
||||
check_bon8(1000u, {0xC9, 0x40});
|
||||
check_bon8(100000u, {0xE2, 0x77, 0x78});
|
||||
check_bon8(1000000u, {0xF0, 0x07, 0x33, 0x18});
|
||||
check_bon8(-9, {0xC0});
|
||||
check_bon8(-100, {0xC3, 0xD9});
|
||||
check_bon8(-100000, {0xE5, 0xFF, 0x15});
|
||||
check_bon8(-1000000, {0xF0, 0xCB, 0x3A, 0xB5});
|
||||
}
|
||||
|
||||
SECTION("every integer from -300000 to 600000")
|
||||
{
|
||||
for (std::int64_t i = -300000; i <= 600000; ++i)
|
||||
{
|
||||
const json j = i;
|
||||
const auto packed = json::to_bon8(j);
|
||||
CHECK(packed.size() == (i >= -10 && i <= 39 ? 1u : i >= -1930 && i <= 3879 ? 2u : i >= -264074 && i <= 528167 ? 3u : 4u));
|
||||
const json read = json::from_bon8(packed);
|
||||
if (read != j)
|
||||
{
|
||||
CAPTURE(i)
|
||||
CHECK(read == j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("signed values are read back as unsigned when not negative")
|
||||
{
|
||||
const json j = json::from_bon8(json::to_bon8(json(std::int64_t(1000))));
|
||||
CHECK(j.is_number_unsigned());
|
||||
CHECK(j == 1000);
|
||||
}
|
||||
|
||||
SECTION("unsigned integers above int64")
|
||||
{
|
||||
json _;
|
||||
const json j = 9223372036854775808u;
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(j), "[json.exception.out_of_range.407] integer number 9223372036854775808 cannot be represented by BON8 as it does not fit int64", json::out_of_range&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("floating-point numbers")
|
||||
{
|
||||
SECTION("one-byte values")
|
||||
{
|
||||
check_bon8(-1.0, {0xFB});
|
||||
check_bon8(0.0, {0xFC});
|
||||
check_bon8(1.0, {0xFD});
|
||||
}
|
||||
|
||||
SECTION("binary32")
|
||||
{
|
||||
check_bon8(2.0, {0x8E, 0x40, 0x00, 0x00, 0x00});
|
||||
check_bon8(0.5, {0x8E, 0x3F, 0x00, 0x00, 0x00});
|
||||
check_bon8(-1.5, {0x8E, 0xBF, 0xC0, 0x00, 0x00});
|
||||
check_bon8(3.4028234663852886e38, {0x8E, 0x7F, 0x7F, 0xFF, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("binary64")
|
||||
{
|
||||
check_bon8(100000000.1, {0x8F, 0x41, 0x97, 0xD7, 0x84, 0x00, 0x66, 0x66, 0x66});
|
||||
check_bon8(3.14159, {0x8F, 0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E});
|
||||
check_bon8(1e300, {0x8F, 0x7E, 0x37, 0xE4, 0x3C, 0x88, 0x00, 0x75, 0x9C});
|
||||
}
|
||||
|
||||
SECTION("-0.0 is written as binary32")
|
||||
{
|
||||
const json j = -0.0;
|
||||
CHECK(json::to_bon8(j) == bytes{0x8E, 0x80, 0x00, 0x00, 0x00});
|
||||
const json read = json::from_bon8(json::to_bon8(j));
|
||||
CHECK(read.get<double>() == 0.0);
|
||||
CHECK(std::signbit(read.get<double>()));
|
||||
}
|
||||
|
||||
SECTION("infinity is written as binary32")
|
||||
{
|
||||
check_bon8(std::numeric_limits<double>::infinity(), {0x8E, 0x7F, 0x80, 0x00, 0x00});
|
||||
check_bon8(-std::numeric_limits<double>::infinity(), {0x8E, 0xFF, 0x80, 0x00, 0x00});
|
||||
}
|
||||
|
||||
SECTION("NaN is written as binary32 0x7F800001")
|
||||
{
|
||||
const json j = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK(json::to_bon8(j) == bytes{0x8E, 0x7F, 0x80, 0x00, 0x01});
|
||||
CHECK(std::isnan(json::from_bon8(json::to_bon8(j)).get<double>()));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("strings")
|
||||
{
|
||||
SECTION("empty string")
|
||||
{
|
||||
check_bon8("", {0xFF});
|
||||
}
|
||||
|
||||
SECTION("ASCII")
|
||||
{
|
||||
check_bon8("a", {'a', 0xFF});
|
||||
check_bon8("This is a string.", concat(bytes{'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 's', 't', 'r', 'i', 'n', 'g', '.'}, {0xFF}));
|
||||
check_bon8(str({0x00}), {0x00, 0xFF});
|
||||
check_bon8(str({0x7F}), {0x7F, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("multi-byte UTF-8")
|
||||
{
|
||||
check_bon8("\xC2\xA3", {0xC2, 0xA3, 0xFF}); // U+00A3
|
||||
check_bon8("\xEF\xB8\xBB", {0xEF, 0xB8, 0xBB, 0xFF}); // U+FE3B
|
||||
check_bon8("\xF0\x9F\x80\x84", {0xF0, 0x9F, 0x80, 0x84, 0xFF}); // U+1F004
|
||||
check_bon8("\xC2\x80", {0xC2, 0x80, 0xFF}); // U+0080
|
||||
check_bon8("\xDF\xBF", {0xDF, 0xBF, 0xFF}); // U+07FF
|
||||
check_bon8("\xE0\xA0\x80", {0xE0, 0xA0, 0x80, 0xFF}); // U+0800
|
||||
check_bon8("\xED\x9F\xBF", {0xED, 0x9F, 0xBF, 0xFF}); // U+D7FF
|
||||
check_bon8("\xEE\x80\x80", {0xEE, 0x80, 0x80, 0xFF}); // U+E000
|
||||
check_bon8("\xF0\x90\x80\x80", {0xF0, 0x90, 0x80, 0x80, 0xFF}); // U+10000
|
||||
check_bon8("\xF4\x8F\xBF\xBF", {0xF4, 0x8F, 0xBF, 0xBF, 0xFF}); // U+10FFFF
|
||||
check_bon8("a\xC2\xA3" "b", {'a', 0xC2, 0xA3, 'b', 0xFF});
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8 cannot be written")
|
||||
{
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0x80", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({'a', 0xC0, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xC0", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xE0, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xE0", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xED, 0xA0, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xF4, 0x90, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xF4", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xF5, 0x80, 0x80, 0x80})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xF5", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({0xC2, 'a'})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC2", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(str({'a', 0xE2, 0x82})), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xE2", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::to_bon8(json::object({{str({0xFF}), 1}})), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("arrays")
|
||||
{
|
||||
check_bon8(json::array(), {0x80});
|
||||
check_bon8({false}, {0x81, 0xF8});
|
||||
check_bon8({false, nullptr}, {0x82, 0xF8, 0xFA});
|
||||
check_bon8({false, nullptr, true}, {0x83, 0xF8, 0xFA, 0xF9});
|
||||
check_bon8({false, nullptr, true, 1.0}, {0x84, 0xF8, 0xFA, 0xF9, 0xFD});
|
||||
check_bon8({false, nullptr, true, 1.0, json::array(), 0.0}, {0x85, 0xF8, 0xFA, 0xF9, 0xFD, 0x80, 0xFC, 0xFE});
|
||||
check_bon8({{{1}}}, {0x81, 0x81, 0x81, 0x91});
|
||||
check_bon8({{{"foo"}}}, {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF});
|
||||
check_bon8({{{""}}}, {0x81, 0x81, 0x81, 0xFF});
|
||||
|
||||
SECTION("large array")
|
||||
{
|
||||
json j = json::array();
|
||||
bytes expected = {0x85};
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
j.push_back(nullptr);
|
||||
expected.push_back(0xFA);
|
||||
}
|
||||
expected.push_back(0xFE);
|
||||
check_bon8(j, expected);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("objects")
|
||||
{
|
||||
check_bon8(json::object(), {0x86});
|
||||
check_bon8({{"foo", nullptr}}, {0x87, 'f', 'o', 'o', 0xFA});
|
||||
check_bon8({{"", true}, {"foo", nullptr}}, {0x88, 0xFF, 0xF9, 'f', 'o', 'o', 0xFA});
|
||||
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}}, {0x89, 'a', 0x91, 'b', 0x92, 'c', 0x93});
|
||||
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}, {0x8A, 'a', 0x91, 'b', 0x92, 'c', 0x93, 'd', 0x94});
|
||||
const json five = {{"one", 1}, {"two", 2}, {"three", 3}, {"four", 4}, {"five", 5}};
|
||||
check_bon8(five, {0x8B, 'f', 'i', 'v', 'e', 0x95, 'f', 'o', 'u', 'r', 0x94, 'o', 'n', 'e', 0x91, 't', 'h', 'r', 'e', 'e', 0x93, 't', 'w', 'o', 0x92, 0xFE});
|
||||
}
|
||||
|
||||
SECTION("binary values are written as arrays of integers")
|
||||
{
|
||||
CHECK(json::to_bon8(json::binary({})) == bytes{0x80});
|
||||
CHECK(json::to_bon8(json::binary({0x00, 0x27, 0x28, 0xFF})) == bytes{0x84, 0x90, 0xB7, 0xC2, 0x00, 0xC3, 0x57});
|
||||
CHECK(json::to_bon8(json::binary({1, 2, 3, 4, 5}, 42)) == bytes{0x85, 0x91, 0x92, 0x93, 0x94, 0x95, 0xFE});
|
||||
CHECK(json::to_bon8({"a", json::binary({1})}) == bytes{0x82, 'a', 0x81, 0x91});
|
||||
CHECK(json::from_bon8(json::to_bon8(json::binary({1, 2, 3, 4, 5}))) == json({1, 2, 3, 4, 5}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("examples from the specification")
|
||||
{
|
||||
check_bon8("ab", {'a', 'b', 0xFF});
|
||||
check_bon8({"ab", "bc"}, {0x82, 'a', 'b', 0xFF, 'b', 'c', 0xFF});
|
||||
check_bon8({"a", "b", "c", "d", "e"}, {0x85, 'a', 0xFF, 'b', 0xFF, 'c', 0xFF, 'd', 0xFF, 'e', 0xFE});
|
||||
check_bon8({{"ab", 1}, {"bc", 2}}, {0x88, 'a', 'b', 0x91, 'b', 'c', 0x92});
|
||||
// the specification prints this example without the end-of-string
|
||||
// markers after "b" and "c", which its own rules require: without
|
||||
// them, the bytes read as the single string "bcd"
|
||||
check_bon8({{"a", {"b", "c"}}, {"d", 1}}, {0x88, 'a', 0x82, 'b', 0xFF, 'c', 0xFF, 'd', 0x91});
|
||||
check_bon8({{"", 1}, {"a", 2}}, {0x88, 0xFF, 0x91, 'a', 0x92});
|
||||
}
|
||||
|
||||
SECTION("examples from the discussion of the specification (#2980)")
|
||||
{
|
||||
check_bon8({{"a", ""}, {"c", "d"}}, {0x88, 'a', 0xFF, 0xFF, 'c', 0xFF, 'd', 0xFF});
|
||||
}
|
||||
|
||||
SECTION("end of strings")
|
||||
{
|
||||
SECTION("a string ends at the first byte of an integer")
|
||||
{
|
||||
check_bon8({{"a", 100}}, {0x87, 'a', 0xC2, 0x3C});
|
||||
check_bon8({{"a", -9}}, {0x87, 'a', 0xC0});
|
||||
check_bon8({{"a", -10}}, {0x87, 'a', 0xC1});
|
||||
check_bon8({{"a", 5}}, {0x87, 'a', 0x95});
|
||||
check_bon8({{"a", 100000}}, {0x87, 'a', 0xE2, 0x77, 0x78});
|
||||
check_bon8({{"a", -1000000}}, {0x87, 'a', 0xF0, 0xCB, 0x3A, 0xB5});
|
||||
check_bon8({{"a", 67637032}}, {0x87, 'a', 0x8C, 0x04, 0x08, 0x0F, 0x28});
|
||||
check_bon8({{"a", 2147483648}}, {0x87, 'a', 0x8D, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8({"\xC2\xA3", 100}, {0x82, 0xC2, 0xA3, 0xC2, 0x3C});
|
||||
check_bon8({"\xF0\x9F\x80\x84", -1000000}, {0x82, 0xF0, 0x9F, 0x80, 0x84, 0xF0, 0xCB, 0x3A, 0xB5});
|
||||
}
|
||||
|
||||
SECTION("a string ends at the first byte of other values")
|
||||
{
|
||||
check_bon8({"a", 2.0}, {0x82, 'a', 0x8E, 0x40, 0x00, 0x00, 0x00});
|
||||
check_bon8({"a", 0.1}, {0x82, 'a', 0x8F, 0x3F, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A});
|
||||
check_bon8({"a", nullptr, "b", true, "c", false}, {0x85, 'a', 0xFA, 'b', 0xF9, 'c', 0xF8, 0xFE});
|
||||
check_bon8({"a", -1.0, "b", 0.0, "c", 1.0}, {0x85, 'a', 0xFB, 'b', 0xFC, 'c', 0xFD, 0xFE});
|
||||
check_bon8({"a", json::array()}, {0x82, 'a', 0x80});
|
||||
check_bon8({"a", json::object()}, {0x82, 'a', 0x86});
|
||||
check_bon8({{"a", {1, 2, 3, 4, 5}}}, {0x87, 'a', 0x85, 0x91, 0x92, 0x93, 0x94, 0x95, 0xFE});
|
||||
check_bon8({{"a", {{"b", 1}, {"c", 2}, {"d", 3}, {"e", 4}, {"f", 5}}}}, {0x87, 'a', 0x8B, 'b', 0x91, 'c', 0x92, 'd', 0x93, 'e', 0x94, 'f', 0x95, 0xFE});
|
||||
}
|
||||
|
||||
SECTION("a string ends at the end of its container")
|
||||
{
|
||||
check_bon8({1, 2, 3, 4, "e"}, {0x85, 0x91, 0x92, 0x93, 0x94, 'e', 0xFE});
|
||||
check_bon8({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", "f"}}, {0x8B, 'a', 0x91, 'b', 0x92, 'c', 0x93, 'd', 0x94, 'e', 0xFF, 'f', 0xFE});
|
||||
check_bon8({{1, 2, 3, 4, "e"}, 1}, {0x82, 0x85, 0x91, 0x92, 0x93, 0x94, 'e', 0xFE, 0x91});
|
||||
check_bon8({{1, 2, 3, 4, "\xC2\xA3"}, 100}, {0x82, 0x85, 0x91, 0x92, 0x93, 0x94, 0xC2, 0xA3, 0xFE, 0xC2, 0x3C});
|
||||
}
|
||||
|
||||
SECTION("a string that another string follows is terminated")
|
||||
{
|
||||
check_bon8({"s", "s"}, {0x82, 's', 0xFF, 's', 0xFF});
|
||||
check_bon8({"", "s"}, {0x82, 0xFF, 's', 0xFF});
|
||||
check_bon8({"s", ""}, {0x82, 's', 0xFF, 0xFF});
|
||||
check_bon8({"", ""}, {0x82, 0xFF, 0xFF});
|
||||
check_bon8({{"a", "b"}, {"c", "d"}}, {0x88, 'a', 0xFF, 'b', 0xFF, 'c', 0xFF, 'd', 0xFF});
|
||||
check_bon8({{"a", ""}}, {0x87, 'a', 0xFF, 0xFF});
|
||||
check_bon8({{"", ""}}, {0x87, 0xFF, 0xFF});
|
||||
}
|
||||
|
||||
SECTION("a container that ends with a string and that a string follows")
|
||||
{
|
||||
check_bon8({{"a"}, "b"}, {0x82, 0x81, 'a', 0xFF, 'b', 0xFF});
|
||||
check_bon8({{{{"a"}}}, "b"}, {0x82, 0x81, 0x81, 0x81, 'a', 0xFF, 'b', 0xFF});
|
||||
check_bon8({{"a", {"x"}}, {"b", 1}}, {0x88, 'a', 0x81, 'x', 0xFF, 'b', 0x91});
|
||||
check_bon8({{"a", {{"b", "c"}}}, {"d", 1}}, {0x88, 'a', 0x87, 'b', 0xFF, 'c', 0xFF, 'd', 0x91});
|
||||
check_bon8({{"a"}, {"b"}}, {0x82, 0x81, 'a', 0x81, 'b', 0xFF});
|
||||
}
|
||||
|
||||
SECTION("a string that ends the message is terminated")
|
||||
{
|
||||
check_bon8({"a"}, {0x81, 'a', 0xFF});
|
||||
check_bon8({{"a", "b"}}, {0x87, 'a', 0xFF, 'b', 0xFF});
|
||||
check_bon8({{{"a"}}}, {0x81, 0x81, 0x81, 'a', 0xFF});
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("non-canonical input is accepted")
|
||||
{
|
||||
// an integer with a longer encoding than necessary
|
||||
CHECK(json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00, 0x01}) == 1);
|
||||
CHECK(json::from_bon8(bytes{0x8D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) == -1);
|
||||
// a float with a longer encoding than necessary
|
||||
CHECK(json::from_bon8(bytes{0x8F, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) == 1.0);
|
||||
CHECK(json::from_bon8(bytes{0x8E, 0x00, 0x00, 0x00, 0x00}) == 0.0);
|
||||
// an unsized container with few elements
|
||||
CHECK(json::from_bon8(bytes{0x85, 0x91, 0x92, 0xFE}) == json({1, 2}));
|
||||
CHECK(json::from_bon8(bytes{0x85, 0xFE}) == json::array());
|
||||
CHECK(json::from_bon8(bytes{0x8B, 'a', 0x91, 0xFE}) == json({{"a", 1}}));
|
||||
// unsorted keys
|
||||
CHECK(json::from_bon8(bytes{0x88, 'b', 0x91, 'a', 0x92}) == json({{"a", 2}, {"b", 1}}));
|
||||
// an end-of-string marker that is not needed
|
||||
CHECK(json::from_bon8(bytes{0x82, 'a', 0xFF, 0x91}) == json({"a", 1}));
|
||||
CHECK(json::from_bon8(bytes{0x87, 'a', 0xFF, 0x91}) == json({{"a", 1}}));
|
||||
}
|
||||
|
||||
SECTION("types of values read")
|
||||
{
|
||||
CHECK(json::from_bon8(bytes{0x90}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0xC2, 0x00}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00, 0x00}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0xB8}).is_number_integer());
|
||||
CHECK(!json::from_bon8(bytes{0xB8}).is_number_unsigned());
|
||||
CHECK(json::from_bon8(bytes{0xC2, 0xC0}).is_number_integer());
|
||||
CHECK(json::from_bon8(bytes{0xFC}).is_number_float());
|
||||
CHECK(json::from_bon8(bytes{0x8E, 0x40, 0x00, 0x00, 0x00}).is_number_float());
|
||||
CHECK(json::from_bon8(bytes{0xFF}).is_string());
|
||||
}
|
||||
|
||||
SECTION("errors")
|
||||
{
|
||||
json _;
|
||||
|
||||
SECTION("empty input")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes()), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bon8(bytes(), true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("too short numbers")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8C}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8C, 0x00, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8E, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8F, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xC2}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE0, 0x00}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x00, 0x00}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x82, 'a', 0xF0, 0xC0}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 number: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("unterminated strings")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{'a'}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 'b'}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x9F, 0x80, 0x84}), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x9F, 0x80}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("invalid UTF-8")
|
||||
{
|
||||
// overlong
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE0, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF0, 0x80, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
||||
// surrogate
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xED, 0xA0, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0xA0", json::parse_error&);
|
||||
// above U+10FFFF
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF4, 0x90, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x90", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xF5, 0x80, 0x80, 0x80, 0xFF}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80", json::parse_error&);
|
||||
// missing continuation byte
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xE2, 0x82, 'a', 0xFF}), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x61", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 'a', 0xE2, 0x82, 'b'}), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x62", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("end-of-container marker where a value is expected")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0xFE}), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x81, 0xFE}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8B, 'a', 0xFE}), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BON8 value: invalid byte: 0xFE", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("keys that are not strings")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0x91, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0x91", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xFA, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xFA", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0x80, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0x80", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xFE}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xFE", json::parse_error&);
|
||||
// an integer that begins with a UTF-8 lead byte
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 0xC2, 0x00, 0x91}), "[json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 key: expected a string; last byte: 0xC2", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x88, 'a', 0x91, 0xF0, 0xC0, 0x00, 0x00, 0x91}), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BON8 key: expected a string; last byte: 0xF0", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("unterminated containers")
|
||||
{
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x82, 0x91}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x85, 0x91}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x8B, 'a', 0x91}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 key: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a'}), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 string: unexpected end of input", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(bytes{0x87, 'a', 0xFF}), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("strict mode")
|
||||
{
|
||||
const bytes vec = {0x90, 0x90};
|
||||
CHECK(json::from_bon8(vec, false) == 0);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&);
|
||||
CHECK(json::from_bon8(vec, true, false).is_discarded());
|
||||
|
||||
// the byte after a string that ends a container
|
||||
const bytes vec2 = {0x81, 'a', 0x90};
|
||||
CHECK(json::from_bon8(vec2, false) == json({"a"}));
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec2), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0x90", json::parse_error&);
|
||||
|
||||
const bytes vec3 = {0x81, 'a', 0xC2, 0x00};
|
||||
CHECK(json::from_bon8(vec3, false) == json({"a"}));
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(vec3), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing BON8 value: expected end of input; last byte: 0xC2", json::parse_error&);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("SAX aborts")
|
||||
{
|
||||
SECTION("start_array(len)")
|
||||
{
|
||||
const bytes v = {0x83, 0x91, 0x92, 0x93};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("start_array()")
|
||||
{
|
||||
const bytes v = {0x85, 0x91, 0xFE};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("end_array()")
|
||||
{
|
||||
const bytes v = {0x85, 0x91, 0xFE};
|
||||
SaxCountdown scp(2);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("start_object(len)")
|
||||
{
|
||||
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("key()")
|
||||
{
|
||||
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
||||
SaxCountdown scp(1);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("end_object()")
|
||||
{
|
||||
const bytes v = {0x87, 'f', 'o', 'o', 0xF8};
|
||||
SaxCountdown scp(3);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("values")
|
||||
{
|
||||
const std::vector<bytes> values =
|
||||
{
|
||||
{0xFA}, {0xF9}, {0x91}, {0xB8}, {0xC2, 0x00}, {0xC2, 0xC0}, {0xE0, 0x00, 0x00},
|
||||
{0x8C, 0, 0, 0, 1}, {0x8D, 0, 0, 0, 0, 0, 0, 0, 1}, {0xFB}, {0xFC}, {0xFD},
|
||||
{0x8E, 0x40, 0, 0, 0}, {0x8F, 0x40, 0, 0, 0, 0, 0, 0, 0}, {'a', 0xFF}, {0xFF}
|
||||
};
|
||||
for (const auto& v : values)
|
||||
{
|
||||
SaxCountdown scp(0);
|
||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::bon8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use this testcase outside [hide] to run it with Valgrind
|
||||
TEST_CASE("BON8 nesting does not consume the call stack")
|
||||
{
|
||||
// Note that deeply nested values must not be compared, copied or dumped
|
||||
// here: those operations are still recursive. Depth is measured by
|
||||
// descending instead.
|
||||
|
||||
SECTION("an unterminated chain is reported, not crashed on")
|
||||
{
|
||||
json _;
|
||||
const bytes input(300000, 0x81);
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bon8(input), "[json.exception.parse_error.110] parse error at byte 300001: syntax error while parsing BON8 value: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bon8(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read through the SAX interface")
|
||||
{
|
||||
bytes input(300000, 0x85);
|
||||
input.push_back(0x91); // innermost value
|
||||
input.insert(input.end(), 300000, 0xFE);
|
||||
|
||||
SaxCountdown accept_all(600001);
|
||||
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::bon8));
|
||||
}
|
||||
|
||||
SECTION("a well-formed deep value is read into a value")
|
||||
{
|
||||
const std::size_t depth = 10000;
|
||||
bytes input;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
input.push_back(0x87);
|
||||
input.push_back('a');
|
||||
}
|
||||
// the innermost key is followed by a string, so it is terminated
|
||||
input.push_back(0xFF);
|
||||
input.push_back('b');
|
||||
input.push_back(0xFF);
|
||||
|
||||
json j = json::from_bon8(input);
|
||||
|
||||
std::size_t measured = 0;
|
||||
const json* p = &j;
|
||||
while (p->is_object() && !p->empty())
|
||||
{
|
||||
p = &p->at("a");
|
||||
++measured;
|
||||
}
|
||||
CHECK(measured == depth);
|
||||
CHECK(*p == "b");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("single BON8 roundtrip")
|
||||
{
|
||||
SECTION("sample.json")
|
||||
{
|
||||
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
|
||||
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BON8 file
|
||||
auto packed = utils::read_binary_file(filename + ".bon8");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(packed));
|
||||
|
||||
// compare parsed JSON values
|
||||
CHECK(j1 == j2);
|
||||
|
||||
SECTION("roundtrips")
|
||||
{
|
||||
SECTION("std::ostringstream")
|
||||
{
|
||||
std::basic_ostringstream<char> ss;
|
||||
json::to_bon8(j1, ss);
|
||||
json j3 = json::from_bon8(ss.str());
|
||||
CHECK(j1 == j3);
|
||||
}
|
||||
|
||||
SECTION("std::string")
|
||||
{
|
||||
std::string s;
|
||||
json::to_bon8(j1, s);
|
||||
json j3 = json::from_bon8(s);
|
||||
CHECK(j1 == j3);
|
||||
}
|
||||
}
|
||||
|
||||
// check with different start index
|
||||
packed.insert(packed.begin(), 5, 0xff);
|
||||
CHECK(j1 == json::from_bon8(packed.begin() + 5, packed.end()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Parse BON8 directly from a file using iterator and sentinel")
|
||||
{
|
||||
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.bon8";
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
const std::istreambuf_iterator<char> first(file);
|
||||
const json parsed = json::from_bon8(first, utils::istreambuf_sentinel{});
|
||||
CHECK((parsed.is_object() || parsed.is_array()));
|
||||
}
|
||||
|
||||
TEST_CASE("BON8 roundtrips" * doctest::skip())
|
||||
{
|
||||
SECTION("input from HikoGUI")
|
||||
{
|
||||
// The .bon8 files were created with the BON8 encoder of HikoGUI
|
||||
// (https://github.com/hikoworks/hikogui), the reference implementation
|
||||
// of the format. Both implementations sort object keys, so the output
|
||||
// is compared byte for byte.
|
||||
for (std::string filename :
|
||||
{
|
||||
TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/1.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/2.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/3.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/4.json",
|
||||
TEST_DATA_DIRECTORY "/json.org/5.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
|
||||
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
|
||||
TEST_DATA_DIRECTORY "/json_testsuite/sample.json",
|
||||
TEST_DATA_DIRECTORY "/json_tests/pass1.json",
|
||||
TEST_DATA_DIRECTORY "/json_tests/pass2.json",
|
||||
TEST_DATA_DIRECTORY "/json_tests/pass3.json",
|
||||
TEST_DATA_DIRECTORY "/regression/floats.json",
|
||||
TEST_DATA_DIRECTORY "/regression/signed_ints.json",
|
||||
TEST_DATA_DIRECTORY "/regression/working_file.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty-string.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_false.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_null.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e+1.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e1.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_after_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_one.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_real.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty_key.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_comments.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_null_escape.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_pi.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_space.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_uEscape.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf8.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
|
||||
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json"
|
||||
})
|
||||
{
|
||||
CAPTURE(filename)
|
||||
|
||||
// parse JSON file
|
||||
std::ifstream f_json(filename);
|
||||
const json j1 = json::parse(f_json);
|
||||
|
||||
// parse BON8 file
|
||||
const auto packed = utils::read_binary_file(filename + ".bon8");
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(packed));
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": std::ifstream");
|
||||
std::ifstream f_bon8(filename + ".bon8", std::ios::binary);
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(f_bon8));
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": iterator pair");
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bon8(packed.begin(), packed.end()));
|
||||
CHECK(j1 == j2);
|
||||
}
|
||||
|
||||
{
|
||||
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
||||
std::vector<uint8_t> vec;
|
||||
json::to_bon8(j1, vec);
|
||||
CHECK(vec == packed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
TEST_CASE("BON8 with std::byte")
|
||||
{
|
||||
SECTION("vector roundtrip")
|
||||
{
|
||||
const json original =
|
||||
{
|
||||
{"name", "test"},
|
||||
{"value", 42},
|
||||
{"array", {1, 2, 3}}
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||
std::vector<std::byte> bon8_data(temp.size());
|
||||
for (size_t i = 0; i < temp.size(); ++i)
|
||||
{
|
||||
bon8_data[i] = std::byte(temp[i]);
|
||||
}
|
||||
|
||||
json from_bytes;
|
||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_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_bon8(empty_data);
|
||||
return true;
|
||||
}
|
||||
(),
|
||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||
json::parse_error&);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -2747,7 +2747,7 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
||||
|
||||
SECTION("binary formats have no text positions")
|
||||
{
|
||||
// binary formats (CBOR, MessagePack, UBJSON, BSON, BJData) are
|
||||
// binary formats (BJData, BON8, BSON, CBOR, MessagePack, UBJSON) are
|
||||
// parsed via detail::binary_reader, which never sets
|
||||
// start_position/end_position on the values it produces (they
|
||||
// have no notion of a text offset), so every value's position
|
||||
@@ -2764,6 +2764,10 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
||||
CHECK(from_msgpack.start_pos() == std::string::npos);
|
||||
CHECK(from_msgpack.end_pos() == std::string::npos);
|
||||
|
||||
const json from_bon8 = json::from_bon8(json::to_bon8(src));
|
||||
CHECK(from_bon8.start_pos() == std::string::npos);
|
||||
CHECK(from_bon8.end_pos() == std::string::npos);
|
||||
|
||||
const json from_ubjson = json::from_ubjson(json::to_ubjson(src));
|
||||
CHECK(from_ubjson.start_pos() == std::string::npos);
|
||||
CHECK(from_ubjson.end_pos() == std::string::npos);
|
||||
|
||||
@@ -74,6 +74,8 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
|
||||
// UBJSON has no binary type, so binary values are written as an array
|
||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
||||
// the same holds for BON8
|
||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -297,6 +297,7 @@ TEST_CASE("object type without key_compare")
|
||||
const auto j = no_key_compare_json::parse(R"({"a":[1,2,3],"b":"x"})");
|
||||
CHECK(no_key_compare_json::from_cbor(no_key_compare_json::to_cbor(j)) == j);
|
||||
CHECK(no_key_compare_json::from_msgpack(no_key_compare_json::to_msgpack(j)) == j);
|
||||
CHECK(no_key_compare_json::from_bon8(no_key_compare_json::to_bon8(j)) == j);
|
||||
}
|
||||
|
||||
SECTION("flatten and unflatten")
|
||||
|
||||
@@ -363,3 +363,52 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
||||
{
|
||||
// Both merge objects nested more than detail::recursion_depth_limit()
|
||||
// (128) levels deep without recursing; the values they add or replace
|
||||
// there must still know their parents.
|
||||
// The values are built rather than parsed, so that the expected messages
|
||||
// carry no byte positions under JSON_DIAGNOSTIC_POSITIONS.
|
||||
const std::size_t depth = 200;
|
||||
json target = {{"x", 1}};
|
||||
json patch = {{"y", 2}};
|
||||
std::string path;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
target = json{{"a", std::move(target)}};
|
||||
patch = json{{"a", std::move(patch)}};
|
||||
path += "/a";
|
||||
}
|
||||
const std::string expected_x = "[json.exception.type_error.304] (" + path + "/x) cannot use at() with number";
|
||||
const std::string expected_y = "[json.exception.type_error.304] (" + path + "/y) cannot use at() with number";
|
||||
|
||||
SECTION("update()")
|
||||
{
|
||||
json j = target;
|
||||
j.update(patch, true);
|
||||
|
||||
// walk down through const references, which leave m_parent alone
|
||||
const json* p = &j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
p = &p->at("a");
|
||||
}
|
||||
CHECK_THROWS_WITH_AS(p->at("x").at(0), expected_x.c_str(), json::type_error);
|
||||
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
|
||||
}
|
||||
|
||||
SECTION("merge_patch()")
|
||||
{
|
||||
json j = target;
|
||||
j.merge_patch(patch);
|
||||
|
||||
const json* p = &j;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
p = &p->at("a");
|
||||
}
|
||||
CHECK_THROWS_WITH_AS(p->at("x").at(0), expected_x.c_str(), json::type_error);
|
||||
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,78 @@ using json = nlohmann::json;
|
||||
using ordered_json = nlohmann::ordered_json;
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
// how detail::hash defines the hash of an array or object: the seeds of the
|
||||
// elements, combined in order. Recursive, so only usable on values nested a
|
||||
// few hundred levels deep - which is exactly what is needed to check that the
|
||||
// iterative path taken below detail::recursion_depth_limit() computes the same.
|
||||
template<typename BasicJsonType>
|
||||
std::size_t reference_hash(const BasicJsonType& j)
|
||||
{
|
||||
using nlohmann::detail::combine;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
|
||||
if (!j.is_structured())
|
||||
{
|
||||
return std::hash<BasicJsonType> {}(j);
|
||||
}
|
||||
|
||||
auto seed = combine(static_cast<std::size_t>(j.type()), j.size());
|
||||
for (const auto& element : j.items())
|
||||
{
|
||||
if (j.is_object())
|
||||
{
|
||||
seed = combine(seed, std::hash<string_t> {}(element.key()));
|
||||
}
|
||||
seed = combine(seed, reference_hash(element.value()));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
// a value nested `depth` levels deep, with siblings on every level
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType nested(const std::size_t depth, const bool objects)
|
||||
{
|
||||
BasicJsonType value = "leaf";
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (objects)
|
||||
{
|
||||
value = BasicJsonType{{"before", i}, {"nested", std::move(value)}, {"after", {i, "x"}}};
|
||||
}
|
||||
else
|
||||
{
|
||||
value = BasicJsonType::array({i, std::move(value), BasicJsonType::object({{"k", i}})});
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string nested_text(const std::size_t depth, const bool objects)
|
||||
{
|
||||
std::string text;
|
||||
if (objects)
|
||||
{
|
||||
text.reserve((6 * depth) + 1);
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
text += "{\"a\":";
|
||||
}
|
||||
text += "1";
|
||||
text.append(depth, '}');
|
||||
}
|
||||
else
|
||||
{
|
||||
text.assign(depth, '[');
|
||||
text += "1";
|
||||
text.append(depth, ']');
|
||||
}
|
||||
return text;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("hash<nlohmann::json>")
|
||||
{
|
||||
@@ -111,3 +183,44 @@ TEST_CASE("hash<nlohmann::ordered_json>")
|
||||
|
||||
CHECK(hashes.size() == 21);
|
||||
}
|
||||
|
||||
TEST_CASE("hash of deeply nested values")
|
||||
{
|
||||
SECTION("hashing past the descent bound computes the same values")
|
||||
{
|
||||
// every depth on either side of where the iterative path takes over
|
||||
for (std::size_t depth = 0; depth <= (2 * nlohmann::detail::recursion_depth_limit()) + 10; ++depth)
|
||||
{
|
||||
CAPTURE(depth);
|
||||
const auto arrays = nested<json>(depth, false);
|
||||
const auto objects = nested<json>(depth, true);
|
||||
const auto ordered = nested<ordered_json>(depth, true);
|
||||
CHECK(std::hash<json> {}(arrays) == reference_hash(arrays));
|
||||
CHECK(std::hash<json> {}(objects) == reference_hash(objects));
|
||||
CHECK(std::hash<ordered_json> {}(ordered) == reference_hash(ordered));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("values nested too deeply for the call stack (#5545)")
|
||||
{
|
||||
// recursing once per level used to exhaust the call stack here; the
|
||||
// values are only parsed and hashed, never copied or compared, since
|
||||
// those recurse as well
|
||||
const std::size_t depth = 100000;
|
||||
for (const bool objects :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(objects);
|
||||
const auto text = nested_text(depth, objects);
|
||||
const auto a = json::parse(text);
|
||||
const auto b = json::parse(text);
|
||||
CHECK(std::hash<json> {}(a) == std::hash<json> {}(b));
|
||||
|
||||
const auto c = ordered_json::parse(text);
|
||||
const auto d = ordered_json::parse(text);
|
||||
CHECK(std::hash<ordered_json> {}(c) == std::hash<ordered_json> {}(d));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,60 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
// RFC 7396's MergePatch, written recursively as in the RFC; only usable on
|
||||
// values nested a few hundred levels deep
|
||||
void reference_merge_patch(json& target, const json& patch)
|
||||
{
|
||||
if (!patch.is_object())
|
||||
{
|
||||
target = patch;
|
||||
return;
|
||||
}
|
||||
if (!target.is_object())
|
||||
{
|
||||
target = json::object();
|
||||
}
|
||||
for (auto it = patch.begin(); it != patch.end(); ++it)
|
||||
{
|
||||
if (it.value().is_null())
|
||||
{
|
||||
target.erase(it.key());
|
||||
}
|
||||
else
|
||||
{
|
||||
reference_merge_patch(target[it.key()], it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// objects nested `depth` levels deep under the key "a", with members that
|
||||
// differ by `variant` on the way down
|
||||
std::string nested_objects(const std::size_t depth, const int variant)
|
||||
{
|
||||
std::string text;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
text += "{";
|
||||
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
|
||||
{
|
||||
text += "\"s" + std::to_string(variant) + "\":" + std::to_string(i) + ",";
|
||||
}
|
||||
if (variant == 2 && i % 5 == 0)
|
||||
{
|
||||
text += "\"s0\":null,";
|
||||
}
|
||||
text += "\"a\":";
|
||||
}
|
||||
text += variant == 1 ? R"({"x":1,"y":null})" : "{\"y\":2}";
|
||||
text.append(depth, '}');
|
||||
return text;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("JSON Merge Patch")
|
||||
{
|
||||
SECTION("examples from RFC 7396")
|
||||
@@ -242,3 +296,52 @@ TEST_CASE("JSON Merge Patch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON Merge Patch on deeply nested values")
|
||||
{
|
||||
SECTION("patching past the descent bound gives the same result")
|
||||
{
|
||||
// every depth on either side of where the iterative version takes
|
||||
// over (detail::recursion_depth_limit(), 128)
|
||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||
{
|
||||
CAPTURE(depth);
|
||||
for (int variant = 0; variant < 3; ++variant)
|
||||
{
|
||||
CAPTURE(variant);
|
||||
const json patch = json::parse(nested_objects(depth, variant));
|
||||
|
||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||
json expected = result;
|
||||
result.merge_patch(patch);
|
||||
reference_merge_patch(expected, patch);
|
||||
CHECK(result == expected);
|
||||
|
||||
// a target that is not an object, and an empty one
|
||||
json from_null;
|
||||
from_null.merge_patch(patch);
|
||||
json expected_from_null;
|
||||
reference_merge_patch(expected_from_null, patch);
|
||||
CHECK(from_null == expected_from_null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("patches nested too deeply for the call stack (#5393)")
|
||||
{
|
||||
// applying a patch used to recurse once per nesting level. The result
|
||||
// is only walked, never copied or compared, since those recurse too.
|
||||
const std::size_t depth = 100000;
|
||||
json target = json::parse(nested_objects(depth, 0));
|
||||
target.merge_patch(json::parse(nested_objects(depth, 1)));
|
||||
|
||||
const json* p = ⌖
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
p = &p->at("a");
|
||||
}
|
||||
// {"y":2} patched with {"x":1,"y":null}
|
||||
CHECK(p->size() == 1);
|
||||
CHECK(p->at("x") == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,53 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
// update(source, true) as documented, written recursively; only usable on
|
||||
// values nested a few hundred levels deep
|
||||
void reference_update(json& target, const json& source)
|
||||
{
|
||||
for (auto it = source.begin(); it != source.end(); ++it)
|
||||
{
|
||||
const auto existing = target.find(it.key());
|
||||
if (it.value().is_object() && existing != target.end() && existing->is_object())
|
||||
{
|
||||
reference_update(*existing, it.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
target[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// objects nested `depth` levels deep under the key "a", with members that
|
||||
// differ by `variant` on the way down
|
||||
std::string nested_objects(const std::size_t depth, const int variant)
|
||||
{
|
||||
std::string text;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
text += "{";
|
||||
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
|
||||
{
|
||||
text += "\"s" + std::to_string(variant) + "\":" + std::to_string(i) + ",";
|
||||
}
|
||||
if (variant == 2 && i % 5 == 0)
|
||||
{
|
||||
// an object replacing a primitive, which is not merged
|
||||
text += R"("s0":{"o":1},)";
|
||||
}
|
||||
text += "\"a\":";
|
||||
}
|
||||
text += variant == 1 ? "{\"x\":1}" : "{\"y\":2}";
|
||||
text.append(depth, '}');
|
||||
return text;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("modifiers")
|
||||
{
|
||||
SECTION("clear()")
|
||||
@@ -988,3 +1035,44 @@ TEST_CASE("modifiers")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update() on deeply nested values")
|
||||
{
|
||||
SECTION("merging past the descent bound gives the same result")
|
||||
{
|
||||
// every depth on either side of where the iterative version takes
|
||||
// over (detail::recursion_depth_limit(), 128)
|
||||
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||
{
|
||||
CAPTURE(depth);
|
||||
for (int variant = 0; variant < 3; ++variant)
|
||||
{
|
||||
CAPTURE(variant);
|
||||
const json source = json::parse(nested_objects(depth, variant));
|
||||
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
|
||||
json expected = result;
|
||||
result.update(source, true);
|
||||
reference_update(expected, source);
|
||||
CHECK(result == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("objects nested too deeply for the call stack (#5545)")
|
||||
{
|
||||
// merging used to recurse once per nesting level. The result is only
|
||||
// walked, never copied or compared, since those recurse too.
|
||||
const std::size_t depth = 100000;
|
||||
json target = json::parse(nested_objects(depth, 0));
|
||||
target.update(json::parse(nested_objects(depth, 1)), true);
|
||||
|
||||
const json* p = ⌖
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
p = &p->at("a");
|
||||
}
|
||||
CHECK(p->size() == 2);
|
||||
CHECK(p->at("x") == 1);
|
||||
CHECK(p->at("y") == 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +326,15 @@ TEST_CASE("ordered_json across binary formats")
|
||||
CHECK(collect_keys(restored) == original_keys);
|
||||
CHECK(collect_keys(restored["mango"]) == original_mango_keys);
|
||||
}
|
||||
|
||||
SECTION("BON8")
|
||||
{
|
||||
const auto bytes = ordered_json::to_bon8(original);
|
||||
const auto restored = ordered_json::from_bon8(bytes);
|
||||
CHECK(restored == original);
|
||||
CHECK(collect_keys(restored) == original_keys);
|
||||
CHECK(collect_keys(restored["mango"]) == original_mango_keys);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("alt_json (custom string_t) across binary formats")
|
||||
@@ -353,6 +362,13 @@ TEST_CASE("alt_json (custom string_t) across binary formats")
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("BON8")
|
||||
{
|
||||
const auto bytes = alt_json::to_bon8(original);
|
||||
const auto restored = alt_json::from_bon8(bytes);
|
||||
CHECK(restored == original);
|
||||
}
|
||||
|
||||
SECTION("BSON")
|
||||
{
|
||||
const auto bytes = alt_json::to_bson(original);
|
||||
|
||||
@@ -332,6 +332,7 @@ TEST_CASE("regression tests 2")
|
||||
CHECK(float_json::from_cbor(float_json::to_cbor(j)) == j);
|
||||
CHECK(float_json::from_msgpack(float_json::to_msgpack(j)) == j);
|
||||
CHECK(float_json::from_ubjson(float_json::to_ubjson(j)) == j);
|
||||
CHECK(float_json::from_bon8(float_json::to_bon8(j)) == j);
|
||||
|
||||
float_json j2 = {1000.0, 2000.0, 3000.0};
|
||||
CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
|
||||
|
||||
@@ -894,6 +894,7 @@ TEST_CASE("regression test #5476 - array type without reserve()")
|
||||
// the binary formats pass a definite length to start_array()
|
||||
CHECK(deque_json::from_cbor(deque_json::to_cbor(j)) == j);
|
||||
CHECK(deque_json::from_msgpack(deque_json::to_msgpack(j)) == j);
|
||||
CHECK(deque_json::from_bon8(deque_json::to_bon8(j)) == j);
|
||||
|
||||
// parse() instantiates the callback parser as well, which reserves too
|
||||
const auto with_callback = deque_json::parse(R"([1,2,3])", [](int /*depth*/, deque_json::parse_event_t /*event*/, deque_json& /*parsed*/) noexcept
|
||||
|
||||
Reference in New Issue
Block a user