From d6431a91044e0e64f270127e19178aa2413c7226 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 17:11:25 +0100 Subject: [PATCH 01/63] Throw out_of_range.411 for JSON Patch add into a null parent (#5917) (#5918) Claude-Session: https://claude.ai/code/session_01MKJf48vnGtTE25KscmjfaW Signed-off-by: Niels Lohmann Co-authored-by: Claude Sonnet 5.5 --- docs/mkdocs/docs/api/basic_json/patch.md | 1 + .../docs/api/basic_json/patch_inplace.md | 1 + docs/mkdocs/docs/home/exceptions.md | 2 ++ include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 2 +- tests/src/unit-json_patch.cpp | 18 ++++++++++++++++++ 6 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/patch.md b/docs/mkdocs/docs/api/basic_json/patch.md index ed26135a9..39f601683 100644 --- a/docs/mkdocs/docs/api/basic_json/patch.md +++ b/docs/mkdocs/docs/api/basic_json/patch.md @@ -108,6 +108,7 @@ is thrown. In any case, the original value is not changed: the patch is applied - Added in version 2.0.0. - Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's target location has a non-object/non-array parent in version 3.13.0. + This includes a `null` parent, which was previously silently converted to an object, in version 3.13.0. - Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target location has a non-object/non-array parent in version 3.13.0. - Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 5b0d501bb..1bcfc0067 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -106,6 +106,7 @@ function throws an exception. - Added in version 3.11.0. - Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's target location has a non-object/non-array parent in version 3.13.0. + This includes a `null` parent, which was previously silently converted to an object, in version 3.13.0. - Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target location has a non-object/non-array parent in version 3.13.0. - Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 4b8f6edd9..0a31d2444 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -1100,6 +1100,8 @@ A JSON Patch `add` operation cannot be applied because the target location's par This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled. + A `null` parent is rejected as well. Before version 3.13.0, an `add` into a `null` parent silently converted it to an object (e.g., `add` at `/foo/bar` turned `{"foo":null}` into `{"foo":{"bar":1}}`). + ### json.exception.out_of_range.412 BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer, and MessagePack diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 3d40682d9..c2dddd55a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -7003,7 +7003,6 @@ public: switch (parent.m_data.m_type) { - case value_t::null: case value_t::object: { // use operator[] to add value @@ -7035,6 +7034,7 @@ public: // the parent of an "add" target must be an object or array // (see #4292) + case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 1beba149b..f0978d69e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -42793,7 +42793,6 @@ public: switch (parent.m_data.m_type) { - case value_t::null: case value_t::object: { // use operator[] to add value @@ -42825,6 +42824,7 @@ public: // the parent of an "add" target must be an object or array // (see #4292) + case value_t::null: case value_t::string: case value_t::boolean: case value_t::number_integer: diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index 67ffee6dd..de868737f 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -1515,6 +1515,24 @@ TEST_CASE("JSON patch - add to a primitive parent (regression #4292)") #endif } + SECTION("null parent") + { + json const doc = {{"foo", nullptr}}; + json const patch = {{{"op", "add"}, {"path", "/foo/bar"}, {"value", 1}}}; +#if JSON_DIAGNOSTICS + CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] (/foo) cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&); +#else + CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&); +#endif + } + + SECTION("null document") + { + json const doc = nullptr; + json const patch = {{{"op", "add"}, {"path", "/-"}, {"value", 1}}}; + CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type null, but must be an object or array", json::out_of_range&); + } + SECTION("number parent") { json const doc = {{"foo", 1}}; From 8679c011db4f660b21cb490f08a8034faae196a2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 17:11:36 +0100 Subject: [PATCH 02/63] Use flawfinder --exclude instead of listing directories (#5935) Flawfinder 2.0.20 has an --exclude PATTERN option. Scan `./` and exclude `*/single_include/*` so that new top-level directories are covered automatically. The scanned file set is identical to the explicit directory list. Signed-off-by: Niels Lohmann --- .github/workflows/flawfinder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flawfinder.yml b/.github/workflows/flawfinder.yml index 2aebc6306..0c6e5fa9c 100644 --- a/.github/workflows/flawfinder.yml +++ b/.github/workflows/flawfinder.yml @@ -43,8 +43,8 @@ jobs: - name: flawfinder_scan uses: david-a-wheeler/flawfinder@c4216b74cf2639ffa98503768bd6e4299b5440c9 # v2.0.20 with: - # single_include/ is omitted: it is generated from include/ and would report every finding twice - arguments: '--sarif ./cmake ./docs ./include ./src ./tests ./tools' + # single_include/ is generated from include/ and would report every finding twice + arguments: "--sarif --exclude '*/single_include/*' ./" output: 'flawfinder_results.sarif' - name: Upload analysis results to GitHub Security tab From c20b6eddf45651fa6bb81958fe61c7416bd5d525 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 17:11:46 +0100 Subject: [PATCH 03/63] docs: correct JSON Pointer documentation and add tests (#5937) Fix inaccurate or missing statements about unflatten (third round-trip failure, empty token, typo), value (type_error.306 before pointer lookup), contains (pointer overload), parent_pointer and operator<< (bad_alloc), out_of_range.410 example, template parameter requirements (resize(n)), and document the URI fragment form and non-unique member names of RFC 6901. Add tests for the unflatten and value() cases. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/contains.md | 11 +++- docs/mkdocs/docs/api/basic_json/unflatten.md | 15 ++++-- docs/mkdocs/docs/api/basic_json/value.md | 3 ++ .../docs/api/json_pointer/parent_pointer.md | 3 +- docs/mkdocs/docs/api/operator_ltlt.md | 3 +- docs/mkdocs/docs/features/json_pointer.md | 11 ++++ .../features/types/template_parameters.md | 1 + docs/mkdocs/docs/home/exceptions.md | 7 ++- tests/src/unit-json_pointer.cpp | 50 +++++++++++++++++++ 9 files changed, 94 insertions(+), 10 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index c6e2e7d77..5ed088a55 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -61,11 +61,18 @@ Strong exception safety: if an exception occurs, the original value stays intact ## Complexity -Logarithmic in the size of the JSON object. +1. Logarithmic in the size of the JSON object. +2. Logarithmic in the size of the JSON object. +3. At most one lookup per reference token of `ptr`, each logarithmic in the size of the container at that level: + O(*n* log *m*) for *n* reference tokens and containers of size at most *m*. +4. Deleted; a call with an integral argument does not compile. ## Notes -- This method always returns `#!cpp false` when executed on a JSON type that is not an object. +- Overloads (1) and (2) always return `#!cpp false` when executed on a JSON type that is not an object. Overload (3) + also works on arrays and on primitive values: the empty JSON pointer resolves in every JSON value (so + `#!cpp json(1).contains(""_json_pointer)` returns `#!cpp true`), and `#!cpp json({1, 2}).contains("/1"_json_pointer)` + returns `#!cpp true`. - This method can be executed on any JSON value type. - Calling this function with an integer argument (for example, `#!cpp contains(0)`) does not compile: such an argument would otherwise implicitly convert to a null `#!cpp const char*` and, from there, cause undefined behavior when diff --git a/docs/mkdocs/docs/api/basic_json/unflatten.md b/docs/mkdocs/docs/api/basic_json/unflatten.md index 661ffa3e8..1ae81dd15 100644 --- a/docs/mkdocs/docs/api/basic_json/unflatten.md +++ b/docs/mkdocs/docs/api/basic_json/unflatten.md @@ -36,9 +36,9 @@ The function can throw the following exceptions: - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in a key is not a number; example: `"array index 'one' is not a number"` - Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a level becomes an array - (because one of its keys is `0`) and another key at that level begins with a digit but is not a valid array index - (such as `1a`), or is `-`; example: - `"unresolved reference token '-'"` + (because one of its keys is `0`) and another key at that level is empty (as in `/a/`), is `-`, or begins with a digit + but is not a valid array index (such as `1a`); examples: `"unresolved reference token '-'"`, + `"unresolved reference token ''"` ## Complexity @@ -46,7 +46,7 @@ Linear in the size of the JSON value. ## Notes -Empty objects and arrays are flattened by [`flatten()`](flatten.md) to `#!json null` values and cannot unflattened to +Empty objects and arrays are flattened by [`flatten()`](flatten.md) to `#!json null` values and cannot be unflattened to their original type. A flattened array and a flattened object whose keys are array indices are indistinguishable, because both are @@ -54,7 +54,12 @@ described by the same JSON pointers. A value is therefore restored as an array i reference token `0`, and as an object otherwise: `#!json {"2": 1}` is restored unchanged, whereas `#!json {"0": 1}` is restored as `#!json [1]`. This decision does not depend on the order in which the flattened object is iterated. -Apart from these two cases, for a JSON value `j`, the following is always true: +A third case is an object that has the key `0` next to a key that is not a valid array index: `#!json {"0": 1, "b": 2}` +is flattened to `#!json {"/0": 1, "/b": 2}`, and unflatten treats the level as an array and throws +[`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) because `b` is not a number. (For the keys +`-`, `1a`, or the empty key instead of `b`, it throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404).) + +Apart from these three cases, for a JSON value `j`, the following is always true: `#!cpp j == j.flatten().unflatten()`. ## Examples diff --git a/docs/mkdocs/docs/api/basic_json/value.md b/docs/mkdocs/docs/api/basic_json/value.md index aee09a537..5aa056c92 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -45,6 +45,9 @@ This is equivalent to Python's `dict.get(key, default)`. return default_value; } ``` + with one exception: if the JSON value is neither an array nor an object, the function throws + [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) before looking at `ptr`, even for the empty + pointer, whereas `at` returns the value itself for the empty pointer. !!! note "Differences to `at` and `operator[]`" diff --git a/docs/mkdocs/docs/api/json_pointer/parent_pointer.md b/docs/mkdocs/docs/api/json_pointer/parent_pointer.md index d37889e7e..e8627714d 100644 --- a/docs/mkdocs/docs/api/json_pointer/parent_pointer.md +++ b/docs/mkdocs/docs/api/json_pointer/parent_pointer.md @@ -12,7 +12,8 @@ Parent of this JSON pointer; in case this JSON pointer is the root, the root its ## Exception safety -No-throw guarantee: this function never throws exceptions. +Strong exception safety: if an exception occurs, the original value stays intact. The function is not `#!cpp noexcept` +and allocates a new JSON pointer, so it can throw `#!cpp std::bad_alloc`; it throws no library exceptions. ## Complexity diff --git a/docs/mkdocs/docs/api/operator_ltlt.md b/docs/mkdocs/docs/api/operator_ltlt.md index 6a821b7fb..6bcb160a2 100644 --- a/docs/mkdocs/docs/api/operator_ltlt.md +++ b/docs/mkdocs/docs/api/operator_ltlt.md @@ -37,7 +37,8 @@ the stream `o` 1. Throws [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) if a string stored inside the JSON value is not UTF-8 encoded. Note that unlike the [`dump`](basic_json/dump.md) member functions, no `error_handler` can be set. -2. None. +2. Throws no library exceptions. As the string representation is built with [`to_string`](json_pointer/to_string.md), + `#!cpp std::bad_alloc` is possible. ## Complexity diff --git a/docs/mkdocs/docs/features/json_pointer.md b/docs/mkdocs/docs/features/json_pointer.md index 1bc5eabdb..a26454be0 100644 --- a/docs/mkdocs/docs/features/json_pointer.md +++ b/docs/mkdocs/docs/features/json_pointer.md @@ -37,6 +37,10 @@ Then every value inside the JSON document can be identified as follows: Note `/` does not identify the root (i.e., the whole document), but an object entry with empty key `""`. See [RFC 6901](https://tools.ietf.org/html/rfc6901) for more information. +[RFC 6901, section 4](https://tools.ietf.org/html/rfc6901#section-4) states that evaluation fails if an object has +non-unique member names. A `basic_json` object holds one value per key (see [`object_t`](../api/basic_json/object_t.md) +for how duplicate keys are handled while parsing), so evaluating a JSON Pointer never encounters duplicates. + ## JSON Pointer creation JSON Pointers can be created from a string: @@ -54,6 +58,13 @@ auto p = "/nested/one"_json_pointer; The escaping rules of [RFC 6901](https://tools.ietf.org/html/rfc6901) are implemented. See the [constructor documentation](../api/json_pointer/json_pointer.md) for more information. +!!! note "URI fragment representation" + + [RFC 6901, section 6](https://tools.ietf.org/html/rfc6901#section-6) also defines a URI fragment identifier + representation (`#/a%20b`). It is not supported: `#!cpp json::json_pointer("#/a")` throws + [`parse_error.107`](../home/exceptions.md#jsonexceptionparse_error107). Strip the leading `#` and + percent-decode the remainder before creating the JSON pointer. + ## Value access JSON Pointers can be used in the [`at`](../api/basic_json/at.md), [`operator[]`](../api/basic_json/operator%5B%5D.md), diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index 888590234..29329fea8 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -390,6 +390,7 @@ using array_t = ArrayType>; described in RFC 6901. Neither `find(const StringType&, size_type)`, nor `substr(pos, count)`, nor `replace(pos, count, const StringType&)` is required. - `empty()`. +- `resize(n)` (one argument) -- used by `to_string` to size the result before the reference tokens are copied into it. - `begin()` and `end()` -- used by [`operator[](const json_pointer&)`](../../api/basic_json/operator%5B%5D.md) to decide whether a reference token denotes an array index. diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 0a31d2444..1b8436f91 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -1083,9 +1083,14 @@ a JSON pointer exceeds the range of `size_type` (e.g., on 32-bit platforms). ``` enum value out of range - array index 18446744073709551616 exceeds size_type + array index 18446744073709551615 exceeds size_type ``` + The second message is thrown, for instance, by `#!cpp json::parse("[1, 2]").at("/18446744073709551615"_json_pointer)` + on a 64-bit platform, where `size_type` is `#!cpp std::size_t` and this index is `SIZE_MAX`. An index that does not + fit into 64 bits (such as `18446744073709551616`) is not reported as 410 but as + [`out_of_range.404`](#jsonexceptionout_of_range404). + ### json.exception.out_of_range.411 A JSON Patch `add` operation cannot be applied because the target location's parent is neither an object nor an array. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902), an `add` target must reference a member of an existing object or an element of an existing array; a primitive value (string, number, boolean, etc.) cannot receive a new member or element. diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 7748a375a..111fec815 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -1044,6 +1044,56 @@ TEST_CASE("flatten of structured values") CHECK(flat_object.unflatten() == nlohmann::ordered_json::parse(R"({"b": {"1": 2}})")); } +#if !defined(JSON_NOEXCEPTION) + SECTION("an object with the key \"0\" next to a non-index key does not round-trip") + { + // the third documented case in which j != j.flatten().unflatten() + const json value = {{"a", {{"0", 1}, {"b", 2}}}}; + const json flat = value.flatten(); + CHECK(flat == json({{"/a/0", 1}, {"/a/b", 2}})); + CHECK_THROWS_WITH_AS(flat.unflatten(), + "[json.exception.parse_error.109] parse error: array index 'b' is not a number", json::parse_error&); + + // the same for other keys that are not valid array indices + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1a", 2}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token '1a'", json::out_of_range&); + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/-", 2}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&); + } + + SECTION("unflatten with the empty token among array indices") + { + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/", 2}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); + CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1", 2}, {"/a/", 3}}).unflatten(), + "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); + + // without an index "0", the empty token is an ordinary object key + CHECK(json({{"/a/", 2}}).unflatten() == json({{"a", {{"", 2}}}})); + } + + SECTION("value() with the empty pointer on a primitive root") + { + // unlike at(), value() rejects primitive values before it looks at the pointer + const json number = 1; + CHECK(number.at(""_json_pointer) == 1); + CHECK_THROWS_WITH_AS(number.value(""_json_pointer, 7), + "[json.exception.type_error.306] cannot use value() with number", json::type_error&); + + const json string = "s"; + CHECK_THROWS_WITH_AS(string.value(""_json_pointer, 7), + "[json.exception.type_error.306] cannot use value() with string", json::type_error&); + + // contains() accepts the empty pointer on every value + CHECK(number.contains(""_json_pointer)); + CHECK_FALSE(number.contains("/a"_json_pointer)); + + // arrays and objects return the default for a missing location + CHECK(json::array().value(""_json_pointer, json(7)) == json::array()); + CHECK(json::array().value("/0"_json_pointer, 7) == 7); + } +#endif + SECTION("objects and arrays interleaved") { const json value = From 7a10bf25d2c61964bfe0a24ce398f1cf1c0cc11c Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 17:11:55 +0100 Subject: [PATCH 04/63] docs: state merge_patch complexity and RFC 7396 limitations; test non-object, discarded, binary cases (#5938) Signed-off-by: Niels Lohmann --- .../mkdocs/docs/api/basic_json/merge_patch.md | 28 ++++++- docs/mkdocs/docs/features/merge_patch.md | 14 +++- tests/src/unit-merge_patch.cpp | 83 +++++++++++++++++++ 3 files changed, 120 insertions(+), 5 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/merge_patch.md b/docs/mkdocs/docs/api/basic_json/merge_patch.md index 1fafb1ece..923070080 100644 --- a/docs/mkdocs/docs/api/basic_json/merge_patch.md +++ b/docs/mkdocs/docs/api/basic_json/merge_patch.md @@ -39,13 +39,33 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value ## Complexity -Linear in the lengths of `apply_patch`. +Linear in the total number of values (including nested ones) in `apply_patch`, times the cost of a key-based object +operation (looking up, adding, or erasing a member): logarithmic for [`json`](../json.md), and linear for +[`ordered_json`](../ordered_json.md) (see [`ordered_map` complexity](../ordered_map.md#complexity)). For +`ordered_json`, patching an object that has `n` members with a patch that has `m` members therefore takes O(n·m). The +cost also includes one copy of `apply_patch` (see the notes below). ## Notes -`apply_patch` may be `#!cpp *this` itself or refer to a value contained in `#!cpp *this` (for example, a subobject -returned by `#!cpp (*this)[key]`); it is read as it was when `merge_patch()` was called, before any modification of -`#!cpp *this`. +- `apply_patch` may be `#!cpp *this` itself or refer to a value contained in `#!cpp *this` (for example, a subobject + returned by `#!cpp (*this)[key]`); it is read as it was when `merge_patch()` was called, before any modification of + `#!cpp *this`. To make this possible, the function copies `apply_patch` before it modifies `#!cpp *this`, so the cost + includes one copy of the patch. +- A merge patch uses `#!json null` to remove a member, so it cannot set a member to `#!json null`: applying + `#!json {"a":null}` to `#!json {"a":1}` gives `#!json {}`. +- Arrays are not merged: an array in the patch replaces the target value as a whole, and a `#!json null` inside an + array is kept as an element. `#!json null` members of the target that the patch does not mention are preserved. +- A `#!json null` in the patch only removes a member; it does not prevent the creation of the enclosing objects. + Applying `#!json {"a":{"b":null}}` to `#!json {}` gives `#!json {"a":{}}` (see example 15 in Appendix A of the RFC). +- If `apply_patch` is not an object, it replaces the target as a whole. In particular, a `#!json null` patch turns the + target into `#!json null`. If `apply_patch` is an object and the target is not, the target is first replaced by an + empty object, so applying `#!json {}` to `#!json 1` gives `#!json {}`. +- A merge patch cannot express every change; for example, it cannot reorder array elements or remove a specific array + element. Use [JSON Patch](../../features/json_patch.md) ([RFC 6902](https://tools.ietf.org/html/rfc6902)) for that. + [`merge_diff`](merge_diff.md) has a matching limitation, see its notes. +- A [discarded](is_discarded.md) value is treated like any other non-object value: a discarded + `apply_patch` turns the target into a discarded value, and an object patch replaces a discarded target by an object + as described above. ## Examples diff --git a/docs/mkdocs/docs/features/merge_patch.md b/docs/mkdocs/docs/features/merge_patch.md index ac732c7bf..9d87f4c50 100644 --- a/docs/mkdocs/docs/features/merge_patch.md +++ b/docs/mkdocs/docs/features/merge_patch.md @@ -2,7 +2,7 @@ The library supports JSON Merge Patch ([RFC 7396](https://tools.ietf.org/html/rfc7396)) as a patch format. The merge patch format is primarily intended for use with the HTTP PATCH method as a means of describing a set of -modifications to a target resource's content. This function applies a merge patch to the current JSON value. +modifications to a target resource's content. Applying a merge patch changes the JSON value the patch is applied to. Instead of using [JSON Pointer](json_pointer.md) to specify values to be manipulated, it describes the changes using a syntax that closely mimics the document being modified. Unlike [JSON Patch](json_patch.md), a JSON Merge Patch cannot @@ -19,6 +19,18 @@ another. [`update`](../api/basic_json/update.md) with `merge_objects` set to `#!cpp true`; see [Merging objects](modifying_values.md#merging-objects). +!!! note "Limitations of RFC 7396" + + - A patch cannot set a member to `#!json null`, because `#!json null` removes the member: + `#!json {"a":1}` patched with `#!json {"a":null}` gives `#!json {}`. + - Arrays are replaced as a whole, never merged, and a `#!json null` inside an array is kept. + - `#!json null` members of the target that the patch does not mention are preserved. + - A `#!json null` that would remove a member of a not yet existing object still creates that object: + `#!json {}` patched with `#!json {"a":{"b":null}}` gives `#!json {"a":{}}`. + - A patch that is not an object replaces the whole target (a `#!json null` patch makes the target + `#!json null`); an object patch applied to a non-object target starts from an empty object. + - [`merge_diff`](../api/basic_json/merge_diff.md#notes) has a matching limitation for `#!json null` members. + ??? example The following code shows how a JSON Merge Patch is applied to a JSON document. diff --git a/tests/src/unit-merge_patch.cpp b/tests/src/unit-merge_patch.cpp index 927eb8773..3f5d79b3c 100644 --- a/tests/src/unit-merge_patch.cpp +++ b/tests/src/unit-merge_patch.cpp @@ -423,3 +423,86 @@ TEST_CASE("merge_patch() with an argument that aliases *this (#5641)") } } } + +TEST_CASE("JSON Merge Patch on non-object, discarded, and binary values") +{ + SECTION("an empty object patch turns a non-object target into an empty object") + { + json target = 1; + target.merge_patch(json::object()); + CHECK(target == json::object()); + + json from_null; + from_null.merge_patch(json::object()); + CHECK(from_null == json::object()); + } + + SECTION("a non-object patch replaces a deeply nested target") + { + // checked by type, since == recurses + SECTION("number") + { + json target = json::parse(nested_objects(1000, 0)); + target.merge_patch(json(42)); + CHECK(target.is_number_integer()); + CHECK(target.get() == 42); + } + + SECTION("array") + { + json target = json::parse(nested_objects(1000, 0)); + target.merge_patch(json({1, nullptr})); + CHECK(target.is_array()); + CHECK(target.size() == 2); + CHECK(target[1].is_null()); + } + + SECTION("null") + { + json target = json::parse(nested_objects(1000, 0)); + target.merge_patch(json(nullptr)); + CHECK(target.is_null()); + } + } + + SECTION("discarded values") + { + const json discarded(json::value_t::discarded); + + // a discarded patch is not an object and replaces the target + json target = {{"a", 1}}; + target.merge_patch(discarded); + CHECK(target.is_discarded()); + + // a discarded target is replaced by an empty object first + json discarded_target(json::value_t::discarded); + discarded_target.merge_patch(json({{"a", 1}})); + CHECK(discarded_target == json({{"a", 1}})); + + json both(json::value_t::discarded); + both.merge_patch(discarded); + CHECK(both.is_discarded()); + } + + SECTION("binary values") + { + const json bin = json::binary({1, 2, 3}, 7); + + // a binary patch is not an object and replaces the target + json target = {{"a", 1}}; + target.merge_patch(bin); + CHECK(target.is_binary()); + CHECK(target.get_binary() == bin.get_binary()); + + // a binary target is replaced by an object + json binary_target = bin; + binary_target.merge_patch(json({{"a", 1}})); + CHECK(binary_target == json({{"a", 1}})); + + // binary values nested in the patch are kept + json nested = json::object(); + nested.merge_patch(json({{"a", bin}})); + CHECK(nested["a"].is_binary()); + CHECK(nested["a"].get_binary() == bin.get_binary()); + } +} From c712df0637007b32dc9f4b30f6319491f14f879b Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 17:12:05 +0100 Subject: [PATCH 05/63] docs: make the diff() and patch() documentation accurate; add tests (#5939) * Throw out_of_range.411 for JSON Patch add into a null parent (#5917) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01MKJf48vnGtTE25KscmjfaW Signed-off-by: Niels Lohmann * docs: make the diff() and patch() documentation accurate; add tests Document the exceptions to the diff round-trip invariant (NaN, discarded values, number types), the path parameter, non-minimal patches, array and object operation order, the ordered_json fallback, binary values in serialized patches, the real complexity, duplicate member names in operations, test on missing paths, and the exact out_of_range.403 message. Add tests for these behaviors. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann Signed-off-by: Niels Lohmann Co-authored-by: Claude Sonnet 5.5 --- docs/mkdocs/docs/api/basic_json/diff.md | 45 +++- docs/mkdocs/docs/api/basic_json/patch.md | 11 +- .../docs/api/basic_json/patch_inplace.md | 11 +- docs/mkdocs/docs/features/json_patch.md | 19 +- tests/src/unit-json_patch.cpp | 206 +++++++++++++++++- 5 files changed, 282 insertions(+), 10 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/diff.md b/docs/mkdocs/docs/api/basic_json/diff.md index c4e710a7d..d45a9a094 100644 --- a/docs/mkdocs/docs/api/basic_json/diff.md +++ b/docs/mkdocs/docs/api/basic_json/diff.md @@ -2,17 +2,27 @@ ```cpp static basic_json diff(const basic_json& source, - const basic_json& target); + const basic_json& target, + const string_t& path = ""); ``` Creates a [JSON Patch](http://jsonpatch.com) so that value `source` can be changed into the value `target` by calling [`patch`](patch.md) function. -For two JSON values `source` and `target`, the following code always yields `#!cpp true`: +For two JSON values `source` and `target`, the following code yields `#!cpp true`: ```cpp source.patch(diff(source, target)) == target; ``` +The equality holds up to [`operator==`](operator_eq.md), so it has the following exceptions: + +- A value that is not equal to itself cannot round-trip. This applies to `NaN` (also nested in an array or object) and + to [discarded](is_discarded.md) values: `diff` emits a `replace` operation for them. A `NaN` in the patch is + serialized as `#!json null`, so a patch that was dumped and parsed again would set `#!json null`. +- Numbers that compare equal produce no operation, even if their types or signs differ: `diff(1, 1.0)`, + `diff(1u, 1)` (unsigned and signed integer) and `diff(0.0, -0.0)` all return `#!json []`. The patched value is then + the `source` and keeps its number type. + ## Parameters `source` (in) @@ -21,6 +31,12 @@ source.patch(diff(source, target)) == target; `target` (in) : JSON value to compare against +`path` (in) +: a prefix that is put verbatim in front of the `path` member of every operation (default: `""`, which addresses the + root of the value). The prefix is neither escaped nor validated: `#!cpp diff(1, 2, "abc")` yields an operation with + the `path` `abc`, which is not a valid JSON Pointer. Use it with a valid JSON Pointer such as `/root` and escape + `~` and `/` in its reference tokens (see [JSON Pointer](../../features/json_pointer.md)). + ## Return value a JSON patch to convert the `source` to `target` @@ -31,11 +47,32 @@ Strong guarantee: `source` and `target` are never modified. ## Complexity -Linear in the lengths of `source` and `target`. +Linear in the total number of values (including nested ones) in `source` and `target`, times the cost of a key-based +object operation (looking up a key in an object): logarithmic for [`json`](../json.md), and linear for +[`ordered_json`](../ordered_json.md) (see [`ordered_map` complexity](../ordered_map.md#complexity)). For +`ordered_json`, diffing two objects with `n` and `m` members therefore takes O((n+m)²) when their keys do not line up +(e.g., a key was added or the keys are in a different order). The size of the patch is proportional to the number of +operations times the length of their paths, so a small change deep inside a value yields a long `path`. ## Notes -Currently, only `remove`, `add`, and `replace` operations are generated. +- Currently, only `remove`, `add`, and `replace` operations are generated. +- The patch is correct, but not guaranteed to be minimal. For instance, `diff([1,2,3], [0,1,2,3])` yields three + `replace` operations and one `add` operation instead of a single insertion. +- Differences between two arrays are reported index by index. Elements that were added at the end of the array are added + with `add` operations on the path `/-` (appended in order), and elements that were removed are removed with `remove` + operations from the highest index down to the lowest, so that the remaining indices stay valid. +- For objects, the operations for the members of `source` come first (`replace`, `remove`, or the operations for nested + values, in the iteration order of `source`), followed by `add` operations for the members that only `target` has. +- For [`ordered_json`](../ordered_json.md), objects with the same keys in a different order are different. As a patch + cannot reorder members, `diff` removes all keys of the `source` object and adds them again in the order of the + `target`. +- If `source` and `target` differ as a whole (e.g., different types), the patch consists of a single `replace` + operation for the root of the value, i.e., with the `path` `""` (or the given `path` prefix). +- The `value` members of the patch hold copies of values of `target`. A [binary](../../features/binary_values.md) value + has no JSON representation: when a patch that contains one is serialized, it becomes an object such as + `#!json {"bytes":[1,3],"subtype":null}`, so a parsed patch no longer holds a binary value (and would set that object + instead). ## Examples diff --git a/docs/mkdocs/docs/api/basic_json/patch.md b/docs/mkdocs/docs/api/basic_json/patch.md index 39f601683..1166c9a71 100644 --- a/docs/mkdocs/docs/api/basic_json/patch.md +++ b/docs/mkdocs/docs/api/basic_json/patch.md @@ -40,7 +40,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va where an existing element is required (the "path" of "replace", the "from" of "move" and "copy"); example: `"array index '-' (3) is out of range"`. - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a JSON pointer inside the patch - could not be resolved successfully in the current JSON value; example: `"key baz not found"`. + could not be resolved successfully in the current JSON value; example: `"key 'baz' not found"`. - Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token of a JSON pointer inside the patch cannot be resolved, e.g., `-` in a "remove" operation or `1a` for an array; example: `"unresolved reference token '-'"`. @@ -53,7 +53,10 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from" location is a proper prefix of its "path" location. - Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was - unsuccessful. + unsuccessful. A "test" operation whose "path" does not exist is unsuccessful, too: the failed lookup is reported as a + failed test, even if the operation has no "value" member (which would otherwise be + [`parse_error.105`](../../home/exceptions.md#jsonexceptionparse_error105)). Which of the two errors is reported for + this combination depends on the order in which the operands of a comparison are evaluated, so do not rely on it. ## Complexity @@ -65,6 +68,10 @@ the JSON value, the complexity can usually be neglected. The application of a patch is atomic: Either all operations succeed and the patched document is returned or an exception is thrown. In any case, the original value is not changed: the patch is applied to a copy of the value. +Duplicate member names inside an operation object are resolved by the parser before the patch is applied (the last one +is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a +`remove` operation. + ## Examples ??? example "Example: apply a JSON patch" diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 1bcfc0067..571ca9c12 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -36,7 +36,7 @@ No guarantees, value may be corrupted by an unsuccessful patch operation. where an existing element is required (the "path" of "replace", the "from" of "move" and "copy"); example: `"array index '-' (3) is out of range"`. - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a JSON pointer inside the patch - could not be resolved successfully in the current JSON value; example: `"key baz not found"`. + could not be resolved successfully in the current JSON value; example: `"key 'baz' not found"`. - Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token of a JSON pointer inside the patch cannot be resolved, e.g., `-` in a "remove" operation or `1a` for an array; example: `"unresolved reference token '-'"`. @@ -49,7 +49,10 @@ No guarantees, value may be corrupted by an unsuccessful patch operation. - Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from" location is a proper prefix of its "path" location. - Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was - unsuccessful. + unsuccessful. A "test" operation whose "path" does not exist is unsuccessful, too: the failed lookup is reported as a + failed test, even if the operation has no "value" member (which would otherwise be + [`parse_error.105`](../../home/exceptions.md#jsonexceptionparse_error105)). Which of the two errors is reported for + this combination depends on the order in which the operands of a comparison are evaluated, so do not rely on it. ## Complexity @@ -62,6 +65,10 @@ Unlike [`patch`](patch.md), `patch_inplace` applies the operation "in place" and That makes it faster for large documents by avoiding the copy. However, the JSON value might be corrupted if the function throws an exception. +Duplicate member names inside an operation object are resolved by the parser before the patch is applied (the last one +is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a +`remove` operation. + ## Examples ??? example "Example: apply a JSON patch in place" diff --git a/docs/mkdocs/docs/features/json_patch.md b/docs/mkdocs/docs/features/json_patch.md index 878f0084c..d35ea55b0 100644 --- a/docs/mkdocs/docs/features/json_patch.md +++ b/docs/mkdocs/docs/features/json_patch.md @@ -13,6 +13,12 @@ to the current JSON value by executing all operations from the patch, yielding t If the document is large and the original value is no longer needed, [`patch_inplace`](../api/basic_json/patch_inplace.md) applies the same operations in place instead. +!!! note "Duplicate member names" + + Duplicate member names inside an operation object are resolved by the parser before the patch is applied (the last + one is kept, see [`object_t`](../api/basic_json/object_t.md#behavior)), so + `#!json {"op": "add", "op": "remove", "path": "/a"}` is a `remove` operation. + ??? example "Example: apply a JSON Patch" The following code shows how a JSON patch is applied to a value. @@ -41,12 +47,23 @@ flowchart LR !!! success "Invariant" - For two JSON values *source* and *target*, the following code yields always true: + For two JSON values *source* and *target*, the following code yields true: ```cpp source.patch(diff(source, target)) == target; ``` + The equality holds up to [`operator==`](../api/basic_json/operator_eq.md), with these exceptions: + + - `NaN` and [discarded](../api/basic_json/is_discarded.md) values are not equal to themselves, so the invariant + cannot hold for them. `diff` emits a `replace` operation for such a value, and a `NaN` in a patch is serialized + as `#!json null`. + - Numbers that compare equal produce no operation even if their types differ: `diff(1, 1.0)`, `diff(1u, 1)`, and + `diff(0.0, -0.0)` return `#!json []`, so the patched value keeps the number type of *source*. + - A [binary value](binary_values.md) in a patch is serialized as an object + (`#!json {"bytes":[...],"subtype":null}`), so a patch that was dumped and parsed again no longer holds a binary + value. + ??? example "Example: create a JSON Patch from the difference of two values" The following code shows how a JSON patch is created as a diff for two JSON values. diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index de868737f..b6c78532d 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -15,6 +15,7 @@ using nlohmann::json; #endif #include +#include #include #include #include "make_test_data_available.hpp" @@ -490,7 +491,9 @@ TEST_CASE("JSON patch") } // A.13. Invalid JSON Patch Document - // not applicable + // not applicable: the library cannot tell the duplicate "op" members + // apart, because the parser resolves them (the last one is kept); see + // "duplicate member names in an operation" below SECTION("A.14. Escape Ordering") { @@ -2121,3 +2124,204 @@ TEST_CASE_TEMPLATE("JSON patch - diff round trip on hand-made documents", Json, roundtrip(Json(1), Json::parse("[1]")); } } + +TEST_CASE("JSON patch - behavior documented in the API reference") +{ + SECTION("duplicate member names in an operation") + { + // RFC 6902, A.13: the parser resolves the duplicates (the last one + // is kept), so this is a "remove" and not an "add" + const json patch = json::parse(R"([{"op": "add", "op": "remove", "path": "/a"}])"); + CHECK(patch == json::parse(R"([{"op": "remove", "path": "/a"}])")); + CHECK(json({{"a", 1}, {"b", 2}}).patch(patch) == json({{"b", 2}})); + } + + SECTION("add with a leading zero in an array index") + { + const json patch = R"([{"op": "add", "path": "/01", "value": 1}])"_json; + json doc = json::array(); +#if !defined(JSON_NOEXCEPTION) + CHECK_THROWS_AS(doc.patch(patch), json::parse_error&); + CHECK_THROWS_AS(doc.patch_inplace(patch), json::parse_error&); +#endif + CHECK(doc == json::array()); + } + + SECTION("test compares numbers by value") + { + const json doc = R"({"a": 1, "b": 0, "c": 1.5})"_json; + CHECK(doc.patch(R"([{"op": "test", "path": "/a", "value": 1.0}])"_json) == doc); + CHECK(doc.patch(R"([{"op": "test", "path": "/b", "value": -0.0}])"_json) == doc); + CHECK(doc.patch(R"([{"op": "test", "path": "/c", "value": 1.5}])"_json) == doc); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/a", "value": 2}])"_json), json::other_error&); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/a", "value": "1"}])"_json), json::other_error&); + } + + SECTION("test ignores the order of object members, but not the order of array elements") + { + const json doc = R"({"o": {"x": 1, "y": 2}, "a": [1, 2]})"_json; + CHECK(doc.patch(R"([{"op": "test", "path": "/o", "value": {"y": 2, "x": 1}}])"_json) == doc); + CHECK(doc.patch(R"([{"op": "test", "path": "/a", "value": [1, 2]}])"_json) == doc); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/a", "value": [2, 1]}])"_json), json::other_error&); + } + + SECTION("test on a path that does not exist") + { + const json doc = R"({"a": 1})"_json; + // with a "value" member, the failed lookup is a failed test + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/b", "value": 1}])"_json), json::other_error&); + CHECK_THROWS_AS(doc.patch(R"([{"op": "test", "path": "/b", "value": null}])"_json), json::other_error&); + // without it, an exception is thrown, too; which one depends on the + // order of evaluation of the operands of ==, so it is not checked + CHECK_THROWS(doc.patch(R"([{"op": "test", "path": "/b"}])"_json)); + } + + SECTION("patch() has the strong guarantee") + { + const json doc = R"({"a": [1, 2], "o": {"k": "v"}})"_json; + const json before = doc; + + const std::vector failing = + { + // remove of a member that does not exist + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "remove", "path": "/missing"}])"_json, + // replace of a member that does not exist + R"([{"op": "remove", "path": "/a/0"}, {"op": "replace", "path": "/missing", "value": 1}])"_json, + // move from a member that does not exist + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "move", "from": "/missing", "path": "/m"}])"_json, + // copy from a member that does not exist + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "copy", "from": "/missing", "path": "/m"}])"_json, + // out-of-range array index + R"([{"op": "add", "path": "/n", "value": 1}, {"op": "add", "path": "/a/5", "value": 1}])"_json, + // failing test after a successful operation + R"([{"op": "remove", "path": "/o"}, {"op": "test", "path": "/a", "value": []}])"_json, + }; + for (const auto& patch : failing) + { + CAPTURE(patch.dump()) + CHECK_THROWS_AS(doc.patch(patch), json::exception&); + CHECK(doc == before); + + json copy = doc; + CHECK_THROWS_AS(copy.patch(patch), json::exception&); + CHECK(copy == before); + } + } +} + +TEST_CASE("JSON patch - diff behavior documented in the API reference") +{ + SECTION("path is a verbatim prefix") + { + // not escaped and not validated + CHECK(json::diff(1, 2) == R"([{"op": "replace", "path": "", "value": 2}])"_json); + CHECK(json::diff(1, 2, "abc") == R"([{"op": "replace", "path": "abc", "value": 2}])"_json); + CHECK(json::diff(1, 2, "/root") == R"([{"op": "replace", "path": "/root", "value": 2}])"_json); + // an escaped token in a non-root prefix is kept as given + CHECK(json::diff(R"({"a": 1})"_json, R"({"a": 2})"_json, "/x~1y/~0") + == R"([{"op": "replace", "path": "/x~1y/~0/a", "value": 2}])"_json); + CHECK(json::diff(R"({"a": 1})"_json, R"({"a": 2})"_json, "/p") + == R"([{"op": "replace", "path": "/p/a", "value": 2}])"_json); + } + + SECTION("keys are escaped in the paths") + { + const json source = json::object(); + const json target = + { + {"", 0}, {"/", 1}, {"~", 2}, {"~1", 3}, {"~01", 4} + }; + const json patch = json::diff(source, target); + REQUIRE(patch.size() == 5); + + // the order of the operations is not checked + json paths = json::object(); + for (const auto& op : patch) + { + CHECK(op["op"] == "add"); + paths[op["path"].get()] = op["value"]; + } + CHECK(paths == json({{"/", 0}, {"/~1", 1}, {"/~0", 2}, {"/~01", 3}, {"/~001", 4}})); + CHECK(source.patch(patch) == target); + } + + SECTION("NaN is not equal to itself") + { + const json nan = std::numeric_limits::quiet_NaN(); + const json patch = json::diff(nan, nan); + CHECK(patch.size() == 1); + CHECK(patch[0]["op"] == "replace"); + CHECK(patch[0]["path"] == ""); + CHECK(patch[0]["value"].is_number_float()); + CHECK(nan.patch(patch) != nan); + + // a NaN in a patch is serialized as null + CHECK(json::parse(patch.dump())[0]["value"].is_null()); + + const json nested = {{"a", {1, nan}}}; + const json nested_patch = json::diff(nested, nested); + CHECK(nested_patch.size() == 1); + CHECK(nested_patch[0]["path"] == "/a/1"); + + // infinity is equal to itself + const json inf = std::numeric_limits::infinity(); + CHECK(json::diff(inf, inf).empty()); + } + + SECTION("discarded values are not equal to themselves") + { + const json discarded(json::value_t::discarded); + const json patch = json::diff(discarded, discarded); + CHECK(patch.size() == 1); + CHECK(patch[0]["op"] == "replace"); + CHECK(patch[0]["path"] == ""); + CHECK(patch[0]["value"].is_discarded()); + CHECK(discarded.patch(patch) != discarded); + } + + SECTION("numbers that compare equal produce no operation") + { + CHECK(json::diff(json(1), json(1.0)).empty()); + CHECK(json::diff(json(1.0), json(1)).empty()); + CHECK(json::diff(json(1U), json(1)).empty()); + CHECK(json::diff(json(1), json(1U)).empty()); + CHECK(json::diff(json(0.0), json(-0.0)).empty()); + + // the patched value keeps the number type of the source + const json source = 1; + const json patched = source.patch(json::diff(source, json(1.0))); + CHECK(patched.is_number_integer()); + CHECK(patched == json(1.0)); + + // different values are replaced + CHECK(json::diff(json(1), json(2.0)) == R"([{"op": "replace", "path": "", "value": 2.0}])"_json); + CHECK(json::diff(json(1), json(-1)) == R"([{"op": "replace", "path": "", "value": -1}])"_json); + CHECK(json::diff(json(1.0), json(1.5)) == R"([{"op": "replace", "path": "", "value": 1.5}])"_json); + CHECK(json::diff(json(1), json("1")) == R"([{"op": "replace", "path": "", "value": "1"}])"_json); + } + + SECTION("binary values") + { + const json a = json::binary({1, 2}); + const json b = json::binary({1, 3}); + CHECK(json::diff(a, a).empty()); + + const json patch = json::diff(a, b); + CHECK(patch.size() == 1); + CHECK(patch[0]["op"] == "replace"); + CHECK(patch[0]["path"] == ""); + CHECK(patch[0]["value"].is_binary()); + CHECK(a.patch(patch) == b); + + // serialized, the binary value is an object + const json parsed = json::parse(patch.dump()); + CHECK(parsed[0]["value"].is_object()); + CHECK(parsed[0]["value"] == R"({"bytes": [1, 3], "subtype": null})"_json); + } + + SECTION("a difference as a whole is a replace of the root") + { + CHECK(json::diff(json::array({1}), json::object()) == R"([{"op": "replace", "path": "", "value": {}}])"_json); + CHECK(json::diff(json(nullptr), json::array()) == R"([{"op": "replace", "path": "", "value": []}])"_json); + } +} From 81d66291c5569a5ebc92f6e163e44b22f1f97499 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 17:13:48 +0100 Subject: [PATCH 06/63] Create objects for pointer tokens that cannot be array indices (#5358) * create objects for tokens that cannot be array indices (#5357) When operator[](json_pointer) traverses a level that does not exist yet, the null value is turned into an array or an object depending on the reference token. The check only tested whether all characters are digits, so tokens that can never be a valid array index selected an array and then failed: - "01" (and any other token with a leading '0') threw parse_error.106 - the empty token threw out_of_range.404 Both tokens are valid object keys, and both work when the level already exists as an object, so creating the level changed the outcome. Test the token against the RFC 6901, Sect. 4 grammar for array indices instead, so that such tokens create an object. This only affects pointers that threw before. Signed-off-by: Niels Lohmann * Clarify that the token 0 is a valid array index in operator[] docs The note described valid array indices as "a nonempty sequence of digits without a leading 0", which read literally excludes the token "0" itself, contradicting both the code and the /foo/0/0/0 example in the next sentence. Spell out the RFC 6901 grammar instead. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/operator[].md | 11 ++-- include/nlohmann/detail/json_pointer.hpp | 14 ++-- single_include/nlohmann/json.hpp | 14 ++-- tests/src/unit-json_pointer.cpp | 66 +++++++++++++++++++ 4 files changed, 91 insertions(+), 14 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index 4356969a9..a4872dc39 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -137,10 +137,13 @@ Strong exception safety: if an exception occurs, the original value stays intact When the JSON pointer traverses intermediate levels that don't exist at all yet (not just a missing leaf), each missing level is created as an array or an object depending on whether the corresponding - pointer token parses as a non-negative integer: a numeric token creates an array, a non-numeric token - creates an object. For example, on an initially `#!json null` value, `/foo/0/0/0` creates nested arrays, - while `/foo/one/one/one` creates nested objects. This is not specified by the JSON Pointer RFC; it is - this library's own, intentional disambiguation rule. See also [JSON Pointer](../../features/json_pointer.md). + pointer token is a valid array index: the token `0`, a sequence of digits that does not begin with `0`, + or the token `-` creates an array, and every other token creates an object. For example, on an + initially `#!json null` value, `/foo/0/0/0` creates nested arrays, while `/foo/one/one/one` creates + nested objects. Tokens such as `01` or the empty token cannot be array indices (cf. RFC 6901, Sect. 4) + and therefore create objects, just as they would if the level already existed as an object. This is not + specified by the JSON Pointer RFC; it is this library's own, intentional disambiguation rule. See also + [JSON Pointer](../../features/json_pointer.md). !!! warning "Deprecation" diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 4560714ab..457985480 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -984,15 +984,19 @@ class json_pointer // convert null values to arrays or objects before continuing if (ptr->is_null()) { - // check if the reference token is a number - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const unsigned char x) + // check if the reference token is a valid array index, that is + // a nonempty sequence of digits without a leading '0' + // (cf. RFC 6901, Sect. 4); tokens that could never be a valid + // array index (such as "01" or "") are treated as object keys + const bool nums = !reference_token.empty() + && (reference_token.size() == 1 || reference_token[0] != '0') + && std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) { return std::isdigit(x); }); - // change value to an array for numbers or "-" or to object otherwise + // change value to an array for array indices or "-" or to object otherwise *ptr = (nums || reference_token == "-") ? detail::value_t::array : detail::value_t::object; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f0978d69e..f0b788c9e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -24569,15 +24569,19 @@ class json_pointer // convert null values to arrays or objects before continuing if (ptr->is_null()) { - // check if the reference token is a number - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const unsigned char x) + // check if the reference token is a valid array index, that is + // a nonempty sequence of digits without a leading '0' + // (cf. RFC 6901, Sect. 4); tokens that could never be a valid + // array index (such as "01" or "") are treated as object keys + const bool nums = !reference_token.empty() + && (reference_token.size() == 1 || reference_token[0] != '0') + && std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) { return std::isdigit(x); }); - // change value to an array for numbers or "-" or to object otherwise + // change value to an array for array indices or "-" or to object otherwise *ptr = (nums || reference_token == "-") ? detail::value_t::array : detail::value_t::object; diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 111fec815..1a4e726ed 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -493,6 +493,72 @@ TEST_CASE("JSON pointers") } } + SECTION("creating intermediate levels") + { + SECTION("tokens that are valid array indices create arrays") + { + json j; + j["/0"_json_pointer] = 1; + CHECK(j == json({1})); + + json j2; + j2["/2"_json_pointer] = 1; + CHECK(j2 == json({nullptr, nullptr, 1})); + + json j3; + j3["/-"_json_pointer] = 1; + CHECK(j3 == json({1})); + + json j4; + j4["/foo/0/0"_json_pointer] = 1; + CHECK(j4 == json({{"foo", {{1}}}})); + } + + SECTION("tokens that are no valid array indices create objects") + { + json j; + j["/one"_json_pointer] = 1; + CHECK(j == json({{"one", 1}})); + + // leading '0' can never be a valid array index (RFC 6901, Sect. 4) + json j2; + j2["/01"_json_pointer] = 1; + CHECK(j2 == json({{"01", 1}})); + + // the empty token is a valid object key, but no valid array index + json j3; + j3["/"_json_pointer] = 1; + CHECK(j3 == json({{"", 1}})); + } + + SECTION("creating a level yields the same result as reusing it (#5357)") + { + json j; + j["/a/b/01/d"_json_pointer] = "value"; + + json j_init = json::object(); + j_init["/a/b"_json_pointer] = json::object(); + j_init["/a/b/01/d"_json_pointer] = "value"; + + const json expected = json::parse(R"({"a":{"b":{"01":{"d":"value"}}}})"); + CHECK(j == expected); + CHECK(j_init == expected); + + // unflatten uses the same key + const json flat = {{"/a/b/01/d", "value"}}; + CHECK(flat.unflatten() == expected); + } + + SECTION("existing arrays still reject invalid indices") + { + json j = {1, 2, 3}; + CHECK_THROWS_WITH_AS(j["/01"_json_pointer], + "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&); + CHECK_THROWS_WITH_AS(j.at("/01"_json_pointer), + "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&); + } + } + SECTION("flatten") { json j = From d5da84249f47ae8086ba28d71e8c09e6724d40cd Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 18:25:44 +0100 Subject: [PATCH 07/63] Skip CBOR tags in front of map keys, validate indefinite text chunks, fix CBOR docs (#5927) * Skip CBOR tags in front of map keys and validate indefinite text chunks A tag in front of a text string map key was rejected with parse_error.113 even with cbor_tag_handler_t::ignore or store, although tags in front of values are handled. The tags are now skipped iteratively before the key is read; the handler error still rejects them. The chunks of an indefinite-length text string were concatenated before the single UTF-8 check, so a code point split over two chunks, or an invalid chunk, was accepted. RFC 8949, Section 3.2.3 requires every chunk to be valid on its own. With error_handler_t::strict, replace and ignore each chunk is now checked; keep is unchanged. The chunks are appended with append(ptr, len), so StringType needs nothing new. Signed-off-by: Niels Lohmann * docs: correct and extend the CBOR documentation Fix the negative-integer rows (they used MessagePack boundaries), the number_integer range of the 0x1B row, and the half-precision entry in the list of unused types; correct the NaN history (0xF9 since 3.8.0) and the tagged-item wording (only the tag directly in front of a byte string is stored, and only in the 0xD8..0xDB form). Document that to_cbor is not RFC 8949 deterministic, that duplicate keys are accepted (last wins), tags in front of keys, per-chunk UTF-8 validation, the exceptions parse_error.113, out_of_range.406/408 of from_cbor, the pointer and C array input traps, the partially written output on exceptions, and the store/ignore results in the binary values example. Drop the claim that the CBOR reader needs StringType::append(const StringType&) and BinaryType::insert(pos, first, last). Signed-off-by: Niels Lohmann * Regenerate single_include and apply astyle Result of make amalgamate (including make pretty) for the preceding changes. Signed-off-by: Niels Lohmann * Address review of the CBOR changes Cite RFC 8949 Section 5.3.1 for duplicate keys, describe the out_of_range.408 condition precisely, rewrap a long line, and update the stale get_string comment (per-chunk UTF-8 check). single_include regenerated. Signed-off-by: Niels Lohmann * docs: drop discarded-value remarks Remove the mention that a discarded value cannot be serialized to CBOR (type_error.321) from the exception-safety and mapping sections. The integer table and all added prose already follow the table-format and Oxford-comma rules. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../docs/api/basic_json/cbor_tag_handler_t.md | 6 +- docs/mkdocs/docs/api/basic_json/from_cbor.md | 16 +- docs/mkdocs/docs/api/basic_json/to_cbor.md | 3 +- .../docs/features/binary_formats/cbor.md | 56 +++++-- docs/mkdocs/docs/features/binary_values.md | 11 ++ .../features/types/template_parameters.md | 6 +- .../nlohmann/detail/input/binary_reader.hpp | 86 ++++++++-- single_include/nlohmann/json.hpp | 86 ++++++++-- tests/src/unit-cbor.cpp | 151 +++++++++++++++++- 9 files changed, 372 insertions(+), 49 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md index 55a7954d5..528fb199e 100644 --- a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md @@ -15,11 +15,13 @@ error : report a parse error in case of a tag (the `from_cbor` overloads throw a `parse_error` exception by default) ignore -: ignore tags +: ignore tags (also in front of a text string map key) store : store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are - read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored. + read as if the tag were ignored. If several tags precede a byte string, only the tag directly in front of it is + stored, and only when it has the form 0xd8..0xdb (a tag 0..23 written as 0xc0..0xd7 is skipped). Tags in front of a + text string map key are skipped. ## Examples diff --git a/docs/mkdocs/docs/api/basic_json/from_cbor.md b/docs/mkdocs/docs/api/basic_json/from_cbor.md index 4fcd7fe98..1c14d550d 100644 --- a/docs/mkdocs/docs/api/basic_json/from_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/from_cbor.md @@ -32,8 +32,10 @@ The exact mapping and its limitations are described on a [dedicated page](../../ - 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 C-style array of characters (its terminating NUL is read as trailing data: `parse_error.110` unless `strict` is + `false`) + - a pointer to a null-terminated string of single byte characters (read up to the first 0x00 byte, which is a valid + CBOR byte) - 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`) @@ -89,8 +91,14 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were used in the given input or if the input is not valid CBOR - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of - other types are not supported, as JSON object keys are always strings), or if a string value or object key is not - valid UTF-8 and `error_handler` is `strict` + other types are not supported, as JSON object keys are always strings), if the head of a string or byte string is + malformed (including a chunk of the wrong type or an indefinite-length string nested inside an indefinite-length + string), or if a string value or object key (or one chunk of an indefinite-length string) is not valid UTF-8 and + `error_handler` is `strict` +- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if an array or map announces + a length that `std::size_t` cannot hold, or that equals `std::size_t(-1)` (for example `9B FF FF FF FF FF FF FF FF`) +- Throws [out_of_range.406](../../home/exceptions.md#jsonexceptionout_of_range406) if a floating-point number does not + fit into `number_float_t` ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json/to_cbor.md b/docs/mkdocs/docs/api/basic_json/to_cbor.md index 6508864e1..f0be593f0 100644 --- a/docs/mkdocs/docs/api/basic_json/to_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/to_cbor.md @@ -42,7 +42,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../ ## Exception safety -Strong guarantee: if an exception is thrown, there are no changes in the JSON value. +Strong guarantee: if an exception is thrown, there are no changes in the JSON value. Bytes that were already written to +the output adapter are not taken back. ## Exceptions diff --git a/docs/mkdocs/docs/features/binary_formats/cbor.md b/docs/mkdocs/docs/features/binary_formats/cbor.md index 8933630da..35d72d6a1 100644 --- a/docs/mkdocs/docs/features/binary_formats/cbor.md +++ b/docs/mkdocs/docs/features/binary_formats/cbor.md @@ -11,7 +11,7 @@ extremely small code sizes, fairly small message size, and extensibility without ## Byte layout -CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly, so a reader always knows how many items to expect without a terminator. +CBOR encodes every data item as a *head* followed by optional content. The first byte of the head holds the *major type* in its upper three bits (unsigned integer, negative integer, byte string, text string, array, map, tag, or simple/float) and an *additional information* value in its lower five bits. Additional information below 24 is the argument itself (for example, a small integer or a short length); the values 24 to 27 announce that the argument follows in 1, 2, 4, or 8 big-endian bytes. Strings, arrays, and maps are prefixed with their length (or count of elements/pairs), and content follows directly. A reader of definite-length items knows how many items to expect without a terminator; indefinite-length items (additional information 31) are instead closed by the "break" byte 0xFF, which `from_cbor` reads but `to_cbor` never writes. Consider the following JSON value (shown formatted for readability): @@ -52,16 +52,16 @@ The library uses the following mapping from JSON values types to CBOR types acco | null | `null` | Null | 0xF6 | | boolean | `true` | True | 0xF5 | | boolean | `false` | False | 0xF4 | -| number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B | -| number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A | -| number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 | -| number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 | +| number_integer | -9223372036854775808..-4294967297 | Negative integer (8 bytes follow) | 0x3B | +| number_integer | -4294967296..-65537 | Negative integer (4 bytes follow) | 0x3A | +| number_integer | -65536..-257 | Negative integer (2 bytes follow) | 0x39 | +| number_integer | -256..-25 | Negative integer (1 byte follow) | 0x38 | | number_integer | -24..-1 | Negative integer | 0x20..0x37 | | number_integer | 0..23 | Integer | 0x00..0x17 | | number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 | | number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 | | number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A | -| number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B | +| number_integer | 4294967296..9223372036854775807 | Unsigned integer (8 bytes follow) | 0x1B | | number_unsigned | 0..23 | Integer | 0x00..0x17 | | number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 | | number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 | @@ -95,7 +95,24 @@ see "binary" cells in the table above. !!! success "Complete mapping" - The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. + The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value, with one + exception: an integer outside the range of CBOR integers (only possible with a number type wider than 64 bits) + throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407). + +!!! info "Not a deterministic encoding" + + `to_cbor` does not produce the deterministic encoding of + [RFC 8949, Section 4.2.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-4.2.1), and not always the preferred + serialization of Section 4.1: + + - floating-point numbers are written as single precision (0xFA) when that is exact and as double precision (0xFB) + otherwise; half precision (0xF9) is used only for `NaN` and `Infinity`/`-Infinity` (`1.0` is `fa 3f 80 00 00`); + - a binary subtype 0..23 is written as `d8 xx` instead of the one-byte tag 0xC0..0xD7 (`d8 01 41 01`); + - object members are written in the order of `object_t` (for the default `std::map`: sorted by the key string, not + by the encoded key bytes). + + Integers and lengths use the shortest form, and indefinite-length items are never written. Decoders must accept all + of this; the RFC does not require the preferred serialization. !!! info "NaN/infinity handling" @@ -105,9 +122,8 @@ see "binary" cells in the table above. !!! note - Prior to version 3.13.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB, - 9 bytes total), because the check used to select a smaller encoding compared magnitudes with NaN, which is - always `false` and caused the intended half-precision path to be skipped. + Prior to version 3.8.0, NaN and Infinity were instead serialized as a CBOR double-precision float (type 0xFB, + 9 bytes total). !!! info "Unused CBOR types" @@ -124,7 +140,6 @@ see "binary" cells in the table above. - expected conversions (0xD5..0xD7) - simple values (0xE0..0xF3, 0xF8) - undefined (0xF7) - - half-precision floats (0xF9) - break (0xFF) !!! info "Tagged items" @@ -226,10 +241,20 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https:// ([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a general-purpose CBOR library instead. + A tag in front of a text string key is handled like a tag in front of a value: it is skipped with + `cbor_tag_handler_t::ignore` or `cbor_tag_handler_t::store` and rejected with the default `error`. A tag does not + turn a key of another type into a text string. + + A map with duplicate keys is well-formed, but invalid under + [RFC 8949, Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1). `from_cbor` accepts it and the + last value wins; the [SAX interface](../parsing/sax_interface.md) reports every occurrence. + !!! warning "Ill-formed UTF-8 in text strings" [RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings (major - type 3) to be valid UTF-8, but leaves it up to the decoder whether to enforce this, so checking is opt-in: with the + type 3) to be valid UTF-8 (a string with ill-formed UTF-8 is "well-formed but invalid"), and + [Section 5.3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3.1) leaves it up to the decoder whether to + verify this, so checking is opt-in: with the [`error_handler`](../../api/basic_json/from_cbor.md) parameter left at `keep` (the default), `from_cbor()` accepts a text string (object keys included) whose bytes are not valid UTF-8 and hands them back unchanged. Passing `error_handler_t::strict` makes `from_cbor()` check and throw @@ -242,9 +267,14 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https:// [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception instead. Byte strings (major type 2) are unaffected, since they are not required to hold text. + The chunks of an indefinite-length text string are checked one by one, as required by + [RFC 8949, Section 3.2.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3): a code point cannot be spread + over two chunks. With `error_handler_t::strict` such a string throws `parse_error.113`; `replace` and `ignore` + sanitize each chunk on its own. + !!! warning "Tagged items" - Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the innermost one is stored. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string. + Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. Passing `cbor_tag_handler_t::store` to function `from_cbor` stores tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored. If several tags precede a byte string, only the tag directly in front of it is stored, and only when it has the form 0xD8..0xDB (a tag 0..23 written as 0xC0..0xD7 is skipped); for example, `D8 05 C1 41 AA` yields subtype `null`, whereas `C1 D8 05 41 AA` yields 5. Tags in front of a text string map key are skipped as well. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string. ??? example "Example: deserialize a JSON value from CBOR" diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index b4c3bab6d..dd683f469 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -301,6 +301,8 @@ representation using the length of the byte array. [`json::from_cbor`](../api/basic_json/from_cbor.md) (see [`cbor_tag_handler_t`](../api/basic_json/cbor_tag_handler_t.md)). + With `ignore`, the subtype is dropped (`"subtype": null`); with `store`, the tag is kept (`"subtype": 42`): + ```json { "binary": { @@ -310,6 +312,15 @@ representation using the length of the byte array. } ``` + ```json + { + "binary": { + "bytes": [202, 254, 186, 190], + "subtype": 42 + } + } + ``` + ### MessagePack [MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index 29329fea8..cc079cd72 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -368,8 +368,7 @@ using array_t = ArrayType>; `#!cpp std::from_chars` is not available or declines the token, and `std::strtold` reads up to the null character. A type whose `data()` is not null-terminated does not fail to compile; with such a `NumberFloatType` it can silently misparse floating-point numbers. -- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md), and `append(const StringType&)`, - used by the CBOR reader for indefinite-length strings. The library's internal string concatenation additionally has +- `append(const char*, size_type)`, used by [`dump`](../../api/basic_json/dump.md) and by the binary readers. The library's internal string concatenation additionally has to append a `#!cpp char` and a `#!cpp const char*`; for each it selects between `append(arg)`, `#!cpp operator+=`, `append(first, last)`, and `append(data, size)`. - The comparison operator `==` against another `StringType`, and `<` for use as a key of the chosen @@ -679,8 +678,7 @@ using binary_t = nlohmann::byte_container_with_subtype; [`StringType`](#stringtype), whose non-`#!cpp const` `data()` is C++17 and later only. - Default-constructible, copy-constructible, and move-constructible. - Member functions `size()`, `empty()`, `data()`, `resize()`, `operator[]`, `back()`, `begin()`, `end()`, `cbegin()`, - and `cend()` with random-access iterators, and `insert(pos, first, last)`, which the CBOR reader uses to join the - chunks of an indefinite-length byte string. `push_back()` is **not** required. + and `cend()` with random-access iterators. `push_back()` is **not** required. - Comparison operators: `==` is used by [`byte_container_with_subtype`](../../api/byte_container_with_subtype/index.md), the relational operators by [`basic_json`'s comparison operators](../../api/basic_json/operator_le.md). diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index d3c3d5bdc..929ac7573 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1218,6 +1218,10 @@ class binary_reader // level as required by RFC 8949, Section 3.2.3 bool indefinite = false; + // the chunk currently being validated; only used for the chunks of an + // indefinite-length string when the UTF-8 check can do something + string_t chunk; + while (true) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string"))) @@ -1237,26 +1241,82 @@ class binary_reader } // a break marker closes the indefinite-length string; outside - // of one it falls through to the error below + // of one it falls through to the error below. Every chunk has + // been checked on its own, so there is nothing left to check. if (indefinite && current == 0xFF) { - return check_string_utf8(result, context); - } - - if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite))) - { - return false; + return true; } if (!indefinite) { - return check_string_utf8(result, context); + return get_cbor_string_chunk(result, false) && check_string_utf8(result, context); + } + + if (error_handler == error_handler_t::keep) + { + // nothing to check: read the chunk directly into the result + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true))) + { + return false; + } + } + else + { + // RFC 8949, Section 3.2.3: each chunk is a text string of its + // own and must be valid UTF-8 by itself, i.e., a code point + // cannot be spread over two chunks + chunk.clear(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context))) + { + return false; + } + result.append(chunk.data(), chunk.size()); } get(); } } + /*! + @brief skips the tags in front of a CBOR object key + + A tag may wrap any data item, so it may also wrap a key. Like in front of a + value, the tags are skipped for the handlers ignore and store; with the + handler error they are left to @ref get_cbor_object_key, which rejects the + key. The tag loop is iterative, so the number of tags is not limited by the + recursion depth. + + @param[in] tag_handler how CBOR tags should be treated + + @return whether the tags could be skipped (false on EOF in a tag argument) + + @pre @a current is the first byte of the key + */ + bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler) + { + if (tag_handler == cbor_tag_handler_t::error) + { + return true; + } + + // 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in + // the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB) + while (current >= 0xC0 && current <= 0xDB) + { + if (current >= 0xD8) + { + std::uint64_t tag_to_ignore{}; + if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore))) + { + return false; + } + } + get(); + } + return true; + } + /*! @brief reads a CBOR object key @@ -1623,6 +1683,10 @@ class binary_reader if (top.is_object) { + if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler))) + { + return false; + } if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key))) { return false; @@ -4130,9 +4194,9 @@ class binary_reader // sequence), UBJSON, BJData, or BSON requires a decoder to reject // ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict, // rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via - // @ref error_handler, applied once the whole string (all chunks of - // an indefinite-length CBOR string included) has been assembled, by - // @ref check_string_utf8 at the call site. + // @ref error_handler, applied by @ref check_string_utf8 at the call + // site to the whole string or, for an indefinite-length CBOR string, + // to each chunk on its own (RFC 8949, Section 3.2.3). return get_bytes(len, "string", result); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f0b788c9e..5db064c63 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18685,6 +18685,10 @@ class binary_reader // level as required by RFC 8949, Section 3.2.3 bool indefinite = false; + // the chunk currently being validated; only used for the chunks of an + // indefinite-length string when the UTF-8 check can do something + string_t chunk; + while (true) { if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("string"))) @@ -18704,26 +18708,82 @@ class binary_reader } // a break marker closes the indefinite-length string; outside - // of one it falls through to the error below + // of one it falls through to the error below. Every chunk has + // been checked on its own, so there is nothing left to check. if (indefinite && current == 0xFF) { - return check_string_utf8(result, context); - } - - if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite))) - { - return false; + return true; } if (!indefinite) { - return check_string_utf8(result, context); + return get_cbor_string_chunk(result, false) && check_string_utf8(result, context); + } + + if (error_handler == error_handler_t::keep) + { + // nothing to check: read the chunk directly into the result + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, true))) + { + return false; + } + } + else + { + // RFC 8949, Section 3.2.3: each chunk is a text string of its + // own and must be valid UTF-8 by itself, i.e., a code point + // cannot be spread over two chunks + chunk.clear(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(chunk, true) || !check_string_utf8(chunk, context))) + { + return false; + } + result.append(chunk.data(), chunk.size()); } get(); } } + /*! + @brief skips the tags in front of a CBOR object key + + A tag may wrap any data item, so it may also wrap a key. Like in front of a + value, the tags are skipped for the handlers ignore and store; with the + handler error they are left to @ref get_cbor_object_key, which rejects the + key. The tag loop is iterative, so the number of tags is not limited by the + recursion depth. + + @param[in] tag_handler how CBOR tags should be treated + + @return whether the tags could be skipped (false on EOF in a tag argument) + + @pre @a current is the first byte of the key + */ + bool skip_cbor_key_tags(const cbor_tag_handler_t tag_handler) + { + if (tag_handler == cbor_tag_handler_t::error) + { + return true; + } + + // 0xC0..0xDB: tag with the tag number in the head (0xC0..0xD7) or in + // the 1, 2, 4, or 8 bytes that follow (0xD8..0xDB) + while (current >= 0xC0 && current <= 0xDB) + { + if (current >= 0xD8) + { + std::uint64_t tag_to_ignore{}; + if (JSON_HEDLEY_UNLIKELY(!get_cbor_argument(tag_to_ignore))) + { + return false; + } + } + get(); + } + return true; + } + /*! @brief reads a CBOR object key @@ -19090,6 +19150,10 @@ class binary_reader if (top.is_object) { + if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler))) + { + return false; + } if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key))) { return false; @@ -21597,9 +21661,9 @@ class binary_reader // sequence), UBJSON, BJData, or BSON requires a decoder to reject // ill-formed UTF-8. Checking (and, with @ref error_handler_t::strict, // rejecting, or with `replace`/`ignore`, sanitizing) is opt-in via - // @ref error_handler, applied once the whole string (all chunks of - // an indefinite-length CBOR string included) has been assembled, by - // @ref check_string_utf8 at the call site. + // @ref error_handler, applied by @ref check_string_utf8 at the call + // site to the whole string or, for an indefinite-length CBOR string, + // to each chunk on its own (RFC 8949, Section 3.2.3). return get_bytes(len, "string", result); } diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 9ee371495..44cfa7723 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -1807,6 +1807,93 @@ TEST_CASE("CBOR") CHECK(json::from_cbor(std::vector({0xa1, 0xff, 0x01}), true, false).is_discarded()); } + SECTION("tag in front of a key") + { + const auto ignore = json::cbor_tag_handler_t::ignore; + const auto store = json::cbor_tag_handler_t::store; + const json expected = {{"a", 10}}; + + // the tags are skipped in front of a text string key, like in + // front of a value + const std::vector> tagged = + { + {0xA1, 0xC0, 0x61, 0x61, 0x0A}, // tag in the head + {0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A}, // 1-byte tag number + {0xA1, 0xD9, 0x01, 0x2C, 0x61, 0x61, 0x0A}, // 2-byte tag number + {0xA1, 0xDA, 0x00, 0x01, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 4-byte tag number + {0xA1, 0xDB, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x61, 0x0A}, // 8-byte tag number + {0xA1, 0xC0, 0xD8, 0x05, 0xC1, 0x61, 0x61, 0x0A}, // chained tags + {0xBF, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // indefinite-length map + {0xBF, 0x61, 0x62, 0x01, 0xC0, 0x61, 0x61, 0x0A, 0xFF}, // second key tagged + {0xA1, 0xC0, 0x7F, 0x61, 0x61, 0xFF, 0x0A}, // indefinite-length text key + {0xA1, 0xC0, 0x61, 0x61, 0xC0, 0x0A}, // tagged key and value + }; + + for (const auto& input : tagged) + { + CAPTURE(input) + json const j_ignore = json::from_cbor(input, true, true, ignore); + json const j_store = json::from_cbor(input, true, true, store); + CHECK(j_ignore.at("a") == 10); + CHECK(j_store.at("a") == 10); + CHECK(j_ignore == j_store); + } + CHECK(json::from_cbor(tagged[0], true, true, ignore) == expected); + CHECK(json::from_cbor(tagged[1], true, true, store) == expected); + + // nested maps + CHECK(json::from_cbor(std::vector({0xA1, 0xC0, 0x61, 0x61, 0xA1, 0xD8, 0x05, 0x61, 0x62, 0x01}), true, true, ignore) == json{{"a", {{"b", 1}}}}); + + // the tag handler error still rejects every tag + for (const auto& input : tagged) + { + CAPTURE(input) + json _; + CHECK_THROWS_AS(_ = json::from_cbor(input), json::parse_error&); + } + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xD8, 0x05, 0x61, 0x61, 0x0A})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a tag; last byte: 0xD8", json::parse_error&); + } + + // a tag does not turn a key of another type into a text string + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xC0, 0x01, 0x01}), true, true, ignore), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xD8, 0x05, 0x41, 0x61, 0x01}), true, true, store), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR object key: only string keys are supported, but found a byte string; last byte: 0x41", json::parse_error&); + CHECK(json::from_cbor(std::vector({0xA1, 0xC0, 0x01, 0x01}), true, false, ignore).is_discarded()); + } + + // end of input after the tag, or inside the tag number + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector({0xA1, 0xC0}), true, true, ignore), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&); + CHECK_THROWS_AS(_ = json::from_cbor(std::vector({0xA1, 0xD8}), true, true, ignore), json::parse_error&); + CHECK_THROWS_AS(_ = json::from_cbor(std::vector({0xA1, 0xDB, 0x00, 0x00}), true, true, store), json::parse_error&); + CHECK_THROWS_AS(_ = json::from_cbor(std::vector({0xBF, 0xC0}), true, true, store), json::parse_error&); + } + + // SAX: the key event is reported without the tag (start_object, + // key, number_unsigned, end_object are four events) + { + const std::vector input = {0xA1, 0xC0, 0x61, 0x61, 0x0A}; + SaxCountdown four(4); + CHECK(json::sax_parse(input, &four, json::input_format_t::cbor, true, false, false, ignore)); + SaxCountdown three(3); + CHECK(!json::sax_parse(input, &three, json::input_format_t::cbor, true, false, false, ignore)); + SaxCountdown error_handler(4); + CHECK(!json::sax_parse(input, &error_handler, json::input_format_t::cbor)); + } + + // a long chain of tags needs no recursion + { + std::vector input = {0xA1}; + input.insert(input.end(), 100000, 0xC0); + input.insert(input.end(), {0x61, 0x61, 0x0A}); + CHECK(json::from_cbor(input, true, true, ignore) == expected); + } + } + SECTION("non-string key (see #2766 and #3381)") { // only text strings map to JSON object keys; any other key is @@ -1915,9 +2002,10 @@ TEST_CASE("CBOR") { json _; - // the chunks are concatenated as is, without checking that each - // chunk is valid UTF-8 on its own (RFC 8949, Section 3.2.3), so - // a code point split across two chunks yields a valid string + // with the default error handler keep, the chunks are concatenated + // as is, without checking that each chunk is valid UTF-8 on its + // own (RFC 8949, Section 3.2.3), so a code point split across two + // chunks yields a valid string CHECK_NOTHROW(_ = json::from_cbor(std::vector({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}))); CHECK(_ == "\xc3\xa9"); CHECK(_.dump() == "\"\xc3\xa9\""); @@ -1937,6 +2025,63 @@ TEST_CASE("CBOR") CHECK(json::from_cbor(std::vector({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6"); } + SECTION("ill-formed UTF-8 in a chunk of an indefinite-length string (RFC 8949, Section 3.2.3)") + { + const auto strict = json::error_handler_t::strict; + const auto replace = json::error_handler_t::replace; + const auto ignore = json::error_handler_t::ignore; + const auto keep = json::error_handler_t::keep; + const auto tags = json::cbor_tag_handler_t::error; + + // a code point split over two chunks + const std::vector split = {0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}; + // a 4-byte code point split over two chunks + const std::vector split_emoji = {0x7f, 0x62, 0xf0, 0x9f, 0x62, 0x98, 0x80, 0xff}; + // the same in an object key + const std::vector split_key = {0xa1, 0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff, 0x01}; + // a valid chunk followed by one with a truncated code point + const std::vector truncated = {0x7f, 0x62, 0x41, 0xc3, 0x61, 0xa9, 0xff}; + + // strict: the string is invalid + for (const auto& input : + { + split, split_emoji, split_key, truncated + }) + { + CAPTURE(input) + json _; + CHECK_THROWS_AS(_ = json::from_cbor(input, true, true, tags, strict), json::parse_error&); + CHECK(json::from_cbor(input, true, false, tags, strict).is_discarded()); + } + { + json _; + CHECK_THROWS_WITH_AS(_ = json::from_cbor(split, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_cbor(split_key, true, true, tags, strict), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing CBOR key: invalid string: ill-formed UTF-8 byte", json::parse_error&); + } + + // replace and ignore: every chunk is sanitized on its own + CHECK(json::from_cbor(split, true, true, tags, replace) == "\xEF\xBF\xBD\xEF\xBF\xBD"); + CHECK(json::from_cbor(split, true, true, tags, ignore) == ""); + CHECK(json::from_cbor(truncated, true, true, tags, replace) == "A\xEF\xBF\xBD\xEF\xBF\xBD"); + CHECK(json::from_cbor(truncated, true, true, tags, ignore) == "A"); + CHECK(json::from_cbor(split_key, true, true, tags, replace) == json{{"\xEF\xBF\xBD\xEF\xBF\xBD", 1}}); + + // keep: unchanged, the chunks are concatenated as is + CHECK(json::from_cbor(split, true, true, tags, keep) == "\xc3\xa9"); + CHECK(json::from_cbor(split_emoji, true, true, tags, keep) == "\xf0\x9f\x98\x80"); + + // conforming input is unaffected by the error handler + const std::vector valid = {0x7f, 0x62, 0xc3, 0xa9, 0x64, 0xf0, 0x9f, 0x98, 0x80, 0x60, 0xff}; + for (const auto handler : + { + strict, replace, ignore, keep + }) + { + CHECK(json::from_cbor(valid, true, true, tags, handler) == "\xc3\xa9\xf0\x9f\x98\x80"); + CHECK(json::from_cbor(std::vector({0x7f, 0xff}), true, true, tags, handler) == ""); + } + } + SECTION("many chunks in indefinite-length string") { constexpr std::size_t chunks = 100000; From 2bbe80c0435dba2805747d81482fd2acc7861868 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 18:25:44 +0100 Subject: [PATCH 08/63] Document MessagePack ext types and fix related doc errors (#5928) * Document MessagePack ext types and fix related doc errors Add an "Extension types (ext, fixext)" section to the MessagePack page: ext/fixext are read as binary, the signed ext type is exposed as an unsigned subtype 0..255 (-1 reads as 255), Timestamp is not interpreted, subtypes are always written as ext/fixext, and subtypes above 255 throw out_of_range.415. Add the 0xC1 row to the deserialization table, note that duplicate keys keep the last value, and correct the NaN/Infinity explanation, the "31 bytes / 15 elements" wording, the ill-formed UTF-8 sentence, and the generic 0xC1-key error message. Add fixext16 and the unsigned subtype mapping to binary_t.md and binary_values.md, reword "maximum of the MessagePack ext type" in to_msgpack.md and exceptions.md, and mark MessagePack deserialization as incomplete (non-string keys) in the binary formats overview. Signed-off-by: Niels Lohmann * Address review of the MessagePack documentation Name parse_error.113 for a 0xC1 map key, move the duplicate-key sentence out of the ext section, and keep the 0xC1 row in marker order. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/binary_t.md | 5 ++- docs/mkdocs/docs/api/basic_json/to_msgpack.md | 2 +- .../docs/features/binary_formats/index.md | 2 +- .../features/binary_formats/messagepack.md | 37 ++++++++++++++++--- docs/mkdocs/docs/features/binary_values.md | 5 ++- docs/mkdocs/docs/home/exceptions.md | 2 +- 6 files changed, 41 insertions(+), 12 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/binary_t.md b/docs/mkdocs/docs/api/basic_json/binary_t.md index da13c22a2..2e9517429 100644 --- a/docs/mkdocs/docs/api/basic_json/binary_t.md +++ b/docs/mkdocs/docs/api/basic_json/binary_t.md @@ -90,8 +90,9 @@ type `#!cpp binary_t*` must be dereferenced. - MessagePack - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1, - fixext2, fixext4, fixext8) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The subtype is - then added as a signed 8-bit integer. + fixext2, fixext4, fixext8, fixext16) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. The + subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative (reserved) ext + types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255. - If no subtype is given, the bin family (bin8, bin16, bin32) is used. - BSON diff --git a/docs/mkdocs/docs/api/basic_json/to_msgpack.md b/docs/mkdocs/docs/api/basic_json/to_msgpack.md index dca66271d..181948ef1 100644 --- a/docs/mkdocs/docs/api/basic_json/to_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/to_msgpack.md @@ -53,7 +53,7 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va value, array, or object exceeds 4294967295, the maximum MessagePack can store; example: `"MessagePack length 4294967296 exceeds maximum of 4294967295"` - Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value - exceeds 255, the maximum of the MessagePack ext type; example: + exceeds 255, the largest value the library can store in the one-byte MessagePack ext type; example: `"subtype 70000 is too large for the MessagePack ext type (max 255)"` - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` diff --git a/docs/mkdocs/docs/features/binary_formats/index.md b/docs/mkdocs/docs/features/binary_formats/index.md index 8024b99cf..9c55a9a52 100644 --- a/docs/mkdocs/docs/features/binary_formats/index.md +++ b/docs/mkdocs/docs/features/binary_formats/index.md @@ -22,7 +22,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors. | 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 | +| MessagePack | complete | incomplete, but all JSON types are supported | | UBJSON | complete | complete | ### Binary values diff --git a/docs/mkdocs/docs/features/binary_formats/messagepack.md b/docs/mkdocs/docs/features/binary_formats/messagepack.md index 90e4c295e..cca6e7660 100644 --- a/docs/mkdocs/docs/features/binary_formats/messagepack.md +++ b/docs/mkdocs/docs/features/binary_formats/messagepack.md @@ -11,7 +11,7 @@ extra byte in addition to the strings themselves. ## Byte layout -MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 or 15 elements) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed. +MessagePack encodes each value as a one-byte *format marker*, optionally followed by a length or the payload. The marker range is partitioned to keep common values small: small non-negative integers (0 to 127) and small negative integers (-32 to -1) are the marker byte itself; short strings, arrays, and maps (up to 31 bytes for strings and up to 15 elements for arrays and maps) hide their length in the marker's lower bits. Larger values use dedicated markers that announce the width (8, 16, 32, or 64 bits) of the length or number that follows, in big-endian byte order. Like CBOR, containers are length-prefixed, so no terminator is needed. Consider the following JSON value (shown formatted for readability): @@ -109,8 +109,9 @@ specification: !!! note Prior to version 3.13.0, NaN and Infinity were instead serialized as a MessagePack float 64 (type 0xCB, 9 bytes - total), because the check used to select the smaller float 32 encoding compared magnitudes with NaN, which is - always `false` and caused the float 32 path to be skipped. + total), because the check used to select the smaller float 32 encoding failed for NaN (all comparisons are `false`) + and for Infinity and -Infinity (outside the float range checked against `std::numeric_limits::lowest()` and + `max()`), so the float 32 path was skipped. ??? example "Example: serialize a JSON value to MessagePack" @@ -135,6 +136,7 @@ The library maps MessagePack types to JSON value types as follows: | fixarray | array | 0x90..0x9F | | fixstr | string | 0xA0..0xBF | | nil | `null` | 0xC0 | +| (never used) | – | 0xC1 | | false | `false` | 0xC2 | | true | `true` | 0xC3 | | float 32 | number_float | 0xCA | @@ -167,6 +169,30 @@ The library maps MessagePack types to JSON value types as follows: | fixext 16 | binary | 0xD8 | | negative fixint | number_integer | 0xE0-0xFF | +The byte 0xC1 is reserved as "never used" by the specification. As a value it is rejected with +[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) (`invalid byte: 0xC1`); as a map key it is +rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) and the generic "expected length +specification" message, because the key is read as a string. + +Duplicate keys in a map are accepted; the last value wins. + +### Extension types (ext, fixext) + +MessagePack ext and fixext values are read as binary values, and the ext type byte is stored as the binary subtype. +Although the specification defines the type as a signed 8-bit integer (0 to 127 for applications, -128 to -1 reserved, +with -1 being the Timestamp extension), the library reads it as an unsigned byte: a negative ext type `n` becomes +subtype `256+n`, so ext type -1 is read as subtype 255 and -128 as subtype 128. The Timestamp extension is not +interpreted; it is returned as binary with subtype 255. Use +[`has_subtype()`](../../api/byte_container_with_subtype/has_subtype.md) to tell a `bin` value (no subtype) from an ext +value (subtype, even if it is 0). + +When serializing, a binary value with a subtype is always written as fixext (1, 2, 4, 8, or 16 bytes) or ext, even +for subtype 0 (`d6 00 01 02 03 04`); a binary value without a subtype is written as bin. Subtypes 128 to 255 are +written as the reserved negative ext types (255 as -1), so `json::binary({1,2,3,4}, 255)` becomes +`d6 ff 01 02 03 04`, which other MessagePack libraries decode as a 32-bit timestamp. A subtype above 255 throws +[`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415). The payload length is limited to +4294967295 bytes like all other lengths. + !!! info Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`. @@ -177,7 +203,8 @@ The library maps MessagePack types to JSON value types as follows: JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with - `allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance: + `allow_exceptions` set to `false`, a discarded value). For most key types the message names the type that was + found, for instance: ``` [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0 @@ -189,7 +216,7 @@ The library maps MessagePack types to JSON value types as follows: !!! warning "Ill-formed UTF-8 in string values" The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain - a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged. + a byte sequence that is not valid UTF-8, and recommends that a deserializer provide a way to obtain the original bytes. This library follows that by default: with its [`error_handler`](../../api/basic_json/from_msgpack.md) parameter left at `keep` (the default), `from_msgpack()` reads `str` bytes (object keys included) as-is, without validating them, so such a value diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index dd683f469..f5a87472b 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -324,8 +324,9 @@ representation using the length of the byte array. ### MessagePack [MessagePack](binary_formats/messagepack.md) supports binary values and subtypes. If a subtype is given, the ext family -is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, ext8, ext16, and -ext32. The subtype is then added as a signed 8-bit integer. +is used. The library will choose the smallest representation among fixext1, fixext2, fixext4, fixext8, fixext16, ext8, +ext16, and ext32. The subtype is then written as the signed 8-bit ext type: subtypes 128 to 255 map to the negative +(reserved) ext types, and on reading the ext type is exposed as an unsigned subtype from 0 to 255. If no subtype is given, the bin family (bin8, bin16, bin32) is used. diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 1b8436f91..aedfe504e 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -1160,7 +1160,7 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa ### json.exception.out_of_range.415 MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a -[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255. +[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255, the largest value the library can store in that byte. !!! failure "Example message" From 0c3570d44a95f096b2c5131c0b952899084261c9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 18:25:45 +0100 Subject: [PATCH 09/63] Validate the UBJSON/BJData container type marker for count 0; fix UBJSON docs (#5929) * Validate the optimized-container type marker for count 0 in UBJSON and BJData The '$' type marker of an optimized container was only checked when an element was read, so containers with count 0 accepted any marker: `[$x#i0`, `{$x#i0` and `[$E#i0` parsed as empty containers although a non-empty container with the same marker is rejected and the UBJSON specification requires a legal marker after '$'. get_ubjson_size_type() now checks the marker right after '$' against the allowed set (UBJSON: Z N T F i U I l L d D H C S [ {; BJData: B U i u I m l M L h d D C, with the existing exclusion of T F Z N S H [ {) and reports parse_error.112 "marker ... is not a permitted optimized array type". Input that was accepted and is now rejected: an optimized container with count 0 and an invalid '$' type marker. Valid input is unaffected. Two existing tests that used an invalid marker incidentally ("[$#" and a negative-count case) were adapted. Signed-off-by: Niels Lohmann * Correct and complete the UBJSON documentation Fix statements that disagree with the behavior (checked with probes): - `Z` is null; the unused marker is `N`. - `$` requires `#`; use_type with use_size=false throws other_error.502 only for non-empty arrays, objects and binary values. - NaN and infinity are written as IEEE 754 float64 and read back unchanged; the UBJSON specification encodes infinity as null. - `H` is documented (semantics, parse_error.115, out_of_range.406); the "complete mapping" claim is qualified. - use_type "same type" means same marker. - Long Z/T/F arrays lose only the `$` annotation; the `#` count and the element markers are kept. - from_ubjson lists the 112, 113, 115, 406 and 408 causes that occur for UBJSON, without the BJData-only ND-array wording. - Duplicate object keys keep the last value; the `$` type marker is validated even for count 0. - Smaller fixes: float32 row, "map" -> "object", https link, exception 110 strict case, to_ubjson history of discarded values. Signed-off-by: Niels Lohmann * Regenerate single_include and apply astyle Result of make amalgamate (including make pretty) for the preceding changes. Signed-off-by: Niels Lohmann * Address review of the UBJSON documentation Clarify that empty containers carry no $ annotation of their own and note that a counted N object is not supported. Signed-off-by: Niels Lohmann * docs: format tables, use the Oxford comma, drop discarded-value remarks Reformat the two UBJSON mapping tables that this PR changed (uniform column widths, padded cells), use the serial comma in lists of three or more in the added text, and restore the original type_error.321 bullet in to_ubjson.md. Signed-off-by: Niels Lohmann * Fix narrowing of the optimized-container count in BJData and UBJSON tests Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../mkdocs/docs/api/basic_json/from_ubjson.md | 15 +- .../docs/features/binary_formats/ubjson.md | 133 +++++++++++------- docs/mkdocs/docs/home/exceptions.md | 9 +- .../nlohmann/detail/input/binary_reader.hpp | 46 ++++++ single_include/nlohmann/json.hpp | 46 ++++++ tests/src/unit-bjdata.cpp | 61 +++++++- tests/src/unit-ubjson.cpp | 54 ++++++- 7 files changed, 304 insertions(+), 60 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/from_ubjson.md b/docs/mkdocs/docs/api/basic_json/from_ubjson.md index 3881f9ea4..ca947ef05 100644 --- a/docs/mkdocs/docs/api/basic_json/from_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/from_ubjson.md @@ -79,11 +79,18 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - 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 +- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if an invalid type marker is read + (including a type marker after `$` of an optimized container, even if its count is 0), or if `$` is not followed by `#` - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed - successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict` -- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container - or n-dimensional array cannot be represented by `std::size_t` + successfully (negative or non-integer length, `C` followed by a byte above 0x7F), if the count of an optimized + container is negative, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict` +- Throws [parse_error.115](../../home/exceptions.md#jsonexceptionparse_error115) if the text of a high-precision number + (`H`) is not a valid JSON number +- Throws [out_of_range.406](../../home/exceptions.md#jsonexceptionout_of_range406) if a high-precision number, or a + float64 when `number_float_t` is `float`, does not fit into `number_float_t` +- Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the count of an optimized + container exceeds the capacity of the container (or of `std::size_t`), or if an optimized array of `Z`, `T`, or `F` has + more than 1,048,576 elements ## Complexity diff --git a/docs/mkdocs/docs/features/binary_formats/ubjson.md b/docs/mkdocs/docs/features/binary_formats/ubjson.md index ba12ee4f6..0e7ea2ca1 100644 --- a/docs/mkdocs/docs/features/binary_formats/ubjson.md +++ b/docs/mkdocs/docs/features/binary_formats/ubjson.md @@ -5,11 +5,11 @@ achieve the generality of JSON, combined with being much easier to process than !!! abstract "References" - - [UBJSON Website](http://ubjson.org) + - [UBJSON Website](https://ubjson.org) ## Byte layout -UBJSON is built from just a one-byte, human-readable ASCII *type marker* (for example, `i` for int8, `S` for string, `[` for array) followed by the payload of that type. Strings and the like are prefixed with their length, which is itself a UBJSON integer value including its marker. Numbers are stored in big-endian byte order. Containers can be written in two ways: as an *unoptimized* container that is opened with `[` or `{` and closed with `]` or `}`, or as an *optimized* container that declares an element type (`$`) and/or a count (`#`) after the opening marker, which saves per-element markers and (for a count) the closing marker. +UBJSON is built from just a one-byte, human-readable ASCII *type marker* (for example, `i` for int8, `S` for string, `[` for array) followed by the payload of that type. Strings and the like are prefixed with their length, which is itself a UBJSON integer value including its marker. Numbers are stored in big-endian byte order. Containers can be written in two ways: as an *unoptimized* container that is opened with `[` or `{` and closed with `]` or `}`, or as an *optimized* container that declares a count (`#`) after the opening marker, optionally preceded by an element type (`$`); an element type is only allowed together with a count. An optimized container saves the closing marker and, with a type, the per-element markers. Consider the following JSON value (shown formatted for readability): @@ -44,29 +44,29 @@ Consider the following JSON value (shown formatted for readability): The library uses the following mapping from JSON values types to UBJSON types according to the UBJSON specification: -| JSON value type | value/range | UBJSON type | marker | -|-----------------|-------------------------------------------|----------------|--------| -| null | `null` | null | `Z` | -| boolean | `true` | true | `T` | -| boolean | `false` | false | `F` | -| number_integer | -9223372036854775808..-2147483649 | int64 | `L` | -| number_integer | -2147483648..-32769 | int32 | `l` | -| number_integer | -32768..-129 | int16 | `I` | -| number_integer | -128..127 | int8 | `i` | -| number_integer | 128..255 | uint8 | `U` | -| number_integer | 256..32767 | int16 | `I` | -| number_integer | 32768..2147483647 | int32 | `l` | -| number_integer | 2147483648..9223372036854775807 | int64 | `L` | -| number_unsigned | 0..127 | int8 | `i` | -| number_unsigned | 128..255 | uint8 | `U` | -| number_unsigned | 256..32767 | int16 | `I` | -| number_unsigned | 32768..2147483647 | int32 | `l` | -| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` | -| number_unsigned | 9223372036854775808..18446744073709551615 | high-precision | `H` | -| number_float | *any value* | float64 | `D` | -| string | *with shortest length indicator* | string | `S` | -| array | *see notes on optimized format* | array | `[` | -| object | *see notes on optimized format* | map | `{` | +| JSON value type | value/range | UBJSON type | marker | +|-----------------|-------------------------------------------|--------------------------------------------------|-----------| +| null | `null` | null | `Z` | +| boolean | `true` | true | `T` | +| boolean | `false` | false | `F` | +| number_integer | -9223372036854775808..-2147483649 | int64 | `L` | +| number_integer | -2147483648..-32769 | int32 | `l` | +| number_integer | -32768..-129 | int16 | `I` | +| number_integer | -128..127 | int8 | `i` | +| number_integer | 128..255 | uint8 | `U` | +| number_integer | 256..32767 | int16 | `I` | +| number_integer | 32768..2147483647 | int32 | `l` | +| number_integer | 2147483648..9223372036854775807 | int64 | `L` | +| number_unsigned | 0..127 | int8 | `i` | +| number_unsigned | 128..255 | uint8 | `U` | +| number_unsigned | 256..32767 | int16 | `I` | +| number_unsigned | 32768..2147483647 | int32 | `l` | +| number_unsigned | 2147483648..9223372036854775807 | int64 | `L` | +| number_unsigned | 9223372036854775808..18446744073709551615 | high-precision | `H` | +| number_float | *any value* | float64 (float32 if `number_float_t` is `float`) | `D` (`d`) | +| string | *with shortest length indicator* | string | `S` | +| array | *see notes on optimized format* | array | `[` | +| object | *see notes on optimized format* | object | `{` | !!! success "Complete mapping" @@ -93,20 +93,27 @@ The library uses the following mapping from JSON values types to UBJSON types ac The following markers are not used in the conversion: - - `Z`: no-op values are not created. + - `N`: no-op values are never written (they are skipped when reading). - `C`: single-byte strings are serialized with `S` markers. !!! info "NaN/infinity handling" - If NaN or Infinity are stored inside a JSON number, they are serialized properly. This behavior differs from the - [`dump()`](../../api/basic_json/dump.md) function which serializes NaN or Infinity to `null`. + NaN and infinity are written as IEEE 754 float64 values (`D`) and read back unchanged. This differs from + [`dump()`](../../api/basic_json/dump.md), which serializes them to `null`, and from the UBJSON specification, which + encodes infinity as `null` (it does not mention NaN); other UBJSON readers may therefore not accept these values. !!! info "Optimized formats" The optimized formats for containers are supported: Parameter `use_size` adds size information to the beginning of a container and removes the closing marker. Parameter `use_type` further checks whether all elements of a container have the same type and adds the type marker to the beginning of the container. The `use_type` parameter must only be - used together with `use_size = true`. + used together with `use_size = true`: combining `use_type` with `use_size = false` throws + [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if the value contains a non-empty array, + object, or binary value. + + Two elements have the same type if they are written with the same type marker. For example, `[1, 2]` is written with + the type `i`, but `[1, 200]` (`i`, `U`), `[true, false]` (`T`, `F`), and `[1, 2.5]` (`i`, `D`) are not; an empty + container is never given a `$` annotation of its own (an array of empty arrays is still typed `[`). Note that `use_size = true` alone may result in larger representations - the benefit of this parameter is that the receiving side is immediately informed on the number of elements of the container. @@ -115,8 +122,9 @@ The library uses the following mapping from JSON values types to UBJSON types ac already is the value. Its declared count is therefore the only thing that decides how much memory the receiving side allocates, and a handful of bytes can describe billions of elements. `from_ubjson` rejects such an array with [`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408) when the count exceeds 1,048,576 - (`1 << 20`), and `to_ubjson` writes longer arrays of these types without the annotation, so any value it produces - can be read back. + (`1 << 20`), and `to_ubjson` writes longer arrays of these types in the counted but untyped form: the `$` type marker is + dropped, the `#` count is kept, and every element is written with its own marker (`[#l 1048577 Z Z ...`), so any value + it produces can be read back. Objects and untyped counted arrays are not subject to this limit. !!! info "Binary values" @@ -140,27 +148,54 @@ The library uses the following mapping from JSON values types to UBJSON types ac The library maps UBJSON types to JSON value types as follows: -| UBJSON type | JSON value type | marker | -|-------------|-----------------------------------------|--------| -| no-op | *no value, next value is read* | `N` | -| null | `null` | `Z` | -| false | `false` | `F` | -| true | `true` | `T` | -| float32 | number_float | `d` | -| float64 | number_float | `D` | -| uint8 | number_unsigned | `U` | -| int8 | number_integer | `i` | -| int16 | number_integer | `I` | -| int32 | number_integer | `l` | -| int64 | number_integer | `L` | -| string | string | `S` | -| char | string | `C` | -| array | array (optimized values are supported) | `[` | -| object | object (optimized values are supported) | `{` | +| UBJSON type | JSON value type | marker | +|----------------|--------------------------------------------------|--------| +| no-op | *no value, next value is read* | `N` | +| null | `null` | `Z` | +| false | `false` | `F` | +| true | `true` | `T` | +| float32 | number_float | `d` | +| float64 | number_float | `D` | +| uint8 | number_unsigned | `U` | +| int8 | number_integer | `i` | +| int16 | number_integer | `I` | +| int32 | number_integer | `l` | +| int64 | number_integer | `L` | +| string | string | `S` | +| char | string | `C` | +| high-precision | number_unsigned, number_integer, or number_float | `H` | +| array | array (optimized values are supported) | `[` | +| object | object (optimized values are supported) | `{` | + +!!! info "High-precision numbers" + + The payload of `H` is a length-prefixed string that is parsed as a JSON number (surrounding whitespace is ignored). + Integer text in the range of `number_unsigned_t` (non-negative) or `number_integer_t` (negative) is stored as such; + any other valid number text (larger or smaller integers, fractions, and exponents such as `1e2`) is stored as + `number_float`, so precision may be lost. Text that is not a valid JSON number raises + [`parse_error.115`](../../home/exceptions.md#jsonexceptionparse_error115), a number outside the range of + `number_float_t` (`1e999`) raises [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). !!! success "Complete mapping" - The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value. + Every well-formed UBJSON value can be converted to a JSON value, with these exceptions: a high-precision number + outside the range of `number_float_t` ([`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406)), + an optimized array of `Z`, `T`, or `F` with more than 1,048,576 elements, and a count that exceeds the capacity of the + container ([`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408)). High-precision numbers that + do not fit an integer type are stored as floating-point numbers and may lose precision. Ill-formed input is rejected + with a parse error. + +!!! info "Duplicate object keys" + + If an object contains the same key more than once, the last value is kept (the UBJSON specification does not + address duplicate keys). + +!!! info "Type marker of optimized containers" + + The type marker after `$` must be one of `Z`, `N`, `T`, `F`, `i`, `U`, `I`, `l`, `L`, `d`, `D`, `H`, `C`, `S`, `[`, or + `{`. It is checked even if the count is 0; any other marker raises + [`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112). A counted object typed `N` (`{$N#i1 ...`) + is not supported. !!! warning "Ill-formed UTF-8 in string values and object keys" diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index aedfe504e..ddf9d69cd 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -330,7 +330,7 @@ where they cannot be resolved. ### json.exception.parse_error.110 When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has -been read. +been read, or (with `strict = true`) further bytes follow the value. !!! failure "Example messages" @@ -1045,8 +1045,8 @@ or the encoded length for [CBOR](../features/binary_formats/cbor.md). The exception is also thrown for a [UBJSON](../features/binary_formats/ubjson.md) array of a type that is encoded by its marker alone (`Z`, `T` or `F`) whose declared count exceeds 1,048,576 (`1 << 20`). Such an array has no payload, so its count alone decides how much memory is allocated, and a handful of bytes would otherwise describe billions of values. -[`to_ubjson`](../api/basic_json/to_ubjson.md) writes longer arrays of these types without the size and type annotation, -so any value it produces can still be read back. +[`to_ubjson`](../api/basic_json/to_ubjson.md) writes longer arrays of these types without the `$` type annotation (the `#` +count and one marker per element are kept), so any value it produces can still be read back. !!! failure "Example messages" @@ -1236,7 +1236,8 @@ A JSON Patch operation 'test' failed. The unsuccessful operation is also printed ### json.exception.other_error.502 [`to_ubjson`](../api/basic_json/to_ubjson.md) and [`to_bjdata`](../api/basic_json/to_bjdata.md) were called with -`use_type = true` but `use_size = false`. UBJSON requires a size marker (`#`) after a type marker (`$`). +`use_type = true` but `use_size = false` for a value that contains a non-empty array, object, or binary value. UBJSON +requires a size marker (`#`) after a type marker (`$`); scalars and empty containers do not throw. !!! failure "Example message" diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 929ac7573..4c8d6ed0f 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -3000,6 +3000,14 @@ class binary_reader return false; } + // validate the type marker up front: otherwise it would only be + // checked when an element is read, and a container with count 0 + // would accept any marker + if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second))) + { + return optimized_type_error(); + } + get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { @@ -4496,6 +4504,44 @@ class binary_reader std::size_t bon8_pushback_size = 0; JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief check whether @a marker is a type marker that may follow '$' + UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C + (BJData's excluded markers are rejected separately). + */ + bool is_valid_optimized_type(const char_int_type marker) const noexcept + { + switch (marker) + { + case 'i': + case 'U': + case 'I': + case 'l': + case 'L': + case 'd': + case 'D': + case 'C': + return true; + case 'Z': + case 'N': + case 'T': + case 'F': + case 'H': + case 'S': + case '[': + case '{': + return input_format != input_format_t::bjdata; + case 'B': + case 'u': + case 'm': + case 'M': + case 'h': + return input_format == input_format_t::bjdata; + default: + return false; + } + } + /*! @brief whether @a marker is excluded from BJData's optimized ND-array types @return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{' diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5db064c63..c4b7f159a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20467,6 +20467,14 @@ class binary_reader return false; } + // validate the type marker up front: otherwise it would only be + // checked when an element is read, and a container with count 0 + // would accept any marker + if (JSON_HEDLEY_UNLIKELY(!is_valid_optimized_type(result.second))) + { + return optimized_type_error(); + } + get_ignore_noop(); if (JSON_HEDLEY_UNLIKELY(current != '#')) { @@ -21963,6 +21971,44 @@ class binary_reader std::size_t bon8_pushback_size = 0; JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief check whether @a marker is a type marker that may follow '$' + UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C + (BJData's excluded markers are rejected separately). + */ + bool is_valid_optimized_type(const char_int_type marker) const noexcept + { + switch (marker) + { + case 'i': + case 'U': + case 'I': + case 'l': + case 'L': + case 'd': + case 'D': + case 'C': + return true; + case 'Z': + case 'N': + case 'T': + case 'F': + case 'H': + case 'S': + case '[': + case '{': + return input_format != input_format_t::bjdata; + case 'B': + case 'u': + case 'm': + case 'M': + case 'h': + return input_format == input_format_t::bjdata; + default: + return false; + } + } + /*! @brief whether @a marker is excluded from BJData's optimized ND-array types @return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{' diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 67e9e0a05..4a34125ba 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3134,7 +3134,7 @@ TEST_CASE("BJData") std::vector const v1 = {'[', '#', 'i', 0xF1}; std::vector const v2 = {'[', '$', 'I', '#', 'i', 0xF2}; std::vector const v3 = {'[', '$', 'I', '#', '[', 'i', 0xF4, 'i', 0x02, ']'}; - std::vector const v4 = {'[', '$', 0xF6, '#', 'i', 0xF7}; + std::vector const v4 = {'[', '$', 'l', '#', 'i', 0xF7}; std::vector const v5 = {'[', '$', 'I', '#', '[', 'i', 0xF5, 'i', 0xF1, ']'}; std::vector const v6 = {'[', '#', '[', 'i', 0xF3, 'i', 0x02, ']'}; @@ -3455,7 +3455,7 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(v0, true, false).is_discarded()); std::vector const vi = {'[', '$', '#'}; - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing BJData value: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x23 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_bjdata(vi, true, false).is_discarded()); std::vector const vU = {'[', '$', 'U'}; @@ -4622,3 +4622,60 @@ TEST_CASE("BJData large strings and binaries (chunked reader)") } } } + +TEST_CASE("BJData optimized container type marker is validated for count 0") +{ + // the '$' type marker must be a legal marker even if no element follows + SECTION("invalid marker") + { + // 'Z', 'T', 'F', 'N', 'S', 'H', '[' and '{' are excluded in BJData + for (const char marker : + {'x', 'E', 'Z', 'T', 'F', 'N', 'S', 'H', '[', '{' + }) + { + for (const char open : + {'[', '{' + }) + { + for (const int count : + { + 0, 1 + }) + { + CAPTURE(marker) + CAPTURE(open) + CAPTURE(count) + const std::vector v = {static_cast(open), '$', static_cast(marker), '#', 'i', static_cast(count)}; + json _; + CHECK_THROWS_AS(_ = json::from_bjdata(v), json::parse_error&); + CHECK(json::from_bjdata(v, true, false).is_discarded()); + } + } + } + } + + SECTION("error message") + { + const std::vector v = {'[', '$', 'x', '#', 'i', 0}; + json _; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing BJData type: marker 0x78 is not a permitted optimized array type", json::parse_error&); + } + + SECTION("valid marker") + { + for (const char marker : + {'i', 'U', 'I', 'u', 'l', 'm', 'L', 'M', 'h', 'd', 'D', 'C' + }) + { + CAPTURE(marker) + const std::vector va = {'[', '$', static_cast(marker), '#', 'i', 0}; + const std::vector vo = {'{', '$', static_cast(marker), '#', 'i', 0}; + CHECK(json::from_bjdata(va) == json::array()); + CHECK(json::from_bjdata(vo) == json::object()); + } + + // an optimized array of bytes is read as a binary value + const std::vector vb = {'[', '$', 'B', '#', 'i', 0}; + CHECK(json::from_bjdata(vb) == json::binary({})); + } +} diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 4cce74dff..5c6378216 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2023,7 +2023,7 @@ TEST_CASE("UBJSON") CHECK(json::from_ubjson(v0, true, false).is_discarded()); std::vector const vi = {'[', '$', '#'}; - CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing UBJSON value: unexpected end of input", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x23 is not a permitted optimized array type", json::parse_error&); CHECK(json::from_ubjson(vi, true, false).is_discarded()); std::vector const vT = {'[', '$', 'T'}; @@ -3348,3 +3348,55 @@ TEST_CASE("UBJSON large strings (chunked reader)") } } } + +TEST_CASE("UBJSON optimized container type marker is validated for count 0") +{ + // the '$' type marker must be a legal marker even if no element follows + SECTION("invalid marker") + { + for (const char marker : + {'x', 'E', 'B', 'u', 'm', 'M', 'h' + }) + { + for (const char open : + {'[', '{' + }) + { + for (const int count : + { + 0, 1 + }) + { + CAPTURE(marker) + CAPTURE(open) + CAPTURE(count) + const std::vector v = {static_cast(open), '$', static_cast(marker), '#', 'i', static_cast(count)}; + json _; + CHECK_THROWS_AS(_ = json::from_ubjson(v), json::parse_error&); + CHECK(json::from_ubjson(v, true, false).is_discarded()); + } + } + } + } + + SECTION("error message") + { + const std::vector v = {'[', '$', 'x', '#', 'i', 0}; + json _; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing UBJSON type: marker 0x78 is not a permitted optimized array type", json::parse_error&); + } + + SECTION("valid marker") + { + for (const char marker : + {'Z', 'N', 'T', 'F', 'i', 'U', 'I', 'l', 'L', 'd', 'D', 'H', 'C', 'S', '[', '{' + }) + { + CAPTURE(marker) + const std::vector va = {'[', '$', static_cast(marker), '#', 'i', 0}; + const std::vector vo = {'{', '$', static_cast(marker), '#', 'i', 0}; + CHECK(json::from_ubjson(va) == json::array()); + CHECK(json::from_ubjson(vo) == json::object()); + } + } +} From dc768a7d5da7e789e85514510915a029bbbb5b8e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 18:25:46 +0100 Subject: [PATCH 10/63] Fix BSON binary subtype 0x02 and check the declared document size earlier (#5930) * Write and read the inner length of BSON binary subtype 0x02 The BSON specification defines the payload of the "binary (old)" subtype 0x02 as an int32 followed by that many bytes. to_bson wrote the plain payload instead, so decoders such as pymongo and libbson-based readers rejected the bytes, and from_bson returned the inner length as part of the payload (documented as lenient behavior in gh-5333). The writer now emits the outer length (payload + 4), the subtype, the inner int32 holding the payload size, and the payload. The reader requires an outer length of at least 4 and an inner length equal to the outer length minus 4 (the cases the bson-corpus lists as decode errors), reports parse error 112 otherwise, and returns only the payload. Both directions change together so that the round trip keeps working. The single-reservation fast path for scalar entries declines subtype 0x02 because it has no room for the inner length. This is a deliberate exception to the opt-in rule for behavior changes: the previous output was invalid BSON. Signed-off-by: Niels Lohmann * Check the declared BSON document size before reading its content The BSON reader compared a document's declared size with the number of bytes read only when it reached the terminator. The declared size never bounded the reads, so a SAX handler saw the keys and values of a document whose size was too small before parse error 112 was reported. The container frame now stores the position where the document ends according to its declared size. open_bson_document rejects a size below 5 (size prefix plus terminator) and an embedded document that does not end before the terminator of its enclosing document; the element loop rejects an element whose type byte leaves no room for the name terminator and the document terminator. The terminator check stays and still catches a declared size that is too large. No input that was accepted before is rejected now; documents with a wrong size are reported earlier, with fewer SAX events, a different byte position and, in three cases, a different message. The error id (parse_error.112) is unchanged and strict=false still does not skip the check. The tests that asserted the old messages and positions are updated, with the outer sizes of two inputs corrected so that they still exercise the embedded document. New tests cover the SAX events, a size below 5, and an embedded document that fits exactly. Signed-off-by: Niels Lohmann * Document BSON subtype 0x02, document size checks, and lenient reads Describe the inner length of binary subtype 0x02 that to_bson now writes and from_bson checks and removes. Add the document size checks (parse_error.112, not affected by strict=false) to bson.md and from_bson.md, and extend the list of lenient reads with the UUID and MD5 payload lengths (not checked), duplicate keys (last value wins), embedded NUL bytes in strings, and the ignored trailing bytes with strict=false. State that the members of an object are written in iteration order. Correct the statement that element names are never validated on read: they are checked and sanitized like string values by the error handler. Add the BSON message to the examples of out_of_range.415 and type_error.321, the subtype limit of 255 to the binary values page, CBOR to the formats that use byte_container_with_subtype, and fix a typo in the incomplete mapping box. Signed-off-by: Niels Lohmann * Regenerate single_include and apply astyle Result of make amalgamate (including make pretty) for the preceding changes. Signed-off-by: Niels Lohmann * Address review of the BSON documentation Describe exactly when a too small document size is detected and note that subtype 2 data written by earlier versions is not readable. Signed-off-by: Niels Lohmann * docs: use the Oxford comma, drop discarded-value remarks Add the serial comma in a list added by this PR and remove the BSON example of type_error.321 (with the type_error.317 remark) so the docs do not mention discarded values for BSON; restore the original singular heading of that section. No tables were changed by this PR. Signed-off-by: Niels Lohmann * Fix -Weffc++ in the BSON recorder test and a clang-tidy use-auto hit Signed-off-by: Niels Lohmann * Fix clang-tidy readability-redundant-member-init in BSON test Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/from_bson.md | 3 +- docs/mkdocs/docs/api/basic_json/to_bson.md | 2 +- .../api/byte_container_with_subtype/index.md | 2 +- .../docs/features/binary_formats/bson.md | 46 ++- docs/mkdocs/docs/features/binary_values.md | 5 +- docs/mkdocs/docs/home/exceptions.md | 6 +- .../nlohmann/detail/input/binary_reader.hpp | 71 +++++ .../nlohmann/detail/output/binary_writer.hpp | 27 +- single_include/nlohmann/json.hpp | 98 ++++++- tests/src/unit-bson.cpp | 266 ++++++++++++++++-- 10 files changed, 491 insertions(+), 35 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/from_bson.md b/docs/mkdocs/docs/api/basic_json/from_bson.md index 6e0beafce..c04933eef 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bson.md +++ b/docs/mkdocs/docs/api/basic_json/from_bson.md @@ -80,7 +80,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [`parse_error.110`](../../home/exceptions.md#jsonexceptionparse_error110) if the given input ends prematurely or the end of the input was not reached when `strict` was set to true - Throws [`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs (e.g., an - invalid string or byte array length) + invalid string or byte array length, an invalid inner length of a binary value with subtype `0x02`, or a document size + that does not match the content; `strict` does not affect the document size checks) - Throws [`parse_error.114`](../../home/exceptions.md#jsonexceptionparse_error114) if an unsupported BSON record type is encountered - Throws [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) if a string value or object key is diff --git a/docs/mkdocs/docs/api/basic_json/to_bson.md b/docs/mkdocs/docs/api/basic_json/to_bson.md index 315477e19..b3b346266 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bson.md +++ b/docs/mkdocs/docs/api/basic_json/to_bson.md @@ -13,7 +13,7 @@ static void to_bson(const basic_json& j, detail::output_adapter o, ``` BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are stored as a single entity (a -so-called document). +so-called document). The members of an object are written in the order in which iteration visits them. 1. Returns a byte vector containing the BSON serialization. 2. Writes the BSON serialization to an output adapter. diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/index.md b/docs/mkdocs/docs/api/byte_container_with_subtype/index.md index b184d8496..f3a2cc9a3 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/index.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/index.md @@ -6,7 +6,7 @@ class byte_container_with_subtype : public BinaryType; ``` This type extends the template parameter `BinaryType` provided to [`basic_json`](../basic_json/index.md) with a subtype -used by BSON and MessagePack. This type exists so that the user does not have to specify a type themselves with a +used by BSON, CBOR, and MessagePack. This type exists so that the user does not have to specify a type themselves with a specific naming scheme in order to override the binary type. ## Template parameters diff --git a/docs/mkdocs/docs/features/binary_formats/bson.md b/docs/mkdocs/docs/features/binary_formats/bson.md index 9c82594dd..04aef6950 100644 --- a/docs/mkdocs/docs/features/binary_formats/bson.md +++ b/docs/mkdocs/docs/features/binary_formats/bson.md @@ -66,7 +66,12 @@ The library uses the following mapping from JSON values types to BSON types: !!! warning "Incomplete mapping" The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON. - Also, keys may not contain U+0000, since they are serialized a zero-terminated c-strings. + Also, keys may not contain U+0000, since they are serialized as zero-terminated C strings. + +!!! info "Order of object members" + + The members of an object are written in the order in which iteration visits them: sorted by key for + [`json`](../../api/json.md), in insertion order for [`ordered_json`](../../api/ordered_json.md). !!! warning "BSON type 0x11 interoperability" @@ -81,6 +86,19 @@ The library uses the following mapping from JSON values types to BSON types: As a result, serializing and deserializing a JSON object containing such a value produces a different JSON object, even though the binary data is unchanged. +!!! info "Binary subtype 0x02 (old binary)" + + The BSON specification defines the payload of the "binary (old)" subtype `0x02` as an int32 followed by that many + bytes, so the value is preceded by two lengths: the length of the binary element and, as the first four bytes of + the payload, the number of bytes that follow. A binary value with subtype `2` is therefore written with the outer + length 4 bytes larger than its size and the size as inner length: `binary({1, 2, 3}, 2)` becomes + `07 00 00 00 02 03 00 00 00 01 02 03`. When reading, the inner length is checked and removed, so the value + contains only the bytes that follow it. `from_bson()` throws + [`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) if the outer length is less than 4 or the + inner length is not the outer length minus 4. Versions before 3.13.0 did not write the inner length and returned + it as part of the payload; BSON data written by those versions with subtype 2 is not readable any more and must be + written again. + ??? example "Example: serialize a JSON value to BSON" ```cpp @@ -135,12 +153,29 @@ The library maps BSON record types to JSON value types as follows: The BSON reader is lenient in a few areas where the BSON specification is more restrictive: - array element keys are not checked against the required decimal sequence (`0`, `1`, `2`, ...), - - any non-zero byte is accepted as `true` for the boolean type, and - - the payload for binary subtype `0x02` is returned as-is, including its inner length prefix. + - any non-zero byte is accepted as `true` for the boolean type, + - the payloads of the binary subtypes `0x03` (UUID, old), `0x04` (UUID), and `0x05` (MD5) are not checked to have + 16 bytes, + - duplicate keys within a document are accepted; the last value wins (`{"a": 1, "a": 2}` is read as `{"a": 2}`), + while a SAX handler receives every occurrence, + - a `string` value may contain embedded `0x00` bytes, and + - with `strict` set to `false`, bytes after the end of the document are ignored. If BSON input must be validated for strict specification compliance, validate it separately before passing it to `from_bson()`. +!!! note "Document size" + + Every document starts with its size. `from_bson()` rejects a document with + [`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) if the size is less than 5 (the size prefix and the terminator), if an embedded document does not end before the + end of the document that contains it, if the elements do not fit into the declared size, or if the size does not + match the number of bytes read when the terminating `0x00` byte is reached. A size that is too small is detected + before an element is read whose type byte and name no longer fit; an element that starts inside the declared size + but extends past it is read, and reported to a SAX handler, before the mismatch is found at the terminator. A size + that is too large is likewise found only at the terminator. Passing `strict` as `false` skips only the check for bytes after + the end of the document ([`parse_error.110`](../../home/exceptions.md#jsonexceptionparse_error110)), not the + document size checks. + !!! warning "Ill-formed UTF-8 in string values" The BSON specification requires `string` values (type `0x02`) to be valid UTF-8, but this is not required of a @@ -154,8 +189,9 @@ The library maps BSON record types to JSON value types as follows: ill-formed bytes. `to_bson()`'s own `error_handler` parameter defaults to `keep`, so such a string value or element (key) name is written unchanged; with `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) is enabled), it throws the same exception - instead. Element (key) names are never validated on read, since they are read byte-by-byte as a C string. `binary` - values (type `0x05`) are unaffected, since they are not required to hold text. + instead. Element (key) names are treated like string values: with `keep` they are read unchanged, with `strict`, + `replace`, or `ignore` they are checked and handled in the same way. `binary` values (type `0x05`) are unaffected, + since they are not required to hold text. ??? example "Example: deserialize a JSON value from BSON" diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index f5a87472b..e4dee45d4 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -227,7 +227,10 @@ array of integers. ### BSON [BSON](binary_formats/bson.md) supports binary values and subtypes. If a subtype is given, it is used and added as an -unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00 is used. +unsigned 8-bit integer; a subtype above 255 throws +[`out_of_range.415`](../home/exceptions.md#jsonexceptionout_of_range415). If no subtype is given, the generic binary +subtype 0x00 is used. The payload of the "old binary" subtype 0x02 is preceded by a second length, which the library +writes and removes again when reading; it is not part of the payload. ??? example "Example: serialize a binary value to BSON" diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index ddf9d69cd..48825cb25 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -1162,12 +1162,16 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a [`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255, the largest value the library can store in that byte. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.out_of_range.415] subtype 70000 is too large for the MessagePack ext type (max 255) ``` + ``` + [json.exception.out_of_range.415] subtype 70000 is too large for the BSON binary subtype (max 255) + ``` + !!! note This exception was added in version 3.13.0. Before that, subtypes above 255 were silently truncated modulo 256 instead of raising an error. diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 4c8d6ed0f..b44fd868c 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -235,6 +235,9 @@ class binary_reader /// UBJSON/BJData: the type marker of an optimized container, so that /// its elements are read without one of their own; 0 otherwise char_int_type type_marker; + /// BSON: the position right after this document, according to the + /// size it declares + std::size_t end_position = 0; /// BSON: the size this document declares, in bytes std::int32_t declared_size = 0; /// whether to close this container with end_object() or end_array() @@ -371,6 +374,23 @@ class binary_reader return false; } + // the smallest document is the size prefix and the terminator + if (JSON_HEDLEY_UNLIKELY(document_size < 5)) + { + return bson_document_size_error("document size ", document_size, " is less than 5"); + } + + // the declared size bounds where the document ends: an embedded + // document must end before the terminator of the document that contains it + const auto unsigned_size = static_cast(document_size); + const std::size_t document_end = unsigned_size > (std::numeric_limits::max)() - document_start + ? (std::numeric_limits::max)() + : document_start + unsigned_size; + if (JSON_HEDLEY_UNLIKELY(!container_stack.empty() && document_end >= container_stack.back().end_position)) + { + return bson_document_size_error("document size ", document_size, " exceeds the size of the enclosing document"); + } + if (JSON_HEDLEY_UNLIKELY(!enter_container(is_object, detail::unknown_size()))) { return false; @@ -378,6 +398,7 @@ class binary_reader container_frame& frame = container_stack.back(); frame.start_position = document_start; + frame.end_position = document_end; frame.declared_size = document_size; return true; } @@ -436,6 +457,13 @@ class binary_reader return false; } + // an element needs at least its name's terminator, and the + // document its own terminator, within the declared size + if (JSON_HEDLEY_UNLIKELY(chars_read + 2 > container_stack.back().end_position)) + { + return bson_document_size_error("document size ", container_stack.back().declared_size, " is too small for its content"); + } + const std::size_t element_type_parse_position = chars_read; key.clear(); if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) @@ -584,9 +612,46 @@ class binary_reader } result.set_subtype(subtype); + // the "old binary" subtype 0x02 starts with an int32 that repeats the + // size of the payload following it; the payload does not include it + if (subtype == 0x02) + { + return get_bson_old_binary(len, result); + } + return get_binary(len, result); } + /*! + @brief Parses the payload of a BSON binary value with subtype 0x02. + @param[in] len The length of the binary value, including the inner int32. + @param[in,out] result A reference to the binary variable where the payload + (without the inner int32) is to be stored. + @pre len >= 0 + @return `true` if the inner length was present and matches @a len + */ + template + bool get_bson_old_binary(const NumberType len, binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 4)) + { + return bson_length_error("length of binary subtype 0x02 must be at least 4, is ", len, "binary"); + } + + std::int32_t inner_length{}; + if (JSON_HEDLEY_UNLIKELY((!get_number(inner_length)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(static_cast(inner_length) != static_cast(len) - 4)) + { + return bson_length_error("inner length of binary subtype 0x02 does not match the payload size; inner length is ", inner_length, "binary"); + } + + return get_binary(inner_length, result); + } + /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @@ -4399,6 +4464,12 @@ class binary_reader return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document"); } + JSON_HEDLEY_NEVER_INLINE + bool bson_document_size_error(const char* prefix, const std::int32_t document_size, const char* suffix) const + { + return last_byte_error(exception_id::unexpected_byte, concat(prefix, std::to_string(document_size), suffix), "document"); + } + JSON_HEDLEY_NEVER_INLINE bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const { diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 7ff828074..d8b3e7162 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -1684,7 +1684,15 @@ class binary_writer throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j); } - return sizeof(std::int32_t) + value.size() + 1ul; + // the "old binary" subtype 0x02 repeats the payload size in an inner + // int32 in front of the payload + return sizeof(std::int32_t) + (is_bson_old_binary(value) ? sizeof(std::int32_t) : 0ul) + value.size() + 1ul; + } + + /// @return whether @a value has the BSON subtype 0x02 ("binary (old)") + static bool is_bson_old_binary(const binary_t& value) noexcept + { + return value.has_subtype() && value.subtype() == 0x02; } /*! @@ -1697,10 +1705,18 @@ class binary_writer { write_bson_entry_header(name, 0x05); - write_number(to_bson_length(value.size()), true); + const bool old_binary = is_bson_old_binary(value); + + // the length field counts the inner int32 of the old binary subtype + write_number(to_bson_length(value.size() + (old_binary ? sizeof(std::int32_t) : 0ul)), true); write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); + if (old_binary) + { + write_number(to_bson_length(value.size()), true); + } + write_bytes(value.data(), value.size()); } @@ -1860,7 +1876,8 @@ class binary_writer (or the header of a string or binary value) in one reservation @return whether the entry was written; false for a discarded value, a string or binary value whose length does not fit the length - field, in which case nothing was written + field, or a binary value with subtype 0x02, in which case nothing + was written @pre @a name is at most bson_scalar_max_name_size characters long and is not changed by the error handler */ @@ -1921,7 +1938,9 @@ class binary_writer if (j.is_binary()) { const binary_t& value = *j.m_data.m_value.binary; - if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(value.size()))) + // the old binary subtype 0x02 has an inner length that the single + // reservation has no room for; write_bson_binary writes it + if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(value.size()) || is_bson_old_binary(value))) { return false; } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c4b7f159a..b67223f0c 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -17702,6 +17702,9 @@ class binary_reader /// UBJSON/BJData: the type marker of an optimized container, so that /// its elements are read without one of their own; 0 otherwise char_int_type type_marker; + /// BSON: the position right after this document, according to the + /// size it declares + std::size_t end_position = 0; /// BSON: the size this document declares, in bytes std::int32_t declared_size = 0; /// whether to close this container with end_object() or end_array() @@ -17838,6 +17841,23 @@ class binary_reader return false; } + // the smallest document is the size prefix and the terminator + if (JSON_HEDLEY_UNLIKELY(document_size < 5)) + { + return bson_document_size_error("document size ", document_size, " is less than 5"); + } + + // the declared size bounds where the document ends: an embedded + // document must end before the terminator of the document that contains it + const auto unsigned_size = static_cast(document_size); + const std::size_t document_end = unsigned_size > (std::numeric_limits::max)() - document_start + ? (std::numeric_limits::max)() + : document_start + unsigned_size; + if (JSON_HEDLEY_UNLIKELY(!container_stack.empty() && document_end >= container_stack.back().end_position)) + { + return bson_document_size_error("document size ", document_size, " exceeds the size of the enclosing document"); + } + if (JSON_HEDLEY_UNLIKELY(!enter_container(is_object, detail::unknown_size()))) { return false; @@ -17845,6 +17865,7 @@ class binary_reader container_frame& frame = container_stack.back(); frame.start_position = document_start; + frame.end_position = document_end; frame.declared_size = document_size; return true; } @@ -17903,6 +17924,13 @@ class binary_reader return false; } + // an element needs at least its name's terminator, and the + // document its own terminator, within the declared size + if (JSON_HEDLEY_UNLIKELY(chars_read + 2 > container_stack.back().end_position)) + { + return bson_document_size_error("document size ", container_stack.back().declared_size, " is too small for its content"); + } + const std::size_t element_type_parse_position = chars_read; key.clear(); if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) @@ -18051,9 +18079,46 @@ class binary_reader } result.set_subtype(subtype); + // the "old binary" subtype 0x02 starts with an int32 that repeats the + // size of the payload following it; the payload does not include it + if (subtype == 0x02) + { + return get_bson_old_binary(len, result); + } + return get_binary(len, result); } + /*! + @brief Parses the payload of a BSON binary value with subtype 0x02. + @param[in] len The length of the binary value, including the inner int32. + @param[in,out] result A reference to the binary variable where the payload + (without the inner int32) is to be stored. + @pre len >= 0 + @return `true` if the inner length was present and matches @a len + */ + template + bool get_bson_old_binary(const NumberType len, binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 4)) + { + return bson_length_error("length of binary subtype 0x02 must be at least 4, is ", len, "binary"); + } + + std::int32_t inner_length{}; + if (JSON_HEDLEY_UNLIKELY((!get_number(inner_length)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(static_cast(inner_length) != static_cast(len) - 4)) + { + return bson_length_error("inner length of binary subtype 0x02 does not match the payload size; inner length is ", inner_length, "binary"); + } + + return get_binary(inner_length, result); + } + /*! @brief Read a BSON document element of the given @a element_type. @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html @@ -21866,6 +21931,12 @@ class binary_reader return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document"); } + JSON_HEDLEY_NEVER_INLINE + bool bson_document_size_error(const char* prefix, const std::int32_t document_size, const char* suffix) const + { + return last_byte_error(exception_id::unexpected_byte, concat(prefix, std::to_string(document_size), suffix), "document"); + } + JSON_HEDLEY_NEVER_INLINE bool bson_unsupported_type_error(const std::size_t position, const char_int_type element_type) const { @@ -28045,7 +28116,15 @@ class binary_writer throw_subtype_out_of_range(value.subtype(), "the BSON binary subtype", j); } - return sizeof(std::int32_t) + value.size() + 1ul; + // the "old binary" subtype 0x02 repeats the payload size in an inner + // int32 in front of the payload + return sizeof(std::int32_t) + (is_bson_old_binary(value) ? sizeof(std::int32_t) : 0ul) + value.size() + 1ul; + } + + /// @return whether @a value has the BSON subtype 0x02 ("binary (old)") + static bool is_bson_old_binary(const binary_t& value) noexcept + { + return value.has_subtype() && value.subtype() == 0x02; } /*! @@ -28058,10 +28137,18 @@ class binary_writer { write_bson_entry_header(name, 0x05); - write_number(to_bson_length(value.size()), true); + const bool old_binary = is_bson_old_binary(value); + + // the length field counts the inner int32 of the old binary subtype + write_number(to_bson_length(value.size() + (old_binary ? sizeof(std::int32_t) : 0ul)), true); write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); + if (old_binary) + { + write_number(to_bson_length(value.size()), true); + } + write_bytes(value.data(), value.size()); } @@ -28221,7 +28308,8 @@ class binary_writer (or the header of a string or binary value) in one reservation @return whether the entry was written; false for a discarded value, a string or binary value whose length does not fit the length - field, in which case nothing was written + field, or a binary value with subtype 0x02, in which case nothing + was written @pre @a name is at most bson_scalar_max_name_size characters long and is not changed by the error handler */ @@ -28282,7 +28370,9 @@ class binary_writer if (j.is_binary()) { const binary_t& value = *j.m_data.m_value.binary; - if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(value.size()))) + // the old binary subtype 0x02 has an inner length that the single + // reservation has no room for; write_bson_binary writes it + if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(value.size()) || is_bson_old_binary(value))) { return false; } diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 03c901cee..b4756c9b5 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -802,12 +802,11 @@ TEST_CASE("BSON") CHECK(json::from_bson(result, true, false) == j); } - SECTION("binary member with subtype 0x02 (old binary) keeps its inner length prefix (lenient parsing)") + SECTION("binary member with subtype 0x02 (old binary) carries an inner length") { - // documented lenient behavior (see gh-5333): the payload for - // binary subtype 0x02 ("old binary") is returned as-is, - // including its own inner 4-byte length prefix; it is not - // stripped or reinterpreted + // the payload of binary subtype 0x02 ("old binary") starts with an + // int32 that repeats the payload size; the bytes below are what + // pymongo and the bson-corpus (binary.json) use std::vector const input = { 0x17, 0x00, 0x00, 0x00, // size (little endian) @@ -815,16 +814,87 @@ TEST_CASE("BSON") 0x06, 0x00, 0x00, 0x00, // size of binary (little endian) 0x02, // "old binary" subtype - 0x02, 0x00, 0x00, 0x00, // inner length prefix (part of the old-binary payload) + 0x02, 0x00, 0x00, 0x00, // inner length 0x68, 0x69, // payload ('h', 'i') 0x00 // end marker }; - // the inner length prefix is part of the (unmodified) payload - const std::vector expected_payload = {0x02, 0x00, 0x00, 0x00, 0x68, 0x69}; - const json expected = { { "entry", json::binary(expected_payload, 0x02) } }; + // the inner length is not part of the payload + const json expected = { { "entry", json::binary({0x68, 0x69}, 0x02) } }; CHECK(json::from_bson(input) == expected); + CHECK(json::to_bson(expected) == input); + CHECK(json::from_bson(json::to_bson(expected)) == expected); + } + + SECTION("binary member with subtype 0x02 (old binary) and empty payload") + { + std::vector const input = + { + 0x15, 0x00, 0x00, 0x00, // size (little endian) + 0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary + 0x04, 0x00, 0x00, 0x00, // size of binary (little endian) + 0x02, // "old binary" subtype + 0x00, 0x00, 0x00, 0x00, // inner length + 0x00 // end marker + }; + + const json expected = { { "entry", json::binary({}, 0x02) } }; + CHECK(json::from_bson(input) == expected); + CHECK(json::to_bson(expected) == input); + } + + SECTION("binary member with subtype 0x02 (old binary) with an invalid inner length") + { + std::vector input = + { + 0x17, 0x00, 0x00, 0x00, // size (little endian) + 0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary + 0x06, 0x00, 0x00, 0x00, // size of binary (little endian) + 0x02, // "old binary" subtype + 0x02, 0x00, 0x00, 0x00, // inner length + 0x68, 0x69, // payload ('h', 'i') + 0x00 // end marker + }; + + SECTION("inner length too long") + { + input[19] = 0x03; + } + + SECTION("inner length too short") + { + input[19] = 0x01; + } + + SECTION("inner length negative") + { + input[19] = 0xFF; + input[20] = 0xFF; + input[21] = 0xFF; + input[22] = 0xFF; + } + + CAPTURE(input) + json _; + CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&); + } + + SECTION("binary member with subtype 0x02 (old binary) without an inner length") + { + // the payload is shorter than the inner length field + std::vector const input = + { + 0x14, 0x00, 0x00, 0x00, // size (little endian) + 0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary + 0x03, 0x00, 0x00, 0x00, // size of binary (little endian) + 0x02, // "old binary" subtype + 0x01, 0x02, 0x03, // payload without an inner length + 0x00 // end marker + }; + + json _; + CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&); } SECTION("Some more complex document") @@ -1178,7 +1248,7 @@ TEST_CASE("BSON document size mismatch") SECTION("top-level document with a negative size") { std::vector const input = {0xFF, 0xFF, 0xFF, 0xFF, 0x00}; - CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size -1 does not match the number of bytes read (5)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BSON document: document size -1 is less than 5", json::parse_error&); CHECK(json::from_bson(input, true, false).is_discarded()); } @@ -1190,7 +1260,7 @@ TEST_CASE("BSON document size mismatch") // enclosing document instead of "d" std::vector const input = { - 0x00, 0x00, 0x00, 0x00, // outer size + 0x14, 0x00, 0x00, 0x00, // outer size 0x03, 'd', 0x00, // entry: embedded document "d" 0xFF, 0xFF, 0xFF, 0x7F, // embedded size 0x7FFFFFFF 0x0A, 'a', 0x00, // entry: null "a" @@ -1198,7 +1268,24 @@ TEST_CASE("BSON document size mismatch") 0x08, 'h', 0x00, 0x01, // entry: bool "h" = true 0x00 // outer end marker }; - CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 2147483647 does not match the number of bytes read (8)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 11: syntax error while parsing BSON document: document size 2147483647 exceeds the size of the enclosing document", json::parse_error&); + CHECK(json::from_bson(input, true, false).is_discarded()); + } + + SECTION("embedded document whose size is smaller than its content") + { + // {"a":null} is 8 bytes, but the length prefix claims 7 + std::vector const input = + { + 0x14, 0x00, 0x00, 0x00, // outer size + 0x03, 'd', 0x00, // entry: embedded document "d" + 0x07, 0x00, 0x00, 0x00, // embedded size 7 (real is 8) + 0x0A, 'a', 0x00, // entry: null "a" + 0x00, // embedded end marker + 0x08, 'h', 0x00, 0x01, // entry: bool "h" = true + 0x00 // outer end marker + }; + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 7 does not match the number of bytes read (8)", json::parse_error&); CHECK(json::from_bson(input, true, false).is_discarded()); } @@ -1207,18 +1294,163 @@ TEST_CASE("BSON document size mismatch") // array [42] is 12 bytes, but the length prefix claims 13 std::vector const input = { - 0x00, 0x00, 0x00, 0x00, // outer size + 0x14, 0x00, 0x00, 0x00, // outer size 0x04, 'a', 0x00, // entry: array "a" 0x0D, 0x00, 0x00, 0x00, // array size 13 (real is 12) 0x10, '0', 0x00, 0x2A, 0x00, 0x00, 0x00, // entry: int32 "0" = 42 0x00, // array end marker 0x00 // outer end marker }; - CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 13 does not match the number of bytes read (12)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 11: syntax error while parsing BSON document: document size 13 exceeds the size of the enclosing document", json::parse_error&); CHECK(json::from_bson(input, true, false).is_discarded()); } } +TEST_CASE("BSON declared document size bounds the content") +{ + // records the events that a SAX parser receives + struct recorder : nlohmann::json_sax + { + std::string events; + + bool null() override + { + events += "null "; + return true; + } + bool boolean(bool /*val*/) override + { + events += "boolean "; + return true; + } + bool number_integer(json::number_integer_t /*val*/) override + { + events += "integer "; + return true; + } + bool number_unsigned(json::number_unsigned_t /*val*/) override + { + events += "unsigned "; + return true; + } + bool number_float(json::number_float_t /*val*/, const std::string& /*s*/) override + { + events += "float "; + return true; + } + bool string(std::string& /*val*/) override + { + events += "string "; + return true; + } + bool binary(json::binary_t& /*val*/) override + { + events += "binary "; + return true; + } + bool start_object(std::size_t /*elements*/) override + { + events += "{ "; + return true; + } + bool key(std::string& /*val*/) override + { + events += "key "; + return true; + } + bool end_object() override + { + events += "} "; + return true; + } + bool start_array(std::size_t /*elements*/) override + { + events += "[ "; + return true; + } + bool end_array() override + { + events += "] "; + return true; + } + bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override + { + events += "error "; + return false; + } + }; + + SECTION("the content of a too small document is not reported") + { + // {"a":1} is 12 bytes; the length prefix claims 5, so the element + // does not fit + std::vector const input = + { + 0x05, 0x00, 0x00, 0x00, + 0x10, 'a', 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00 + }; + recorder rec; + CHECK(!json::sax_parse(input, &rec, json::input_format_t::bson)); + CHECK(rec.events == "{ error "); + + // also without strict mode + recorder rec2; + CHECK(!json::sax_parse(input, &rec2, json::input_format_t::bson, false)); + CHECK(rec2.events == "{ error "); + } + + SECTION("an embedded document that does not fit its parent is not opened") + { + // the embedded document ends where the enclosing one does, so there + // is no room for the terminator of the enclosing document + std::vector const input = + { + 0x0C, 0x00, 0x00, 0x00, + 0x03, 'd', 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + recorder rec; + CHECK(!json::sax_parse(input, &rec, json::input_format_t::bson)); + CHECK(rec.events == "{ key error "); + } + + SECTION("an embedded document that fits exactly") + { + std::vector const input = + { + 0x0D, 0x00, 0x00, 0x00, + 0x03, 'd', 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + CHECK(json::from_bson(input) == json({{"d", json::object()}})); + } + + SECTION("a document size that is less than 5") + { + for (const std::int32_t size : + { + 0, 1, 4, -1, (std::numeric_limits::min)() + }) + { + CAPTURE(size) + std::vector input = + { + 0x00, 0x00, 0x00, 0x00, 0x00 + }; + input[0] = static_cast(size & 0xFF); + input[1] = static_cast((size >> 8) & 0xFF); + input[2] = static_cast((size >> 16) & 0xFF); + input[3] = static_cast((size >> 24) & 0xFF); + json _; + CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&); + CHECK(json::from_bson(input, true, false).is_discarded()); + } + } +} + TEST_CASE("BSON nesting does not consume the call stack") { // An embedded document or array used to be read by calling back into the @@ -1923,7 +2155,7 @@ TEST_CASE("Invalid document size handling") { std::vector const v = {0x04, 0x00, 0x00, 0x00, 0x00}; json _; - CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 4 does not match the number of bytes read (5)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BSON document: document size 4 is less than 5", json::parse_error&); CHECK(json::from_bson(v, true, false).is_discarded()); } @@ -1938,7 +2170,7 @@ TEST_CASE("Invalid document size handling") 0x00 }; json _; - CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 16: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (16)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 5 is too small for its content", json::parse_error&); CHECK(json::from_bson(v, true, false).is_discarded()); } @@ -1969,7 +2201,7 @@ TEST_CASE("Invalid document size handling") 0x00 // object terminator }; json _; - CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (12)", json::parse_error&); + CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 12: syntax error while parsing BSON document: document size 5 is too small for its content", json::parse_error&); CHECK(json::from_bson(v, true, false).is_discarded()); } From 60107eda3fe346097ce5b2c065b2854a688de909 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 18:25:46 +0100 Subject: [PATCH 11/63] Read BJData ND-arrays of float16, restrict ND-array char to 0..127, correct BJData docs (#5931) * Read ND-arrays of half floats and enforce the char limit in ND-arrays BJData N-dimensional arrays with element type 'h' (float16) failed to read with a misleading parse_error.112, because the reader's dtype name table lacked 'h'. It now maps to "half", the JData name. The writer still does not pack "half"; such an object is written as a plain object and reads back equal. BJData limits 'C' (char) to 0..127 in Draft 2 and Draft 3, and the 1-D reader enforces this, but ND-arrays with _ArrayType_ "char" wrote values up to 255 as 'C' elements and read them back without a check. The writer now only packs values in 0..127 (others fall back to a plain object that round-trips), and the reader rejects 'C' elements above 127 with parse_error.113 like the 1-D reader. Signed-off-by: Niels Lohmann * docs: correct and complete the BJData documentation Verify the BJData chapter against the library and the Draft 2/3/4 specifications and fix what did not match: - bjdata.md: correct the list of unused markers (N, not Z), the ND-array example (3x2, not 2x3), the conditions under which an object is packed as an ND-array (exactly three keys, row vectors, float-only elements for single/double, char 0..127, byte only in Draft 3), the binary table row and notes (which flag/draft combinations write [$B), the "$" type restriction (Draft 3, Z is excluded too), the H marker in the deserialization table, and the claim of a complete deserialization mapping. Document the supported drafts, that Draft 4 (SoA containers, extension type E) and column-major ND-arrays are rejected, ND-arrays of half floats, duplicate object keys, and link the Draft 3 specifications instead of master. - Add an API page for basic_json::bjdata_version_t with an example, and link it from the nav, the API index and to_bjdata. - Update binary_formats/index.md and binary_values.md, which still claimed that BJData has no binary type, and fix a wrong byte comment there. - Document parse_error.115 and the out_of_range.408 cases for from_bjdata, the precise other_error.502 condition for to_bjdata, and reword the note on truncated unsigned integers in exceptions.md (only ND-array elements were truncated before 3.13.0). Signed-off-by: Niels Lohmann * Regenerate single_include and apply astyle Result of make amalgamate (including make pretty) for the preceding changes. Signed-off-by: Niels Lohmann * Address review of the BJData documentation The $ type restriction exists since Draft 2 (Draft 3 added B), $ requires a count, ND char above 127 is rejected on read, and the new API page is listed in alphabetical order in the nav. Signed-off-by: Niels Lohmann * docs: format tables, use the Oxford comma Reformat the tables changed by this PR (BJData serialization and deserialization mappings, completeness and binary-value tables) so all rows are aligned, and add the Oxford comma to lists of three or more items in the added text. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../docs/api/basic_json/bjdata_version_t.md | 48 ++++ .../mkdocs/docs/api/basic_json/from_bjdata.md | 5 +- docs/mkdocs/docs/api/basic_json/index.md | 1 + docs/mkdocs/docs/api/basic_json/to_bjdata.md | 7 +- .../mkdocs/docs/examples/bjdata_version_t.cpp | 33 +++ .../docs/examples/bjdata_version_t.output | 4 + .../docs/features/binary_formats/bjdata.md | 214 +++++++++++------- .../docs/features/binary_formats/index.md | 32 +-- docs/mkdocs/docs/features/binary_values.md | 11 +- docs/mkdocs/docs/home/exceptions.md | 14 +- docs/mkdocs/mkdocs.yml | 1 + .../nlohmann/detail/input/binary_reader.hpp | 22 ++ .../nlohmann/detail/output/binary_writer.hpp | 7 +- single_include/nlohmann/json.hpp | 29 ++- tests/src/unit-bjdata.cpp | 67 ++++++ 15 files changed, 380 insertions(+), 115 deletions(-) create mode 100644 docs/mkdocs/docs/api/basic_json/bjdata_version_t.md create mode 100644 docs/mkdocs/docs/examples/bjdata_version_t.cpp create mode 100644 docs/mkdocs/docs/examples/bjdata_version_t.output diff --git a/docs/mkdocs/docs/api/basic_json/bjdata_version_t.md b/docs/mkdocs/docs/api/basic_json/bjdata_version_t.md new file mode 100644 index 000000000..72eece5e5 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json/bjdata_version_t.md @@ -0,0 +1,48 @@ +# nlohmann::basic_json::bjdata_version_t + +```cpp +enum class bjdata_version_t +{ + draft2, + draft3 +}; +``` + +This enumeration is used in [`to_bjdata`](to_bjdata.md) to choose the version of the BJData specification to write: + +draft2 +: BJData Draft 2 (the default). Binary values are written as arrays of `U` integers, and ND-arrays with + `"_ArrayType_": "byte"` are written as regular objects, since the `B` marker is not part of Draft 2. + +draft3 +: BJData Draft 3. Binary values are written as `B` elements: as an optimized binary array (`[$B#`) if both `use_size` + and `use_type` are set, and as an array of `B`-prefixed elements otherwise. ND-arrays with + `"_ArrayType_": "byte"` are written as packed arrays (`[$B#[...]`). + +Only the writer is affected by the version. [`from_bjdata`](from_bjdata.md) reads the markers of both drafts. + +## Examples + +??? example + + The example below shows how the different values of the `bjdata_version_t` influence the serialization of a binary + value with [`to_bjdata`](to_bjdata.md). + + ```cpp + --8<-- "examples/bjdata_version_t.cpp" + ``` + + Output: + + ```json + --8<-- "examples/bjdata_version_t.output" + ``` + +## See also + +- [to_bjdata](to_bjdata.md) serializes a JSON value to BJData +- [BJData](../../features/binary_formats/bjdata.md) - the article on the BJData format + +## Version history + +- Added in version 3.12.0. diff --git a/docs/mkdocs/docs/api/basic_json/from_bjdata.md b/docs/mkdocs/docs/api/basic_json/from_bjdata.md index 370124c8a..2b697711c 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -82,8 +82,11 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if a parse error occurs - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string could not be parsed successfully, or if a string value or object key is not valid UTF-8 and `error_handler` is `strict` +- Throws [parse_error.115](../../home/exceptions.md#jsonexceptionparse_error115) if the text of a high-precision + number (`H`) is not a valid number - Throws [out_of_range.408](../../home/exceptions.md#jsonexceptionout_of_range408) if the size of an optimized container - or n-dimensional array cannot be represented by `std::size_t` + cannot be represented by `std::size_t` or exceeds the container's `max_size()`, or if the product of the dimensions of + an n-dimensional array overflows ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json/index.md b/docs/mkdocs/docs/api/basic_json/index.md index acb67401f..96745922f 100644 --- a/docs/mkdocs/docs/api/basic_json/index.md +++ b/docs/mkdocs/docs/api/basic_json/index.md @@ -121,6 +121,7 @@ The class satisfies the following concept requirements: - [**json_pointer**](../json_pointer/index.md) - JSON Pointer implementation - [**json_serializer**](json_serializer.md) - type of the serializer to for conversions from/to JSON - [**error_handler_t**](error_handler_t.md) - type to choose behavior on decoding errors +- [**bjdata_version_t**](bjdata_version_t.md) - type to choose the BJData version to write - [**cbor_tag_handler_t**](cbor_tag_handler_t.md) - type to choose how to handle CBOR tags - **initializer_list_t** - type for initializer lists of `basic_json` values - [**input_format_t**](input_format_t.md) - type to choose the format to parse diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index a85d8ff8a..e0728cfdd 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -43,7 +43,8 @@ The exact mapping and its limitations are described on a [dedicated page](../../ `#!cpp false` by default. `version` (in) -: which version of BJData to use (see note on "Binary values" on [BJData](../../features/binary_formats/bjdata.md)); +: which version of BJData to use, see [`bjdata_version_t`](bjdata_version_t.md) and the note on "Binary values" on +[BJData](../../features/binary_formats/bjdata.md); optional, `#!cpp bjdata_version_t::draft2` by default. `error_handler` (in) @@ -64,7 +65,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va ## Exceptions - Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size` - is false, and `j` contains a non-empty array, object, or binary value. + is false, and `j` contains a non-empty array or object, or a non-empty binary value (with + `version = bjdata_version_t::draft3`, any binary value, including an empty one). + Scalars, empty arrays, and empty objects do not throw. - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is not valid UTF-8 and `error_handler` is `strict` (the default only if [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) diff --git a/docs/mkdocs/docs/examples/bjdata_version_t.cpp b/docs/mkdocs/docs/examples/bjdata_version_t.cpp new file mode 100644 index 000000000..77660a0a1 --- /dev/null +++ b/docs/mkdocs/docs/examples/bjdata_version_t.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +using json = nlohmann::json; + +void print_hex(const char* label, const std::vector& v) +{ + std::cout << label << ':'; + for (auto b : v) + { + std::cout << ' ' << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); + } + std::cout << std::dec << std::setfill(' ') << '\n'; +} + +int main() +{ + // a binary value + json j = json::binary({0xCA, 0xFE, 0xBA, 0xBE}); + + // Draft 2 (the default) writes an array of integers + auto v2 = json::to_bjdata(j, true, true, json::bjdata_version_t::draft2); + print_hex("draft2", v2); + + // Draft 3 writes an optimized binary array + auto v3 = json::to_bjdata(j, true, true, json::bjdata_version_t::draft3); + print_hex("draft3", v3); + + // reading yields an array for Draft 2 and a binary value for Draft 3 + std::cout << json::from_bjdata(v2) << '\n'; + std::cout << json::from_bjdata(v3) << '\n'; +} diff --git a/docs/mkdocs/docs/examples/bjdata_version_t.output b/docs/mkdocs/docs/examples/bjdata_version_t.output new file mode 100644 index 000000000..a37322f1c --- /dev/null +++ b/docs/mkdocs/docs/examples/bjdata_version_t.output @@ -0,0 +1,4 @@ +draft2: 5b 24 55 23 69 04 ca fe ba be +draft3: 5b 24 42 23 69 04 ca fe ba be +[202,254,186,190] +{"bytes":[202,254,186,190],"subtype":null} diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md index 3a7a1798a..bc0599fcc 100644 --- a/docs/mkdocs/docs/features/binary_formats/bjdata.md +++ b/docs/mkdocs/docs/features/binary_formats/bjdata.md @@ -2,9 +2,9 @@ The [BJData format](https://neurojson.org) was derived from and improved upon [Universal Binary JSON(UBJSON)](https://ubjson.org) specification (Draft 12). Specifically, it introduces an optimized -array container for efficient storage of N-dimensional packed arrays (**ND-arrays**); it also adds 5 new type markers - -`[u] - uint16`, `[m] - uint32`, `[M] - uint64`, `[h] - float16` and `[B] - byte` - to unambiguously map common binary -numeric types; furthermore, it uses little-endian (LE) to store all numerics instead of big-endian (BE) as in UBJSON to +array container for efficient storage of N-dimensional packed arrays (**ND-arrays**); it also adds four new type markers - +`[u] - uint16`, `[m] - uint32`, `[M] - uint64`, and `[h] - float16` - to unambiguously map common binary numeric types +(Draft 3 adds `[B] - byte`); furthermore, it uses little-endian (LE) to store all numerics instead of big-endian (BE) as in UBJSON to avoid unnecessary conversions on commonly available platforms. Compared to other binary JSON-like formats such as MessagePack and CBOR, both BJData and UBJSON demonstrate a rare @@ -15,11 +15,30 @@ simple processing. !!! abstract "References" - - [BJData Specification](https://neurojson.org/bjdata/draft2) + - [BJData Specification (Draft 3)](https://neurojson.org/bjdata/draft3) + - [BJData Specification (Draft 2)](https://neurojson.org/bjdata/draft2) + +!!! info "Supported drafts" + + The library supports BJData Draft 2 (the default) and Draft 3, except for column-major N-dimensional arrays (see + below). The draft used for writing is selected with the + [`version` parameter](../../api/basic_json/bjdata_version_t.md) of + [`to_bjdata`](../../api/basic_json/to_bjdata.md); [`from_bjdata`](../../api/basic_json/from_bjdata.md) has no such + parameter and reads the markers of both drafts. + +!!! warning "Draft 4 and column-major ND-arrays are not supported" + + BJData Draft 4 (April 2026) adds structure-of-arrays (SoA) containers (`[$` or `{$` followed by a schema object) + and the extension type `E`. `from_bjdata` does not implement them and throws + [`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) for such input; `to_bjdata` never writes + them. + + Likewise, column-major ND-arrays of Draft 3 (the `#` marker followed by an array that wraps the dimension vector) + are rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113). ## Byte layout -BJData follows the structure of UBJSON: a one-byte, human-readable ASCII *type marker* followed by the payload of that type, with strings prefixed by a length that is itself an integer value. In contrast to UBJSON, numbers are stored in little-endian byte order, and additional markers cover unsigned 16/32/64-bit integers, half-precision floats, and bytes. Containers can be written unoptimized (opened with `[` or `{`, closed with `]` or `}`) or optimized with a declared element type (`$`) and/or count (`#`). BJData extends the optimized form: the count can be a list of dimensions, which stores an N-dimensional packed array as a flat sequence of values without any per-element markers. +BJData follows the structure of UBJSON: a one-byte, human-readable ASCII *type marker* followed by the payload of that type, with strings prefixed by a length that is itself an integer value. In contrast to UBJSON, numbers are stored in little-endian byte order, and additional markers cover unsigned 16/32/64-bit integers, half-precision floats, and bytes. Containers can be written unoptimized (opened with `[` or `{`, closed with `]` or `}`) or optimized with a count (`#`), optionally preceded by a declared element type (`$`). BJData extends the optimized form: the count can be a list of dimensions, which stores an N-dimensional packed array as a flat sequence of values without any per-element markers. Consider the following JSON value (shown formatted for readability): @@ -54,35 +73,35 @@ Consider the following JSON value (shown formatted for readability): The library uses the following mapping from JSON values types to BJData types according to the BJData specification: -| JSON value type | value/range | BJData type | marker | -|-----------------|-------------------------------------------|----------------|--------| -| null | `null` | null | `Z` | -| boolean | `true` | true | `T` | -| boolean | `false` | false | `F` | -| number_integer | -9223372036854775808..-2147483649 | int64 | `L` | -| number_integer | -2147483648..-32769 | int32 | `l` | -| number_integer | -32768..-129 | int16 | `I` | -| number_integer | -128..127 | int8 | `i` | -| number_integer | 128..255 | uint8 | `U` | -| number_integer | 256..32767 | int16 | `I` | -| number_integer | 32768..65535 | uint16 | `u` | -| number_integer | 65536..2147483647 | int32 | `l` | -| number_integer | 2147483648..4294967295 | uint32 | `m` | -| number_integer | 4294967296..9223372036854775807 | int64 | `L` | -| number_integer | 9223372036854775808..18446744073709551615 | uint64 | `M` | -| number_unsigned | 0..127 | int8 | `i` | -| number_unsigned | 128..255 | uint8 | `U` | -| number_unsigned | 256..32767 | int16 | `I` | -| number_unsigned | 32768..65535 | uint16 | `u` | -| number_unsigned | 65536..2147483647 | int32 | `l` | -| number_unsigned | 2147483648..4294967295 | uint32 | `m` | -| number_unsigned | 4294967296..9223372036854775807 | int64 | `L` | -| number_unsigned | 9223372036854775808..18446744073709551615 | uint64 | `M` | -| number_float | *any value* | float64 | `D` | -| string | *with shortest length indicator* | string | `S` | -| array | *see notes on optimized format/ND-array* | array | `[` | -| object | *see notes on optimized format* | map | `{` | -| binary | *see notes on binary values* | array | `[$B` | +| JSON value type | value/range | BJData type | marker | +|-----------------|-------------------------------------------|-------------|-------------------------------------------------------------------------| +| null | `null` | null | `Z` | +| boolean | `true` | true | `T` | +| boolean | `false` | false | `F` | +| number_integer | -9223372036854775808..-2147483649 | int64 | `L` | +| number_integer | -2147483648..-32769 | int32 | `l` | +| number_integer | -32768..-129 | int16 | `I` | +| number_integer | -128..127 | int8 | `i` | +| number_integer | 128..255 | uint8 | `U` | +| number_integer | 256..32767 | int16 | `I` | +| number_integer | 32768..65535 | uint16 | `u` | +| number_integer | 65536..2147483647 | int32 | `l` | +| number_integer | 2147483648..4294967295 | uint32 | `m` | +| number_integer | 4294967296..9223372036854775807 | int64 | `L` | +| number_integer | 9223372036854775808..18446744073709551615 | uint64 | `M` | +| number_unsigned | 0..127 | int8 | `i` | +| number_unsigned | 128..255 | uint8 | `U` | +| number_unsigned | 256..32767 | int16 | `I` | +| number_unsigned | 32768..65535 | uint16 | `u` | +| number_unsigned | 65536..2147483647 | int32 | `l` | +| number_unsigned | 2147483648..4294967295 | uint32 | `m` | +| number_unsigned | 4294967296..9223372036854775807 | int64 | `L` | +| number_unsigned | 9223372036854775808..18446744073709551615 | uint64 | `M` | +| number_float | *any value* | float64 | `D` | +| string | *with shortest length indicator* | string | `S` | +| array | *see notes on optimized format/ND-array* | array | `[` | +| object | *see notes on optimized format* | map | `{` | +| binary | *see notes on binary values* | array | `[$B` (Draft 3 with `use_size` and `use_type`), `[U`, or `[B` otherwise | !!! success "Complete mapping" @@ -107,10 +126,16 @@ The library uses the following mapping from JSON values types to BJData types ac !!! info "Unused BJData markers" - The following markers are not used in the conversion: + The following markers are not written: - - `Z`: no-op values are not created. - - `C`: single-byte strings are serialized with `S` markers. + - `N`: no-op values are not created (they are skipped when reading). + - `C`: single-character strings are serialized with `S` markers (`C` is read, and written for the elements of + ND-arrays with `"_ArrayType_": "char"`). + - `h`: floating-point numbers are written as `D`, not as float16 (`h` is read). + - `B`: only written in Draft 3 mode. + - `d` is only written for `single` ND-arrays or if `number_float_t` is `float`; `H` is only written for integers + wider than 64 bits (custom `number_integer_t`/`number_unsigned_t`). An unsigned integer above 9223372036854775807 + is written as `M`. !!! info "NaN/infinity handling" @@ -142,7 +167,7 @@ The library uses the following mapping from JSON values types to BJData types ac BJData extends UBJSON's optimized array **size** marker to support ND-arrays of uniform numerical data types (referred to as *packed arrays*). For example, the 2-D `uint8` integer array `[[1,2],[3,4],[5,6]]`, stored as nested optimized array in UBJSON `[ [$U#i2 1 2 [$U#i2 3 4 [$U#i2 5 6 ]`, can be further compressed in BJData to - `[$U#[$i#i2 2 3 1 2 3 4 5 6` or `[$U#[i2 i3] 1 2 3 4 5 6`. + `[$U#[$i#i2 3 2 1 2 3 4 5 6` or `[$U#[i3 i2] 1 2 3 4 5 6`. To maintain type and size information, ND-arrays are converted to JSON objects following the **annotated array format** (defined in the [JData specification (Draft 3)][JDataAAFmt]), when parsed using @@ -152,11 +177,14 @@ The library uses the following mapping from JSON values types to BJData types ac ```json { "_ArrayType_": "uint8", - "_ArraySize_": [2,3], + "_ArraySize_": [3,2], "_ArrayData_": [1,2,3,4,5,6] } ``` + ND-arrays with the element type `h` (float16) are returned with `"_ArrayType_": "half"`. `to_bjdata` does not pack + `"half"` objects; they are written as regular objects, which read back as an equal value. + Likewise, when a JSON object in the above form is serialized using [`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. @@ -167,34 +195,40 @@ The library uses the following mapping from JSON values types to BJData types ac object; otherwise it is serialized as a regular JSON object, so the annotation is never lost in a round trip. This requires all of the following: + - the object has exactly the three keys `"_ArrayType_"`, `"_ArraySize_"`, and `"_ArrayData_"` (any other key, e.g., + `"_ArrayOrder_"`, makes it a regular object), - `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`, - `double`, `char`, or `byte`, + `double`, `char`, or (only with `version = bjdata_version_t::draft3`, since the `B` marker is not part of Draft 2) + `byte`; the names are case-sensitive, - `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length, - - `"_ArraySize_"` has at least two entries and is not a 1×N row vector (first entry 1), since other shapes are - parsed back as a regular array, + - `"_ArraySize_"` has at least two entries and is not a two-entry row vector (`[1,N]`), since other shapes are + parsed back as a regular array (`[1,2,2]` is packed, `[1,4]` is not), - every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`, - `"_ArrayData_"` is an array holding exactly that many elements, and - - every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"`: for the integer types, a - value that fits the named width; for `double`, any value; for `single`, a value that survives narrowing to - `float` and back without change (for instance, `0.1` does not, since it is not exactly representable as - `float`). + - every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"`: for the integer types, an + integer (a floating-point number such as `1.0` does not qualify) that fits the named width (for `char`, 0..127); + for `double`, a floating-point number (an integer such as `1` does not qualify); for `single`, a floating-point + number that survives narrowing to `float` and back without change (for instance, `0.1` does not, since it is not + exactly representable as `float`). - An annotated object is always read back with its keys in the order shown above, `"_ArrayType_"`, `"_ArraySize_"`, - `"_ArrayData_"`, regardless of the order the ND-array's header stores them in on the wire. This matters for - `ordered_json`, whose comparison takes key order into account. + With `ordered_json`, an annotated object read from BJData has its keys in the order `"_ArrayType_"`, + `"_ArraySize_"`, `"_ArrayData_"`; with `json`, they are sorted like those of any other object. The current version of this library does not yet support automatic detection of and conversion from a nested JSON array input to a BJData ND-array. - [JDataAAFmt]: https://github.com/NeuroJSON/jdata/blob/master/JData_specification.md#annotated-storage-of-n-d-arrays + [JDataAAFmt]: https://github.com/NeuroJSON/jdata/blob/Draft-3/JData_specification.md#annotated-storage-of-n-d-arrays !!! info "Restrictions in optimized data types for arrays and objects" - Due to diminished space saving, hampered readability, and increased security risks, in BJData, the allowed data - types following the `$` marker in an optimized array and object container are restricted to + Due to diminished space saving, hampered readability, and increased security risks, in BJData, the + allowed data types following the `$` marker in an optimized array and object container are restricted to **non-zero-fixed-length** data types. Therefore, the valid optimized type markers can only be one of - `UiuImlMLhdDCB`. This also means other variable (`[{SH`) or zero-length types (`TFN`) can not be used in an - optimized array or object in BJData. + `UiuImlMLhdDC`, plus `B` since Draft 3 (the reader accepts `B` in both drafts). This also means other variable (`[{SH`) or zero-length types (`TFZN`) can not be used in an + optimized array or object in BJData. The library enforces this restriction for both drafts. A `C` (char) element above 127 is rejected on read with + [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113), in 1-D and in N-dimensional arrays. With `use_type`, the + elements must have the same marker (`[1,200,3]` is not optimized, since it mixes `i` and `U`); booleans, null, + strings, arrays, and objects are never optimized with `$`. !!! info "Binary values" @@ -202,14 +236,16 @@ The library uses the following mapping from JSON values types to BJData types ac in optimized arrays to designate binary data. This means that, unlike UBJSON, binary data can be both serialized and deserialized. - To preserve compatibility with BJData Draft 2, the Draft 3 optimized binary array must be explicitly enabled using - the `version` parameter of [`to_bjdata`](../../api/basic_json/to_bjdata.md). + To preserve compatibility with BJData Draft 2, the Draft 3 optimized binary array must be explicitly enabled: pass + `version = bjdata_version_t::draft3` together with `use_size = true` and `use_type = true` to + [`to_bjdata`](../../api/basic_json/to_bjdata.md). With Draft 3 and only `use_size` (or neither), a binary value is + written as an array of `B` elements. - In Draft2 mode (default), if the JSON data contains the binary type, the value stored as a list of integers, as + In Draft 2 mode (default), if the JSON data contains the binary type, the value is stored as a list of integers, as suggested by the BJData documentation. In particular, this means that the serialization and the deserialization of JSON containing binary values into BJData and back will result in a different JSON object. - [BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md#optimized-binary-array + [BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/Draft-3/Binary_JData_Specification.md#optimized-binary-array ??? example "Example: serialize JSON values to BJData, with and without size/type optimization" @@ -227,34 +263,46 @@ The library uses the following mapping from JSON values types to BJData types ac The library maps BJData types to JSON value types as follows: -| BJData type | JSON value type | marker | -|-------------|------------------------------------------|----------| -| no-op | *no value, next value is read* | `N` | -| null | `null` | `Z` | -| false | `false` | `F` | -| true | `true` | `T` | -| float16 | number_float | `h` | -| float32 | number_float | `d` | -| float64 | number_float | `D` | -| uint8 | number_unsigned | `U` | -| int8 | number_integer | `i` | -| uint16 | number_unsigned | `u` | -| int16 | number_integer | `I` | -| uint32 | number_unsigned | `m` | -| int32 | number_integer | `l` | -| uint64 | number_unsigned | `M` | -| int64 | number_integer | `L` | -| byte | number_unsigned | `B` | -| string | string | `S` | -| char | string | `C` | -| array | array (optimized values are supported) | `[` | -| ND-array | object (in JData annotated array format) | `[$.#[.` | -| object | object (optimized values are supported) | `{` | -| binary | binary (strongly-typed byte array) | `[$B` | +| BJData type | JSON value type | marker | +|-----------------------|--------------------------------------------------|----------| +| no-op | *no value, next value is read* | `N` | +| null | `null` | `Z` | +| false | `false` | `F` | +| true | `true` | `T` | +| float16 | number_float | `h` | +| float32 | number_float | `d` | +| float64 | number_float | `D` | +| high-precision number | number_integer, number_unsigned, or number_float | `H` | +| uint8 | number_unsigned | `U` | +| int8 | number_integer | `i` | +| uint16 | number_unsigned | `u` | +| int16 | number_integer | `I` | +| uint32 | number_unsigned | `m` | +| int32 | number_integer | `l` | +| uint64 | number_unsigned | `M` | +| int64 | number_integer | `L` | +| byte | number_unsigned | `B` | +| string | string | `S` | +| char | string | `C` | +| array | array (optimized values are supported) | `[` | +| ND-array | object (in JData annotated array format) | `[$.#[.` | +| object | object (optimized values are supported) | `{` | +| binary | binary (strongly-typed byte array) | `[$B` | !!! success "Complete mapping" - The mapping is **complete** in the sense that any BJData value can be converted to a JSON value. + The mapping is **complete** for the BJData types of Draft 2 and Draft 3 with one exception: column-major + N-dimensional arrays are rejected with [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113). + The Draft 4 constructs (structure-of-arrays containers and the extension type `E`) are not supported either. + + The digits of an `H` value are parsed like a JSON number: integers that fit 64 bits become `number_integer` or + `number_unsigned`, anything else a `number_float`; invalid text throws + [`parse_error.115`](../../home/exceptions.md#jsonexceptionparse_error115). + +!!! info "Duplicate object keys" + + If an object contains the same key more than once, `from_bjdata` accepts it and keeps the last value; with + `ordered_json`, the key keeps the position of its first occurrence. !!! warning "Ill-formed UTF-8 in string values and object keys" diff --git a/docs/mkdocs/docs/features/binary_formats/index.md b/docs/mkdocs/docs/features/binary_formats/index.md index 9c55a9a52..6e1ada110 100644 --- a/docs/mkdocs/docs/features/binary_formats/index.md +++ b/docs/mkdocs/docs/features/binary_formats/index.md @@ -16,25 +16,25 @@ to efficiently encode JSON values to byte vectors and to decode such vectors. ### Completeness -| 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 | incomplete, but all JSON types are supported | -| UBJSON | complete | complete | +| Format | Serialization | Deserialization | +|-------------|-----------------------------------------------|-----------------------------------------------------------------------| +| BJData | complete | incomplete: no column-major ND-arrays, no Draft 4 SoA/extension types | +| 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 | incomplete, but all JSON types are supported | +| UBJSON | complete | complete | ### Binary values -| Format | Binary values | Binary subtypes | -|-------------|---------------|-----------------| -| BJData | not supported | not supported | -| BON8 | not supported | not supported | -| BSON | supported | supported | -| CBOR | supported | supported | -| MessagePack | supported | supported | -| UBJSON | not supported | not supported | +| Format | Binary values | Binary subtypes | +|-------------|--------------------------|-----------------| +| BJData | supported (Draft 3 only) | not supported | +| BON8 | not supported | not supported | +| BSON | supported | supported | +| CBOR | supported | supported | +| MessagePack | supported | supported | +| UBJSON | not supported | not supported | See [binary values](../binary_values.md) for more information. diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index e4dee45d4..f0e10247a 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -132,8 +132,11 @@ is an integer or `null`. ### BJData -[BJData](binary_formats/bjdata.md) neither supports binary values nor subtypes and proposes to serialize binary values -as an array of uint8 values. The library implements this translation. +[BJData](binary_formats/bjdata.md) Draft 3 supports binary values as optimized arrays of `B` (byte) elements, but no +subtypes. With `version = bjdata_version_t::draft3` and both `use_size` and `use_type` set, `to_bjdata` writes a binary +value as `[$B#...`, and `from_bjdata` reads it back as a binary value (without subtype). In all other cases, including +the default Draft 2 mode, the library follows the Draft 2 proposal and writes an array of integers (`U` in Draft 2, `B` +in Draft 3), which is read back as an array. ??? example "Example: serialize a binary value to BJData" @@ -171,7 +174,7 @@ as an array of uint8 values. The library implements this translation. ```c 0x7B // '{' - 0x23 0x69 0x01 // '#' 'i' type of the array elements: unsigned integers + 0x23 0x69 0x01 // '#' 'i' 1 number of object members 0x69 0x06 // i 6 (length of the key) 0x62 0x69 0x6E 0x61 0x72 0x79 // "binary" 0x5B // '[' array @@ -180,7 +183,7 @@ as an array of uint8 values. The library implements this translation. 0xCA 0xFE 0xBA 0xBE // content ``` - Note that subtype (42) is **not** serialized and that BJData has **no binary type**, and deserializing `v` would + Note that subtype (42) is **not** serialized and that, in Draft 2 mode, BJData has **no binary type**, and deserializing `v` would yield the following value: ```json diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 48825cb25..93dbd0997 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -418,13 +418,16 @@ The parsing of the corresponding BSON record type is not implemented (yet). ### json.exception.parse_error.115 -A UBJSON high-precision number could not be parsed. +A UBJSON or BJData high-precision number could not be parsed. -!!! failure "Example message" +!!! failure "Example messages" ``` [json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A ``` + ``` + [json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A + ``` ### json.exception.parse_error.116 @@ -1025,7 +1028,7 @@ exception, because they serialize integers beyond 64 bits as high-precision numb - Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers. Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData - truncated unsigned integers wider than 64 bits. + truncated unsigned integers wider than 64 bits that were elements of ND-arrays with `"_ArrayType_": "uint64"`. !!! failure "Example messages" @@ -1240,8 +1243,9 @@ A JSON Patch operation 'test' failed. The unsuccessful operation is also printed ### json.exception.other_error.502 [`to_ubjson`](../api/basic_json/to_ubjson.md) and [`to_bjdata`](../api/basic_json/to_bjdata.md) were called with -`use_type = true` but `use_size = false` for a value that contains a non-empty array, object, or binary value. UBJSON -requires a size marker (`#`) after a type marker (`$`); scalars and empty containers do not throw. +`use_type = true` but `use_size = false`, and the value contains a non-empty array or object (or, with BJData Draft 3, +any binary value). UBJSON requires a size marker (`#`) after a type marker (`$`); scalars and empty arrays or objects +do not throw. !!! failure "Example message" diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 7e1c23aa8..f2b99cf3a 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -173,6 +173,7 @@ nav: - 'begin': api/basic_json/begin.md - 'binary': api/basic_json/binary.md - 'binary_t': api/basic_json/binary_t.md + - 'bjdata_version_t': api/basic_json/bjdata_version_t.md - 'boolean_t': api/basic_json/boolean_t.md - 'cbegin': api/basic_json/cbegin.md - 'cbor_tag_handler_t': api/basic_json/cbor_tag_handler_t.md diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index b44fd868c..9ed0e4d1b 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -3277,6 +3277,8 @@ class binary_reader // get_ubjson_size_value() (the type marker is known before the dimension vector // that determines size_and_type.first is read, so it is emitted first there to // match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order) + // 'C' elements are read as unsigned bytes, but BJData limits a char to 0..127 + const bool nd_is_char = size_and_type.second == 'C'; if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; @@ -3290,6 +3292,24 @@ class binary_reader for (std::size_t i = 0; i < size_and_type.first; ++i) { + if (nd_is_char) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + return last_byte_error(exception_id::invalid_string_or_size, "byte after 'C' must be in range 0x00..0x7F", "char", ""); + } + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast(current)))) + { + return false; + } + continue; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; @@ -4655,6 +4675,8 @@ class binary_reader return "uint8"; case 'd': return "single"; + case 'h': + return "half"; case 'i': return "int8"; case 'l': diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index d8b3e7162..8732e52b4 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -3848,7 +3848,7 @@ class binary_writer A C++11 `constexpr` function cannot contain a `switch`, so this is a plain comparison chain instead; it is only reached once per ND-array candidate object. Keep in sync with binary_reader's `bjd_type_name()`, which maps - the other way. + the other way (it also reads "half", which is never written). */ static CharType bjdata_ndarray_type_marker(const string_t& name) { @@ -4056,10 +4056,13 @@ class binary_writer switch (dtype) { case 'U': - case 'C': case 'B': ok = write_bjdata_ndarray_element(el, dry_run); break; + case 'C': + // BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges) + ok = bjdata_ndarray_value_in_range(el) && bjdata_ndarray_value_in_range(el); + break; case 'i': ok = write_bjdata_ndarray_element(el, dry_run); break; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index b67223f0c..eca217237 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20744,6 +20744,8 @@ class binary_reader // get_ubjson_size_value() (the type marker is known before the dimension vector // that determines size_and_type.first is read, so it is emitted first there to // match the documented _ArrayType_, _ArraySize_, _ArrayData_ key order) + // 'C' elements are read as unsigned bytes, but BJData limits a char to 0..127 + const bool nd_is_char = size_and_type.second == 'C'; if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; @@ -20757,6 +20759,24 @@ class binary_reader for (std::size_t i = 0; i < size_and_type.first; ++i) { + if (nd_is_char) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + return last_byte_error(exception_id::invalid_string_or_size, "byte after 'C' must be in range 0x00..0x7F", "char", ""); + } + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast(current)))) + { + return false; + } + continue; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) { return false; @@ -22122,6 +22142,8 @@ class binary_reader return "uint8"; case 'd': return "single"; + case 'h': + return "half"; case 'i': return "int8"; case 'l': @@ -30280,7 +30302,7 @@ class binary_writer A C++11 `constexpr` function cannot contain a `switch`, so this is a plain comparison chain instead; it is only reached once per ND-array candidate object. Keep in sync with binary_reader's `bjd_type_name()`, which maps - the other way. + the other way (it also reads "half", which is never written). */ static CharType bjdata_ndarray_type_marker(const string_t& name) { @@ -30488,10 +30510,13 @@ class binary_writer switch (dtype) { case 'U': - case 'C': case 'B': ok = write_bjdata_ndarray_element(el, dry_run); break; + case 'C': + // BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges) + ok = bjdata_ndarray_value_in_range(el) && bjdata_ndarray_value_in_range(el); + break; case 'i': ok = write_bjdata_ndarray_element(el, dry_run); break; diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 4a34125ba..e4dffb6d0 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2916,6 +2916,35 @@ TEST_CASE("BJData") CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}})); } + SECTION("ndarray with _ArrayType_ \"char\" is limited to 0..127") + { + // both BJData drafts limit a char to 0..127; larger values must not + // be written as 'C' elements (the reader rejects them), so such an + // object stays a plain object that round-trips + for (const auto version : + { + json::bjdata_version_t::draft2, json::bjdata_version_t::draft3 + }) + { + json const j_ok = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {0, 97, 98, 127}}}); + const auto out_ok = json::to_bjdata(j_ok, true, true, version); + CHECK(out_ok == std::vector({'[', '$', 'C', '#', '[', '$', 'i', '#', 'i', 2, 2, 2, 0, 97, 98, 127})); + CHECK(json::from_bjdata(out_ok) == j_ok); + + for (const int bad : + { + 128, 200, 255, 256, -1 + }) + { + CAPTURE(bad) + json const j_bad = json({{"_ArrayType_", "char"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {97, 98, 99, bad}}}); + const auto out_bad = json::to_bjdata(j_bad, true, true, version); + CHECK(out_bad.at(0) == '{'); + CHECK(json::from_bjdata(out_bad) == j_bad); + } + } + } + SECTION("ndarray annotation keys are read back in the documented order") { // from_bjdata() must emit the annotation object's keys in the order @@ -3324,6 +3353,44 @@ TEST_CASE("BJData") } } + SECTION("ndarray of half-precision floats can be read") + { + // the 'h' element type is read as _ArrayType_ "half" + json _; + const std::vector half = {'[', '$', 'h', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 0x00, 0x3C, 0x00, 0x40, 0x00, 0x42, 0x00, 0x44}; + const json j_half = json::parse(R"({"_ArrayType_":"half","_ArraySize_":[2,2],"_ArrayData_":[1.0,2.0,3.0,4.0]})"); + CHECK(json::from_bjdata(half) == j_half); + + // the writer does not pack "half": it writes a plain object that reads back equal + CHECK(json::from_bjdata(json::to_bjdata(j_half)) == j_half); + CHECK(json::from_bjdata(json::to_bjdata(j_half, true, true, json::bjdata_version_t::draft3)) == j_half); + + // truncated half data + CHECK_THROWS_AS(_ = json::from_bjdata(std::vector(half.begin(), half.end() - 1)), json::parse_error&); + } + + SECTION("ndarray of char rejects values above 127") + { + json _; + const std::vector ok = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c', 0x7F}; + CHECK(json::from_bjdata(ok) == json::parse(R"({"_ArrayType_":"char","_ArraySize_":[2,2],"_ArrayData_":[97,98,99,127]})")); + + std::vector bad = ok; + bad.back() = 0xC8; + CHECK_THROWS_AS(_ = json::from_bjdata(bad), json::parse_error&); + CHECK(json::from_bjdata(bad, true, false).is_discarded()); + + // the same restriction holds for 1-D arrays + CHECK_THROWS_AS(_ = json::from_bjdata(std::vector({'[', '$', 'C', '#', 'i', 1, 0xC8})), json::parse_error&); + + // truncated input + CHECK_THROWS_AS(_ = json::from_bjdata(std::vector(ok.begin(), ok.end() - 1)), json::parse_error&); + + // 'B' (byte) elements may use the full range + const std::vector byte_nd = {'[', '$', 'B', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 1, 2, 3, 0xC8}; + CHECK(json::from_bjdata(byte_nd) == json::parse(R"({"_ArrayType_":"byte","_ArraySize_":[2,2],"_ArrayData_":[1,2,3,200]})")); + } + SECTION("do not accept NTFZ markers in ndarray optimized type (with count)") { json _; From 24078c862d99f0b24eac6bb0c1a245ca2e6db74f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 18:25:46 +0100 Subject: [PATCH 12/63] docs: fix BON8 documentation gaps found in the format review (#5932) * docs: fix BON8 documentation gaps found in the format review Verified each claim with small C++ probes before writing it. - index.md: BON8 serialization is also incomplete for invalid UTF-8. - bon8.md: document duplicate keys (last wins), the error ids for malformed input incl. 0xF5..0xF7 + 0x80..0xBF, exact string termination wording, wide integers and discarded values in the unsupported list, and title two untitled admonitions. - exceptions.md: to_bon8 in type_error.316, BON8 in parse_error.112 (with example) and .113, wide integers in out_of_range.407. - binary_values.md: bytes 0..39 take one byte in BON8. - from_bon8.md: note on strict UTF-8 and duplicate keys. Signed-off-by: Niels Lohmann * Address review of the BON8 documentation Restore the correct statement that marker bytes cannot begin a UTF-8 character (0x80..0xBF occur in valid UTF-8 as continuation bytes), and reword two sentences. Signed-off-by: Niels Lohmann * docs: drop the discarded-value remark from the BON8 page Discarded values are not mentioned on the individual format pages. Signed-off-by: Niels Lohmann * docs: do not list invalid UTF-8 strings as a BON8 serialization gap JSON text must be valid UTF-8, and to_bon8 rejects ill-formed strings by design (type_error.316), as documented on the BON8 page. Restore the original cell and realign the table. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/from_bon8.md | 5 +++++ .../docs/features/binary_formats/bon8.md | 22 ++++++++++++++----- docs/mkdocs/docs/features/binary_values.md | 2 +- docs/mkdocs/docs/home/exceptions.md | 18 +++++++++++---- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/from_bon8.md b/docs/mkdocs/docs/api/basic_json/from_bon8.md index e566b8688..cd0bac558 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/from_bon8.md @@ -74,6 +74,11 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va - 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 +The UTF-8 validity of strings is always checked, independent of +[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md), because the end of a BON8 string is determined from +its encoding. Duplicate object keys are accepted; the last value wins (see +[BON8](../../features/binary_formats/bon8.md)). + ## Complexity Linear in the size of the input. diff --git a/docs/mkdocs/docs/features/binary_formats/bon8.md b/docs/mkdocs/docs/features/binary_formats/bon8.md index b972dc035..9efa00b73 100644 --- a/docs/mkdocs/docs/features/binary_formats/bon8.md +++ b/docs/mkdocs/docs/features/binary_formats/bon8.md @@ -1,6 +1,6 @@ # BON8 -BON8 (Binary Object Notation 8) is a compact binary serialization format for JSON values. It uses the byte values that +BON8 (Binary Object Notation 8) is a compact binary serialization format for JSON values. It uses 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. @@ -12,7 +12,7 @@ values -1.0, 0.0, and 1.0 take a single byte, and arrays and objects with up to ## Byte layout -BON8 exploits the fact that some byte values can never start a UTF-8 character. These bytes serve as *type markers*, so strings can be stored as plain UTF-8 without a length prefix: a string ends at the first byte that cannot continue it (often the marker of the next value). Small integers, `true`, `false`, `null`, and a few common floats are a single byte. Arrays and objects are introduced by a marker; those with up to four elements encode their size in the marker and need no terminator, while larger ones are closed by the end marker `0xFE`. +BON8 exploits the fact that some byte values can never start a UTF-8 character. These bytes serve as *type markers*, so strings can be stored as plain UTF-8 without a length prefix: a string ends at the first byte that cannot continue it: a marker byte, or a lead byte 0xC2..0xF7 followed by a byte outside 0x80..0xBF. The ending byte is often the marker of the next value. Small integers, `true`, `false`, `null`, and a few common floats are a single byte. Arrays and objects are introduced by a marker; those with up to four elements encode their size in the marker and need no terminator, while larger ones are closed by the end marker `0xFE`. Consider the following JSON value (shown formatted for readability): @@ -33,7 +33,7 @@ Consider the following JSON value (shown formatted for readability):
- + @@ -100,7 +100,8 @@ serialized as 0x81 0x65 0xFF, but `[1,2,3,4,"e"]` as 0x85 0x91 0x92 0x93 0x94 0x The following values can **not** be converted to a BON8 value: - - unsigned integers above 9223372036854775807, because BON8 has no unsigned 64-bit integer type + - integers outside the int64 range (unsigned integers above 9223372036854775807 and, for number types wider than + 64 bits, any larger or smaller integer), because BON8 integers are limited to int64 ([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)) @@ -168,7 +169,13 @@ The library maps BON8 types to JSON value types as follows: Non-negative integers are read as number_unsigned, negative integers as number_integer. -!!! info +A byte 0xF5..0xF7 followed by 0x80..0xBF is neither a string nor an integer and is rejected with +[parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) (invalid UTF-8 byte). The same error is +reported for any other invalid byte (such as a stray 0xFE), for ill-formed UTF-8, and for an object key that is not a +string. Input that ends prematurely, trailing data after the value, and a missing final 0xFF after a string at the end +of the message are reported as [parse_error.110](../../home/exceptions.md#jsonexceptionparse_error110). + +!!! info "Non-canonical input" 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 a 0xFF after a @@ -176,7 +183,10 @@ Non-negative integers are read as number_unsigned, negative integers as number_i Strings must be valid UTF-8, and a string at the very end of a message must be terminated by 0xFF. -!!! info + Duplicate object keys are accepted; the last value wins (`ordered_json` keeps the position of the first occurrence). + The specification does not say how to treat them; the HikoGUI reference decoder keeps the first value. + +!!! info "Round trips" Any BON8 output created by `to_bon8` can be successfully parsed by `from_bon8`. diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index f0e10247a..5be9e5ba7 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -216,7 +216,7 @@ array of integers. 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) + 0xC3 0x22 0xC3 0x56 0xC3 0x12 0xC3 0x16 // content (each byte, here all above 39, as a 2-byte integer) ``` Note that the subtype is lost, and deserializing `v` would yield the following value: diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 93dbd0997..65b31c93c 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -346,7 +346,7 @@ been read, or (with `strict = true`) further bytes follow the value. ### json.exception.parse_error.112 -An unexpected byte was read in a [binary format](../features/binary_formats/index.md) or length information is invalid ([BSON](../features/binary_formats/bson.md)). +An unexpected byte was read in a [binary format](../features/binary_formats/index.md) or length information is invalid ([BSON](../features/binary_formats/bson.md)). In [BON8](../features/binary_formats/bon8.md), this exception is also thrown for a key that is not a string and for ill-formed UTF-8. !!! failure "Example messages" @@ -371,15 +371,21 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde ``` [json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5) ``` + ``` + [json.exception.parse_error.112] parse error at byte 2: syntax error while parsing BON8 string: invalid UTF-8 byte: 0x80 + ``` ### json.exception.parse_error.113 A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a string was read where one was required (for instance as a map key), the string's length specification is invalid, or the string's bytes are not valid UTF-8 and the `error_handler` parameter of the corresponding `from_*` function is -set to `strict`. By default (`error_handler_t::keep`), the bytes of a string are not checked for valid UTF-8 on read; -see the ill-formed UTF-8 notes on the individual [binary format](../features/binary_formats/index.md) pages for how +set to `strict`. By default (`error_handler_t::keep`), the bytes of a string are not checked for valid UTF-8 on read. +This applies to the `from_*` functions that have an `error_handler` parameter; `from_bon8` does not have one. +See the ill-formed UTF-8 notes on the individual [binary format](../features/binary_formats/index.md) pages for how such a string is handled depending on `error_handler`. +[`from_bon8()`](../api/basic_json/from_bon8.md) never throws this exception: BON8 non-string keys and ill-formed UTF-8 +are reported as [parse_error.112](#jsonexceptionparse_error112). CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other type (for instance integers or `null`) are therefore not supported; see the notes on @@ -815,6 +821,9 @@ The binary writers [`to_cbor()`](../api/basic_json/to_cbor.md), [`to_ubjson()`]( as well for a string value or object key that is not valid UTF-8 if their `error_handler` is `strict` (the default if [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) is enabled). So does [`to_msgpack()`](../api/basic_json/to_msgpack.md) if `error_handler_t::strict` is passed. +[`to_bon8()`](../api/basic_json/to_bon8.md) always throws this exception for such a string or key, regardless of +[`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md), because the end of a BON8 string is determined +from its UTF-8 encoding. !!! failure "Example message" @@ -1009,7 +1018,8 @@ double-precision number when `number_float_t` is `#!cpp float`. An integer number cannot be represented by the binary format it is serialized to: - [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64, so - [`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807. + [`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807 and, with a + number type wider than 64 bits, for any integer outside the int64 range. - [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/messagepack.md), and [BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or From 731f1b5bfb06527fcf10b4f62291ed363472974e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 20:13:30 +0100 Subject: [PATCH 13/63] json_view: view_mode enum instead of bool Editable, node_kind for node::kind (#5919) * json_view: view_mode enum instead of bool Editable, node_kind for node::kind basic_json_document and basic_json_view take a view_mode (read_only, the default, or editable) instead of a bool: basic_json_document did not say what true means. The shortcut aliases are unchanged. The kind of a node is a node_kind (the value_t values plus link, 10 as before), which removes the static_cast(value_t::...) of every comparison. The node and image layouts are unchanged. Signed-off-by: Niels Lohmann * json_view: node_kind in the node writers, spell out read_only, comment fixes The builder and the edit functions take and store a node_kind instead of casting a value_t; the numbering assertions and the comments now speak of node_kind, and the link kind notes that it follows value_t::discarded. The read-only aliases name view_mode::read_only like the editable ones. view_mode.md says the mode is a compile-time property of the type. Signed-off-by: Niels Lohmann * json_view: views of read-only documents convert to editable views A read-only document has no edits, so a view of it reads correctly as a view of an editable document before its first edit. The implicit conversion lets a function taking a json_editable_view accept the views of both modes. There is no conversion the other way: a read-only view of an editable document would read wrong values after the next edit. The mode comparisons in the cross-mode operator==/!= use an is_other_mode trait, which astyle keeps compact. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- BUILD.bazel | 1 + .../docs/api/basic_json_document/erase.md | 2 +- .../docs/api/basic_json_document/index.md | 25 +- .../docs/api/basic_json_document/insert.md | 2 +- .../docs/api/basic_json_document/push_back.md | 2 +- .../docs/api/basic_json_document/save.md | 2 +- .../docs/api/basic_json_document/set.md | 2 +- .../api/basic_json_view/basic_json_view.md | 21 +- docs/mkdocs/docs/api/basic_json_view/index.md | 11 +- docs/mkdocs/docs/api/json_document.md | 2 +- .../mkdocs/docs/api/json_editable_document.md | 2 +- docs/mkdocs/docs/api/json_editable_view.md | 2 +- docs/mkdocs/docs/api/json_view.md | 2 +- docs/mkdocs/docs/api/ordered_json_document.md | 2 +- .../api/ordered_json_editable_document.md | 2 +- .../docs/api/ordered_json_editable_view.md | 2 +- docs/mkdocs/docs/api/ordered_json_view.md | 2 +- docs/mkdocs/docs/api/view_mode.md | 88 +++++ .../ordered_json_editable_document.cpp | 2 +- docs/mkdocs/docs/examples/view_mode.cpp | 36 ++ docs/mkdocs/docs/examples/view_mode.output | 3 + docs/mkdocs/docs/features/json_view.md | 4 +- docs/mkdocs/mkdocs.yml | 1 + include/nlohmann/detail/view/builder.hpp | 38 +-- .../nlohmann/detail/view/document_data.hpp | 2 +- include/nlohmann/detail/view/edit.hpp | 86 +++-- include/nlohmann/detail/view/edit_storage.hpp | 6 +- include/nlohmann/detail/view/image.hpp | 20 +- include/nlohmann/detail/view/lookup.hpp | 2 +- include/nlohmann/detail/view/materialize.hpp | 2 +- include/nlohmann/detail/view/node.hpp | 41 ++- include/nlohmann/detail/view/serializer.hpp | 2 +- include/nlohmann/detail/view/view_mode.hpp | 52 +++ include/nlohmann/json_view.hpp | 74 ++-- single_include/nlohmann/json_view.hpp | 323 +++++++++++------- tests/src/unit-json_view_builder.cpp | 4 +- tests/src/unit-json_view_edit_values.cpp | 109 +++++- tests/src/unit-json_view_image.cpp | 18 +- 38 files changed, 704 insertions(+), 293 deletions(-) create mode 100644 docs/mkdocs/docs/api/view_mode.md create mode 100644 docs/mkdocs/docs/examples/view_mode.cpp create mode 100644 docs/mkdocs/docs/examples/view_mode.output create mode 100644 include/nlohmann/detail/view/view_mode.hpp diff --git a/BUILD.bazel b/BUILD.bazel index b6b5152a2..2b1c9f146 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -90,6 +90,7 @@ cc_library( "include/nlohmann/detail/view/simd.hpp", "include/nlohmann/detail/view/string_ref.hpp", "include/nlohmann/detail/view/value.hpp", + "include/nlohmann/detail/view/view_mode.hpp", "include/nlohmann/json.hpp", "include/nlohmann/json_fwd.hpp", "include/nlohmann/json_literals.hpp", diff --git a/docs/mkdocs/docs/api/basic_json_document/erase.md b/docs/mkdocs/docs/api/basic_json_document/erase.md index 4cbc50d19..7d453addd 100644 --- a/docs/mkdocs/docs/api/basic_json_document/erase.md +++ b/docs/mkdocs/docs/api/basic_json_document/erase.md @@ -12,7 +12,7 @@ void erase(view_type array, I idx); std::size_t erase(const json_pointer& ptr); ``` -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `erase`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). 1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many diff --git a/docs/mkdocs/docs/api/basic_json_document/index.md b/docs/mkdocs/docs/api/basic_json_document/index.md index ea5f449db..b0743e4ec 100644 --- a/docs/mkdocs/docs/api/basic_json_document/index.md +++ b/docs/mkdocs/docs/api/basic_json_document/index.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -template +template class basic_json_document; ``` @@ -20,9 +20,9 @@ is move-only: a copy would duplicate the whole index, the decoded-string buffer, enough in every use we know of. The index, the decoded strings, and the owned text live in one heap block that the document holds through a `#!cpp std::unique_ptr`; this is also why views survive a move of the document. -With `#!cpp Editable == true`, the document also offers [`set`](set.md), [`push_back`](push_back.md), +With `#!cpp Mode == view_mode::editable`, the document also offers [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md) to change values in place, see [Edits](#edits) below. The source text -itself is never written; a read-only document (`#!cpp Editable == false`, the default) does not carry any of the +itself is never written; a read-only document (`#!cpp view_mode::read_only`, the default) does not carry any of the bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp static_assert`). ## Template parameters @@ -32,9 +32,10 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp [`ordered_json`](../ordered_json.md). Only 64-bit `number_integer_t`/`number_unsigned_t` types are supported; this is checked with a `static_assert`. -`Editable` -: whether the document supports [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and - [`erase`](erase.md) (optional, `#!cpp false` by default). See [Edits](#edits) below. +`Mode` +: a [`view_mode`](../view_mode.md): `#!cpp view_mode::editable` if the document supports [`set`](set.md), + [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md) (optional, `#!cpp view_mode::read_only` + by default). See [Edits](#edits) below. ## Specializations @@ -47,7 +48,7 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp ## Member types -- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view`) +- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view`) - **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md) - **image_check** - how [`load()`](load.md) validates an image (`full`, `bounds`, `none`), see [`image_check`](load.md#image_check) @@ -80,15 +81,15 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp ### Edits - [**set**](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers - to (`#!cpp Editable` documents only) -- [**push_back**](push_back.md) - append to an array (`#!cpp Editable` documents only) -- [**insert**](insert.md) - insert an element into an array before a given position (`#!cpp Editable` documents only) + to (editable documents only) +- [**push_back**](push_back.md) - append to an array (editable documents only) +- [**insert**](insert.md) - insert an element into an array before a given position (editable documents only) - [**erase**](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to - (`#!cpp Editable` documents only) + (editable documents only) ## Edits -An editable document (`#!cpp Editable == true`) can be changed after parsing, with [`set`](set.md), +An editable document (`#!cpp view_mode::editable`) can be changed after parsing, with [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md); [`json_editable_document`](../json_editable_document.md) and [`ordered_json_editable_document`](../ordered_json_editable_document.md) are the corresponding specializations. A few diff --git a/docs/mkdocs/docs/api/basic_json_document/insert.md b/docs/mkdocs/docs/api/basic_json_document/insert.md index b513dfc0d..883acabda 100644 --- a/docs/mkdocs/docs/api/basic_json_document/insert.md +++ b/docs/mkdocs/docs/api/basic_json_document/insert.md @@ -5,7 +5,7 @@ template view_type insert(view_type array, I idx, V&& value); ``` -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `insert`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). Inserts `value` into `array` as a new element before position `idx`, which must not be past the end diff --git a/docs/mkdocs/docs/api/basic_json_document/push_back.md b/docs/mkdocs/docs/api/basic_json_document/push_back.md index 37ce10bc5..96e3f113a 100644 --- a/docs/mkdocs/docs/api/basic_json_document/push_back.md +++ b/docs/mkdocs/docs/api/basic_json_document/push_back.md @@ -13,7 +13,7 @@ editable, and it does not have to be `array`'s own document -- which is copied s source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...). -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `push_back`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). ## Template parameters diff --git a/docs/mkdocs/docs/api/basic_json_document/save.md b/docs/mkdocs/docs/api/basic_json_document/save.md index f69d5c9f8..c47295048 100644 --- a/docs/mkdocs/docs/api/basic_json_document/save.md +++ b/docs/mkdocs/docs/api/basic_json_document/save.md @@ -12,7 +12,7 @@ An edited document is written in its *current* state, with its values in documen parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an [`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity) becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to -the same bytes -- also across `BasicJsonType` and `#!cpp Editable`, since the image reflects document order and +the same bytes -- also across `BasicJsonType` and the [`view_mode`](../view_mode.md), since the image reflects document order and values only, not which specialization produced them. ## Return value diff --git a/docs/mkdocs/docs/api/basic_json_document/set.md b/docs/mkdocs/docs/api/basic_json_document/set.md index ae26ed516..dd5d99611 100644 --- a/docs/mkdocs/docs/api/basic_json_document/set.md +++ b/docs/mkdocs/docs/api/basic_json_document/set.md @@ -18,7 +18,7 @@ template view_type set(const json_pointer& ptr, V&& value); ``` -Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) +Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md)) has `set`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). 1. Replaces the value `target` refers to with `value`. diff --git a/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md b/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md index df1e939fa..7ae259287 100644 --- a/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md +++ b/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md @@ -1,20 +1,32 @@ # nlohmann::basic_json_view::basic_json_view ```cpp +// (1) basic_json_view() noexcept = default; + +// (2) only for Mode == view_mode::editable +basic_json_view(const basic_json_view& other) noexcept; ``` -Creates an invalid (discarded) view: [`type()`](type.md) is `#!cpp value_t::discarded` and -[`is_discarded()`](is_discarded.md) is `#!cpp true`. +1. Creates an invalid (discarded) view: [`type()`](type.md) is `#!cpp value_t::discarded` and + [`is_discarded()`](is_discarded.md) is `#!cpp true`. +2. Converts a view of a read-only document into a view of an editable document (implicitly), so that a function + taking a [`json_editable_view`](../json_editable_view.md) accepts both. The result is a view of the same value of + the same read-only document. See [`view_mode`](../view_mode.md) for why there is no conversion the other way. -This is the only constructor a caller can use directly. Every other view is obtained from a +## Parameters + +`other` (in) +: a view of a read-only document with the same `BasicJsonType` + +Apart from these, a view is obtained from a [`basic_json_document`](../basic_json_document/index.md), via [`root()`](../basic_json_document/root.md) or by navigating into a container with [`operator[]`](operator[].md), [`at`](at.md), [`front`](front.md), [`back`](back.md), [`find`](find.md), or iteration. ## Exception safety -No-throw guarantee: this constructor never throws exceptions. +No-throw guarantee: these constructors never throw exceptions. ## Complexity @@ -45,6 +57,7 @@ placeholder for "no value yet" and later be assigned a real view. - [is_discarded](is_discarded.md) - return whether the view is invalid - [root](../basic_json_document/root.md) - the view of a document's root value +- [view_mode](../view_mode.md) - whether a document and its views can be edited ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md index 5f0d90694..5049e2242 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -template +template class basic_json_view; ``` @@ -37,7 +37,7 @@ Moving the document itself does not invalidate its views: the index is heap-allo `#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or `#!cpp get_ref()`; see [`get`](get.md). `basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an -**editable** document (`#!cpp Editable == true`) sees every edit made through +**editable** document (`#!cpp view_mode::editable`) sees every edit made through [`basic_json_document::set`](../basic_json_document/set.md) and [`basic_json_document::push_back`](../basic_json_document/push_back.md): once a value is changed, every view that still refers to it -- including ones taken before the change -- reads the new value. See @@ -49,9 +49,10 @@ still refers to it -- including ones taken before the change -- reads the new va : a specialization of [`basic_json`](../basic_json/index.md), matching the [`basic_json_document`](../basic_json_document/index.md) the view was taken from. -`Editable` -: whether the view is of an editable document, matching the [`basic_json_document`](../basic_json_document/index.md) - it was taken from (optional, `#!cpp false` by default). See [Edits](../basic_json_document/index.md#edits). +`Mode` +: a [`view_mode`](../view_mode.md): whether the view is of an editable document, matching the + [`basic_json_document`](../basic_json_document/index.md) it was taken from (optional, `#!cpp view_mode::read_only` by + default). See [Edits](../basic_json_document/index.md#edits). ## Specializations diff --git a/docs/mkdocs/docs/api/json_document.md b/docs/mkdocs/docs/api/json_document.md index cfa22e80b..2da6fe548 100644 --- a/docs/mkdocs/docs/api/json_document.md +++ b/docs/mkdocs/docs/api/json_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_document = basic_json_document; +using json_document = basic_json_document; ``` This type is a [`basic_json_document`](basic_json_document/index.md) of the default [`json`](json.md) diff --git a/docs/mkdocs/docs/api/json_editable_document.md b/docs/mkdocs/docs/api/json_editable_document.md index ec5226b78..2acea534c 100644 --- a/docs/mkdocs/docs/api/json_editable_document.md +++ b/docs/mkdocs/docs/api/json_editable_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_editable_document = basic_json_document; +using json_editable_document = basic_json_document; ``` This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default diff --git a/docs/mkdocs/docs/api/json_editable_view.md b/docs/mkdocs/docs/api/json_editable_view.md index ddee4e372..37301d3d6 100644 --- a/docs/mkdocs/docs/api/json_editable_view.md +++ b/docs/mkdocs/docs/api/json_editable_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_editable_view = basic_json_view; +using json_editable_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a diff --git a/docs/mkdocs/docs/api/json_view.md b/docs/mkdocs/docs/api/json_view.md index 89229ed6f..0998549d9 100644 --- a/docs/mkdocs/docs/api/json_view.md +++ b/docs/mkdocs/docs/api/json_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using json_view = basic_json_view; +using json_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a [`json_document`](json_document.md). diff --git a/docs/mkdocs/docs/api/ordered_json_document.md b/docs/mkdocs/docs/api/ordered_json_document.md index b737c2e4d..40039774d 100644 --- a/docs/mkdocs/docs/api/ordered_json_document.md +++ b/docs/mkdocs/docs/api/ordered_json_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_document = basic_json_document; +using ordered_json_document = basic_json_document; ``` This type is a [`basic_json_document`](basic_json_document/index.md) of the [`ordered_json`](ordered_json.md) diff --git a/docs/mkdocs/docs/api/ordered_json_editable_document.md b/docs/mkdocs/docs/api/ordered_json_editable_document.md index cf86307e5..b47dfc3c2 100644 --- a/docs/mkdocs/docs/api/ordered_json_editable_document.md +++ b/docs/mkdocs/docs/api/ordered_json_editable_document.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_editable_document = basic_json_document; +using ordered_json_editable_document = basic_json_document; ``` This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the diff --git a/docs/mkdocs/docs/api/ordered_json_editable_view.md b/docs/mkdocs/docs/api/ordered_json_editable_view.md index 1c9953d57..a42ca42f5 100644 --- a/docs/mkdocs/docs/api/ordered_json_editable_view.md +++ b/docs/mkdocs/docs/api/ordered_json_editable_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_editable_view = basic_json_view; +using ordered_json_editable_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an diff --git a/docs/mkdocs/docs/api/ordered_json_view.md b/docs/mkdocs/docs/api/ordered_json_view.md index 9df72040f..017f2e9a8 100644 --- a/docs/mkdocs/docs/api/ordered_json_view.md +++ b/docs/mkdocs/docs/api/ordered_json_view.md @@ -3,7 +3,7 @@ Defined in header `` ```cpp -using ordered_json_view = basic_json_view; +using ordered_json_view = basic_json_view; ``` This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an diff --git a/docs/mkdocs/docs/api/view_mode.md b/docs/mkdocs/docs/api/view_mode.md new file mode 100644 index 000000000..c0339b5fb --- /dev/null +++ b/docs/mkdocs/docs/api/view_mode.md @@ -0,0 +1,88 @@ +# nlohmann::view_mode + +Defined in header `` + +```cpp +enum class view_mode : std::uint8_t +{ + read_only, + editable +}; +``` + +The second template parameter of [`basic_json_document`](basic_json_document/index.md) and +[`basic_json_view`](basic_json_view/index.md): whether a document can be changed after parsing, and whether a view is +of such a document. + +`read_only` +: the default: the document is parsed once and then only read. It carries none of the bookkeeping edits need, and + calling [`set`](basic_json_document/set.md), [`push_back`](basic_json_document/push_back.md), + [`insert`](basic_json_document/insert.md), or [`erase`](basic_json_document/erase.md) on it fails to compile. + +`editable` +: the document also offers [`set`](basic_json_document/set.md), [`push_back`](basic_json_document/push_back.md), + [`insert`](basic_json_document/insert.md), and [`erase`](basic_json_document/erase.md); its views see every edit, + see [Edits](basic_json_document/index.md#edits). + +The shortcuts name both modes: [`json_document`](json_document.md) and [`json_view`](json_view.md) are read-only, +[`json_editable_document`](json_editable_document.md) and [`json_editable_view`](json_editable_view.md) are editable +(and likewise for `ordered_json`). + +## Notes + +The mode is a compile-time property of the document's and the view's type, not a setting of a call. A read-only view +reads a container in place, while a view of an editable document follows the links that edits leave behind. A view of +a read-only document converts implicitly to a view of an editable document of the same `BasicJsonType`: a read-only +document has no edits, so the converted view reads it like an editable document before its first edit (at the cost of +one check per access). There is no conversion the other way, because a read-only view of an editable document would +read wrong values after the next edit. + +A function that takes a [`json_editable_view`](json_editable_view.md) therefore accepts the views of both modes. A +function template deduces the mode from its argument instead, without conversions, so it names the modes it accepts in +its signature: + +```cpp +// views of editable documents only +template +void f(basic_json_view v); + +// views of read-only documents only +template +void f(basic_json_view v); + +// either +template +void f(basic_json_view v); +``` + +A function that takes a [`json_view`](json_view.md) accepts neither an [`ordered_json_view`](ordered_json_view.md) +nor a [`json_editable_view`](json_editable_view.md). Views of the two modes compare with +[`operator==`](basic_json_view/operator_eq.md). + +## Examples + +??? example + + The example below shows a function template that accepts the views of both modes, one that accepts only views of + editable documents, and a function taking a `json_editable_view` that also accepts a view of a read-only document. + + ```cpp + --8<-- "examples/view_mode.cpp" + ``` + + Output: + + ``` + --8<-- "examples/view_mode.output" + ``` + +## See also + +- [basic_json_document](basic_json_document/index.md) - a parsed JSON text +- [basic_json_view](basic_json_view/index.md) - a handle to one value of a document +- [json_editable_document](json_editable_document.md) - an editable document of `json` +- [(constructor)](basic_json_view/basic_json_view.md) - the conversion from a read-only to an editable view + +## Version history + +Added in version 3.13.0. diff --git a/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp b/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp index 9d7821066..7089466e4 100644 --- a/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp +++ b/docs/mkdocs/docs/examples/ordered_json_editable_document.cpp @@ -5,7 +5,7 @@ using nlohmann::ordered_json_editable_document; int main() { - // ordered_json_editable_document is basic_json_document + // ordered_json_editable_document is basic_json_document ordered_json_editable_document doc = ordered_json_editable_document::parse(R"({"z": 1, "a": 2, "m": 3})"); doc.set(doc.root(), "b", 4); // set() always appends a new member at the end diff --git a/docs/mkdocs/docs/examples/view_mode.cpp b/docs/mkdocs/docs/examples/view_mode.cpp new file mode 100644 index 000000000..5cdd7718b --- /dev/null +++ b/docs/mkdocs/docs/examples/view_mode.cpp @@ -0,0 +1,36 @@ +#include +#include + +using nlohmann::basic_json_view; +using nlohmann::view_mode; + +// accepts the views of read-only and of editable documents, of json and of ordered_json +template +std::size_t count_members(basic_json_view v) +{ + return v.is_object() ? v.size() : 0; +} + +// accepts only the views of editable documents +template +bool is_edited(basic_json_view v) +{ + return v.is_string() && v.get_string() == "edited"; +} + +// a function (no template) taking a json_editable_view also accepts the view of a read-only document +std::size_t size_of(nlohmann::json_editable_view v) +{ + return v.size(); +} + +int main() +{ + const nlohmann::json_document doc = nlohmann::json_document::parse(R"({"a": 1, "b": 2})"); + nlohmann::json_editable_document edoc = nlohmann::json_editable_document::parse(R"({"a": 1})"); + edoc.set(edoc.root()["a"], "edited"); + + std::cout << count_members(doc.root()) << ' ' << count_members(edoc.root()) << '\n'; + std::cout << std::boolalpha << is_edited(edoc.root()["a"]) << '\n'; + std::cout << size_of(doc.root()) << ' ' << size_of(edoc.root()) << '\n'; +} diff --git a/docs/mkdocs/docs/examples/view_mode.output b/docs/mkdocs/docs/examples/view_mode.output new file mode 100644 index 000000000..0e56385a0 --- /dev/null +++ b/docs/mkdocs/docs/examples/view_mode.output @@ -0,0 +1,3 @@ +2 1 +true +2 1 diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 4f0938273..4b5498af5 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -252,14 +252,14 @@ the stream's `width`/`fill` for indentation. ## Editing a document Everything above is read-only: a `json_document`/`json_view` lets you look at a parsed text without copying it, but -not change it. [`basic_json_document`](../api/basic_json_document/index.md) -- more conveniently +not change it. [`basic_json_document`](../api/basic_json_document/index.md) -- more conveniently spelled [`json_editable_document`](../api/json_editable_document.md) or [`ordered_json_editable_document`](../api/ordered_json_editable_document.md) -- also lets you [`set`](../api/basic_json_document/set.md) a value, [`push_back`](../api/basic_json_document/push_back.md) onto or [`insert`](../api/basic_json_document/insert.md) into an array, and [`erase`](../api/basic_json_document/erase.md) an object member or an array element, still without ever building a `basic_json` tree for parts you do not touch. -`#!cpp Editable` defaults to `#!cpp false`, so `json_document`/`ordered_json_document` are unaffected -- they carry +The [mode](../api/view_mode.md) defaults to `#!cpp view_mode::read_only`, so `json_document`/`ordered_json_document` are unaffected -- they carry none of the bookkeeping edits need, and calling `set`/`push_back`/`insert`/`erase` on one is a compile error, not a runtime one. diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index f2b99cf3a..66bac7e80 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -411,6 +411,7 @@ nav: - 'ordered_json_editable_view': api/ordered_json_editable_view.md - 'ordered_json_view': api/ordered_json_view.md - 'ordered_map': api/ordered_map.md + - 'view_mode': api/view_mode.md - macros: - 'Overview': api/macros/index.md - 'JSON_ASSERT': api/macros/json_assert.md diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 3e4ea268a..f7b560f50 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -639,11 +639,11 @@ run_found: switch (cur()) { case '{': - open(value_t::object); + open(node_kind::object); ++p; goto obj_first; case '[': - open(value_t::array); + open(node_kind::array); ++p; goto arr_first; default: @@ -665,11 +665,11 @@ run_found: if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \ break; \ case '{': \ - open(value_t::object); \ + open(node_kind::object); \ ++p; \ goto obj_first; \ case '[': \ - open(value_t::array); \ + open(node_kind::array); \ ++p; \ goto arr_first; \ case '-': \ @@ -886,17 +886,17 @@ root_done: NLOHMANN_VIEW_ALWAYS_INLINE bool literal_true() { - return literal("true", value_t::boolean, node_flags::is_true); + return literal("true", node_kind::boolean, node_flags::is_true); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_false() { - return literal("false", value_t::boolean, 0); + return literal("false", node_kind::boolean, 0); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_null() { - return literal("null", value_t::null, 0); + return literal("null", node_kind::null, 0); } /// skip whitespace (and comments); false on a malformed comment @@ -962,7 +962,7 @@ indent_done: /// append a node: (kind, flags, extra, off) and the second word (len, or /// an integer's value); two stores on little-endian targets - NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) + NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) { if (NLOHMANN_VIEW_UNLIKELY(out == cap)) { @@ -978,7 +978,7 @@ indent_done: std::memcpy(reinterpret_cast(n), &first, 8); std::memcpy(reinterpret_cast(n) + 8, &second, 8); #else - n->kind = static_cast(k); + n->kind = k; n->flags = flags; n->extra = extra; n->off = static_cast(off); @@ -990,7 +990,7 @@ indent_done: return n; } - NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k) + NLOHMANN_VIEW_ALWAYS_INLINE void open(node_kind k) { // (base is read after push_node(), which moves the node array when it // grows; in one expression the order of the two is unspecified) @@ -1017,7 +1017,7 @@ indent_done: max_depth = (std::max)(max_depth, depth); cur_idx = idx; cur_count = 0; - cur_is_object = k == value_t::object; + cur_is_object = k == node_kind::object; } NLOHMANN_VIEW_ALWAYS_INLINE void close() @@ -1047,7 +1047,7 @@ indent_done: /// the literal `text` at p (N counts the terminating NUL) template - NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], value_t k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal + NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], node_kind k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal { const std::size_t n = N - 1; if (NLOHMANN_VIEW_UNLIKELY(e - p < static_cast(n) || std::memcmp(p, text, n) != 0)) @@ -1126,23 +1126,23 @@ indent_done: is_float = true; } - value_t kind = value_t::number_float; + node_kind kind = node_kind::number_float; if (!is_float) { // integers that do not fit become floats, as in parse() if (negative) { - kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? value_t::number_float : value_t::number_integer; + kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? node_kind::number_float : node_kind::number_integer; } else { - kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? value_t::number_float : value_t::number_unsigned; + kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? node_kind::number_float : node_kind::number_unsigned; } } // parse() rejects floats that overflow; only numbers whose magnitude // could reach the largest FloatType (1e308 for double, 1e38 for // float) need the conversion - if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == value_t::number_float)) + if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == node_kind::number_float)) { if (builder::float_overflows(s, p)) { @@ -1152,7 +1152,7 @@ indent_done: } const auto layout = static_cast((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8)); auto second = static_cast(p - s); - if (kind != value_t::number_float) + if (kind != node_kind::number_float) { // integers are converted now, while their digits are in cache const std::uint64_t m = int_digits <= 19 ? parse_upto19(int_start, static_cast(int_digits), e) @@ -1175,7 +1175,7 @@ indent_done: p = scan_string_run(p, e); if (NLOHMANN_VIEW_LIKELY(cur() == '"')) { - push_node(value_t::string, 0, 0, static_cast(s - b), static_cast(p - s)); + push_node(node_kind::string, 0, 0, static_cast(s - b), static_cast(p - s)); ++p; return true; } @@ -1185,7 +1185,7 @@ indent_done: return false; } p = r.p; - push_node(value_t::string, node_flags::escaped, 0, r.start, r.len); + push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len); return true; } }; diff --git a/include/nlohmann/detail/view/document_data.hpp b/include/nlohmann/detail/view/document_data.hpp index 55f9d8c76..afef5a7a2 100644 --- a/include/nlohmann/detail/view/document_data.hpp +++ b/include/nlohmann/detail/view/document_data.hpp @@ -232,7 +232,7 @@ struct document_data /// following loads. static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept { - return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n); + return NLOHMANN_VIEW_LIKELY(n->kind != node_kind::link) ? n : follow_link(n); } static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept diff --git a/include/nlohmann/detail/view/edit.hpp b/include/nlohmann/detail/view/edit.hpp index bad8d9da3..f47bf5985 100644 --- a/include/nlohmann/detail/view/edit.hpp +++ b/include/nlohmann/detail/view/edit.hpp @@ -26,12 +26,10 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN -template -class basic_json_view; - namespace detail { namespace view @@ -120,15 +118,15 @@ class editor View set(const View& object, string_view_t key, V&& value) { node* const o = own(object); - if (o->kind != static_cast(value_t::object) && o->kind != static_cast(value_t::null)) + if (o->kind != node_kind::object && o->kind != node_kind::null) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); - if (o->kind == static_cast(value_t::null)) + if (o->kind == node_kind::null) { - become_empty(o, value_t::object); + become_empty(o, node_kind::object); } // an existing member: assign it (and drop later duplicates, so that // lookups, iteration, and materialize() agree) @@ -171,7 +169,7 @@ class editor View set(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } @@ -188,14 +186,14 @@ class editor View push_back(const View& array, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array) && a->kind != static_cast(value_t::null)) + if (a->kind != node_kind::array && a->kind != node_kind::null) { throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); - if (a->kind == static_cast(value_t::null)) + if (a->kind == node_kind::null) { - become_empty(a, value_t::array); + become_empty(a, node_kind::array); } node* const slot = new_slot(e); node* const h = block_of(m_doc, a, 1); @@ -212,7 +210,7 @@ class editor View insert(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } @@ -232,7 +230,7 @@ class editor std::size_t erase(const View& object, string_view_t key) { node* const o = own(object); - if (o->kind != static_cast(value_t::object)) + if (o->kind != node_kind::object) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } @@ -250,7 +248,7 @@ class editor void erase(const View& array, std::size_t idx) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } @@ -324,11 +322,11 @@ class editor } /// turn a null into an empty array/object in place - static void become_empty(node* n, value_t k) noexcept + static void become_empty(node* n, node_kind k) noexcept { const std::uint8_t linked = n->flags & node_flags::linked; *n = node{}; - n->kind = static_cast(k); + n->kind = k; n->flags = static_cast(node_flags::is_new | linked); n->next = 1; } @@ -405,8 +403,8 @@ class editor template struct is_view : std::false_type {}; - template - struct is_view> : std::true_type {}; + template + struct is_view> : std::true_type {}; template encoded encode(V&& v) @@ -421,8 +419,8 @@ class editor } /// a view of any document (copied; nothing is shared with it) - template - encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) + template + encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) { if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) { @@ -434,8 +432,8 @@ class editor r.scalar = copy_scalar(*v.m_doc, *v.m_node); return r; } - r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); - fill_nodes(*v.m_doc, v.m_node, r.region); + r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); + fill_nodes(*v.m_doc, v.m_node, r.region); edit_state_of(m_doc).regions.emplace(r.region, nullptr); return r; } @@ -457,14 +455,14 @@ class editor encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/) { encoded r; - r.scalar = plain_node(value_t::null); + r.scalar = plain_node(node_kind::null); return r; } encoded encode_impl(bool b, encode_tag<3> /*boolean*/) { encoded r; - r.scalar = plain_node(value_t::boolean); + r.scalar = plain_node(node_kind::boolean); r.scalar.flags = static_cast(r.scalar.flags | (b ? node_flags::is_true : 0)); return r; } @@ -493,10 +491,10 @@ class editor return encode_impl(BasicJsonType(std::forward(x)), encode_tag<1> {}); } - static node plain_node(value_t k) noexcept + static node plain_node(node_kind k) noexcept { node n{}; - n.kind = static_cast(k); + n.kind = k; n.flags = node_flags::is_new; return n; } @@ -510,19 +508,19 @@ class editor template node number_node(T x, std::integral_constant /*signed*/) { - return integer_node(static_cast(static_cast(x)), value_t::number_integer); + return integer_node(static_cast(static_cast(x)), node_kind::number_integer); } template node number_node(T x, std::integral_constant /*unsigned*/) { - return integer_node(static_cast(x), value_t::number_unsigned); + return integer_node(static_cast(x), node_kind::number_unsigned); } /// an integer with its canonical token in the edit arena - node integer_node(std::uint64_t bits, value_t k) + node integer_node(std::uint64_t bits, node_kind k) { - const bool negative = k == value_t::number_integer && static_cast(bits) < 0; + const bool negative = k == node_kind::number_integer && static_cast(bits) < 0; std::uint64_t magnitude = negative ? 0 - bits : bits; std::array buf{}; char* p = buf.data() + buf.size(); // NOLINT(misc-const-correctness): digits are written through p @@ -541,7 +539,7 @@ class editor n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, p, len); // number_length() adds one for the sign of number_integer nodes - n.extra = static_cast(k == value_t::number_integer ? len - 1 : len); + n.extra = static_cast(k == node_kind::number_integer ? len - 1 : len); set_integer_bits(n, bits); return n; } @@ -563,7 +561,7 @@ class editor { text = BasicJsonType(x).dump(); } - node n = plain_node(value_t::number_float); + node n = plain_node(node_kind::number_float); n.flags = static_cast(n.flags | node_flags::edited); n.extra = 0xFFFFu; // (the digit layout is not recorded) n.off = append_text(m_doc, text.data(), text.size()); @@ -578,7 +576,7 @@ class editor { throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE } - node n = plain_node(value_t::string); + node n = plain_node(node_kind::string); n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, s, len); n.len = static_cast(len); @@ -600,10 +598,10 @@ class editor return string_node(from.str(n), n.len); case value_t::number_integer: case value_t::number_unsigned: - return integer_node(integer_bits(n), static_cast(n.kind)); + return integer_node(integer_bits(n), n.kind); case value_t::number_float: { - node r = plain_node(value_t::number_float); + node r = plain_node(node_kind::number_float); r.flags = static_cast(r.flags | node_flags::edited); r.off = append_text(m_doc, from.str(n), n.len); r.len = n.len; @@ -612,7 +610,7 @@ class editor } case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (n.flags & node_flags::is_true)); return r; } @@ -622,7 +620,7 @@ class editor case value_t::binary: case value_t::discarded: default: - return plain_node(value_t::null); + return plain_node(node_kind::null); } } @@ -631,17 +629,17 @@ class editor switch (j.type()) { case value_t::null: - return plain_node(value_t::null); + return plain_node(node_kind::null); case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (j.template get() ? node_flags::is_true : 0)); return r; } case value_t::number_integer: - return integer_node(static_cast(static_cast(j.template get())), value_t::number_integer); + return integer_node(static_cast(static_cast(j.template get())), node_kind::number_integer); case value_t::number_unsigned: - return integer_node(static_cast(j.template get()), value_t::number_unsigned); + return integer_node(static_cast(j.template get()), node_kind::number_unsigned); case value_t::number_float: return float_node(j.template get()); case value_t::string: @@ -681,7 +679,7 @@ class editor ++r; if (is_container(*n)) { - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object)}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object}); } // the next value: close finished containers, then step over the key for (;;) @@ -728,9 +726,9 @@ class editor if (is_container(*n)) { node* const self = out++; - *self = plain_node(static_cast(n->kind)); + *self = plain_node(n->kind); self->len = n->len; - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object), self}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object, self}); } else { @@ -819,7 +817,7 @@ class editor if (n->is_structured()) { node* const self = out++; - *self = plain_node(n->type()); + *self = plain_node(static_cast(n->type())); self->len = static_cast(n->size()); open.push_back(frame{n->cbegin(), n->cend(), n->is_object(), self}); } diff --git a/include/nlohmann/detail/view/edit_storage.hpp b/include/nlohmann/detail/view/edit_storage.hpp index 9a45bfebd..e5b7ddb90 100644 --- a/include/nlohmann/detail/view/edit_storage.hpp +++ b/include/nlohmann/detail/view/edit_storage.hpp @@ -27,7 +27,7 @@ // live in chunks that never move; strings and number tokens written by edits // live in the edit arena. An array/object whose elements change gets // node_flags::moved: its elements then live in a separate sequence (a header -// node, then the entries), whose entries link to the values (kind_link). +// node, then the entries), whose entries link to the values (node_kind::link). NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -185,7 +185,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra) return nh; } reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before) - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; const std::size_t used = 1 + (static_cast(n->len) * (object ? 2 : 1)); const std::size_t cap = used + extra; node* const h = alloc_nodes(d, cap); @@ -251,7 +251,7 @@ inline node* find_parent(const document_data& d, const node* target) { return nullptr; // LCOV_EXCL_LINE (the value is inside c) } - const bool object = c->kind == static_cast(value_t::object); + const bool object = c->kind == node_kind::object; const node* down = nullptr; for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;) { diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index af9994712..c13d269e1 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -117,7 +117,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.len = 0; // counted below r.next = 0; // set when the container is complete - stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast(value_t::object)}); + stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == node_kind::object}); break; case value_t::string: r = string_node(*v); @@ -138,7 +138,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) { r = node{}; // nan and infinity: null, as dump() writes them - r.kind = static_cast(value_t::null); + r.kind = node_kind::null; break; } r.off = static_cast(d.size + text_tail.size()); @@ -221,7 +221,7 @@ inline std::vector save_image(const document_data& d) // extra of an object, and the same document saves to the same bytes for (std::size_t i = 0; i < count; ++i) { - if (nodes[i].kind == static_cast(value_t::object) && nodes[i].extra != 0) + if (nodes[i].kind == node_kind::object && nodes[i].extra != 0) { std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra)); } @@ -337,7 +337,7 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu if (static_cast(t.int_digits) + t.exponent > 300) { node n{}; - n.kind = static_cast(value_t::number_float); + n.kind = node_kind::number_float; n.len = static_cast(len); const auto v = float_value(reinterpret_cast(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) return v <= (std::numeric_limits::max)() && v >= -(std::numeric_limits::max)(); @@ -358,7 +358,7 @@ inline bool check_integer(const node& n, const unsigned char* text) } // the token's value is the stored one; number_integer nodes of edits can // be non-negative (as basic_json keeps the type of a value) - const bool integer = n.kind == static_cast(value_t::number_integer); + const bool integer = n.kind == node_kind::number_integer; if (t.is_float || t.int_digits > 20 || (t.negative && !integer)) { return false; @@ -540,7 +540,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - if (n.kind != static_cast(value_t::number_float)) + if (n.kind != node_kind::number_float) { return (n.extra >> 8u) == 0; } @@ -581,7 +581,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha const node& n = nodes[i]; if (!stack.empty() && stack.back().object && stack.back().expect_key) { - if (n.kind != static_cast(value_t::string) || !check_string(n)) + if (n.kind != node_kind::string || !check_string(n)) { return false; } @@ -622,7 +622,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { // (the contents of float tokens are checked later; the token of an // integer has at most 256 characters) - if (n.kind == static_cast(value_t::number_float)) + if (n.kind == node_kind::number_float) { floats.push_back(float_range{n.off, n.len, n.extra}); } @@ -640,7 +640,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast(value_t::object), true}); + stack.push_back(frame{i + n.next, n.len, 0, n.kind == node_kind::object, true}); complete = false; break; } @@ -729,7 +729,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t for (std::size_t i = 0; i < count; ++i) { const node& n = d.nodes[i]; - if (n.kind == static_cast(value_t::object) && n.len >= document_data::index_min_members) + if (n.kind == node_kind::object && n.len >= document_data::index_min_members) { d.large_objects.push_back(static_cast(i)); } diff --git a/include/nlohmann/detail/view/lookup.hpp b/include/nlohmann/detail/view/lookup.hpp index e2038a956..f288927bb 100644 --- a/include/nlohmann/detail/view/lookup.hpp +++ b/include/nlohmann/detail/view/lookup.hpp @@ -182,7 +182,7 @@ const node* element_at(const document_data& d, const node* array, std::size_t id template const node* last_child(const document_data& d, const node* container) noexcept { - const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const std::size_t value_offset = container->kind == node_kind::object ? 1 : 0; const node* const end = navigation::end(d, container); const node* last = navigation::first(d, container); if (value_offset == 0 && container->next == container->len + 1u && (!navigation::editable() || (container->flags & node_flags::moved) == 0)) diff --git a/include/nlohmann/detail/view/materialize.hpp b/include/nlohmann/detail/view/materialize.hpp index 62adb30a9..1bab0e896 100644 --- a/include/nlohmann/detail/view/materialize.hpp +++ b/include/nlohmann/detail/view/materialize.hpp @@ -101,7 +101,7 @@ BasicJsonType materialize(const document_data& d, const node* n) case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; if (object) { sax.start_object(n->len); diff --git a/include/nlohmann/detail/view/node.hpp b/include/nlohmann/detail/view/node.hpp index 7a05c8e0a..8987a8860 100644 --- a/include/nlohmann/detail/view/node.hpp +++ b/include/nlohmann/detail/view/node.hpp @@ -41,9 +41,22 @@ struct node_flags static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters }; -/// kind of an entry of an edited sequence that stands for a value stored -/// elsewhere (the address of the value's node is kept in the len/next bytes) -constexpr std::uint8_t kind_link = 10; +/// the kind of a node: the value_t of its value, or link +enum class node_kind : std::uint8_t +{ + null = static_cast(value_t::null), + object = static_cast(value_t::object), + array = static_cast(value_t::array), + string = static_cast(value_t::string), // NOLINT(build/include_what_you_use): an enumerator, not std::string + boolean = static_cast(value_t::boolean), + number_integer = static_cast(value_t::number_integer), + number_unsigned = static_cast(value_t::number_unsigned), + number_float = static_cast(value_t::number_float), + /// an entry of an edited sequence that stands for a value stored elsewhere + /// (the address of the value's node is kept in the len/next bytes); placed + /// after value_t::discarded, which no node ever has + link = static_cast(value_t::discarded) + 1, +}; /// One entry of the flat index, in document order. An object's members are /// stored as key node followed by the value's subtree. Integers keep their @@ -51,7 +64,7 @@ constexpr std::uint8_t kind_link = 10; /// always the next one, and the token length follows from `extra`). struct node { - std::uint8_t kind; ///< value_t, or kind_link + node_kind kind; ///< the kind of the value, or link std::uint8_t flags; ///< node_flags std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved @@ -60,13 +73,13 @@ struct node }; static_assert(sizeof(node) == 16, "node must stay 16 bytes"); -// the node kinds are value_t values; the test below (object and array are 1 -// and 2) and those of the number kinds depend on this numbering -static_assert(static_cast(value_t::null) == 0 && static_cast(value_t::object) == 1 - && static_cast(value_t::array) == 2 && static_cast(value_t::string) == 3 - && static_cast(value_t::boolean) == 4 && static_cast(value_t::number_integer) == 5 - && static_cast(value_t::number_unsigned) == 6 && static_cast(value_t::number_float) == 7, - "the node format depends on the numbering of value_t"); +// images store the node kinds as bytes, and the test below (object and array +// are 1 and 2) and those of the number kinds depend on this numbering +static_assert(static_cast(node_kind::null) == 0 && static_cast(node_kind::object) == 1 + && static_cast(node_kind::array) == 2 && static_cast(node_kind::string) == 3 + && static_cast(node_kind::boolean) == 4 && static_cast(node_kind::number_integer) == 5 + && static_cast(node_kind::number_unsigned) == 6 && static_cast(node_kind::number_float) == 7, + "the node format depends on the numbering of node_kind"); NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept { @@ -86,7 +99,7 @@ inline void make_link(node& n, node* target) noexcept { target->flags = static_cast(target->flags | node_flags::linked); n = node{}; - n.kind = kind_link; + n.kind = node_kind::link; std::memcpy(reinterpret_cast(&n) + 8, static_cast(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } @@ -116,8 +129,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noex /// token length of a number node NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept { - return n.kind == static_cast(value_t::number_float) ? n.len - : (n.extra & 0xFFu) + (n.kind == static_cast(value_t::number_integer) ? 1u : 0u); + return n.kind == node_kind::number_float ? n.len + : (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u); } /// estimated number of nodes for an input of `size` bytes (one node per ~12 diff --git a/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index 76df8f89b..b41ac683f 100644 --- a/include/nlohmann/detail/view/serializer.hpp +++ b/include/nlohmann/detail/view/serializer.hpp @@ -874,7 +874,7 @@ class view_serializer case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; room(8); if (n->len == 0) { diff --git a/include/nlohmann/detail/view/view_mode.hpp b/include/nlohmann/detail/view/view_mode.hpp new file mode 100644 index 000000000..3aeccd0b9 --- /dev/null +++ b/include/nlohmann/detail/view/view_mode.hpp @@ -0,0 +1,52 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#pragma once + +#include // uint8_t +#include // false_type, true_type + +#include + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// whether a basic_json_document and its views can be edited +enum class view_mode : std::uint8_t +{ + read_only, ///< parsed once, then only read (the default) + editable, ///< also has set(), push_back(), insert(), and erase() +}; + +template +class basic_json_view; + +namespace detail +{ +namespace view +{ + +/// whether two view modes differ +template +struct is_other_mode : std::true_type {}; + +template +struct is_other_mode : std::false_type {}; + +/// whether a view of mode From converts implicitly to one of mode To: a +/// read-only document has no edits, so a view of it is read correctly as a +/// view of an editable document that has not been edited yet +template +struct is_widening_mode : std::false_type {}; + +template<> +struct is_widening_mode : std::true_type {}; + +} // namespace view +} // namespace detail + +NLOHMANN_JSON_NAMESPACE_END diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 85026008a..42a8cb99e 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -84,6 +84,7 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN @@ -93,13 +94,16 @@ NLOHMANN_JSON_NAMESPACE_BEGIN Trivially copyable (two pointers). Valid as long as the document is alive and has not been re-parsed, and as long as a borrowed source text is alive. */ -template +template class basic_json_view { + /// whether this is a view of an editable document + static constexpr bool is_editable = Mode == view_mode::editable; + using node = detail::view::node; using document_data = detail::view::document_data; /// how the index is walked (with edits only for editable documents) - using navigation = detail::view::navigation; + using navigation = detail::view::navigation; public: using value_t = detail::value_t; @@ -120,6 +124,15 @@ class basic_json_view /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; + /// a view of a read-only document as a view of an editable one, so that a + /// function taking an editable view accepts both (a read-only document has + /// no edits; the view reads it as an editable document before its first + /// edit, at the cost of one check per access) + template::value, int> = 0> + basic_json_view(const basic_json_view& other) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : m_doc(other.m_doc), m_node(other.m_node) + {} + ////////// // type // ////////// @@ -446,7 +459,7 @@ class basic_json_view if (is_structured() && m_node->len != 0) { return unedited() ? basic_json_view(m_doc, detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0)) - : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); + : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); } return front(); } @@ -504,7 +517,7 @@ class basic_json_view /// more than once); 0 if this is not an object size_type count(string_view_t key) const { - return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; + return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; } size_type count(const char* key) const @@ -646,7 +659,7 @@ class basic_json_view } else { - detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); + detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); } return out; } @@ -682,14 +695,14 @@ class basic_json_view } /// a view of an editable document compares with one of a read-only document - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator==(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator==(const basic_json_view& a, const basic_json_view& b) { - return detail::view::equal(side(a), detail::view::view_side>(b)); + return detail::view::equal(side(a), detail::view::view_side>(b)); } - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator!=(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator!=(const basic_json_view& a, const basic_json_view& b) { return !(a == b); } @@ -727,7 +740,7 @@ class basic_json_view { return BasicJsonType(value_t::discarded); } - return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); + return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); } /// byte offset of this value in the source text (for strings: of the @@ -740,8 +753,8 @@ class basic_json_view } private: - template friend class basic_json_document; - template friend class basic_json_view; + template friend class basic_json_document; + template friend class basic_json_view; template friend class detail::view::editor; friend iterator; @@ -757,11 +770,11 @@ class basic_json_view } /// the template argument as a runtime condition: testing it directly in - /// `Editable && ...` makes the condition constant, which MSVC reports as + /// `is_editable && ...` makes the condition constant, which MSVC reports as /// C4127 static bool editable() noexcept { - return Editable; + return is_editable; } /// the number of source bytes of this value (estimated for values with @@ -810,7 +823,7 @@ class basic_json_view const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); } - const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); } @@ -824,7 +837,7 @@ class basic_json_view /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const { - return find_key(key, std::integral_constant {}); + return find_key(key, std::integral_constant {}); } NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const @@ -846,7 +859,7 @@ class basic_json_view { return basic_json_view(m_doc, detail::view::element_at(*m_doc, m_node, idx)); } - return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); + return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); } // --- get() dispatch --- @@ -952,16 +965,19 @@ Borrowed parses keep a pointer to the caller's text, which must outlive the document. Owned parses (parse_copy, rvalue std::string, streams, and inputs that are not contiguous byte ranges) keep their own copy. */ -template +template class basic_json_document { + /// whether the document can be edited + static constexpr bool is_editable = Mode == view_mode::editable; + using document_data = detail::view::document_data; static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8, "json_view supports 64-bit integer types only"); public: - using view_type = basic_json_view; + using view_type = basic_json_view; using value_t = detail::value_t; /// an empty (discarded) document @@ -1357,7 +1373,7 @@ class basic_json_document detail::view::editor editor() { - static_assert(Editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); + static_assert(is_editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) { detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); @@ -1634,21 +1650,21 @@ class basic_json_document }; /// a parsed JSON text for json -using json_document = basic_json_document; +using json_document = basic_json_document; /// a value of a json_document -using json_view = basic_json_view; +using json_view = basic_json_view; /// a parsed JSON text for ordered_json -using ordered_json_document = basic_json_document; +using ordered_json_document = basic_json_document; /// a value of an ordered_json_document -using ordered_json_view = basic_json_view; +using ordered_json_view = basic_json_view; /// an editable parsed JSON text for json -using json_editable_document = basic_json_document; +using json_editable_document = basic_json_document; /// a value of a json_editable_document -using json_editable_view = basic_json_view; +using json_editable_view = basic_json_view; /// an editable parsed JSON text for ordered_json -using ordered_json_editable_document = basic_json_document; +using ordered_json_editable_document = basic_json_document; /// a value of an ordered_json_editable_document -using ordered_json_editable_view = basic_json_view; +using ordered_json_editable_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 338b55492..1fee76e63 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -239,9 +239,22 @@ struct node_flags static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters }; -/// kind of an entry of an edited sequence that stands for a value stored -/// elsewhere (the address of the value's node is kept in the len/next bytes) -constexpr std::uint8_t kind_link = 10; +/// the kind of a node: the value_t of its value, or link +enum class node_kind : std::uint8_t +{ + null = static_cast(value_t::null), + object = static_cast(value_t::object), + array = static_cast(value_t::array), + string = static_cast(value_t::string), // NOLINT(build/include_what_you_use): an enumerator, not std::string + boolean = static_cast(value_t::boolean), + number_integer = static_cast(value_t::number_integer), + number_unsigned = static_cast(value_t::number_unsigned), + number_float = static_cast(value_t::number_float), + /// an entry of an edited sequence that stands for a value stored elsewhere + /// (the address of the value's node is kept in the len/next bytes); placed + /// after value_t::discarded, which no node ever has + link = static_cast(value_t::discarded) + 1, +}; /// One entry of the flat index, in document order. An object's members are /// stored as key node followed by the value's subtree. Integers keep their @@ -249,7 +262,7 @@ constexpr std::uint8_t kind_link = 10; /// always the next one, and the token length follows from `extra`). struct node { - std::uint8_t kind; ///< value_t, or kind_link + node_kind kind; ///< the kind of the value, or link std::uint8_t flags; ///< node_flags std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved @@ -258,13 +271,13 @@ struct node }; static_assert(sizeof(node) == 16, "node must stay 16 bytes"); -// the node kinds are value_t values; the test below (object and array are 1 -// and 2) and those of the number kinds depend on this numbering -static_assert(static_cast(value_t::null) == 0 && static_cast(value_t::object) == 1 - && static_cast(value_t::array) == 2 && static_cast(value_t::string) == 3 - && static_cast(value_t::boolean) == 4 && static_cast(value_t::number_integer) == 5 - && static_cast(value_t::number_unsigned) == 6 && static_cast(value_t::number_float) == 7, - "the node format depends on the numbering of value_t"); +// images store the node kinds as bytes, and the test below (object and array +// are 1 and 2) and those of the number kinds depend on this numbering +static_assert(static_cast(node_kind::null) == 0 && static_cast(node_kind::object) == 1 + && static_cast(node_kind::array) == 2 && static_cast(node_kind::string) == 3 + && static_cast(node_kind::boolean) == 4 && static_cast(node_kind::number_integer) == 5 + && static_cast(node_kind::number_unsigned) == 6 && static_cast(node_kind::number_float) == 7, + "the node format depends on the numbering of node_kind"); NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept { @@ -284,7 +297,7 @@ inline void make_link(node& n, node* target) noexcept { target->flags = static_cast(target->flags | node_flags::linked); n = node{}; - n.kind = kind_link; + n.kind = node_kind::link; std::memcpy(reinterpret_cast(&n) + 8, static_cast(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } @@ -314,8 +327,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noex /// token length of a number node NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept { - return n.kind == static_cast(value_t::number_float) ? n.len - : (n.extra & 0xFFu) + (n.kind == static_cast(value_t::number_integer) ? 1u : 0u); + return n.kind == node_kind::number_float ? n.len + : (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u); } /// estimated number of nodes for an input of `size` bytes (one node per ~12 @@ -547,7 +560,7 @@ struct document_data /// following loads. static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept { - return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n); + return NLOHMANN_VIEW_LIKELY(n->kind != node_kind::link) ? n : follow_link(n); } static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept @@ -1830,11 +1843,11 @@ run_found: switch (cur()) { case '{': - open(value_t::object); + open(node_kind::object); ++p; goto obj_first; case '[': - open(value_t::array); + open(node_kind::array); ++p; goto arr_first; default: @@ -1856,11 +1869,11 @@ run_found: if (NLOHMANN_VIEW_UNLIKELY(!string())) { return false; } \ break; \ case '{': \ - open(value_t::object); \ + open(node_kind::object); \ ++p; \ goto obj_first; \ case '[': \ - open(value_t::array); \ + open(node_kind::array); \ ++p; \ goto arr_first; \ case '-': \ @@ -2077,17 +2090,17 @@ root_done: NLOHMANN_VIEW_ALWAYS_INLINE bool literal_true() { - return literal("true", value_t::boolean, node_flags::is_true); + return literal("true", node_kind::boolean, node_flags::is_true); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_false() { - return literal("false", value_t::boolean, 0); + return literal("false", node_kind::boolean, 0); } NLOHMANN_VIEW_ALWAYS_INLINE bool literal_null() { - return literal("null", value_t::null, 0); + return literal("null", node_kind::null, 0); } /// skip whitespace (and comments); false on a malformed comment @@ -2153,7 +2166,7 @@ indent_done: /// append a node: (kind, flags, extra, off) and the second word (len, or /// an integer's value); two stores on little-endian targets - NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) + NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) { if (NLOHMANN_VIEW_UNLIKELY(out == cap)) { @@ -2169,7 +2182,7 @@ indent_done: std::memcpy(reinterpret_cast(n), &first, 8); std::memcpy(reinterpret_cast(n) + 8, &second, 8); #else - n->kind = static_cast(k); + n->kind = k; n->flags = flags; n->extra = extra; n->off = static_cast(off); @@ -2181,7 +2194,7 @@ indent_done: return n; } - NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k) + NLOHMANN_VIEW_ALWAYS_INLINE void open(node_kind k) { // (base is read after push_node(), which moves the node array when it // grows; in one expression the order of the two is unspecified) @@ -2208,7 +2221,7 @@ indent_done: max_depth = (std::max)(max_depth, depth); cur_idx = idx; cur_count = 0; - cur_is_object = k == value_t::object; + cur_is_object = k == node_kind::object; } NLOHMANN_VIEW_ALWAYS_INLINE void close() @@ -2238,7 +2251,7 @@ indent_done: /// the literal `text` at p (N counts the terminating NUL) template - NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], value_t k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal + NLOHMANN_VIEW_ALWAYS_INLINE bool literal(const char (&text)[N], node_kind k, std::uint8_t flags) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): a string literal { const std::size_t n = N - 1; if (NLOHMANN_VIEW_UNLIKELY(e - p < static_cast(n) || std::memcmp(p, text, n) != 0)) @@ -2317,23 +2330,23 @@ indent_done: is_float = true; } - value_t kind = value_t::number_float; + node_kind kind = node_kind::number_float; if (!is_float) { // integers that do not fit become floats, as in parse() if (negative) { - kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? value_t::number_float : value_t::number_integer; + kind = digits_exceed(int_start, int_digits, "9223372036854775808") ? node_kind::number_float : node_kind::number_integer; } else { - kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? value_t::number_float : value_t::number_unsigned; + kind = digits_exceed(int_start, int_digits, "18446744073709551615") ? node_kind::number_float : node_kind::number_unsigned; } } // parse() rejects floats that overflow; only numbers whose magnitude // could reach the largest FloatType (1e308 for double, 1e38 for // float) need the conversion - if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == value_t::number_float)) + if (NLOHMANN_VIEW_UNLIKELY(static_cast(int_digits) + exponent > std::numeric_limits::max_exponent10 - 8 && kind == node_kind::number_float)) { if (builder::float_overflows(s, p)) { @@ -2343,7 +2356,7 @@ indent_done: } const auto layout = static_cast((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8)); auto second = static_cast(p - s); - if (kind != value_t::number_float) + if (kind != node_kind::number_float) { // integers are converted now, while their digits are in cache const std::uint64_t m = int_digits <= 19 ? parse_upto19(int_start, static_cast(int_digits), e) @@ -2366,7 +2379,7 @@ indent_done: p = scan_string_run(p, e); if (NLOHMANN_VIEW_LIKELY(cur() == '"')) { - push_node(value_t::string, 0, 0, static_cast(s - b), static_cast(p - s)); + push_node(node_kind::string, 0, 0, static_cast(s - b), static_cast(p - s)); ++p; return true; } @@ -2376,7 +2389,7 @@ indent_done: return false; } p = r.p; - push_node(value_t::string, node_flags::escaped, 0, r.start, r.len); + push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len); return true; } }; @@ -3001,7 +3014,7 @@ NLOHMANN_JSON_NAMESPACE_END // live in chunks that never move; strings and number tokens written by edits // live in the edit arena. An array/object whose elements change gets // node_flags::moved: its elements then live in a separate sequence (a header -// node, then the entries), whose entries link to the values (kind_link). +// node, then the entries), whose entries link to the values (node_kind::link). NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -3159,7 +3172,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra) return nh; } reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before) - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; const std::size_t used = 1 + (static_cast(n->len) * (object ? 2 : 1)); const std::size_t cap = used + extra; node* const h = alloc_nodes(d, cap); @@ -3225,7 +3238,7 @@ inline node* find_parent(const document_data& d, const node* target) { return nullptr; // LCOV_EXCL_LINE (the value is inside c) } - const bool object = c->kind == static_cast(value_t::object); + const bool object = c->kind == node_kind::object; const node* down = nullptr; for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;) { @@ -3692,7 +3705,7 @@ const node* element_at(const document_data& d, const node* array, std::size_t id template const node* last_child(const document_data& d, const node* container) noexcept { - const std::size_t value_offset = container->kind == static_cast(value_t::object) ? 1 : 0; + const std::size_t value_offset = container->kind == node_kind::object ? 1 : 0; const node* const end = navigation::end(d, container); const node* last = navigation::first(d, container); if (value_offset == 0 && container->next == container->len + 1u && (!navigation::editable() || (container->flags & node_flags::moved) == 0)) @@ -3714,10 +3727,32 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // uint8_t +#include // false_type, true_type + +// #include NLOHMANN_JSON_NAMESPACE_BEGIN -template +/// whether a basic_json_document and its views can be edited +enum class view_mode : std::uint8_t +{ + read_only, ///< parsed once, then only read (the default) + editable, ///< also has set(), push_back(), insert(), and erase() +}; + +template class basic_json_view; namespace detail @@ -3725,6 +3760,35 @@ namespace detail namespace view { +/// whether two view modes differ +template +struct is_other_mode : std::true_type {}; + +template +struct is_other_mode : std::false_type {}; + +/// whether a view of mode From converts implicitly to one of mode To: a +/// read-only document has no edits, so a view of it is read correctly as a +/// view of an editable document that has not been edited yet +template +struct is_widening_mode : std::false_type {}; + +template<> +struct is_widening_mode : std::true_type {}; + +} // namespace view +} // namespace detail + +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +namespace detail +{ +namespace view +{ + /// the index of the first true condition (the number of conditions if none is) template struct first_true : std::integral_constant {}; @@ -3808,15 +3872,15 @@ class editor View set(const View& object, string_view_t key, V&& value) { node* const o = own(object); - if (o->kind != static_cast(value_t::object) && o->kind != static_cast(value_t::null)) + if (o->kind != node_kind::object && o->kind != node_kind::null) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); - if (o->kind == static_cast(value_t::null)) + if (o->kind == node_kind::null) { - become_empty(o, value_t::object); + become_empty(o, node_kind::object); } // an existing member: assign it (and drop later duplicates, so that // lookups, iteration, and materialize() agree) @@ -3859,7 +3923,7 @@ class editor View set(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } @@ -3876,14 +3940,14 @@ class editor View push_back(const View& array, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array) && a->kind != static_cast(value_t::null)) + if (a->kind != node_kind::array && a->kind != node_kind::null) { throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); - if (a->kind == static_cast(value_t::null)) + if (a->kind == node_kind::null) { - become_empty(a, value_t::array); + become_empty(a, node_kind::array); } node* const slot = new_slot(e); node* const h = block_of(m_doc, a, 1); @@ -3900,7 +3964,7 @@ class editor View insert(const View& array, std::size_t idx, V&& value) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } @@ -3920,7 +3984,7 @@ class editor std::size_t erase(const View& object, string_view_t key) { node* const o = own(object); - if (o->kind != static_cast(value_t::object)) + if (o->kind != node_kind::object) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } @@ -3938,7 +4002,7 @@ class editor void erase(const View& array, std::size_t idx) { node* const a = own(array); - if (a->kind != static_cast(value_t::array)) + if (a->kind != node_kind::array) { throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } @@ -4012,11 +4076,11 @@ class editor } /// turn a null into an empty array/object in place - static void become_empty(node* n, value_t k) noexcept + static void become_empty(node* n, node_kind k) noexcept { const std::uint8_t linked = n->flags & node_flags::linked; *n = node{}; - n->kind = static_cast(k); + n->kind = k; n->flags = static_cast(node_flags::is_new | linked); n->next = 1; } @@ -4093,8 +4157,8 @@ class editor template struct is_view : std::false_type {}; - template - struct is_view> : std::true_type {}; + template + struct is_view> : std::true_type {}; template encoded encode(V&& v) @@ -4109,8 +4173,8 @@ class editor } /// a view of any document (copied; nothing is shared with it) - template - encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) + template + encoded encode_impl(const basic_json_view& v, encode_tag<0> /*view*/) { if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) { @@ -4122,8 +4186,8 @@ class editor r.scalar = copy_scalar(*v.m_doc, *v.m_node); return r; } - r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); - fill_nodes(*v.m_doc, v.m_node, r.region); + r.region = alloc_nodes(m_doc, count_nodes(*v.m_doc, v.m_node)); + fill_nodes(*v.m_doc, v.m_node, r.region); edit_state_of(m_doc).regions.emplace(r.region, nullptr); return r; } @@ -4145,14 +4209,14 @@ class editor encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/) { encoded r; - r.scalar = plain_node(value_t::null); + r.scalar = plain_node(node_kind::null); return r; } encoded encode_impl(bool b, encode_tag<3> /*boolean*/) { encoded r; - r.scalar = plain_node(value_t::boolean); + r.scalar = plain_node(node_kind::boolean); r.scalar.flags = static_cast(r.scalar.flags | (b ? node_flags::is_true : 0)); return r; } @@ -4181,10 +4245,10 @@ class editor return encode_impl(BasicJsonType(std::forward(x)), encode_tag<1> {}); } - static node plain_node(value_t k) noexcept + static node plain_node(node_kind k) noexcept { node n{}; - n.kind = static_cast(k); + n.kind = k; n.flags = node_flags::is_new; return n; } @@ -4198,19 +4262,19 @@ class editor template node number_node(T x, std::integral_constant /*signed*/) { - return integer_node(static_cast(static_cast(x)), value_t::number_integer); + return integer_node(static_cast(static_cast(x)), node_kind::number_integer); } template node number_node(T x, std::integral_constant /*unsigned*/) { - return integer_node(static_cast(x), value_t::number_unsigned); + return integer_node(static_cast(x), node_kind::number_unsigned); } /// an integer with its canonical token in the edit arena - node integer_node(std::uint64_t bits, value_t k) + node integer_node(std::uint64_t bits, node_kind k) { - const bool negative = k == value_t::number_integer && static_cast(bits) < 0; + const bool negative = k == node_kind::number_integer && static_cast(bits) < 0; std::uint64_t magnitude = negative ? 0 - bits : bits; std::array buf{}; char* p = buf.data() + buf.size(); // NOLINT(misc-const-correctness): digits are written through p @@ -4229,7 +4293,7 @@ class editor n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, p, len); // number_length() adds one for the sign of number_integer nodes - n.extra = static_cast(k == value_t::number_integer ? len - 1 : len); + n.extra = static_cast(k == node_kind::number_integer ? len - 1 : len); set_integer_bits(n, bits); return n; } @@ -4251,7 +4315,7 @@ class editor { text = BasicJsonType(x).dump(); } - node n = plain_node(value_t::number_float); + node n = plain_node(node_kind::number_float); n.flags = static_cast(n.flags | node_flags::edited); n.extra = 0xFFFFu; // (the digit layout is not recorded) n.off = append_text(m_doc, text.data(), text.size()); @@ -4266,7 +4330,7 @@ class editor { throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE } - node n = plain_node(value_t::string); + node n = plain_node(node_kind::string); n.flags = static_cast(n.flags | node_flags::edited); n.off = append_text(m_doc, s, len); n.len = static_cast(len); @@ -4288,10 +4352,10 @@ class editor return string_node(from.str(n), n.len); case value_t::number_integer: case value_t::number_unsigned: - return integer_node(integer_bits(n), static_cast(n.kind)); + return integer_node(integer_bits(n), n.kind); case value_t::number_float: { - node r = plain_node(value_t::number_float); + node r = plain_node(node_kind::number_float); r.flags = static_cast(r.flags | node_flags::edited); r.off = append_text(m_doc, from.str(n), n.len); r.len = n.len; @@ -4300,7 +4364,7 @@ class editor } case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (n.flags & node_flags::is_true)); return r; } @@ -4310,7 +4374,7 @@ class editor case value_t::binary: case value_t::discarded: default: - return plain_node(value_t::null); + return plain_node(node_kind::null); } } @@ -4319,17 +4383,17 @@ class editor switch (j.type()) { case value_t::null: - return plain_node(value_t::null); + return plain_node(node_kind::null); case value_t::boolean: { - node r = plain_node(value_t::boolean); + node r = plain_node(node_kind::boolean); r.flags = static_cast(r.flags | (j.template get() ? node_flags::is_true : 0)); return r; } case value_t::number_integer: - return integer_node(static_cast(static_cast(j.template get())), value_t::number_integer); + return integer_node(static_cast(static_cast(j.template get())), node_kind::number_integer); case value_t::number_unsigned: - return integer_node(static_cast(j.template get()), value_t::number_unsigned); + return integer_node(static_cast(j.template get()), node_kind::number_unsigned); case value_t::number_float: return float_node(j.template get()); case value_t::string: @@ -4369,7 +4433,7 @@ class editor ++r; if (is_container(*n)) { - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object)}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object}); } // the next value: close finished containers, then step over the key for (;;) @@ -4416,9 +4480,9 @@ class editor if (is_container(*n)) { node* const self = out++; - *self = plain_node(static_cast(n->kind)); + *self = plain_node(n->kind); self->len = n->len; - open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast(value_t::object), self}); + open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object, self}); } else { @@ -4507,7 +4571,7 @@ class editor if (n->is_structured()) { node* const self = out++; - *self = plain_node(n->type()); + *self = plain_node(static_cast(n->type())); self->len = static_cast(n->size()); open.push_back(frame{n->cbegin(), n->cend(), n->is_object(), self}); } @@ -4903,7 +4967,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.len = 0; // counted below r.next = 0; // set when the container is complete - stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast(value_t::object)}); + stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == node_kind::object}); break; case value_t::string: r = string_node(*v); @@ -4924,7 +4988,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) { r = node{}; // nan and infinity: null, as dump() writes them - r.kind = static_cast(value_t::null); + r.kind = node_kind::null; break; } r.off = static_cast(d.size + text_tail.size()); @@ -5007,7 +5071,7 @@ inline std::vector save_image(const document_data& d) // extra of an object, and the same document saves to the same bytes for (std::size_t i = 0; i < count; ++i) { - if (nodes[i].kind == static_cast(value_t::object) && nodes[i].extra != 0) + if (nodes[i].kind == node_kind::object && nodes[i].extra != 0) { std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra)); } @@ -5123,7 +5187,7 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu if (static_cast(t.int_digits) + t.exponent > 300) { node n{}; - n.kind = static_cast(value_t::number_float); + n.kind = node_kind::number_float; n.len = static_cast(len); const auto v = float_value(reinterpret_cast(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) return v <= (std::numeric_limits::max)() && v >= -(std::numeric_limits::max)(); @@ -5144,7 +5208,7 @@ inline bool check_integer(const node& n, const unsigned char* text) } // the token's value is the stored one; number_integer nodes of edits can // be non-negative (as basic_json keeps the type of a value) - const bool integer = n.kind == static_cast(value_t::number_integer); + const bool integer = n.kind == node_kind::number_integer; if (t.is_float || t.int_digits > 20 || (t.negative && !integer)) { return false; @@ -5326,7 +5390,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - if (n.kind != static_cast(value_t::number_float)) + if (n.kind != node_kind::number_float) { return (n.extra >> 8u) == 0; } @@ -5367,7 +5431,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha const node& n = nodes[i]; if (!stack.empty() && stack.back().object && stack.back().expect_key) { - if (n.kind != static_cast(value_t::string) || !check_string(n)) + if (n.kind != node_kind::string || !check_string(n)) { return false; } @@ -5408,7 +5472,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { // (the contents of float tokens are checked later; the token of an // integer has at most 256 characters) - if (n.kind == static_cast(value_t::number_float)) + if (n.kind == node_kind::number_float) { floats.push_back(float_range{n.off, n.len, n.extra}); } @@ -5426,7 +5490,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { return false; } - stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast(value_t::object), true}); + stack.push_back(frame{i + n.next, n.len, 0, n.kind == node_kind::object, true}); complete = false; break; } @@ -5515,7 +5579,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t for (std::size_t i = 0; i < count; ++i) { const node& n = d.nodes[i]; - if (n.kind == static_cast(value_t::object) && n.len >= document_data::index_min_members) + if (n.kind == node_kind::object && n.len >= document_data::index_min_members) { d.large_objects.push_back(static_cast(i)); } @@ -6108,7 +6172,7 @@ BasicJsonType materialize(const document_data& d, const node* n) case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; if (object) { sax.start_object(n->len); @@ -7222,7 +7286,7 @@ class view_serializer case value_t::object: case value_t::array: { - const bool object = n->kind == static_cast(value_t::object); + const bool object = n->kind == node_kind::object; room(8); if (n->len == 0) { @@ -7773,6 +7837,8 @@ struct is_direct_value, BasicJsonType, View> : } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN @@ -7782,13 +7848,16 @@ NLOHMANN_JSON_NAMESPACE_BEGIN Trivially copyable (two pointers). Valid as long as the document is alive and has not been re-parsed, and as long as a borrowed source text is alive. */ -template +template class basic_json_view { + /// whether this is a view of an editable document + static constexpr bool is_editable = Mode == view_mode::editable; + using node = detail::view::node; using document_data = detail::view::document_data; /// how the index is walked (with edits only for editable documents) - using navigation = detail::view::navigation; + using navigation = detail::view::navigation; public: using value_t = detail::value_t; @@ -7809,6 +7878,15 @@ class basic_json_view /// an invalid view (type() == value_t::discarded) basic_json_view() noexcept = default; + /// a view of a read-only document as a view of an editable one, so that a + /// function taking an editable view accepts both (a read-only document has + /// no edits; the view reads it as an editable document before its first + /// edit, at the cost of one check per access) + template::value, int> = 0> + basic_json_view(const basic_json_view& other) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + : m_doc(other.m_doc), m_node(other.m_node) + {} + ////////// // type // ////////// @@ -8135,7 +8213,7 @@ class basic_json_view if (is_structured() && m_node->len != 0) { return unedited() ? basic_json_view(m_doc, detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0)) - : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); + : basic_json_view(m_doc, navigation::value(detail::view::last_child(*m_doc, m_node) + (is_object() ? 1 : 0))); } return front(); } @@ -8193,7 +8271,7 @@ class basic_json_view /// more than once); 0 if this is not an object size_type count(string_view_t key) const { - return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; + return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0; } size_type count(const char* key) const @@ -8335,7 +8413,7 @@ class basic_json_view } else { - detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); + detail::view::view_serializer(*m_doc, out, estimate, style).dump(m_node); } return out; } @@ -8371,14 +8449,14 @@ class basic_json_view } /// a view of an editable document compares with one of a read-only document - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator==(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator==(const basic_json_view& a, const basic_json_view& b) { - return detail::view::equal(side(a), detail::view::view_side>(b)); + return detail::view::equal(side(a), detail::view::view_side>(b)); } - template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > - friend bool operator!=(const basic_json_view& a, const basic_json_view& b) + template::value, int> = 0> + friend bool operator!=(const basic_json_view& a, const basic_json_view& b) { return !(a == b); } @@ -8416,7 +8494,7 @@ class basic_json_view { return BasicJsonType(value_t::discarded); } - return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); + return unedited() ? detail::view::materialize(*m_doc, m_node) : detail::view::materialize(*m_doc, m_node); } /// byte offset of this value in the source text (for strings: of the @@ -8429,8 +8507,8 @@ class basic_json_view } private: - template friend class basic_json_document; - template friend class basic_json_view; + template friend class basic_json_document; + template friend class basic_json_view; template friend class detail::view::editor; friend iterator; @@ -8446,11 +8524,11 @@ class basic_json_view } /// the template argument as a runtime condition: testing it directly in - /// `Editable && ...` makes the condition constant, which MSVC reports as + /// `is_editable && ...` makes the condition constant, which MSVC reports as /// C4127 static bool editable() noexcept { - return Editable; + return is_editable; } /// the number of source bytes of this value (estimated for values with @@ -8499,7 +8577,7 @@ class basic_json_view const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); } - const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); + const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size()); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); } @@ -8513,7 +8591,7 @@ class basic_json_view /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const { - return find_key(key, std::integral_constant {}); + return find_key(key, std::integral_constant {}); } NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const @@ -8535,7 +8613,7 @@ class basic_json_view { return basic_json_view(m_doc, detail::view::element_at(*m_doc, m_node, idx)); } - return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); + return basic_json_view(m_doc, navigation::value(detail::view::element_at(*m_doc, m_node, idx))); } // --- get() dispatch --- @@ -8641,16 +8719,19 @@ Borrowed parses keep a pointer to the caller's text, which must outlive the document. Owned parses (parse_copy, rvalue std::string, streams, and inputs that are not contiguous byte ranges) keep their own copy. */ -template +template class basic_json_document { + /// whether the document can be edited + static constexpr bool is_editable = Mode == view_mode::editable; + using document_data = detail::view::document_data; static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8, "json_view supports 64-bit integer types only"); public: - using view_type = basic_json_view; + using view_type = basic_json_view; using value_t = detail::value_t; /// an empty (discarded) document @@ -9046,7 +9127,7 @@ class basic_json_document detail::view::editor editor() { - static_assert(Editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); + static_assert(is_editable, "only an editable document can be edited: use basic_json_document (json_editable_document)"); if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) { detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); @@ -9323,21 +9404,21 @@ class basic_json_document }; /// a parsed JSON text for json -using json_document = basic_json_document; +using json_document = basic_json_document; /// a value of a json_document -using json_view = basic_json_view; +using json_view = basic_json_view; /// a parsed JSON text for ordered_json -using ordered_json_document = basic_json_document; +using ordered_json_document = basic_json_document; /// a value of an ordered_json_document -using ordered_json_view = basic_json_view; +using ordered_json_view = basic_json_view; /// an editable parsed JSON text for json -using json_editable_document = basic_json_document; +using json_editable_document = basic_json_document; /// a value of a json_editable_document -using json_editable_view = basic_json_view; +using json_editable_view = basic_json_view; /// an editable parsed JSON text for ordered_json -using ordered_json_editable_document = basic_json_document; +using ordered_json_editable_document = basic_json_document; /// a value of an ordered_json_editable_document -using ordered_json_editable_view = basic_json_view; +using ordered_json_editable_view = basic_json_view; NLOHMANN_JSON_NAMESPACE_END diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 6935981fb..1074f7018 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -592,7 +592,7 @@ TEST_CASE("json_view node integer bits") }) { CAPTURE(v) - n.kind = 0x5A; + n.kind = static_cast(0x5A); n.flags = 0xA5; n.extra = 0x1234; n.off = 0x89ABCDEFu; @@ -601,7 +601,7 @@ TEST_CASE("json_view node integer bits") CHECK(n.len == static_cast(v)); CHECK(n.next == static_cast(v >> 32)); // the other fields are untouched - CHECK(n.kind == 0x5A); + CHECK(static_cast(n.kind) == 0x5A); CHECK(n.flags == 0xA5); CHECK(n.extra == 0x1234); CHECK(n.off == 0x89ABCDEFu); diff --git a/tests/src/unit-json_view_edit_values.cpp b/tests/src/unit-json_view_edit_values.cpp index 59aee3a9d..39db8d0ba 100644 --- a/tests/src/unit-json_view_edit_values.cpp +++ b/tests/src/unit-json_view_edit_values.cpp @@ -26,12 +26,119 @@ using nlohmann::json_editable_view; #include #include #include +#include #include #include // These tests were split off unit-json_view_edit.cpp, whose object file got too // large for the MinGW linker (see json_view_test_helpers.hpp). +namespace +{ +// the three ways a function can accept views: editable ones, read-only ones, or both +template +int accepts_editable(nlohmann::basic_json_view /*unused*/) +{ + return 1; +} + +template +int accepts_read_only(nlohmann::basic_json_view /*unused*/) +{ + return 2; +} + +template +int accepts_any(nlohmann::basic_json_view /*unused*/) +{ + return Mode == nlohmann::view_mode::editable ? 3 : 4; +} + +template +auto overload(View v) -> decltype(accepts_editable(v)) +{ + return accepts_editable(v); +} + +template +auto overload(View v) -> decltype(accepts_read_only(v)) +{ + return accepts_read_only(v); +} + +// a function (no template) that takes an editable view also accepts a read-only one +std::size_t sum_of(json_editable_view v) +{ + std::size_t sum = 0; + for (const json_editable_view x : v.at("values")) + { + sum += x.get(); + } + return sum; +} +} // namespace + +TEST_CASE("json_view modes") +{ + using nlohmann::basic_json_document; + using nlohmann::basic_json_view; + using nlohmann::ordered_json; + using nlohmann::view_mode; + + SECTION("read-only is the default, and the shortcuts name the modes") + { + static_assert(std::is_same, basic_json_view>::value, "default mode"); + static_assert(std::is_same, basic_json_document>::value, "default mode"); + static_assert(std::is_same>::value, "json_view"); + static_assert(std::is_same>::value, "json_editable_view"); + static_assert(std::is_same>::value, "json_editable_document"); + static_assert(std::is_same>::value, "ordered_json_editable_view"); + static_assert(std::is_same::value, "view_type"); + static_assert(std::is_same::value, "view_type"); + } + + SECTION("a function template selects views by their mode") + { + const json_document d = json_document::parse("[1]"); + const json_editable_document e = json_editable_document::parse("[1]"); + const nlohmann::ordered_json_editable_document o = nlohmann::ordered_json_editable_document::parse("[1]"); + CHECK(overload(e.root()) == 1); + CHECK(overload(o.root()) == 1); + CHECK(overload(d.root()) == 2); + CHECK(accepts_any(e.root()) == 3); + CHECK(accepts_any(d.root()) == 4); + } + + SECTION("a view of a read-only document converts to an editable view, not the other way") + { + static_assert(std::is_convertible::value, "read-only to editable"); + static_assert(!std::is_convertible::value, "not editable to read-only"); + static_assert(!std::is_convertible::value, "same BasicJsonType only"); + + const json_document d = json_document::parse(R"({"values": [1, 2, 3], "name": "a"})"); + json_editable_document e = json_editable_document::parse(R"({"values": [1, 2, 3], "name": "a"})"); + CHECK(sum_of(d.root()) == 6); + CHECK(sum_of(e.root()) == 6); + + const json_editable_view v = d.root(); + CHECK(v.size() == 2); + CHECK(v.at("name").get() == "a"); + CHECK(v.source_offset() == d.root().source_offset()); + CHECK((v == e.root())); + CHECK((v == d.root())); + + e.push_back(e.root().at("values"), 4); + CHECK(sum_of(e.root()) == 10); + CHECK((v != e.root())); + +#if !defined(JSON_NOEXCEPTION) + // the converted view still belongs to the read-only document + CHECK_THROWS_WITH_AS(e.push_back(v.at("values"), 5), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&); +#endif + CHECK(sum_of(d.root()) == 6); + } +} + TEST_CASE("json_view edits: views and values") { SECTION("a value that is no longer part of the document") @@ -100,7 +207,7 @@ TEST_CASE("json_view edits: views and values") // doubles have their own path to the output; other float types are // written as basic_json writes them, non-finite values as null using json_float = nlohmann::basic_json; - using document_float = nlohmann::basic_json_document; + using document_float = nlohmann::basic_json_document; document_float d = document_float::parse("[1.5]"); d.push_back(d.root(), std::numeric_limits::quiet_NaN()); d.push_back(d.root(), -std::numeric_limits::infinity()); diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index eaf27eb9f..e2a738a4a 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -480,7 +480,7 @@ TEST_CASE("json_view images: edited documents") { CAPTURE(i) CHECK((node_at(image, i).flags & nlohmann::detail::view::node_flags::linked) == 0); - CHECK(node_at(image, i).kind != nlohmann::detail::view::kind_link); + CHECK((node_at(image, i).kind != nlohmann::detail::view::node_kind::link)); } for (const image_check check : { @@ -810,13 +810,13 @@ TEST_CASE("json_view images: check") { rejected(corrupted(image, 12, [&](node & n) { - n.kind = kind; + n.kind = static_cast(kind); }), true); } // a key that is not a string rejected(corrupted(image, 1, [](node & n) { - n.kind = 0; + n.kind = nlohmann::detail::view::node_kind::null; n.len = 0; n.off = 0; }), true); @@ -960,11 +960,11 @@ TEST_CASE("json_view images: check") }), true); rejected(corrupted(image, 0, [](node & n) { - n.kind = 2; // an array: the "keys" are values, and the counts do not match + n.kind = nlohmann::detail::view::node_kind::array; // an array: the "keys" are values, and the counts do not match }), true); const std::vector as_array = corrupted(image, 16, [](node & n) { - n.kind = 2; // {} as []: fine + n.kind = nlohmann::detail::view::node_kind::array; // {} as []: fine }); CHECK(load_result(as_array, image_check::full).empty()); const std::string expected_dump = R"({"s":"x\"y","i":-12,"u":7,"f":1.5e+300,"b":true,"n":null,"a":["t",[]]})"; @@ -1013,13 +1013,13 @@ TEST_CASE("json_view images: check") rejected(at_token(u6, 0, '8'), false); // 8 != 7 rejected(corrupted(image, 4, [](node & n) { - n.kind = 6; // "-12" as unsigned: a sign + n.kind = nlohmann::detail::view::node_kind::number_unsigned; // "-12" as unsigned: a sign n.extra = 3; }), false); // a non-negative number_integer (as edits write it): fine const std::vector positive = corrupted(image, 6, [](node & n) { - n.kind = 5; + n.kind = nlohmann::detail::view::node_kind::number_integer; n.extra = 0; }); CHECK(load_result(positive, image_check::full).empty()); @@ -1027,7 +1027,7 @@ TEST_CASE("json_view images: check") CHECK(pos.root()["u"].is_number_integer()); rejected(corrupted(image, 8, [](node & n) { - n.kind = 6; // a float token as integer + n.kind = nlohmann::detail::view::node_kind::number_unsigned; // a float token as integer n.extra = 7; }), false); } @@ -1197,7 +1197,7 @@ TEST_CASE("json_view images: check") { std::vector b = img; node n = node_at(b, i); - n.kind = kind; + n.kind = static_cast(kind); n.extra = extra; set_node(b, i, n); return load_result(b, image_check::full); From 4cdfa6d0f486a76a211093918e5a26cc11f4640c Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 20:13:30 +0100 Subject: [PATCH 14/63] json_view: no save() and load() on big-endian targets (#5920) The image format is little-endian only. Instead of throwing type_error.320 at runtime, save_image(), load_image() and the members save() and load() do not exist on a big-endian target, so a call fails to compile, like set() on a read-only document. type_error.320 remains for saving a discarded document. Signed-off-by: Niels Lohmann --- .../docs/api/basic_json_document/load.md | 8 +++---- .../docs/api/basic_json_document/save.md | 12 +++------- docs/mkdocs/docs/features/json_view.md | 7 +++--- docs/mkdocs/docs/home/exceptions.md | 5 ---- include/nlohmann/detail/exceptions.hpp | 2 +- include/nlohmann/detail/view/image.hpp | 10 ++++---- include/nlohmann/json_view.hpp | 5 ++++ single_include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json_view.hpp | 15 +++++++----- tests/src/unit-json_view_image.cpp | 23 ++++++++++++++++++- 10 files changed, 53 insertions(+), 36 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_document/load.md b/docs/mkdocs/docs/api/basic_json_document/load.md index 36223f63c..f389c0581 100644 --- a/docs/mkdocs/docs/api/basic_json_document/load.md +++ b/docs/mkdocs/docs/api/basic_json_document/load.md @@ -55,10 +55,7 @@ validate with overload 2 first, if the original vector must survive a failed loa ## Exceptions -On a big-endian target, throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) -- the same -exception [`save()`](save.md#exceptions) throws there, since the image format is little-endian only. - -Otherwise throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one +Throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one `save()` could have written, or fails the requested `check`: | message | when | @@ -95,6 +92,9 @@ are checked once, however many nodes refer to it. ## Notes +**Little-endian targets only.** On a big-endian target, `load()` does not exist, just like +[`save()`](save.md#notes): the image format is little-endian, so a call fails to compile. + ### `image_check` { #image_check } ```cpp diff --git a/docs/mkdocs/docs/api/basic_json_document/save.md b/docs/mkdocs/docs/api/basic_json_document/save.md index c47295048..c87d90f07 100644 --- a/docs/mkdocs/docs/api/basic_json_document/save.md +++ b/docs/mkdocs/docs/api/basic_json_document/save.md @@ -31,9 +31,6 @@ Throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) i [discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/ [`read()`](read.md) with `allow_exceptions == false` left discarded. -On a big-endian target, throws `type_error.320` with a different message instead: the image format is little-endian -only (see [Notes](#notes)). - Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets [`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to. @@ -44,9 +41,6 @@ the decoded strings of the image would individually reach 4 GiB -- the same 32-b [json.exception.type_error.320] cannot save a discarded json_document ``` ``` - [json.exception.type_error.320] json_document images need a little-endian target - ``` - ``` [json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document ``` @@ -70,9 +64,9 @@ else -- see [`load`'s Exceptions](load.md#exceptions). the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original JSON text if you need to read a saved document back with a future library version. -**Little-endian only.** The image is written as raw little-endian bytes, with no byte-swapping. `save()` (and -[`load`](load.md)) throw `type_error.320` on a big-endian target rather than silently produce bytes a big-endian -reader could not interpret correctly. +**Little-endian targets only.** The image is written as raw little-endian bytes, with no byte-swapping. On a +big-endian target, `save()` and [`load`](load.md) do not exist: a call fails to compile rather than produce bytes a +big-endian reader could not interpret correctly. ## Examples diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 4b5498af5..89e26bdb3 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -354,9 +354,10 @@ validation entirely, for an image trusted as much as the process's own memory. S !!! warning "Experimental" The image format is versioned but not yet stable, and may change in an incompatible way before it is declared - stable. It is little-endian only, and tied to the library build that wrote it -- use it to cache a document or to - hand one to another process running the *same* build, not as a long-term storage format; keep the original JSON - text if a saved document needs to be readable by a future library version. + stable. It is little-endian only (on big-endian targets, `save()` and `load()` do not exist), and tied to the + library build that wrote it -- use it to cache a document or to hand one to another process running the *same* + build, not as a long-term storage format; keep the original JSON text if a saved document needs to be readable by + a future library version. The idea of a document you can read without parsing comes from zero-copy formats such as [FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index 65b31c93c..aa2afe6ee 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -895,17 +895,12 @@ from JSON text. [`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an [image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document. -[`save()`](../api/basic_json_document/save.md) and [`load()`](../api/basic_json_document/load.md) also throw this -exception on a big-endian target, since the image format is little-endian only. !!! failure "Example messages" ``` [json.exception.type_error.320] cannot save a discarded json_document ``` - ``` - [json.exception.type_error.320] json_document images need a little-endian target - ``` !!! note diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index cdef3019f..d47ed6f17 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -102,7 +102,7 @@ enum class exception_id : int type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string document_binary_value = 319, ///< a binary value is stored in a json_document - document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) + document_image_unsupported = 320, ///< a discarded json_document cannot be saved as an image discarded_value_used = 321, ///< a discarded value is used // out_of_range array_index_out_of_range = 401, ///< an array index is out of range diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index c13d269e1..637350949 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -182,12 +182,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v } } +#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no save() elsewhere) /// the document as an image inline std::vector save_image(const document_data& d) { -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif const std::size_t arena_size = d.arena_size; std::vector compacted; std::string text_tail; @@ -243,6 +241,7 @@ inline std::vector save_image(const document_data& d) *o = 0; return image; } +#endif /// the parts of a number token that the checks need struct number_token @@ -663,13 +662,11 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); } +#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no load() elsewhere) /// Read an image into d. The text and the decoded strings stay in the image; /// the nodes are copied (so that they are aligned, and edits can change them). inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) { -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif if (image == nullptr || size < sizeof(image_header)) { throw_invalid_image("too short"); @@ -738,6 +735,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t std::vector().swap(d.large_objects); // (only needed while building) d.discarded = false; } +#endif } // namespace view } // namespace detail diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 42a8cb99e..79c9b8dde 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -1191,6 +1191,10 @@ class basic_json_document /// how load() checks an image (full, bounds, or none) using image_check = detail::view::image_check; +#if NLOHMANN_VIEW_LITTLE_ENDIAN + // (the image format is little-endian: big-endian targets have no save() + // and load(), so that a call fails to compile instead of always throwing) + /// The document as an image that load() reads without parsing: the node /// index, the text, and the decoded strings. An edited document is /// written in its current state (floats that are not finite become null, @@ -1232,6 +1236,7 @@ class basic_json_document detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); return d; } +#endif /////////// // edits // diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index eca217237..7a68b1c54 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5565,7 +5565,7 @@ enum class exception_id : int type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string document_binary_value = 319, ///< a binary value is stored in a json_document - document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) + document_image_unsupported = 320, ///< a discarded json_document cannot be saved as an image discarded_value_used = 321, ///< a discarded value is used // out_of_range array_index_out_of_range = 401, ///< an array index is out of range diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 1fee76e63..eeb86f061 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -5032,12 +5032,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v } } +#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no save() elsewhere) /// the document as an image inline std::vector save_image(const document_data& d) { -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif const std::size_t arena_size = d.arena_size; std::vector compacted; std::string text_tail; @@ -5093,6 +5091,7 @@ inline std::vector save_image(const document_data& d) *o = 0; return image; } +#endif /// the parts of a number token that the checks need struct number_token @@ -5513,13 +5512,11 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); } +#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no load() elsewhere) /// Read an image into d. The text and the decoded strings stay in the image; /// the nodes are copied (so that they are aligned, and edits can change them). inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) { -#if !NLOHMANN_VIEW_LITTLE_ENDIAN - throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE -#endif if (image == nullptr || size < sizeof(image_header)) { throw_invalid_image("too short"); @@ -5588,6 +5585,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t std::vector().swap(d.large_objects); // (only needed while building) d.discarded = false; } +#endif } // namespace view } // namespace detail @@ -8945,6 +8943,10 @@ class basic_json_document /// how load() checks an image (full, bounds, or none) using image_check = detail::view::image_check; +#if NLOHMANN_VIEW_LITTLE_ENDIAN + // (the image format is little-endian: big-endian targets have no save() + // and load(), so that a call fails to compile instead of always throwing) + /// The document as an image that load() reads without parsing: the node /// index, the text, and the decoded strings. An edited document is /// written in its current state (floats that are not finite become null, @@ -8986,6 +8988,7 @@ class basic_json_document detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); return d; } +#endif /////////// // edits // diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index e2a738a4a..ff1713371 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -28,11 +28,25 @@ using nlohmann::detail::view::node; #include #include #include +#include #include #include #include +namespace json_view_image_test +{ +// whether a document type has save() and load() (only on little-endian targets) +template +struct has_save : std::false_type {}; +template +struct has_save(std::declval().save()))> : std::true_type {}; +template +struct has_load : std::false_type {}; +template +struct has_load(T::load(std::declval&>())))> : std::true_type {}; +} // namespace json_view_image_test + #if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) namespace @@ -171,6 +185,9 @@ std::uint32_t rng() TEST_CASE("json_view images: round trips") { + static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); + static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); + SECTION("small documents") { for (const char* text : @@ -1275,8 +1292,12 @@ TEST_CASE("json_view images: damaged images") TEST_CASE("json_view images: big-endian targets") { + // the image format is little-endian: save() and load() do not exist here + static_assert(!json_view_image_test::has_save::value, "no save() on a big-endian target"); + static_assert(!json_view_image_test::has_load::value, "no load() on a big-endian target"); + static_assert(!json_view_image_test::has_save::value, "no save() on a big-endian target"); const json_document d = json_document::parse("[1]"); - CHECK_THROWS_WITH_AS(d.save(), "[json.exception.type_error.320] json_document images need a little-endian target", json::type_error&); + CHECK(d.root().size() == 1); } #endif From 5a2819c9400cfc352dc8623cccbbecf63d5ac932 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 20:13:31 +0100 Subject: [PATCH 15/63] json_view: one SIMD macro set with the library (#5921) The view's vector code follows JSON_NO_SIMD, like the lexer and the float writer, and skips the CPU check of SSSE3 when the code is compiled for SSSE3 anyway (__SSSE3__), like the lexer. JSON_VIEW_NO_SIMD and JSON_VIEW_USE_SSSE3 and their ABI tags _vns and _vssse3 are removed; they were never released. JSON_NO_SIMD keeps its tag _ns. Signed-off-by: Niels Lohmann --- .github/workflows/check_amalgamation.yml | 6 +- FILES.md | 2 +- Makefile | 2 +- docs/mkdocs/docs/api/macros/index.md | 2 - docs/mkdocs/docs/api/macros/json_no_simd.md | 4 +- .../docs/api/macros/json_view_no_simd.md | 52 - .../docs/api/macros/json_view_use_ssse3.md | 53 - .../api/macros/nlohmann_json_namespace.md | 4 +- docs/mkdocs/docs/features/macros.md | 17 - docs/mkdocs/docs/features/namespace.md | 8 +- docs/mkdocs/docs/home/debugging.md | 4 +- docs/mkdocs/mkdocs.yml | 2 - include/nlohmann/detail/abi_macros.hpp | 22 +- include/nlohmann/detail/view/simd.hpp | 10 +- nlohmann_json.natvis | 3700 ----------------- single_include/nlohmann/json.hpp | 22 +- single_include/nlohmann/json_fwd.hpp | 22 +- single_include/nlohmann/json_view.hpp | 10 +- tests/CMakeLists.txt | 5 +- tests/abi/config/CMakeLists.txt | 4 +- tests/abi/config/default.cpp | 8 - tests/abi/config/noversion.cpp | 8 - tools/generate_natvis/README.md | 8 +- tools/generate_natvis/generate_natvis.py | 2 +- 24 files changed, 44 insertions(+), 3933 deletions(-) delete mode 100644 docs/mkdocs/docs/api/macros/json_view_no_simd.md delete mode 100644 docs/mkdocs/docs/api/macros/json_view_use_ssse3.md diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index 8104a20af..910eeef0f 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -163,9 +163,9 @@ jobs: python3 $GENERATOR --list-tags TAG_COUNT=$(python3 $GENERATOR --list-tags | wc -l) - python3 $GENERATOR --abi-tags diag,_dp,_vns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis - grep -q '' $RUNNER_TEMP/abi_tags.natvis - grep -q '' $RUNNER_TEMP/abi_tags.natvis + python3 $GENERATOR --abi-tags diag,_dp,_ns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis + grep -q '' $RUNNER_TEMP/abi_tags.natvis + grep -q '' $RUNNER_TEMP/abi_tags.natvis test $(grep -c ' - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -3631,302 +3335,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -5259,302 +4667,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -6739,302 +5851,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -8071,302 +6887,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -9255,302 +7775,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -10291,302 +8515,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -11179,302 +9107,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -11919,302 +9551,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -12511,302 +9847,6 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - null @@ -12955,744 +9995,4 @@ - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - - {second} - - second - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - - - null - {*(m_data.m_value.object)} - {*(m_data.m_value.array)} - {*(m_data.m_value.string)} - {m_data.m_value.boolean} - {m_data.m_value.number_integer} - {m_data.m_value.number_unsigned} - {m_data.m_value.number_float} - discarded - - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - - - - diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 7a68b1c54..b6281565f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -193,26 +193,14 @@ #define NLOHMANN_JSON_ABI_TAG_NO_SIMD #endif -#ifdef JSON_VIEW_NO_SIMD - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 _vssse3 -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 -#endif - #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ @@ -226,9 +214,7 @@ NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \ NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \ NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \ - NLOHMANN_JSON_ABI_TAG_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3) + NLOHMANN_JSON_ABI_TAG_NO_SIMD) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ diff --git a/single_include/nlohmann/json_fwd.hpp b/single_include/nlohmann/json_fwd.hpp index cd64b76a6..ce51d81bd 100644 --- a/single_include/nlohmann/json_fwd.hpp +++ b/single_include/nlohmann/json_fwd.hpp @@ -145,26 +145,14 @@ #define NLOHMANN_JSON_ABI_TAG_NO_SIMD #endif -#ifdef JSON_VIEW_NO_SIMD - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 _vssse3 -#else - #define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 -#endif - #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l, m) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ @@ -178,9 +166,7 @@ NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \ NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \ NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \ - NLOHMANN_JSON_ABI_TAG_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \ - NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3) + NLOHMANN_JSON_ABI_TAG_NO_SIMD) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index eeb86f061..0e6c087ab 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -678,25 +678,25 @@ NLOHMANN_JSON_NAMESPACE_END // with different flags would have different definitions of the same inline // functions): the check is compiled for SSSE3 with a function attribute and // used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the -// portable check. JSON_VIEW_USE_SSSE3 skips the CPU check (for code compiled -// for SSSE3 anyway); JSON_VIEW_NO_SIMD selects the portable code. +// portable check. Code compiled for SSSE3 anyway (-mssse3 or more) skips the +// CPU check. JSON_NO_SIMD selects the portable code, as in the lexer. // NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE (set by the GCC module interface, where GCC // ignores the target attribute and then rejects the SSSE3 intrinsics) keeps the // check portable without disabling the SSE2 code. -#if !defined(JSON_VIEW_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN +#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN #include #define NLOHMANN_VIEW_NEON 1 #else #define NLOHMANN_VIEW_NEON 0 #endif // (x86 only: other targets can define __SSE2__ as well, e.g., WebAssembly with -msse2, but have no ) -#if !defined(JSON_VIEW_NO_SIMD) && !NLOHMANN_VIEW_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) +#if !defined(JSON_NO_SIMD) && !NLOHMANN_VIEW_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) #include #define NLOHMANN_VIEW_SSE2 1 #else #define NLOHMANN_VIEW_SSE2 0 #endif -#if NLOHMANN_VIEW_SSE2 && defined(JSON_VIEW_USE_SSSE3) +#if NLOHMANN_VIEW_SSE2 && defined(__SSSE3__) #include #define NLOHMANN_VIEW_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum) #else diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee7e07f08..f4e574fc1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -420,9 +420,9 @@ json_test_add_test_for(src/unit-file_input.cpp ) # the json_view parser again with the portable string scanning instead of -# NEON/SSE2, and on x86-64 with the SSSE3 UTF-8 check (JSON_VIEW_USE_SSSE3) +# NEON/SSE2 (JSON_NO_SIMD), and on x86-64 compiled for SSSE3 (no CPU check) json_test_set_test_options(test-json_view_builder_portable - COMPILE_DEFINITIONS JSON_VIEW_NO_SIMD + COMPILE_DEFINITIONS JSON_NO_SIMD ) json_test_add_test_for(src/unit-json_view_builder.cpp NAME test-json_view_builder_portable @@ -430,7 +430,6 @@ json_test_add_test_for(src/unit-json_view_builder.cpp ) if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC) json_test_set_test_options(test-json_view_builder_ssse3 - COMPILE_DEFINITIONS JSON_VIEW_USE_SSSE3 COMPILE_OPTIONS -mssse3 ) json_test_add_test_for(src/unit-json_view_builder.cpp diff --git a/tests/abi/config/CMakeLists.txt b/tests/abi/config/CMakeLists.txt index 930afeecc..f3f27c2ee 100644 --- a/tests/abi/config/CMakeLists.txt +++ b/tests/abi/config/CMakeLists.txt @@ -24,9 +24,7 @@ foreach(test default noversion) JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 JSON_DISABLE_STREAM_BULK_SCAN=1 JSON_DISABLE_UNLOCKED_FILE_IO=1 - JSON_NO_SIMD - JSON_VIEW_NO_SIMD - JSON_VIEW_USE_SSSE3) + JSON_NO_SIMD) target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main) add_test( NAME test-abi_config_${test}_all_tags diff --git a/tests/abi/config/default.cpp b/tests/abi/config/default.cpp index a9fa472d3..084edf8c9 100644 --- a/tests/abi/config/default.cpp +++ b/tests/abi/config/default.cpp @@ -64,14 +64,6 @@ TEST_CASE("default namespace") expected += "_ns"; #endif -#ifdef JSON_VIEW_NO_SIMD - expected += "_vns"; -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - expected += "_vssse3"; -#endif - expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json"; diff --git a/tests/abi/config/noversion.cpp b/tests/abi/config/noversion.cpp index a090d1d02..66164ff88 100644 --- a/tests/abi/config/noversion.cpp +++ b/tests/abi/config/noversion.cpp @@ -65,14 +65,6 @@ TEST_CASE("default namespace without version component") expected += "_ns"; #endif -#ifdef JSON_VIEW_NO_SIMD - expected += "_vns"; -#endif - -#ifdef JSON_VIEW_USE_SSSE3 - expected += "_vssse3"; -#endif - expected += "::basic_json"; // fallback for Clang diff --git a/tools/generate_natvis/README.md b/tools/generate_natvis/README.md index 8f45f1877..cc4fc4a8f 100644 --- a/tools/generate_natvis/README.md +++ b/tools/generate_natvis/README.md @@ -34,11 +34,11 @@ version other than the one in `abi_macros.hpp`. | Option | Description | |-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `--list-tags` | Print every ABI tag (in namespace order) with the macro that enables it and the `NLOHMANN_JSON_ABI_TAG_*` identifier, then exit. | -| `--abi-tags TAGS` | Add the namespaces of one configuration, given as a comma-separated list of ABI tags (`_diag,_dp,_vns`; the leading underscore is optional, and the order does not matter). Can be repeated for several configurations. Unknown tags are an error. | +| `--abi-tags TAGS` | Add the namespaces of one configuration, given as a comma-separated list of ABI tags (`_diag,_dp,_ns`; the leading underscore is optional, and the order does not matter). Can be repeated for several configurations. Unknown tags are an error. | | `--all` | Generate the namespaces for every combination of ABI tags. The file size doubles with each tag (about 60 MB for 12 tags). Cannot be combined with `--abi-tags`. | | `--output FILE` | Write the Natvis file to `FILE` instead of `nlohmann_json.natvis` in the repository root. | -Each configuration gets both the versioned (`json_abi_diag_dp_vns_v3_12_0`) and the unversioned (`json_abi_diag_dp_vns`, +Each configuration gets both the versioned (`json_abi_diag_dp_ns_v3_12_0`) and the unversioned (`json_abi_diag_dp_ns`, used with `NLOHMANN_JSON_NAMESPACE_NO_VERSION`) namespace. The default namespaces (up to two ABI tags) are always included, so a generated file can replace the checked-in one. @@ -52,10 +52,10 @@ included, so a generated file can replace the checked-in one. ``` 3. Generate a Natvis file with them, for example for `JSON_DIAGNOSTICS`, `JSON_DIAGNOSTIC_POSITIONS`, and - `JSON_VIEW_NO_SIMD`: + `JSON_NO_SIMD`: ```shell - make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis" + make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_ns --output my_nlohmann_json.natvis" ``` 4. Use `my_nlohmann_json.natvis` instead of the shipped `nlohmann_json.natvis`, for example by adding it to your Visual diff --git a/tools/generate_natvis/generate_natvis.py b/tools/generate_natvis/generate_natvis.py index d660c4b63..8452d98e0 100755 --- a/tools/generate_natvis/generate_natvis.py +++ b/tools/generate_natvis/generate_natvis.py @@ -91,7 +91,7 @@ if __name__ == '__main__': 'include/nlohmann/detail/abi_macros.hpp below "repo_root")') selection = parser.add_mutually_exclusive_group() selection.add_argument('--abi-tags', action='append', default=[], metavar='TAGS', - help='Comma-separated list of ABI tags (e.g. "_diag,_dp,_vns", leading underscores are ' + help='Comma-separated list of ABI tags (e.g. "_diag,_dp,_ns", leading underscores are ' 'optional) of one configuration to add to the default namespaces; can be repeated') selection.add_argument('--all', action='store_true', help='Generate the namespaces for every combination of ABI tags; the file size doubles ' From e5fe4ebdd2304d9087bb1f351570563ed80e5f31 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 20:13:31 +0100 Subject: [PATCH 16/63] json_view: from_json() of the macros reads views directly (#5922) get() and get_to() of a view call T's from_json() with the view itself when that from_json() accepts views but not an unrelated probe type, and fall back to materialize() otherwise. The from_json() functions of the NLOHMANN_DEFINE_TYPE_*, NLOHMANN_DEFINE_DERIVED_TYPE_* and NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT) macros accept basic_json values and views; a derived type whose base reads only json reads the base part through materialize(). The library's own from_json() overloads are not called with views: from_json() of a view (and of the probe) only looks up functions by ADL. New public trait nlohmann::is_json_view, and the member type basic_json_type of basic_json_view. With duplicate keys, the members of such a T are those the view's lookups find (the first ones). bench_decode.cpp measures decoding into structs (4-6x faster than materialize().get() on twitter, citm_catalog, and synthetic records). Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json_view/get.md | 28 +- .../mkdocs/docs/api/basic_json_view/get_to.md | 4 +- docs/mkdocs/docs/api/basic_json_view/index.md | 1 + docs/mkdocs/docs/api/is_json_view.md | 55 +++ .../macros/nlohmann_define_derived_type.md | 9 + .../macros/nlohmann_define_type_intrusive.md | 8 + .../nlohmann_define_type_non_intrusive.md | 8 + .../macros/nlohmann_json_serialize_enum.md | 8 + .../nlohmann_json_serialize_enum_strict.md | 6 + docs/mkdocs/docs/examples/is_json_view.cpp | 34 ++ docs/mkdocs/docs/examples/is_json_view.output | 2 + docs/mkdocs/docs/features/json_view.md | 4 + docs/mkdocs/mkdocs.yml | 1 + .../nlohmann/detail/conversions/from_json.hpp | 106 +++++- include/nlohmann/detail/macro_scope.hpp | 60 +-- include/nlohmann/detail/meta/type_traits.hpp | 41 ++ include/nlohmann/json_view.hpp | 52 ++- single_include/nlohmann/json.hpp | 310 +++++++++++---- single_include/nlohmann/json_view.hpp | 52 ++- tests/benchmarks/json_view/README.md | 6 + tests/benchmarks/json_view/bench_decode.cpp | 312 +++++++++++++++ tests/benchmarks/json_view/compare.py | 8 +- tests/src/unit-json_view_from_json.cpp | 357 ++++++++++++++++++ 23 files changed, 1342 insertions(+), 130 deletions(-) create mode 100644 docs/mkdocs/docs/api/is_json_view.md create mode 100644 docs/mkdocs/docs/examples/is_json_view.cpp create mode 100644 docs/mkdocs/docs/examples/is_json_view.output create mode 100644 tests/benchmarks/json_view/bench_decode.cpp create mode 100644 tests/src/unit-json_view_from_json.cpp diff --git a/docs/mkdocs/docs/api/basic_json_view/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md index 7fe30ca7f..40fd1033a 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -27,8 +27,25 @@ For the types below, the conversion works directly on the flat index -- no `Basi [`materialize()`](materialize.md)) does; `#!cpp std::map` keeps views of the members instead of values -Every other `T` -- `#!cpp std::list`, `#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a -`from_json()`, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` +A default-constructible `T` whose `from_json()` accepts views reads the view directly as well, member by member, without +building a `BasicJsonType` value. This covers the types of the +[`NLOHMANN_DEFINE_TYPE_*`](../macros/nlohmann_define_type_intrusive.md) and +[`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../macros/nlohmann_define_derived_type.md) macros and the enumerations of +[`NLOHMANN_JSON_SERIALIZE_ENUM`](../macros/nlohmann_json_serialize_enum.md), and any `from_json()` constrained to +accept views, e.g. with [`is_json_view`](../is_json_view.md). A `from_json()` that also accepts types that are no JSON +values at all, such as the unconstrained `#!cpp template void from_json(const BasicJsonType&, +T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not called with a view: it may use +`BasicJsonType` functions a view does not have. See [json_view](../../features/json_view.md). + +!!! warning "Duplicate keys" + + Such a `from_json()` looks members up in the view, where a repeated key finds the **first** member, while + `#!cpp materialize().get()` sees the **last** one, as [`BasicJsonType::parse()`](../basic_json/parse.md) + keeps it: for `#!cpp {"x":1,"x":2}`, `#!cpp get()` reads `#!cpp x == 1`. See + [Duplicate keys](../../features/json_view.md#duplicate-keys). + +Every other `T` -- `#!cpp std::list`, `#!cpp std::pair`, `#!cpp std::array`, user types whose `from_json()` is +written for `BasicJsonType` only, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` value first (as [`BasicJsonType::parse()`](../basic_json/parse.md) would), and converted from there exactly as [`BasicJsonType::get()`](../basic_json/get.md) would convert it. @@ -55,10 +72,13 @@ Strong guarantee: there are no changes to the view or the document it refers to. an element to `U` throws. - For `#!cpp std::map`/`#!cpp std::unordered_map`: throws `type_error.302` if the value is not an object; otherwise, whatever converting a member to the mapped type throws. +- For a `T` whose `from_json()` reads the view: whatever that `from_json()` throws; for the macros, the same + exceptions with the same messages as [`BasicJsonType::get()`](../basic_json/get.md) of the same value. - For every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws -- typically `type_error.302`, or whatever a user-provided `from_json()` throws. -None of the exceptions thrown directly by this function (the first three bullets above) carry a +None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four +bullets above) carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real `BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled. @@ -76,6 +96,8 @@ document. - `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp std::unordered_map`), times the complexity of converting one member to the mapped type. +- a `T` whose `from_json()` reads the view: the cost of that `from_json()`; for the macros, linear in the number of + members times the cost of a lookup, plus the cost of converting the members. - every other `T`: the cost of [`materialize()`](materialize.md) and of destroying the `BasicJsonType` value afterwards, plus that of [`BasicJsonType::get()`](../basic_json/get.md). diff --git a/docs/mkdocs/docs/api/basic_json_view/get_to.md b/docs/mkdocs/docs/api/basic_json_view/get_to.md index 4f1c6a5d9..4d2dd2fa5 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get_to.md +++ b/docs/mkdocs/docs/api/basic_json_view/get_to.md @@ -5,12 +5,14 @@ template T& get_to(T& v) const; ``` -Converts the value to `T` and stores it in `v`, in one of two ways: +Converts the value to `T` and stores it in `v`, in one of three ways: - For the types that [`get()`](get.md) converts directly -- arithmetic types, `#!cpp bool`, `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `#!cpp std::basic_string`, `BasicJsonType`, `basic_json_view`, `#!cpp std::vector`, and `#!cpp std::map`/`#!cpp std::unordered_map` with string keys -- `v` is assigned: `#!cpp v = get()`. +- For a type whose `from_json()` accepts views (see [`get()`](get.md)), that `from_json()` is called with the + view and the existing `v`, without building a `BasicJsonType` value. - For every other type, the subtree is built into a `BasicJsonType` value and `#!cpp materialize().get_to(v)` is called, i.e. `from_json()` is invoked with the existing `v`, exactly as [`BasicJsonType::get_to()`](../basic_json/get_to.md) does. diff --git a/docs/mkdocs/docs/api/basic_json_view/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md index 5049e2242..4a634b8f0 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -64,6 +64,7 @@ still refers to it -- including ones taken before the change -- reads the new va ## Member types +- **basic_json_type** - `BasicJsonType` (it also marks the class for [`is_json_view`](../is_json_view.md)) - **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md) - **string_t**, **number_integer_t**, **number_unsigned_t**, **number_float_t**, **json_pointer** - the corresponding member types of `BasicJsonType` diff --git a/docs/mkdocs/docs/api/is_json_view.md b/docs/mkdocs/docs/api/is_json_view.md new file mode 100644 index 000000000..53bd8ec3a --- /dev/null +++ b/docs/mkdocs/docs/api/is_json_view.md @@ -0,0 +1,55 @@ +# nlohmann::is_json_view + +Defined in header `` + +```cpp +template +struct is_json_view; +``` + +A type trait: `#!cpp is_json_view::value` is `#!cpp true` if `T` is a [`basic_json_view`](basic_json_view/index.md) +of any `BasicJsonType` and any [`view_mode`](view_mode.md) -- [`json_view`](json_view.md), +[`json_editable_view`](json_editable_view.md), [`ordered_json_view`](ordered_json_view.md), ... -- and +`#!cpp false` otherwise, for [`basic_json`](basic_json/index.md) as well. + +It is meant for constraining templates that accept views, in particular a `from_json()` that should read views +directly: [`get()`](basic_json_view/get.md) on a view calls a `from_json()` with the view only if that `from_json()` +accepts views but not arbitrary types. + +## Template parameters + +`T` +: the type to check (without cv-qualifiers or references) + +## Notes + +A view is recognized by its member type `basic_json_type` (a [`basic_json`](basic_json/index.md) specialization) and +its [`materialize()`](basic_json_view/materialize.md) returning that type, so the trait needs no declaration of the +view class. + +## Examples + +??? example + + The example below shows the trait, and a `from_json()` constrained to `json` values and views: `get()` on a view + reads the view directly. + + ```cpp + --8<-- "examples/is_json_view.cpp" + ``` + + Output: + + ``` + --8<-- "examples/is_json_view.output" + ``` + +## See also + +- [basic_json_view](basic_json_view/index.md) - a handle to one value of a document +- [get](basic_json_view/get.md) - which conversions read the view directly +- [view_mode](view_mode.md) - read-only or editable + +## Version history + +Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md index c539e77a0..ef0a8c441 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md @@ -147,6 +147,13 @@ void to_json(BasicJsonType& j, const B& b) { See Implementation limits for [NLOHMANN_DEFINE_TYPE_INTRUSIVE](nlohmann_define_type_intrusive.md) and [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE](nlohmann_define_type_non_intrusive.md), respectively. +!!! info "Views" + + The generated `from_json()` also reads the views of [`json_document`](../json_document.md)s + (``): [`get()`](../basic_json_view/get.md) on a view looks the members up in the + view, without building a `json` value first. The base part is read from the view as well if the base's + `from_json()` accepts views, and from [`materialize()`](../basic_json_view/materialize.md) otherwise. + ## Examples ??? example "Example: (1) NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE" @@ -192,3 +199,5 @@ void to_json(BasicJsonType& j, const B& b) { 6. Added in version 3.12.0. All six macros were changed to allow an empty member list in version 3.13.0. + +The generated `from_json()` reads the views of `json_document`s since version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md index dab5c7070..b50022e05 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md @@ -81,6 +81,12 @@ See the examples below for the concrete generated code. that serializes a struct's members positionally into a JSON array; for that, write `to_json`/`from_json` by hand, building/reading a `json::array()` of the members in order. +!!! info "Views" + + The generated `from_json()` also reads the views of [`json_document`](../json_document.md)s + (``): [`get()`](../basic_json_view/get.md) on a view looks the members up in the + view, without building a `json` value first. + ## Examples ??? example "Example: (1) NLOHMANN_DEFINE_TYPE_INTRUSIVE" @@ -189,3 +195,5 @@ See the examples below for the concrete generated code. All three macros were changed to work with any `basic_json` specialization (not only `nlohmann::json`) in version 3.12.0, and to allow an empty member list in version 3.13.0. + +The generated `from_json()` reads the views of `json_document`s since version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md index 07d0166d0..373af300f 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md @@ -80,6 +80,12 @@ See the examples below for the concrete generated code. that serializes a struct's members positionally into a JSON array; for that, write `to_json`/`from_json` by hand, building/reading a `json::array()` of the members in order. +!!! info "Views" + + The generated `from_json()` also reads the views of [`json_document`](../json_document.md)s + (``): [`get()`](../basic_json_view/get.md) on a view looks the members up in the + view, without building a `json` value first. + ## Examples ??? example "Example: (1) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE" @@ -188,3 +194,5 @@ See the examples below for the concrete generated code. All three macros were changed to work with any `basic_json` specialization (not only `nlohmann::json`) in version 3.12.0, and to allow an empty member list in version 3.13.0. + +The generated `from_json()` reads the views of `json_document`s since version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md index 4af3b07b8..6382996ba 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md @@ -45,6 +45,12 @@ inline void from_json(const BasicJsonType& j, type& e); Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects with the converted keys. Such maps can be read from both forms. +!!! info "Views" + + The generated `from_json()` also reads the views of [`json_document`](../json_document.md)s + (``), comparing the view with the entries of the mapping, without building a `json` value + first. + ## Examples ??? example "Example: Basic usage" @@ -88,3 +94,5 @@ inline void from_json(const BasicJsonType& j, type& e); ## Version history Added in version 3.4.0. + +The generated `from_json()` reads the views of `json_document`s since version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md index f5806a490..d1513f4e7 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md @@ -48,6 +48,12 @@ inline void from_json(const BasicJsonType& j, type& e); Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects with the converted keys. Such maps can be read from both forms. +!!! info "Views" + + The generated `from_json()` also reads the views of [`json_document`](../json_document.md)s + (``), comparing the view with the entries of the mapping, without building a `json` value + first. + ## Examples ??? example "Example: Basic usage" diff --git a/docs/mkdocs/docs/examples/is_json_view.cpp b/docs/mkdocs/docs/examples/is_json_view.cpp new file mode 100644 index 000000000..c7c34ee1a --- /dev/null +++ b/docs/mkdocs/docs/examples/is_json_view.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +using nlohmann::json; + +namespace ns +{ +struct person +{ + std::string name; + int age = 0; +}; + +// reads json values and views (of any document type), but nothing else +template < typename J, typename std::enable_if < nlohmann::is_json_view::value || std::is_same::value, int >::type = 0 > +void from_json(const J& j, person& p) +{ + p.name = j.at("name").template get(); + p.age = j.at("age").template get(); +} +} // namespace ns + +int main() +{ + std::cout << std::boolalpha + << nlohmann::is_json_view::value << ' ' + << nlohmann::is_json_view::value << ' ' + << nlohmann::is_json_view::value << '\n'; + + const nlohmann::json_document doc = nlohmann::json_document::parse(R"({"name": "Ada", "age": 36})"); + const auto p = doc.root().get(); // reads the view, no json value is built + std::cout << p.name << ' ' << p.age << '\n'; +} diff --git a/docs/mkdocs/docs/examples/is_json_view.output b/docs/mkdocs/docs/examples/is_json_view.output new file mode 100644 index 000000000..df2203a21 --- /dev/null +++ b/docs/mkdocs/docs/examples/is_json_view.output @@ -0,0 +1,2 @@ +true true false +Ada 36 diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 89e26bdb3..7de4a07cd 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -185,6 +185,10 @@ key as well. Other zero-copy parsers that index the source text, such as yyjson member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees duplicates is unpredictable, so neither choice is wrong. +The same holds for [`get()`](../api/basic_json_view/get.md) of a type whose `from_json()` reads the view, such as +the types of the `NLOHMANN_DEFINE_TYPE_*` macros: their members are looked up, so `#!cpp get()` of +`#!cpp {"x":1,"x":2}` reads `#!cpp x == 1`, while `#!cpp materialize().get()` reads `#!cpp 2`. + **What stays the same as `parse()`?** [`materialize()`](../api/basic_json_view/materialize.md) and [`get>()`](../api/basic_json_view/get.md) replay every member in order, so they keep the *last* value exactly like `#!cpp basic_json::parse()` (at the position of the first occurrence of the key, for an diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index c65ca7de8..d88864f09 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -362,6 +362,7 @@ nav: - 'Overview': api/adl_serializer/index.md - 'from_json': api/adl_serializer/from_json.md - 'to_json': api/adl_serializer/to_json.md + - 'is_json_view': api/is_json_view.md - 'json': api/json.md - 'json_document': api/json_document.md - 'json_editable_document': api/json_editable_document.md diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 51f26706c..696afc34b 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -10,6 +10,7 @@ #include // min, transform #include // array +#include // nullptr_t #include // forward_list #include // inserter, front_inserter, end #include // map @@ -27,6 +28,7 @@ #include #include #include +#include #include #include @@ -665,16 +667,114 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) } #endif -struct from_json_fn +namespace from_json_adl { - template - auto operator()(const BasicJsonType& j, T&& val) const +// hides the library's from_json() overloads above (and the from_json object): +// a call from this namespace finds only the from_json() functions of the +// arguments' own namespaces and classes (ADL); the declaration is never viable +// and never defined (not "= delete", which GCC before 6 reports as a use) +struct hidden; +void from_json(const hidden&); + +// a type no from_json() written for JSON values accepts: a from_json() that +// accepts it is not constrained to them (see has_view_from_json; it lives +// here, so that ADL finds no other from_json() for it) +struct probe +{}; + +struct fn +{ + template + auto operator()(const SourceType& j, T&& val) const noexcept(noexcept(from_json(j, std::forward(val)))) -> decltype(from_json(j, std::forward(val))) { return from_json(j, std::forward(val)); } }; +} // namespace from_json_adl + +// the sources whose from_json() is only looked up by ADL: views (the library's +// overloads are written for basic_json) and the probe +template +struct is_adl_only_from_json_source : disjunction, std::is_same> {}; + +struct from_json_fn +{ + template < typename BasicJsonType, typename T, + enable_if_t < !is_adl_only_from_json_source::value, int > = 0 > + auto operator()(const BasicJsonType& j, T && val) const + noexcept(noexcept(from_json(j, std::forward(val)))) + -> decltype(from_json(j, std::forward(val))) + { + return from_json(j, std::forward(val)); + } + + template < typename SourceType, typename T, + enable_if_t < is_adl_only_from_json_source::value, int > = 0 > + auto operator()(const SourceType& j, T && val) const + noexcept(noexcept(from_json_adl::fn {}(j, std::forward(val)))) + -> decltype(from_json_adl::fn {}(j, std::forward(val))) + { + return from_json_adl::fn {}(j, std::forward(val)); + } +}; + +// Whether JSONSerializer::from_json(view, T&) reads T from a view (of +// ) directly: it must accept the view but not the +// probe. An unconstrained from_json() template (written for basic_json, but +// accepting anything) is thus not called with a view, nor are the library's +// own conversions; the view converts such types through materialize(). +template +using serializer_from_json_t = decltype(Serializer::from_json(std::declval(), std::declval())); + +template +struct has_view_from_json + : conjunction < is_detected, + negation>> {}; + +// the base part of a type of the NLOHMANN_DEFINE_DERIVED_TYPE_* macros: from a +// view through its get_to(), which reads the base from the view where the +// base's from_json() accepts views, and from materialize() otherwise +template::value, int> = 0> +void from_json_base(const BasicJsonType& j, T& base) +{ + from_json_fn {}(j, base); +} + +template::value, int> = 0> +void from_json_base(const View& v, T& base) +{ + v.get_to(base); +} + +// the context of an exception thrown by a from_json() of the macros: the value +// itself for basic_json (for JSON_DIAGNOSTICS), none for a view +template::value, int> = 0> +const BasicJsonType * from_json_context(const BasicJsonType& j) noexcept +{ + return &j; +} + +template::value, int> = 0> +std::nullptr_t from_json_context(const View& /*unused*/) noexcept +{ + return nullptr; +} + +// a value as an exception message shows it (invalid UTF-8 replaced; the +// strings of a view are always valid UTF-8) +template::value, int> = 0> +typename BasicJsonType::string_t from_json_dump(const BasicJsonType& j) +{ + return j.dump(-1, ' ', false, error_handler_t::replace); +} + +template::value, int> = 0> +typename View::string_t from_json_dump(const View& v) +{ + return v.dump(); +} } // namespace detail diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 061c48853..4f89f3463 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -291,15 +291,17 @@ }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ - template \ + template::value, int> = 0> \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* (for a view, the table holds basic_json values, which compare with the view) */ \ + using nlohmann_json_value_t = nlohmann::detail::json_type_of_t; \ /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ + static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ + [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ @@ -330,20 +332,22 @@ if (it != std::end(m)) j = it->second; \ else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ - template \ + template::value, int> = 0> \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* (for a view, the table holds basic_json values, which compare with the view) */ \ + using nlohmann_json_value_t = nlohmann::detail::json_type_of_t; \ /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ + static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ + [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::from_json_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -603,6 +607,8 @@ #define NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME(v1, v2) nlohmann_json_t.v2 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(v1, nlohmann_json_default_obj.v2) : nlohmann_json_default_obj.v2; #define NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(...) template::value, int> = 0> __VA_ARGS__ +// from_json() also reads from the views of +#define NLOHMANN_JSON_FROM_TYPE_TEMPLATE(...) template::value, int> = 0> __VA_ARGS__ // Helpers used to dispatch the NLOHMANN_DEFINE_TYPE_*/NLOHMANN_DEFINE_DERIVED_TYPE_* // macros below between a zero-member and a one-or-more-member implementation @@ -655,18 +661,18 @@ */ #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { }) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -676,7 +682,7 @@ */ #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) @@ -685,7 +691,7 @@ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -712,18 +718,18 @@ */ #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { }) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -733,7 +739,7 @@ */ #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) @@ -742,7 +748,7 @@ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -769,18 +775,18 @@ */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); }) #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -790,7 +796,7 @@ */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) @@ -799,7 +805,7 @@ #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -827,18 +833,18 @@ */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); }) #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -848,7 +854,7 @@ */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) @@ -857,7 +863,7 @@ #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 387691b6b..bd72c3a65 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -340,6 +340,47 @@ template struct negation : std::integral_constant < bool, !B::value > { #endif +/////////////////////// +// json_view helpers // +/////////////////////// + +template +using basic_json_type_t = typename T::basic_json_type; + +template +using materialize_function_t = decltype(std::declval().materialize()); + +// a basic_json_view of (detected by its member type +// basic_json_type, a basic_json, and materialize() returning that type, so that +// json.hpp needs no declaration of the view) +template +struct is_json_view : std::false_type {}; + +template +struct is_json_view>::value>> + : std::is_same, basic_json_type_t> {}; + +// the types from_json() functions of the NLOHMANN_DEFINE_TYPE_* and +// NLOHMANN_JSON_SERIALIZE_ENUM macros read from +template +struct is_basic_json_or_view : disjunction, is_json_view> {}; + +// the basic_json type of a basic_json value or of a view +template +struct json_type_of +{ + using type = T; +}; + +template +struct json_type_of::value>> +{ + using type = typename T::basic_json_type; +}; + +template +using json_type_of_t = typename json_type_of::type; + // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g., Clang 3.5 or GCC 4.9. diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 79c9b8dde..ebcb13733 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -106,6 +106,8 @@ class basic_json_view using navigation = detail::view::navigation; public: + /// the basic_json type of the document (it also marks views for is_json_view) + using basic_json_type = BasicJsonType; using value_t = detail::value_t; using string_t = typename BasicJsonType::string_t; using number_integer_t = typename BasicJsonType::number_integer_t; @@ -576,8 +578,10 @@ class basic_json_view /// the value converted to T, as BasicJsonType::get(): arithmetic types, /// strings (string_view_t without a copy), std::nullptr_t, std::vector, - /// maps with string keys, and views are converted directly; other types - /// through materialize().get() + /// maps with string keys, and views are converted directly; types whose + /// from_json() accepts views (those of the NLOHMANN_DEFINE_TYPE_* and + /// NLOHMANN_JSON_SERIALIZE_ENUM macros, say) read the view; other types + /// go through materialize().get() template NLOHMANN_VIEW_ALWAYS_INLINE T get() const { @@ -585,11 +589,12 @@ class basic_json_view } /// as BasicJsonType::get_to(): the types get() converts directly are - /// assigned, all others are passed to from_json with v as it is + /// assigned, all others are passed to from_json with v as it is (with + /// the view if from_json() accepts views, else with materialize()) template T& get_to(T& v) const { - get_to_impl(v, detail::view::is_direct_value {}); + get_to_impl(v, get_to_kind {}); return v; } @@ -932,6 +937,26 @@ class basic_json_view return detail::view::map_value>(*this); } + /// whether the from_json() of T's serializer reads views (see + /// detail::has_view_from_json) + template + using reads_view = detail::has_view_from_json, basic_json_view, T>; + + /// how get_to() converts: 2 directly, 1 with a from_json() that reads + /// views, 0 through materialize() + template + using get_to_kind = std::integral_constant < int, detail::view::is_direct_value::value + ? 2 : static_cast(reads_view::value) >; + + /// types whose from_json() reads views, without materialize() + template < typename T, typename std::enable_if < reads_view::value && std::is_default_constructible::value, int >::type = 0 > + T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<1> /*unused*/) const + { + auto v = T(); + BasicJsonType::template json_serializer::from_json(*this, v); + return v; + } + /// everything else through the BasicJsonType value (from_json included) template T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<0> /*unused*/) const @@ -942,14 +967,20 @@ class basic_json_view // --- get_to() dispatch --- template - void get_to_impl(T& v, std::true_type /*direct*/) const + void get_to_impl(T& v, std::integral_constant /*direct*/) const { v = get(); } /// from_json may keep parts of v (members a JSON object lacks, say) template - void get_to_impl(T& v, std::false_type /*direct*/) const + void get_to_impl(T& v, std::integral_constant /*from_json reads views*/) const + { + BasicJsonType::template json_serializer::from_json(*this, v); + } + + template + void get_to_impl(T& v, std::integral_constant /*through materialize()*/) const { materialize().get_to(v); } @@ -1654,6 +1685,15 @@ class basic_json_document std::unique_ptr m_data{}; // NOLINT(readability-redundant-member-init) }; +/*! +@brief whether T is a basic_json_view (of any BasicJsonType and view_mode) + +For constraining templates that accept views, e.g. a from_json() written for +both basic_json values and views. +*/ +template +struct is_json_view : detail::is_json_view {}; + /// a parsed JSON text for json using json_document = basic_json_document; /// a value of a json_document diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index b6281565f..b0492ec78 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -272,6 +272,7 @@ #include // min, transform #include // array +#include // nullptr_t #include // forward_list #include // inserter, front_inserter, end #include // map @@ -2663,15 +2664,17 @@ JSON_HEDLEY_DIAGNOSTIC_POP }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ - template \ + template::value, int> = 0> \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* (for a view, the table holds basic_json values, which compare with the view) */ \ + using nlohmann_json_value_t = nlohmann::detail::json_type_of_t; \ /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ + static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ + [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ @@ -2702,20 +2705,22 @@ JSON_HEDLEY_DIAGNOSTIC_POP if (it != std::end(m)) j = it->second; \ else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range,"enum value out of range for " #ENUM_TYPE, nullptr)); \ } \ - template \ + template::value, int> = 0> \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* (for a view, the table holds basic_json values, which compare with the view) */ \ + using nlohmann_json_value_t = nlohmann::detail::json_type_of_t; \ /* NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ + static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ + [&j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", j.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace)), &j)); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::from_json_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -2975,6 +2980,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME(v1, v2) nlohmann_json_t.v2 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(v1, nlohmann_json_default_obj.v2) : nlohmann_json_default_obj.v2; #define NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(...) template::value, int> = 0> __VA_ARGS__ +// from_json() also reads from the views of +#define NLOHMANN_JSON_FROM_TYPE_TEMPLATE(...) template::value, int> = 0> __VA_ARGS__ // Helpers used to dispatch the NLOHMANN_DEFINE_TYPE_*/NLOHMANN_DEFINE_DERIVED_TYPE_* // macros below between a zero-member and a one-or-more-member implementation @@ -3027,18 +3034,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { }) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3048,7 +3055,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) @@ -3057,7 +3064,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3084,18 +3091,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { }) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3105,7 +3112,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) @@ -3114,7 +3121,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3141,18 +3148,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); }) #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3162,7 +3169,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) @@ -3171,7 +3178,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3199,18 +3206,18 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }) #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) \ /* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); }) #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__)) #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -3220,7 +3227,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP */ #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }) // identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default #define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) @@ -3229,7 +3236,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \ NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \ - NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) + NLOHMANN_JSON_FROM_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { ::nlohmann::detail::from_json_base(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) }) /*! @brief macro @@ -4516,6 +4523,47 @@ template struct negation : std::integral_constant < bool, !B::value > { #endif +/////////////////////// +// json_view helpers // +/////////////////////// + +template +using basic_json_type_t = typename T::basic_json_type; + +template +using materialize_function_t = decltype(std::declval().materialize()); + +// a basic_json_view of (detected by its member type +// basic_json_type, a basic_json, and materialize() returning that type, so that +// json.hpp needs no declaration of the view) +template +struct is_json_view : std::false_type {}; + +template +struct is_json_view>::value>> + : std::is_same, basic_json_type_t> {}; + +// the types from_json() functions of the NLOHMANN_DEFINE_TYPE_* and +// NLOHMANN_JSON_SERIALIZE_ENUM macros read from +template +struct is_basic_json_or_view : disjunction, is_json_view> {}; + +// the basic_json type of a basic_json value or of a view +template +struct json_type_of +{ + using type = T; +}; + +template +struct json_type_of::value>> +{ + using type = typename T::basic_json_type; +}; + +template +using json_type_of_t = typename json_type_of::type; + // Reimplementation of is_constructible and is_default_constructible, due to them being broken for // std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). // This causes compile errors in e.g., Clang 3.5 or GCC 4.9. @@ -6035,6 +6083,59 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// how to treat decoding errors +/// +/// @ref basic_json::dump uses this to decide what to do with ill-formed +/// UTF-8 while escaping a string, and the binary writers (@ref +/// basic_json::to_cbor, @ref basic_json::to_ubjson, @ref +/// basic_json::to_bjdata, @ref basic_json::to_bson) use it the same way for +/// string values and object keys. The binary readers (@ref +/// basic_json::from_cbor, @ref basic_json::from_msgpack, @ref +/// basic_json::from_ubjson, @ref basic_json::from_bjdata, @ref +/// basic_json::from_bson) use it to decide whether to check text strings +/// and object keys for well-formed UTF-8 at all, since none of those +/// formats requires a decoder to do so. +enum class error_handler_t +{ + strict, ///< throw a type_error/parse_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore, ///< ignore invalid UTF-8 sequences + keep ///< keep invalid UTF-8 sequences unchanged +}; + +/// the default error handler of the CBOR, UBJSON, BJData, and BSON writers: +/// error_handler_t::strict if JSON_STRICT_BINARY_UTF8 is enabled, otherwise +/// error_handler_t::keep (the behavior before version 3.13.0) +constexpr error_handler_t binary_writer_default_error_handler() noexcept +{ +#if JSON_STRICT_BINARY_UTF8 + return error_handler_t::strict; +#else + return error_handler_t::keep; +#endif +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // #include @@ -6675,16 +6776,114 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) } #endif -struct from_json_fn +namespace from_json_adl { - template - auto operator()(const BasicJsonType& j, T&& val) const +// hides the library's from_json() overloads above (and the from_json object): +// a call from this namespace finds only the from_json() functions of the +// arguments' own namespaces and classes (ADL); the declaration is never viable +// and never defined (not "= delete", which GCC before 6 reports as a use) +struct hidden; +void from_json(const hidden&); + +// a type no from_json() written for JSON values accepts: a from_json() that +// accepts it is not constrained to them (see has_view_from_json; it lives +// here, so that ADL finds no other from_json() for it) +struct probe +{}; + +struct fn +{ + template + auto operator()(const SourceType& j, T&& val) const noexcept(noexcept(from_json(j, std::forward(val)))) -> decltype(from_json(j, std::forward(val))) { return from_json(j, std::forward(val)); } }; +} // namespace from_json_adl + +// the sources whose from_json() is only looked up by ADL: views (the library's +// overloads are written for basic_json) and the probe +template +struct is_adl_only_from_json_source : disjunction, std::is_same> {}; + +struct from_json_fn +{ + template < typename BasicJsonType, typename T, + enable_if_t < !is_adl_only_from_json_source::value, int > = 0 > + auto operator()(const BasicJsonType& j, T && val) const + noexcept(noexcept(from_json(j, std::forward(val)))) + -> decltype(from_json(j, std::forward(val))) + { + return from_json(j, std::forward(val)); + } + + template < typename SourceType, typename T, + enable_if_t < is_adl_only_from_json_source::value, int > = 0 > + auto operator()(const SourceType& j, T && val) const + noexcept(noexcept(from_json_adl::fn {}(j, std::forward(val)))) + -> decltype(from_json_adl::fn {}(j, std::forward(val))) + { + return from_json_adl::fn {}(j, std::forward(val)); + } +}; + +// Whether JSONSerializer::from_json(view, T&) reads T from a view (of +// ) directly: it must accept the view but not the +// probe. An unconstrained from_json() template (written for basic_json, but +// accepting anything) is thus not called with a view, nor are the library's +// own conversions; the view converts such types through materialize(). +template +using serializer_from_json_t = decltype(Serializer::from_json(std::declval(), std::declval())); + +template +struct has_view_from_json + : conjunction < is_detected, + negation>> {}; + +// the base part of a type of the NLOHMANN_DEFINE_DERIVED_TYPE_* macros: from a +// view through its get_to(), which reads the base from the view where the +// base's from_json() accepts views, and from materialize() otherwise +template::value, int> = 0> +void from_json_base(const BasicJsonType& j, T& base) +{ + from_json_fn {}(j, base); +} + +template::value, int> = 0> +void from_json_base(const View& v, T& base) +{ + v.get_to(base); +} + +// the context of an exception thrown by a from_json() of the macros: the value +// itself for basic_json (for JSON_DIAGNOSTICS), none for a view +template::value, int> = 0> +const BasicJsonType * from_json_context(const BasicJsonType& j) noexcept +{ + return &j; +} + +template::value, int> = 0> +std::nullptr_t from_json_context(const View& /*unused*/) noexcept +{ + return nullptr; +} + +// a value as an exception message shows it (invalid UTF-8 replaced; the +// strings of a view are always valid UTF-8) +template::value, int> = 0> +typename BasicJsonType::string_t from_json_dump(const BasicJsonType& j) +{ + return j.dump(-1, ' ', false, error_handler_t::replace); +} + +template::value, int> = 0> +typename View::string_t from_json_dump(const View& v) +{ + return v.dump(); +} } // namespace detail @@ -6779,57 +6978,6 @@ NLOHMANN_JSON_NAMESPACE_END // #include // #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/// how to treat decoding errors -/// -/// @ref basic_json::dump uses this to decide what to do with ill-formed -/// UTF-8 while escaping a string, and the binary writers (@ref -/// basic_json::to_cbor, @ref basic_json::to_ubjson, @ref -/// basic_json::to_bjdata, @ref basic_json::to_bson) use it the same way for -/// string values and object keys. The binary readers (@ref -/// basic_json::from_cbor, @ref basic_json::from_msgpack, @ref -/// basic_json::from_ubjson, @ref basic_json::from_bjdata, @ref -/// basic_json::from_bson) use it to decide whether to check text strings -/// and object keys for well-formed UTF-8 at all, since none of those -/// formats requires a decoder to do so. -enum class error_handler_t -{ - strict, ///< throw a type_error/parse_error exception in case of invalid UTF-8 - replace, ///< replace invalid UTF-8 sequences with U+FFFD - ignore, ///< ignore invalid UTF-8 sequences - keep ///< keep invalid UTF-8 sequences unchanged -}; - -/// the default error handler of the CBOR, UBJSON, BJData, and BSON writers: -/// error_handler_t::strict if JSON_STRICT_BINARY_UTF8 is enabled, otherwise -/// error_handler_t::keep (the behavior before version 3.13.0) -constexpr error_handler_t binary_writer_default_error_handler() noexcept -{ -#if JSON_STRICT_BINARY_UTF8 - return error_handler_t::strict; -#else - return error_handler_t::keep; -#endif -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_BEGIN diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 0e6c087ab..44a7c62d9 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -7858,6 +7858,8 @@ class basic_json_view using navigation = detail::view::navigation; public: + /// the basic_json type of the document (it also marks views for is_json_view) + using basic_json_type = BasicJsonType; using value_t = detail::value_t; using string_t = typename BasicJsonType::string_t; using number_integer_t = typename BasicJsonType::number_integer_t; @@ -8328,8 +8330,10 @@ class basic_json_view /// the value converted to T, as BasicJsonType::get(): arithmetic types, /// strings (string_view_t without a copy), std::nullptr_t, std::vector, - /// maps with string keys, and views are converted directly; other types - /// through materialize().get() + /// maps with string keys, and views are converted directly; types whose + /// from_json() accepts views (those of the NLOHMANN_DEFINE_TYPE_* and + /// NLOHMANN_JSON_SERIALIZE_ENUM macros, say) read the view; other types + /// go through materialize().get() template NLOHMANN_VIEW_ALWAYS_INLINE T get() const { @@ -8337,11 +8341,12 @@ class basic_json_view } /// as BasicJsonType::get_to(): the types get() converts directly are - /// assigned, all others are passed to from_json with v as it is + /// assigned, all others are passed to from_json with v as it is (with + /// the view if from_json() accepts views, else with materialize()) template T& get_to(T& v) const { - get_to_impl(v, detail::view::is_direct_value {}); + get_to_impl(v, get_to_kind {}); return v; } @@ -8684,6 +8689,26 @@ class basic_json_view return detail::view::map_value>(*this); } + /// whether the from_json() of T's serializer reads views (see + /// detail::has_view_from_json) + template + using reads_view = detail::has_view_from_json, basic_json_view, T>; + + /// how get_to() converts: 2 directly, 1 with a from_json() that reads + /// views, 0 through materialize() + template + using get_to_kind = std::integral_constant < int, detail::view::is_direct_value::value + ? 2 : static_cast(reads_view::value) >; + + /// types whose from_json() reads views, without materialize() + template < typename T, typename std::enable_if < reads_view::value && std::is_default_constructible::value, int >::type = 0 > + T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<1> /*unused*/) const + { + auto v = T(); + BasicJsonType::template json_serializer::from_json(*this, v); + return v; + } + /// everything else through the BasicJsonType value (from_json included) template T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<0> /*unused*/) const @@ -8694,14 +8719,20 @@ class basic_json_view // --- get_to() dispatch --- template - void get_to_impl(T& v, std::true_type /*direct*/) const + void get_to_impl(T& v, std::integral_constant /*direct*/) const { v = get(); } /// from_json may keep parts of v (members a JSON object lacks, say) template - void get_to_impl(T& v, std::false_type /*direct*/) const + void get_to_impl(T& v, std::integral_constant /*from_json reads views*/) const + { + BasicJsonType::template json_serializer::from_json(*this, v); + } + + template + void get_to_impl(T& v, std::integral_constant /*through materialize()*/) const { materialize().get_to(v); } @@ -9406,6 +9437,15 @@ class basic_json_document std::unique_ptr m_data{}; // NOLINT(readability-redundant-member-init) }; +/*! +@brief whether T is a basic_json_view (of any BasicJsonType and view_mode) + +For constraining templates that accept views, e.g. a from_json() written for +both basic_json values and views. +*/ +template +struct is_json_view : detail::is_json_view {}; + /// a parsed JSON text for json using json_document = basic_json_document; /// a value of a json_document diff --git a/tests/benchmarks/json_view/README.md b/tests/benchmarks/json_view/README.md index c4a05d35a..a0bd2afbc 100644 --- a/tests/benchmarks/json_view/README.md +++ b/tests/benchmarks/json_view/README.md @@ -59,6 +59,12 @@ An editable `json_document` edits in place; yyjson copies its immutable document (`yyjson_doc_mut_copy`); Boost.JSON and `json::parse` build mutable DOMs; simdjson cannot edit a document. All outputs are checked to describe the same value. +`bench_decode.cpp` measures decoding into structs whose `from_json()` comes from the `NLOHMANN_DEFINE_TYPE_*` macros +(twitter statuses, citm_catalog performances, and 20,000 synthetic records): `get()` on a view, which reads the +view directly, against `materialize().get()`, `json::parse` plus `get()`, and `get()` on an already parsed +`json` value. Only nlohmann/json takes part, as the other libraries have no comparable mapping; all engines are +checked to decode the same values. + Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`). Each timed diff --git a/tests/benchmarks/json_view/bench_decode.cpp b/tests/benchmarks/json_view/bench_decode.cpp new file mode 100644 index 000000000..29e3d7f81 --- /dev/null +++ b/tests/benchmarks/json_view/bench_decode.cpp @@ -0,0 +1,312 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// Decoding into structs: the same types, with from_json() generated by the +// NLOHMANN_DEFINE_TYPE_* macros, read three ways. Only nlohmann/json takes +// part; the other libraries have no comparable mapping. +// +// view json_document::parse, then get() on the view (from_json reads the view) +// materialize json_document::parse, then materialize().get() (a json value first) +// json::parse json::parse, then get() +// json::get get() on an already parsed json value (the decoding alone) +// +// Documents: twitter (the statuses with their users and hashtags), citm_catalog +// (the performances with their seat categories), and a synthetic document of +// 20,000 nested records. +// +// Build: see README.md (same flags as bench_view.cpp). +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using nlohmann::json; +using nlohmann::json_document; + +static volatile std::size_t g_sink; + +namespace twitter +{ +struct hashtag +{ + std::string text; + std::vector indices; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(hashtag, text, indices) + +struct entities +{ + std::vector hashtags; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(entities, hashtags) + +struct user +{ + std::uint64_t id = 0; + std::string screen_name; + std::string description; + std::int64_t followers_count = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(user, id, screen_name, description, followers_count) + +struct status +{ + std::uint64_t id = 0; + std::string text; + user user; + entities entities; + std::int64_t retweet_count = 0; + std::int64_t favorite_count = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(status, id, text, user, entities, retweet_count, favorite_count) + +struct document +{ + std::vector statuses; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, statuses) + +std::size_t digest(const document& d) +{ + std::size_t n = 0; + for (const auto& s : d.statuses) + { + n += s.text.size() + s.user.screen_name.size() + s.entities.hashtags.size() + static_cast(s.retweet_count); + } + return n; +} +} // namespace twitter + +namespace citm +{ +struct area +{ + std::int64_t areaId = 0; + std::vector blockIds; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(area, areaId, blockIds) + +struct seat_category +{ + std::vector areas; + std::int64_t seatCategoryId = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(seat_category, areas, seatCategoryId) + +struct performance +{ + std::int64_t id = 0; + std::int64_t eventId = 0; + std::vector seatCategories; + std::int64_t start = 0; + std::string venueCode; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(performance, id, eventId, seatCategories, start, venueCode) + +struct document +{ + std::vector performances; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, performances) + +std::size_t digest(const document& d) +{ + std::size_t n = 0; + for (const auto& p : d.performances) + { + n += p.seatCategories.size() + p.venueCode.size() + static_cast(p.start % 1000); + for (const auto& c : p.seatCategories) + { + n += c.areas.size(); + } + } + return n; +} +} // namespace citm + +namespace synthetic +{ +struct position +{ + double x = 0; + double y = 0; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(position, x, y) + +struct child +{ + std::int64_t id = 0; + std::string label; + bool active = false; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(child, id, label, active) + +struct record +{ + std::int64_t id = 0; + std::string name; + std::vector tags; + position pos; + std::vector children; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(record, id, name, tags, pos, children) + +struct document +{ + std::vector records; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, records) + +std::string make_text(std::size_t n) +{ + std::string s = "{\"records\":["; + for (std::size_t i = 0; i < n; ++i) + { + const std::string id = std::to_string(i); + s += (i == 0 ? "" : ","); + s += "{\"id\":" + id + ",\"name\":\"record number " + id + "\",\"tags\":[\"alpha\",\"beta\",\"gamma\"],"; + s += "\"pos\":{\"x\":" + id + ".25,\"y\":-" + id + ".5},\"children\":["; + for (std::size_t k = 0; k < 3; ++k) + { + s += (k == 0 ? "" : ","); + s += "{\"id\":" + std::to_string(i * 3 + k) + ",\"label\":\"child " + std::to_string(k) + "\",\"active\":" + (k % 2 == 0 ? "true" : "false") + "}"; + } + s += "]}"; + } + s += "]}"; + return s; +} + +std::size_t digest(const document& d) +{ + std::size_t n = 0; + for (const auto& r : d.records) + { + n += r.name.size() + r.tags.size() + r.children.size() + static_cast(r.pos.x); + } + return n; +} +} // namespace synthetic + +namespace +{ +std::string read_file(const std::string& p) +{ + std::ifstream f(p, std::ios::binary); + if (!f) + { + std::fprintf(stderr, "cannot open %s\n", p.c_str()); + std::exit(1); + } + std::stringstream ss; + ss << f.rdbuf(); + return ss.str(); +} + +double time_ns(const std::function& f) +{ + const auto t0 = std::chrono::steady_clock::now(); + f(); + const auto t1 = std::chrono::steady_clock::now(); + return static_cast(std::chrono::duration_cast(t1 - t0).count()); +} + +struct engine +{ + std::string name; + std::function run; +}; + +template +std::vector engines(const std::string& text, const json& parsed) +{ + return + { + {"view", [&text] { const json_document d = json_document::parse(text); return digest(d.root().get()); }}, + {"materialize", [&text] { const json_document d = json_document::parse(text); return digest(d.root().materialize().get()); }}, + {"json::parse", [&text] { return digest(json::parse(text).get()); }}, + {"json::get", [&parsed] { return digest(parsed.get()); }}, + }; +} +} // namespace + +int main(int argc, char** argv) +{ + if (argc < 2) + { + std::fprintf(stderr, "usage: %s [rounds]\n", argv[0]); + return 1; + } + const std::string dir = argv[1]; + const int rounds = argc > 2 ? std::max(1, std::atoi(argv[2])) : 30; + + struct document_case + { + std::string name; + std::string text; + json parsed; + std::vector engines; + }; + std::vector cases; + cases.push_back({"twitter", read_file(dir + "/nativejson-benchmark/twitter.json"), json(), {}}); + cases.push_back({"citm_catalog", read_file(dir + "/nativejson-benchmark/citm_catalog.json"), json(), {}}); + cases.push_back({"synthetic", synthetic::make_text(20000), json(), {}}); + for (auto& c : cases) + { + c.parsed = json::parse(c.text); + } + cases[0].engines = engines(cases[0].text, cases[0].parsed); + cases[1].engines = engines(cases[1].text, cases[1].parsed); + cases[2].engines = engines(cases[2].text, cases[2].parsed); + + std::FILE* csv = std::fopen("bench_decode.csv", "w"); + if (csv == nullptr) + { + std::perror("bench_decode.csv"); + return 1; + } + std::fprintf(csv, "doc,bytes,engine,ns\n"); + for (const auto& c : cases) + { + // all engines must decode the same values + const std::size_t expected = c.engines[0].run(); + bool ok = true; + for (const auto& e : c.engines) + { + ok = ok && e.run() == expected; + } + std::vector best(c.engines.size(), 1e300); + for (int r = 0; r < rounds; ++r) + { + for (std::size_t k = 0; k < c.engines.size(); ++k) + { + g_sink = c.engines[k].run(); // untimed: see bench_view.cpp + best[k] = std::min(best[k], time_ns([&] { g_sink = c.engines[k].run(); })); + } + } + std::printf("%-13s %-7s %s", c.name.c_str(), "decode", ok ? "" : "[MISMATCH] "); + for (std::size_t k = 0; k < c.engines.size(); ++k) + { + const double us = best[k] / 1000.0; + std::printf(" %s %.*fus (%.2fx)", c.engines[k].name.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]); + std::fprintf(csv, "%s,%zu,%s,%.1f\n", c.name.c_str(), c.text.size(), c.engines[k].name.c_str(), best[k]); + } + std::printf("\n"); + } + std::fclose(csv); + return 0; +} diff --git a/tests/benchmarks/json_view/compare.py b/tests/benchmarks/json_view/compare.py index 39d5048f0..6573c88af 100755 --- a/tests/benchmarks/json_view/compare.py +++ b/tests/benchmarks/json_view/compare.py @@ -9,7 +9,7 @@ """Compare json_view with yyjson, simdjson, Boost.JSON, and json::parse. -Builds bench_view.cpp, bench_corpus.cpp, and bench_edit.cpp against the include/ directory of +Builds bench_view.cpp, bench_corpus.cpp, bench_edit.cpp, and bench_decode.cpp against the include/ directory of this checkout, runs them, and writes the results with everything needed to reproduce them (date, commit, CPU, OS, compiler, library versions, flags) to results/-.md and .csv next to this script. @@ -283,7 +283,7 @@ def main(): objects.append(obj) binaries = {} - for bench in ['bench_view', 'bench_corpus', 'bench_edit']: + for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: exe = os.path.join(args.build_dir, bench) run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe]) binaries[bench] = exe @@ -297,6 +297,8 @@ def main(): capture_output=True, text=True).stdout outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir, capture_output=True, text=True).stdout + outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir, + capture_output=True, text=True).stdout for name, text in outputs.items(): print(text) @@ -330,7 +332,7 @@ def main(): f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n') with open(stem + '.csv', 'w', encoding='utf-8') as out: out.write(''.join(f'# {key}: {value}\n' for key, value in meta)) - for name in ['bench_view', 'bench_corpus', 'bench_edit']: + for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: path = os.path.join(args.build_dir, name + '.csv') if os.path.isfile(path): with open(path, encoding='utf-8') as f: diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp new file mode 100644 index 000000000..f106b1fe0 --- /dev/null +++ b/tests/src/unit-json_view_from_json.cpp @@ -0,0 +1,357 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; +using nlohmann::json_document; +using nlohmann::json_view; +using nlohmann::json_editable_document; +using nlohmann::ordered_json_document; + +#include "json_view_test_helpers.hpp" +#if !defined(JSON_NOEXCEPTION) + using json_view_test::exception_of_call; +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef JSON_HAS_CPP_17 + #include +#endif + +// The from_json() functions of the NLOHMANN_DEFINE_TYPE_* and +// NLOHMANN_JSON_SERIALIZE_ENUM macros read views directly; other from_json() +// functions get the value of materialize(). + +namespace view_from_json +{ +struct point +{ + int x = 0; + int y = 0; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(point, x, y) +}; + +struct shape +{ + std::string name; + std::vector points; + point center; + std::map named; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(shape, name, points, center, named) +}; + +struct with_defaults +{ + int a = 1; + std::string b = "default"; + std::vector c = {1, 2}; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(with_defaults, a, b, c) + +struct with_names +{ + int value = 0; + std::string text; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(with_names, "Value", value, "Text", text) + +struct empty +{ + NLOHMANN_DEFINE_TYPE_INTRUSIVE(empty) +}; + +enum class color +{ + red, + green, + blue +}; +NLOHMANN_JSON_SERIALIZE_ENUM(color, +{ + {color::red, "red"}, + {color::green, "green"}, + {color::blue, nullptr}, +}) + +enum class strict_color +{ + red, + green +}; +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_color, +{ + {strict_color::red, "red"}, + {strict_color::green, 1}, +}) + +struct base +{ + int id = 0; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(base, id) +}; + +struct derived : base +{ + color c = color::red; + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived, base, c) +}; + +struct derived_with_defaults : base +{ + std::string label = "none"; + NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(derived_with_defaults, base, label) +}; + +// a base whose from_json() is written for json only: the derived type reads +// its own members from the view and the base part from materialize() +struct legacy_base +{ + int id = 0; +}; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +inline void from_json(const json& j, legacy_base& b) +{ + b.id = j.at("id").get(); +} + +struct derived_from_legacy : legacy_base +{ + int extra = 0; +}; +NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(derived_from_legacy, legacy_base, extra) + +// from_json() for json only: converted through materialize() +struct legacy +{ + int v = 0; +}; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +inline void from_json(const json& j, legacy& l) +{ + l.v = j.at("v").get(); +} + +// an unconstrained from_json() template, as in the documentation of +// arbitrary type conversions: converted through materialize() +struct unconstrained +{ + int v = 0; +}; +template +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +void from_json(const BasicJsonType& j, unconstrained& u) +{ + u.v = j.at("v").template get(); +} + +// a from_json() constrained by the user to views and json: reads the view +struct constrained +{ + int v = 0; +}; +template < typename J, typename std::enable_if < nlohmann::is_json_view::value || std::is_same::value, int >::type = 0 > +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users +void from_json(const J& j, constrained& c) +{ + c.v = j.at("v").template get(); +} +} // namespace view_from_json + +namespace +{ +template +struct reads_view : nlohmann::detail::has_view_from_json, View, T> {}; +} // namespace + +TEST_CASE("json_view from_json") +{ + using namespace view_from_json; // NOLINT(google-build-using-namespace) + + SECTION("which from_json() functions read views") + { + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "macro types read views"); + static_assert(reads_view::value, "enum macro types read views"); + static_assert(reads_view::value, "enum macro types read views"); + static_assert(reads_view::value, "constrained from_json() reads views"); + static_assert(reads_view::value, "editable views as well"); + static_assert(reads_view::value, "views of ordered_json as well"); + static_assert(!reads_view::value, "from_json() for json"); + static_assert(!reads_view::value, "from_json() for json"); + static_assert(!reads_view::value, "unconstrained from_json()"); + + static_assert(nlohmann::is_json_view::value, "is_json_view"); + static_assert(nlohmann::is_json_view::value, "is_json_view"); + static_assert(!nlohmann::is_json_view::value, "is_json_view"); + static_assert(!nlohmann::is_json_view::value, "is_json_view"); + static_assert(!nlohmann::is_json_view::value, "is_json_view"); + } + + SECTION("the library's own conversions are not called with a view (and the probe compiles for them)") + { + // these go through materialize() (vectors, maps with string keys, + // strings, and numbers are converted by the view itself) + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view>::value, "std types"); + static_assert(!reads_view::value, "std types"); + static_assert(!reads_view::value, "std types"); + static_assert(!reads_view::value, "std types"); + static_assert(!reads_view::value, "std types"); + static_assert(!reads_view::value, "std types"); + static_assert(!reads_view::value, "std types"); + static_assert(!reads_view::value, "std types"); +#ifdef JSON_HAS_CPP_17 + static_assert(!reads_view>::value, "std types"); +#endif + CHECK(true); + } + + SECTION("macro types") + { + const json_document d = json_document::parse(R"({"name": "s", "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], + "center": {"x": 5, "y": 6}, "named": {"a": {"x": 7, "y": 8}}})"); + const shape s = d.root().get(); + CHECK(s.name == "s"); + CHECK(s.points.size() == 2); + CHECK(s.points[1].y == 4); + CHECK(s.center.x == 5); + CHECK(s.named.at("a").y == 8); + + point p; + d.root()["center"].get_to(p); + CHECK(p.x == 5); + CHECK(p.y == 6); + + static_cast(d.root()["center"].get()); + } + + SECTION("defaults and names") + { + const json_document d = json_document::parse(R"({"a": 5, "Value": 3, "Text": "t"})"); + const with_defaults w = d.root().get(); + CHECK(w.a == 5); + CHECK(w.b == "default"); + CHECK(w.c == std::vector {1, 2}); + const json_document null_document = json_document::parse("null"); + CHECK(null_document.root().get().a == 1); + const with_names n = d.root().get(); + CHECK(n.value == 3); + CHECK(n.text == "t"); + } + + SECTION("enums") + { + const json_document d = json_document::parse(R"(["green", null, "purple", 1, "red"])"); + CHECK(d.root()[0].get() == color::green); + CHECK(d.root()[1].get() == color::blue); + CHECK(d.root()[2].get() == color::red); // unknown values map to the first entry + CHECK(d.root()[3].get() == strict_color::green); + CHECK(d.root()[4].get() == strict_color::red); +#if !defined(JSON_NOEXCEPTION) + const json j = d.root().materialize(); + CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\""); +#if !JSON_DIAGNOSTICS + CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == exception_of_call([&] { static_cast(j[2].get()); })); +#endif +#endif + } + + SECTION("derived types") + { + const json_document d = json_document::parse(R"({"id": 7, "c": "green", "extra": 3})"); + const derived x = d.root().get(); + CHECK(x.id == 7); + CHECK(x.c == color::green); + const derived_with_defaults y = d.root().get(); + CHECK(y.id == 7); + CHECK(y.label == "none"); + const derived_from_legacy z = d.root().get(); + CHECK(z.id == 7); + CHECK(z.extra == 3); + } + + SECTION("other from_json() functions get the value of materialize()") + { + const json_document d = json_document::parse(R"({"v": 4})"); + CHECK(d.root().get().v == 4); + CHECK(d.root().get().v == 4); + CHECK(d.root().get().v == 4); + legacy l; + d.root().get_to(l); + CHECK(l.v == 4); + } + + SECTION("duplicate keys: members are looked up in the view, where the first one wins") + { + const json_document d = json_document::parse(R"({"x": 1, "y": 2, "x": 3})"); + CHECK(d.root().get().x == 1); + // materialize() keeps the last value, like parse() + CHECK(d.root().materialize().get().x == 3); + } + + SECTION("editable documents and ordered_json") + { + json_editable_document e = json_editable_document::parse(R"({"x": 1, "y": 2})"); + e.set(e.root(), "x", 10); + CHECK(e.root().get().x == 10); + const ordered_json_document o = ordered_json_document::parse(R"({"name": "o", "points": [], "center": {"x": 1, "y": 2}, "named": {}})"); + CHECK(o.root().get().center.y == 2); + } + +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS + SECTION("errors are those of the same conversion of a json value") + { + // (with JSON_DIAGNOSTICS, those of json name the path of the value) + const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])"); + const json j = d.root().materialize(); + for (std::size_t i = 0; i < 3; ++i) + { + CAPTURE(i) + CHECK(exception_of_call([&] { static_cast(d.root()[i].get()); }) == exception_of_call([&] { static_cast(j[i].get()); })); + } + CHECK(exception_of_call([&] { static_cast(d.root()[3].get()); }) == exception_of_call([&] { static_cast(j[3].get()); })); + } +#endif +} From 9e67bfa11503eeb8b4a39ff83189bd92c5a91702 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 20:13:31 +0100 Subject: [PATCH 17/63] json_view: the library's std conversions read views directly (#5923) * json_view: the library's std conversions read views directly The library's from_json() overloads (containers, maps, std::array, C arrays, pairs, tuples, optional, valarray, forward_list, enums, binary, paths, and scalars) accept views: their traits use the view's basic_json type, and the few accesses that differ (get_ptr) go through small helpers. from_json() of a view now finds them; only the probe still looks up by ADL only. get() of a view thus converts these types without materialize(), with the same values and exceptions; from an object, a repeated key keeps the last value, as parse() does. The view keeps its direct paths for vectors of the types it converts directly (faster than from_json()) and for containers of views; its other vector and map paths are replaced by from_json(). For basic_json, std::valarray and std::forward_list are read in one pass (40 % and 3.5 % fewer instructions); all other conversions are unchanged. Signed-off-by: Niels Lohmann * json_view: enable_if_not_view_t instead of '!' in the from_json() templates astyle spaces out the template argument list when it contains a negation; the alias spells the condition with negation<> and leaves the lines alone. Signed-off-by: Niels Lohmann * json_view: compact the template parameter lists astyle had spaced out Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json_view/get.md | 33 +- .../mkdocs/docs/api/basic_json_view/get_to.md | 3 +- .../nlohmann/detail/conversions/from_json.hpp | 284 +++++++++++----- include/nlohmann/detail/exceptions.hpp | 16 +- include/nlohmann/detail/macro_scope.hpp | 2 +- include/nlohmann/detail/view/value.hpp | 12 +- include/nlohmann/json_view.hpp | 19 +- single_include/nlohmann/json.hpp | 302 +++++++++++++----- single_include/nlohmann/json_view.hpp | 31 +- tests/src/unit-json_view_from_json.cpp | 146 +++++++-- 10 files changed, 618 insertions(+), 230 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_view/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md index 40fd1033a..cd0a98bff 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -19,16 +19,19 @@ For the types below, the conversion works directly on the flat index -- no `Basi document's own buffer of decoded strings (see [`get_string()`](get_string.md)) - `BasicJsonType` -- equivalent to [`materialize()`](materialize.md) - `basic_json_view` -- returns `#!cpp *this` -- `#!cpp std::vector` -- from an array, element by element, each converted with `#!cpp get()`; `#!cpp - std::vector` keeps a view of every element instead of a value -- `#!cpp std::map` and `#!cpp std::unordered_map`, if `K` is constructible from a `#!cpp - (const char*, std::size_t)` pair -- from an object, member by member, each value converted with `#!cpp get()`; with a repeated - key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and - [`materialize()`](materialize.md)) does; `#!cpp std::map` keeps views of the members - instead of values +- `#!cpp std::vector` of the types above -- from an array, element by element, each converted with + `#!cpp get()`; `#!cpp std::vector` keeps a view of every element instead of a value +- `#!cpp std::map` and `#!cpp std::unordered_map`, if `K` is + constructible from a `#!cpp (const char*, std::size_t)` pair -- views of the members of an object; with a repeated + key, the view of the *last* member is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) keeps its value -A default-constructible `T` whose `from_json()` accepts views reads the view directly as well, member by member, without -building a `BasicJsonType` value. This covers the types of the +A default-constructible `T` whose `from_json()` accepts views reads the view directly as well, without building a +`BasicJsonType` value. This covers the library's own conversions of the standard library types -- all other +`#!cpp std::vector`s, `#!cpp std::map`/`#!cpp std::unordered_map` (also with other key types), `#!cpp std::list`, +`#!cpp std::deque`, `#!cpp std::set`, `#!cpp std::forward_list`, `#!cpp std::valarray`, `#!cpp std::array`, C arrays, +`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, enumerations, `#!cpp std::filesystem::path`, ... -- +which give the same values as `#!cpp materialize().get()` (from an object, a repeated key keeps the *last* value, +as [`BasicJsonType::parse()`](../basic_json/parse.md) does), as well as the types of the [`NLOHMANN_DEFINE_TYPE_*`](../macros/nlohmann_define_type_intrusive.md) and [`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../macros/nlohmann_define_derived_type.md) macros and the enumerations of [`NLOHMANN_JSON_SERIALIZE_ENUM`](../macros/nlohmann_json_serialize_enum.md), and any `from_json()` constrained to @@ -44,8 +47,8 @@ T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not keeps it: for `#!cpp {"x":1,"x":2}`, `#!cpp get()` reads `#!cpp x == 1`. See [Duplicate keys](../../features/json_view.md#duplicate-keys). -Every other `T` -- `#!cpp std::list`, `#!cpp std::pair`, `#!cpp std::array`, user types whose `from_json()` is -written for `BasicJsonType` only, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` +Every other `T` -- user types whose `from_json()` is written for `BasicJsonType` only, types without a default +constructor, `#!cpp std::tuple`s holding references, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` value first (as [`BasicJsonType::parse()`](../basic_json/parse.md) would), and converted from there exactly as [`BasicJsonType::get()`](../basic_json/get.md) would convert it. @@ -70,10 +73,10 @@ Strong guarantee: there are no changes to the view or the document it refers to. throws for the same JSON type and `T`. - For `#!cpp std::vector`: throws `type_error.302` if the value is not an array; otherwise, whatever converting an element to `U` throws. -- For `#!cpp std::map`/`#!cpp std::unordered_map`: throws `type_error.302` if the value is not an object; otherwise, - whatever converting a member to the mapped type throws. -- For a `T` whose `from_json()` reads the view: whatever that `from_json()` throws; for the macros, the same - exceptions with the same messages as [`BasicJsonType::get()`](../basic_json/get.md) of the same value. +- For `#!cpp std::map`/`#!cpp std::unordered_map` of views: throws `type_error.302` if the value is not an object. +- For a `T` whose `from_json()` reads the view: whatever that `from_json()` throws; for the library's conversions and + the macros, the same exceptions with the same messages as [`BasicJsonType::get()`](../basic_json/get.md) of the + same value. - For every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws -- typically `type_error.302`, or whatever a user-provided `from_json()` throws. diff --git a/docs/mkdocs/docs/api/basic_json_view/get_to.md b/docs/mkdocs/docs/api/basic_json_view/get_to.md index 4d2dd2fa5..d11e7fd1a 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get_to.md +++ b/docs/mkdocs/docs/api/basic_json_view/get_to.md @@ -9,8 +9,7 @@ Converts the value to `T` and stores it in `v`, in one of three ways: - For the types that [`get()`](get.md) converts directly -- arithmetic types, `#!cpp bool`, `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `#!cpp std::basic_string`, `BasicJsonType`, - `basic_json_view`, `#!cpp std::vector`, and `#!cpp std::map`/`#!cpp std::unordered_map` with string keys -- `v` is - assigned: `#!cpp v = get()`. + `basic_json_view`, vectors of these, and maps of views -- `v` is assigned: `#!cpp v = get()`. - For a type whose `from_json()` accepts views (see [`get()`](get.md)), that `from_json()` is called with the view and the existing `v`, without building a `BasicJsonType` value. - For every other type, the subtree is built into a `BasicJsonType` value and `#!cpp materialize().get_to(v)` is diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 696afc34b..8fb9d149b 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -45,6 +45,70 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +// The from_json() overloads below read basic_json values and the views of +// (BasicJsonType is then a view, and +// json_type_of_t its basic_json type). The helpers below are +// the accesses that differ between the two. + +// the enable_if parameter of the overloads that read basic_json values (written +// with negation<> because astyle spaces out a template argument list with '!') +template +using enable_if_not_view_t = enable_if_t>::value, int>; + +// the boolean of j, a boolean +template = 0> +typename BasicJsonType::boolean_t boolean_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +bool boolean_value(const View& v) +{ + return v.template get(); +} + +// the string of j, a string +template = 0> +const typename BasicJsonType::string_t& string_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::string_t string_value(const View& v) +{ + const auto s = v.get_string(); + return typename View::string_t(s.data(), s.size()); +} + +// the binary value of j, a binary value (never a view: a view of a JSON text +// holds no binary values) +template = 0> +const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) +{ + return {}; // LCOV_EXCL_LINE +} + +// the elements of j, an array, for a range-based for loop +template = 0> +const typename BasicJsonType::array_t& array_elements(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +View array_elements(const View& v) noexcept +{ + return v; // (a view is two pointers) +} + template inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { @@ -73,7 +137,7 @@ void from_json(const BasicJsonType& j, std::optional& opt) // read a number value of any of the three number types into val; // returns false (leaving val untouched) if j is not a number -template +template = 0> bool get_number_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) @@ -110,10 +174,22 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val) } } +// a view converts numbers itself (as basic_json does) +template::value, int> = 0> +bool get_number_value(const View& v, ArithmeticType& val) +{ + if (!v.is_number()) + { + return false; + } + val = v.template get(); + return true; +} + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value, + !std::is_same::boolean_t>::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { @@ -124,31 +200,31 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { throw_type_must_be("boolean", j); } - b = *j.template get_ptr(); + b = boolean_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template < typename BasicJsonType, typename StringType, enable_if_t < - std::is_assignable::value - && is_detected_exact::value - && !std::is_same::value + std::is_assignable::string_t>::value + && is_detected_exact::string_t::value_type, value_type_t, StringType>::value + && !std::is_same::string_t, StringType>::value && !is_json_ref::value, int > = 0 > inline void from_json(const BasicJsonType& j, StringType& s) { @@ -157,23 +233,23 @@ inline void from_json(const BasicJsonType& j, StringType& s) throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_float_t& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, number_unsigned_overload_t& val) +inline void from_json(const BasicJsonType& j, number_unsigned_overload_t>& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_integer_t& val) { get_arithmetic_value(j, val); } @@ -185,8 +261,8 @@ inline void from_json(const BasicJsonType& j, EnumType& e) { using underlying_type = typename std::underlying_type::type; // get_arithmetic_value() does not accept boolean_t; read the number that to_json() wrote instead - using value_type = typename std::conditional::value, - typename BasicJsonType::number_unsigned_t, underlying_type>::type; + using value_type = typename std::conditional::boolean_t>::value, + typename json_type_of_t::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); e = static_cast(bool_aware_static_cast(val)); @@ -195,7 +271,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -203,16 +279,16 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l throw_type_must_be("array", j); } l.clear(); - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) + auto pos = l.before_begin(); + for (const auto& elem : array_elements(j)) { - return i.template get(); - }); + pos = l.insert_after(pos, elem.template get()); + } } // valarray doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -220,11 +296,11 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) throw_type_must_be("array", j); } l.resize(j.size()); - std::transform(j.begin(), j.end(), std::begin(l), - [](const BasicJsonType & elem) + std::size_t i = 0; + for (const auto& elem : array_elements(j)) { - return elem.template get(); - }); + l[i++] = elem.template get(); + } } // element is not itself a C array: read it directly @@ -252,7 +328,8 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines from_json_c_array_element(j, arr); } -template +// (for a view, array_t is read element by element below) +template = 0> inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); @@ -261,7 +338,7 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType: // fill a std::array from the JSON array j: the size is checked once and the // elements are read in order, so a short array converts the elements it has and // then throws the out_of_range error at() would throw for the first missing one -template +template = 0> void from_json_std_array(const BasicJsonType& j, std::array& arr) { const auto& inner_array = *j.template get_ptr(); @@ -276,6 +353,21 @@ void from_json_std_array(const BasicJsonType& j, std::array& arr) } } +// (a view has no random access to the elements: they are read in order) +template::value, int> = 0> +void from_json_std_array(const View& v, std::array& arr) +{ + std::size_t size = 0; + for (auto it = v.begin(); size < N && it != v.end(); ++it) + { + arr[size++] = (*it).template get(); + } + if (JSON_HEDLEY_UNLIKELY(size < N)) + { + static_cast(v.at(size)); + } +} + template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) @@ -324,9 +416,8 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p void()) { ConstructibleArrayType ret; - const auto* inner_array = j.template get_ptr(); - reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); - for (const auto& elem : *inner_array) + reserve_if_possible(ret, j.size(), priority_tag<1> {}); + for (const auto& elem : array_elements(j)) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType @@ -337,11 +428,11 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < - is_constructible_array_type::value&& - !is_constructible_object_type::value&& - !is_constructible_string_type::value&& - !std::is_same::value&& - !is_compatible_binary_type::value&& + is_constructible_array_type, ConstructibleArrayType>::value&& + !is_constructible_object_type, ConstructibleArrayType>::value&& + !is_constructible_string_type, ConstructibleArrayType>::value&& + !std::is_same::binary_t>::value&& + !is_compatible_binary_type, ConstructibleArrayType>::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -408,24 +499,24 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { throw_type_must_be("binary", j); } - bin = *j.template get_ptr(); + bin = binary_value(j); } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < is_compatible_binary_type::value, + enable_if_t < is_compatible_binary_type, CompatibleArrayType>::value, int > = 0 > inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) { if (j.is_binary()) { - bin = static_cast(*j.template get_ptr()); + bin = static_cast(binary_value(j)); } else if (j.is_array()) { @@ -454,8 +545,47 @@ void from_json_object_emplace(ConstructibleObjectType& obj, Key&& key, Value&& v obj.emplace(std::forward(key), std::forward(value)); } +// a member of a view's object: a later member with the same key replaces an +// earlier one, as parse() keeps the last value of a repeated key (containers +// without operator[] keep the first) +template +auto from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<1> /*unused*/) +-> decltype(obj[std::forward(key)] = std::forward(value), void()) +{ + obj[std::forward(key)] = std::forward(value); +} + +template +void from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<0> /*unused*/) +{ + obj.emplace(std::forward(key), std::forward(value)); +} + +template = 0> +void from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& ret) +{ + const auto* inner_object = j.template get_ptr(); + reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); + for (const auto& p : *inner_object) + { + from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); + } +} + +template::value, int> = 0> +void from_json_object_impl(const View& v, ConstructibleObjectType& ret) +{ + using string_t = typename View::basic_json_type::object_t::key_type; + reserve_if_possible(ret, v.size(), priority_tag<1> {}); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(ret, string_t(key.data(), key.size()), item.value().template get(), priority_tag<1> {}); + } +} + template::value, int> = 0> + enable_if_t, ConstructibleObjectType>::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) @@ -464,12 +594,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } ConstructibleObjectType ret; - const auto* inner_object = j.template get_ptr(); - reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); - for (const auto& p : *inner_object) - { - from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); - } + from_json_object_impl(j, ret); obj = std::move(ret); } @@ -478,10 +603,10 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value, + !std::is_same::number_unsigned_t>::value&& + !std::is_same::number_integer_t>::value&& + !std::is_same::number_float_t>::value&& + !std::is_same::boolean_t>::value, int > = 0 > inline void from_json(const BasicJsonType& j, ArithmeticType& val) { @@ -495,7 +620,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) { throw_type_must_be("number", j); } - val = static_cast(*j.template get_ptr()); + val = static_cast(boolean_value(j)); } template @@ -505,7 +630,7 @@ detail::uncvref_t from_json_tuple_get_impl(BasicJsonType&& j, detail::iden } template::value, int> = 0> + detail::enable_if_t>, is_compatible_reference_type>::value, int> = 0> Type from_json_tuple_get_impl(BasicJsonType && j, detail::identity_tag /*unused*/, detail::priority_tag<1> /*unused*/) { return std::forward(j).template get_ref(); @@ -549,7 +674,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, p template std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(conjunction>, is_compatible_reference_type>...>::value, + static_assert(conjunction>, conjunction, is_compatible_reference_type>>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } @@ -595,7 +720,7 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) // read a map with enum keys from an object, using the enum's own from_json for // the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written // with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS -template +template = 0> inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/) { if (!j.is_object()) @@ -610,6 +735,24 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::tru return true; } +template::value, int> = 0> +inline bool from_json_enum_keyed_object(const View& v, Map& m, std::true_type /*key is enum*/) +{ + using json_t = typename View::basic_json_type; + if (!v.is_object()) + { + return false; + } + m.clear(); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(m, json_t(typename json_t::string_t(key.data(), key.size())).template get(), + item.value().template get(), priority_tag<1> {}); + } + return true; +} + template inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/) { @@ -618,7 +761,7 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -631,7 +774,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -655,7 +798,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { throw_type_must_be("string", j); } - const auto& s = *j.template get_ptr(); + const auto& s = string_value(j); // Checking for C++20 standard or later can be insufficient in case the // library support for char8_t is either incomplete or was disabled // altogether. Use the __cpp_lib_char8_t feature test instead. @@ -694,15 +837,14 @@ struct fn }; } // namespace from_json_adl -// the sources whose from_json() is only looked up by ADL: views (the library's -// overloads are written for basic_json) and the probe +// the sources whose from_json() is only looked up by ADL: the probe template -struct is_adl_only_from_json_source : disjunction, std::is_same> {}; +struct is_adl_only_from_json_source : std::is_same {}; struct from_json_fn { - template < typename BasicJsonType, typename T, - enable_if_t < !is_adl_only_from_json_source::value, int > = 0 > + template>::value, int> = 0> auto operator()(const BasicJsonType& j, T && val) const noexcept(noexcept(from_json(j, std::forward(val)))) -> decltype(from_json(j, std::forward(val))) @@ -710,8 +852,8 @@ struct from_json_fn return from_json(j, std::forward(val)); } - template < typename SourceType, typename T, - enable_if_t < is_adl_only_from_json_source::value, int > = 0 > + template::value, int> = 0> auto operator()(const SourceType& j, T && val) const noexcept(noexcept(from_json_adl::fn {}(j, std::forward(val)))) -> decltype(from_json_adl::fn {}(j, std::forward(val))) @@ -748,20 +890,6 @@ void from_json_base(const View& v, T& base) v.get_to(base); } -// the context of an exception thrown by a from_json() of the macros: the value -// itself for basic_json (for JSON_DIAGNOSTICS), none for a view -template::value, int> = 0> -const BasicJsonType * from_json_context(const BasicJsonType& j) noexcept -{ - return &j; -} - -template::value, int> = 0> -std::nullptr_t from_json_context(const View& /*unused*/) noexcept -{ - return nullptr; -} - // a value as an exception message shows it (invalid UTF-8 replaced; the // strings of a view are always valid UTF-8) template::value, int> = 0> diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index d47ed6f17..ee2c0feec 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -417,6 +417,20 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +// the context of an exception about j: j itself for a basic_json value (whose +// path JSON_DIAGNOSTICS shows), none for a view of +template::value, int> = 0> +const BasicJsonType * exception_context(const BasicJsonType& j) noexcept +{ + return &j; +} + +template::value, int> = 0> +std::nullptr_t exception_context(const View& /*unused*/) noexcept +{ + return nullptr; +} + /*! @brief throws because @a j does not have the type a conversion expects @param[in] expected the expected type(s), e.g. "array" or "binary or array" @@ -428,7 +442,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); - JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 4f89f3463..65394b504 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -347,7 +347,7 @@ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::from_json_context(j))); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They diff --git a/include/nlohmann/detail/view/value.hpp b/include/nlohmann/detail/view/value.hpp index 945071705..4b54a7785 100644 --- a/include/nlohmann/detail/view/value.hpp +++ b/include/nlohmann/detail/view/value.hpp @@ -130,14 +130,16 @@ struct is_direct_value : std::integral_constant < bool, template struct is_direct_value, BasicJsonType, View> : std::true_type {}; +// (vectors of these, which are faster than through from_json(), and containers +// of views; other containers are read by the library's from_json()) template -struct is_direct_value, BasicJsonType, View> : std::true_type {}; +struct is_direct_value, BasicJsonType, View> : is_direct_value {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; } // namespace view } // namespace detail diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index ebcb13733..071c14904 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -919,22 +919,25 @@ class basic_json_view return *this; } - template + /// vectors of the types converted directly (faster than the library's + /// from_json()) and containers of views (no from_json() reads them: a view + /// is no value of BasicJsonType); other containers are read by from_json() + template::value, int> = 0> std::vector get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { return detail::view::vector_value(*this); } - template::value, int>::type = 0> - std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } - template::value, int>::type = 0> - std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } /// whether the from_json() of T's serializer reads views (see @@ -949,7 +952,7 @@ class basic_json_view ? 2 : static_cast(reads_view::value) >; /// types whose from_json() reads views, without materialize() - template < typename T, typename std::enable_if < reads_view::value && std::is_default_constructible::value, int >::type = 0 > + template, std::is_default_constructible>::value, int> = 0> T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<1> /*unused*/) const { auto v = T(); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index b0492ec78..0c4753d41 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2720,7 +2720,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::from_json_context(j))); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -5914,6 +5914,20 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +// the context of an exception about j: j itself for a basic_json value (whose +// path JSON_DIAGNOSTICS shows), none for a view of +template::value, int> = 0> +const BasicJsonType * exception_context(const BasicJsonType& j) noexcept +{ + return &j; +} + +template::value, int> = 0> +std::nullptr_t exception_context(const View& /*unused*/) noexcept +{ + return nullptr; +} + /*! @brief throws because @a j does not have the type a conversion expects @param[in] expected the expected type(s), e.g. "array" or "binary or array" @@ -5925,7 +5939,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); - JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! @@ -6154,6 +6168,70 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +// The from_json() overloads below read basic_json values and the views of +// (BasicJsonType is then a view, and +// json_type_of_t its basic_json type). The helpers below are +// the accesses that differ between the two. + +// the enable_if parameter of the overloads that read basic_json values (written +// with negation<> because astyle spaces out a template argument list with '!') +template +using enable_if_not_view_t = enable_if_t>::value, int>; + +// the boolean of j, a boolean +template = 0> +typename BasicJsonType::boolean_t boolean_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +bool boolean_value(const View& v) +{ + return v.template get(); +} + +// the string of j, a string +template = 0> +const typename BasicJsonType::string_t& string_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::string_t string_value(const View& v) +{ + const auto s = v.get_string(); + return typename View::string_t(s.data(), s.size()); +} + +// the binary value of j, a binary value (never a view: a view of a JSON text +// holds no binary values) +template = 0> +const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) +{ + return {}; // LCOV_EXCL_LINE +} + +// the elements of j, an array, for a range-based for loop +template = 0> +const typename BasicJsonType::array_t& array_elements(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +View array_elements(const View& v) noexcept +{ + return v; // (a view is two pointers) +} + template inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { @@ -6182,7 +6260,7 @@ void from_json(const BasicJsonType& j, std::optional& opt) // read a number value of any of the three number types into val; // returns false (leaving val untouched) if j is not a number -template +template = 0> bool get_number_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) @@ -6219,10 +6297,22 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val) } } +// a view converts numbers itself (as basic_json does) +template::value, int> = 0> +bool get_number_value(const View& v, ArithmeticType& val) +{ + if (!v.is_number()) + { + return false; + } + val = v.template get(); + return true; +} + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value, + !std::is_same::boolean_t>::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { @@ -6233,31 +6323,31 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { throw_type_must_be("boolean", j); } - b = *j.template get_ptr(); + b = boolean_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template < typename BasicJsonType, typename StringType, enable_if_t < - std::is_assignable::value - && is_detected_exact::value - && !std::is_same::value + std::is_assignable::string_t>::value + && is_detected_exact::string_t::value_type, value_type_t, StringType>::value + && !std::is_same::string_t, StringType>::value && !is_json_ref::value, int > = 0 > inline void from_json(const BasicJsonType& j, StringType& s) { @@ -6266,23 +6356,23 @@ inline void from_json(const BasicJsonType& j, StringType& s) throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_float_t& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, number_unsigned_overload_t& val) +inline void from_json(const BasicJsonType& j, number_unsigned_overload_t>& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_integer_t& val) { get_arithmetic_value(j, val); } @@ -6294,8 +6384,8 @@ inline void from_json(const BasicJsonType& j, EnumType& e) { using underlying_type = typename std::underlying_type::type; // get_arithmetic_value() does not accept boolean_t; read the number that to_json() wrote instead - using value_type = typename std::conditional::value, - typename BasicJsonType::number_unsigned_t, underlying_type>::type; + using value_type = typename std::conditional::boolean_t>::value, + typename json_type_of_t::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); e = static_cast(bool_aware_static_cast(val)); @@ -6304,7 +6394,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -6312,16 +6402,16 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l throw_type_must_be("array", j); } l.clear(); - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) + auto pos = l.before_begin(); + for (const auto& elem : array_elements(j)) { - return i.template get(); - }); + pos = l.insert_after(pos, elem.template get()); + } } // valarray doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -6329,11 +6419,11 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) throw_type_must_be("array", j); } l.resize(j.size()); - std::transform(j.begin(), j.end(), std::begin(l), - [](const BasicJsonType & elem) + std::size_t i = 0; + for (const auto& elem : array_elements(j)) { - return elem.template get(); - }); + l[i++] = elem.template get(); + } } // element is not itself a C array: read it directly @@ -6361,7 +6451,8 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines from_json_c_array_element(j, arr); } -template +// (for a view, array_t is read element by element below) +template = 0> inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); @@ -6370,7 +6461,7 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType: // fill a std::array from the JSON array j: the size is checked once and the // elements are read in order, so a short array converts the elements it has and // then throws the out_of_range error at() would throw for the first missing one -template +template = 0> void from_json_std_array(const BasicJsonType& j, std::array& arr) { const auto& inner_array = *j.template get_ptr(); @@ -6385,6 +6476,21 @@ void from_json_std_array(const BasicJsonType& j, std::array& arr) } } +// (a view has no random access to the elements: they are read in order) +template::value, int> = 0> +void from_json_std_array(const View& v, std::array& arr) +{ + std::size_t size = 0; + for (auto it = v.begin(); size < N && it != v.end(); ++it) + { + arr[size++] = (*it).template get(); + } + if (JSON_HEDLEY_UNLIKELY(size < N)) + { + static_cast(v.at(size)); + } +} + template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) @@ -6433,9 +6539,8 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p void()) { ConstructibleArrayType ret; - const auto* inner_array = j.template get_ptr(); - reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); - for (const auto& elem : *inner_array) + reserve_if_possible(ret, j.size(), priority_tag<1> {}); + for (const auto& elem : array_elements(j)) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType @@ -6446,11 +6551,11 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < - is_constructible_array_type::value&& - !is_constructible_object_type::value&& - !is_constructible_string_type::value&& - !std::is_same::value&& - !is_compatible_binary_type::value&& + is_constructible_array_type, ConstructibleArrayType>::value&& + !is_constructible_object_type, ConstructibleArrayType>::value&& + !is_constructible_string_type, ConstructibleArrayType>::value&& + !std::is_same::binary_t>::value&& + !is_compatible_binary_type, ConstructibleArrayType>::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -6517,24 +6622,24 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { throw_type_must_be("binary", j); } - bin = *j.template get_ptr(); + bin = binary_value(j); } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < is_compatible_binary_type::value, + enable_if_t < is_compatible_binary_type, CompatibleArrayType>::value, int > = 0 > inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) { if (j.is_binary()) { - bin = static_cast(*j.template get_ptr()); + bin = static_cast(binary_value(j)); } else if (j.is_array()) { @@ -6563,8 +6668,47 @@ void from_json_object_emplace(ConstructibleObjectType& obj, Key&& key, Value&& v obj.emplace(std::forward(key), std::forward(value)); } +// a member of a view's object: a later member with the same key replaces an +// earlier one, as parse() keeps the last value of a repeated key (containers +// without operator[] keep the first) +template +auto from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<1> /*unused*/) +-> decltype(obj[std::forward(key)] = std::forward(value), void()) +{ + obj[std::forward(key)] = std::forward(value); +} + +template +void from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<0> /*unused*/) +{ + obj.emplace(std::forward(key), std::forward(value)); +} + +template = 0> +void from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& ret) +{ + const auto* inner_object = j.template get_ptr(); + reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); + for (const auto& p : *inner_object) + { + from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); + } +} + +template::value, int> = 0> +void from_json_object_impl(const View& v, ConstructibleObjectType& ret) +{ + using string_t = typename View::basic_json_type::object_t::key_type; + reserve_if_possible(ret, v.size(), priority_tag<1> {}); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(ret, string_t(key.data(), key.size()), item.value().template get(), priority_tag<1> {}); + } +} + template::value, int> = 0> + enable_if_t, ConstructibleObjectType>::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) @@ -6573,12 +6717,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } ConstructibleObjectType ret; - const auto* inner_object = j.template get_ptr(); - reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); - for (const auto& p : *inner_object) - { - from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); - } + from_json_object_impl(j, ret); obj = std::move(ret); } @@ -6587,10 +6726,10 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value, + !std::is_same::number_unsigned_t>::value&& + !std::is_same::number_integer_t>::value&& + !std::is_same::number_float_t>::value&& + !std::is_same::boolean_t>::value, int > = 0 > inline void from_json(const BasicJsonType& j, ArithmeticType& val) { @@ -6604,7 +6743,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) { throw_type_must_be("number", j); } - val = static_cast(*j.template get_ptr()); + val = static_cast(boolean_value(j)); } template @@ -6614,7 +6753,7 @@ detail::uncvref_t from_json_tuple_get_impl(BasicJsonType&& j, detail::iden } template::value, int> = 0> + detail::enable_if_t>, is_compatible_reference_type>::value, int> = 0> Type from_json_tuple_get_impl(BasicJsonType && j, detail::identity_tag /*unused*/, detail::priority_tag<1> /*unused*/) { return std::forward(j).template get_ref(); @@ -6658,7 +6797,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, p template std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(conjunction>, is_compatible_reference_type>...>::value, + static_assert(conjunction>, conjunction, is_compatible_reference_type>>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } @@ -6704,7 +6843,7 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) // read a map with enum keys from an object, using the enum's own from_json for // the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written // with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS -template +template = 0> inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/) { if (!j.is_object()) @@ -6719,6 +6858,24 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::tru return true; } +template::value, int> = 0> +inline bool from_json_enum_keyed_object(const View& v, Map& m, std::true_type /*key is enum*/) +{ + using json_t = typename View::basic_json_type; + if (!v.is_object()) + { + return false; + } + m.clear(); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(m, json_t(typename json_t::string_t(key.data(), key.size())).template get(), + item.value().template get(), priority_tag<1> {}); + } + return true; +} + template inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/) { @@ -6727,7 +6884,7 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -6740,7 +6897,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -6764,7 +6921,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { throw_type_must_be("string", j); } - const auto& s = *j.template get_ptr(); + const auto& s = string_value(j); // Checking for C++20 standard or later can be insufficient in case the // library support for char8_t is either incomplete or was disabled // altogether. Use the __cpp_lib_char8_t feature test instead. @@ -6803,15 +6960,14 @@ struct fn }; } // namespace from_json_adl -// the sources whose from_json() is only looked up by ADL: views (the library's -// overloads are written for basic_json) and the probe +// the sources whose from_json() is only looked up by ADL: the probe template -struct is_adl_only_from_json_source : disjunction, std::is_same> {}; +struct is_adl_only_from_json_source : std::is_same {}; struct from_json_fn { - template < typename BasicJsonType, typename T, - enable_if_t < !is_adl_only_from_json_source::value, int > = 0 > + template>::value, int> = 0> auto operator()(const BasicJsonType& j, T && val) const noexcept(noexcept(from_json(j, std::forward(val)))) -> decltype(from_json(j, std::forward(val))) @@ -6819,8 +6975,8 @@ struct from_json_fn return from_json(j, std::forward(val)); } - template < typename SourceType, typename T, - enable_if_t < is_adl_only_from_json_source::value, int > = 0 > + template::value, int> = 0> auto operator()(const SourceType& j, T && val) const noexcept(noexcept(from_json_adl::fn {}(j, std::forward(val)))) -> decltype(from_json_adl::fn {}(j, std::forward(val))) @@ -6857,20 +7013,6 @@ void from_json_base(const View& v, T& base) v.get_to(base); } -// the context of an exception thrown by a from_json() of the macros: the value -// itself for basic_json (for JSON_DIAGNOSTICS), none for a view -template::value, int> = 0> -const BasicJsonType * from_json_context(const BasicJsonType& j) noexcept -{ - return &j; -} - -template::value, int> = 0> -std::nullptr_t from_json_context(const View& /*unused*/) noexcept -{ - return nullptr; -} - // a value as an exception message shows it (invalid UTF-8 replaced; the // strings of a view are always valid UTF-8) template::value, int> = 0> diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 44a7c62d9..e611ff540 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -7822,14 +7822,16 @@ struct is_direct_value : std::integral_constant < bool, template struct is_direct_value, BasicJsonType, View> : std::true_type {}; +// (vectors of these, which are faster than through from_json(), and containers +// of views; other containers are read by the library's from_json()) template -struct is_direct_value, BasicJsonType, View> : std::true_type {}; +struct is_direct_value, BasicJsonType, View> : is_direct_value {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; } // namespace view } // namespace detail @@ -8671,22 +8673,25 @@ class basic_json_view return *this; } - template + /// vectors of the types converted directly (faster than the library's + /// from_json()) and containers of views (no from_json() reads them: a view + /// is no value of BasicJsonType); other containers are read by from_json() + template::value, int> = 0> std::vector get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { return detail::view::vector_value(*this); } - template::value, int>::type = 0> - std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } - template::value, int>::type = 0> - std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } /// whether the from_json() of T's serializer reads views (see @@ -8701,7 +8706,7 @@ class basic_json_view ? 2 : static_cast(reads_view::value) >; /// types whose from_json() reads views, without materialize() - template < typename T, typename std::enable_if < reads_view::value && std::is_default_constructible::value, int >::type = 0 > + template, std::is_default_constructible>::value, int> = 0> T get_impl(detail::view::value_tag /*unused*/, detail::priority_tag<1> /*unused*/) const { auto v = T(); diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index f106b1fe0..ec296e5db 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -52,6 +52,11 @@ struct point int x = 0; int y = 0; NLOHMANN_DEFINE_TYPE_INTRUSIVE(point, x, y) + + friend bool operator==(const point& a, const point& b) + { + return a.x == b.x && a.y == b.y; + } }; struct shape @@ -184,6 +189,13 @@ namespace { template struct reads_view : nlohmann::detail::has_view_from_json, View, T> {}; + +// whether a member converts to the same T from the view and from the materialized value +template +bool same(const json_view& v, const json& j, const char* key) +{ + return v[key].get() == j[key].get(); +} } // namespace TEST_CASE("json_view from_json") @@ -215,39 +227,119 @@ TEST_CASE("json_view from_json") static_assert(!nlohmann::is_json_view::value, "is_json_view"); } - SECTION("the library's own conversions are not called with a view (and the probe compiles for them)") + SECTION("the library's own conversions read views") { - // these go through materialize() (vectors, maps with string keys, - // strings, and numbers are converted by the view itself) - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(!reads_view::value, "get() is materialize()"); + static_assert(reads_view::value, "std types"); #ifdef JSON_HAS_CPP_17 - static_assert(!reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); #endif CHECK(true); } + SECTION("std types: the same values as from the materialized value") + { + const json_document d = json_document::parse(R"({"ints": [3, 1, 2, 1], "strings": ["b", "a", "b"], "pairs": [[1, "x"], [2, "y"]], + "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], "object": {"b": 1, "a": 2}, "triple": [1, true, {"x": 5, "y": 6}], + "colors": {"green": 1, "red": 2}, "bytes": [1, 2, 255], "nested": [[1, 2], [3]], "number": 1.5, "flag": true, + "text": "hello", "nothing": null, "mixed": [1, 2.5, -3, 4], "flags": [true, false, true]})"); + const json j = d.root().materialize(); + const json_view v = d.root(); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "strings")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "mixed")); + CHECK((v["pairs"][0].get>() == j["pairs"][0].get>())); + CHECK(same>>(v, j, "pairs")); + CHECK(same>(v, j, "triple")); + CHECK(same>(v, j, "pairs")); + CHECK(same>(v, j, "pairs")); + CHECK(same>(v, j, "object")); + CHECK(same>(v, j, "object")); + CHECK(same>(v, j, "colors")); + CHECK(same>(v, j, "points")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "flags")); + CHECK(same>(v, j, "bytes")); + CHECK(same>>(v, j, "nested")); + CHECK(same>(v, j, "mixed")); + CHECK(same(v, j, "number")); + CHECK(same(v, j, "flag")); + CHECK(same(v, j, "text")); + CHECK(same(v, j, "nothing")); + CHECK(same(v, j, "object")); + const std::valarray va = v["ints"].get>(); + CHECK(va.size() == 4); + CHECK(va[0] == 3); + int c_array[2][2] = {{0, 0}, {0, 0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): the conversion to C arrays + v["nested"][0].get_to(c_array[0]); + CHECK(c_array[0][1] == 2); +#ifdef JSON_HAS_CPP_17 + CHECK(v["points"][0].get>()->y == 2); + CHECK(!v["nothing"].get>().has_value()); +#endif + // containers of views keep views of the elements + const std::vector views = v["points"].get>(); + CHECK(views.size() == 2); + CHECK(views[1]["x"].get() == 3); + CHECK(v["object"].get>().at("a").get() == 2); + } + + SECTION("std types: duplicate keys keep the last value, as from the materialized value") + { + const json_document d = json_document::parse(R"({"a": 1, "b": 2, "a": 3})"); + CHECK((d.root().get>() == d.root().materialize().get>())); + CHECK(d.root().get>().at("a") == 3); + CHECK(d.root().get>().at("a") == 3); + } + +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS + SECTION("std types: the same errors as from the materialized value") + { + const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])"); + const json j = d.root().materialize(); + for (std::size_t i = 0; i < 6; ++i) + { + CAPTURE(i) + const json_view v = d.root()[i]; + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get()); }) == exception_of_call([&] { static_cast(j[i].get()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + } + } +#endif + SECTION("macro types") { const json_document d = json_document::parse(R"({"name": "s", "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], From 94fe31eb9b94debcae52bc0d493a2232c11dbeaf Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 26 Oct 2026 20:13:32 +0100 Subject: [PATCH 18/63] docs: writing generic code for json_view (#5924) * docs: writing generic code for json_view A "Writing generic code" section on the json_view page: why a function taking json_view accepts no other view, the three signatures for editable, read-only, or either kind of view (passed by value), why the modes do not convert, is_json_view, and how a from_json() reads views (constrained to json values and views; the macros and the library's conversions do it already) with what differs then (duplicate keys, JSON_DIAGNOSTICS paths). Signed-off-by: Niels Lohmann * docs: name the documents the generic view function accepts Signed-off-by: Niels Lohmann * docs: views of read-only documents convert to editable views Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json_view/get.md | 2 +- docs/mkdocs/docs/api/is_json_view.md | 1 + docs/mkdocs/docs/api/view_mode.md | 3 +- docs/mkdocs/docs/features/json_view.md | 100 ++++++++++++++++++-- 4 files changed, 98 insertions(+), 8 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_view/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md index cd0a98bff..191fabdf5 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -38,7 +38,7 @@ as [`BasicJsonType::parse()`](../basic_json/parse.md) does), as well as the type accept views, e.g. with [`is_json_view`](../is_json_view.md). A `from_json()` that also accepts types that are no JSON values at all, such as the unconstrained `#!cpp template void from_json(const BasicJsonType&, T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not called with a view: it may use -`BasicJsonType` functions a view does not have. See [json_view](../../features/json_view.md). +`BasicJsonType` functions a view does not have. See [Writing generic code](../../features/json_view.md#writing-generic-code). !!! warning "Duplicate keys" diff --git a/docs/mkdocs/docs/api/is_json_view.md b/docs/mkdocs/docs/api/is_json_view.md index 53bd8ec3a..6153adf30 100644 --- a/docs/mkdocs/docs/api/is_json_view.md +++ b/docs/mkdocs/docs/api/is_json_view.md @@ -49,6 +49,7 @@ view class. - [basic_json_view](basic_json_view/index.md) - a handle to one value of a document - [get](basic_json_view/get.md) - which conversions read the view directly - [view_mode](view_mode.md) - read-only or editable +- [Writing generic code](../features/json_view.md#writing-generic-code) - functions and `from_json()` for views ## Version history diff --git a/docs/mkdocs/docs/api/view_mode.md b/docs/mkdocs/docs/api/view_mode.md index c0339b5fb..c754c7ed4 100644 --- a/docs/mkdocs/docs/api/view_mode.md +++ b/docs/mkdocs/docs/api/view_mode.md @@ -50,7 +50,7 @@ void f(basic_json_view v); template void f(basic_json_view v); -// either +// views of either document type template void f(basic_json_view v); ``` @@ -82,6 +82,7 @@ nor a [`json_editable_view`](json_editable_view.md). Views of the two modes comp - [basic_json_view](basic_json_view/index.md) - a handle to one value of a document - [json_editable_document](json_editable_document.md) - an editable document of `json` - [(constructor)](basic_json_view/basic_json_view.md) - the conversion from a read-only to an editable view +- [Writing generic code](../features/json_view.md#writing-generic-code) - functions that accept views ## Version history diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 7de4a07cd..bef6259de 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -214,14 +214,15 @@ number of distinct keys, or reject duplicates when iterating. ## Getting values out without copying -[`get()`](../api/basic_json_view/get.md) converts many `T` directly from the flat index, without ever building a +[`get()`](../api/basic_json_view/get.md) converts most `T` directly from the flat index, without ever building a `basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`, -`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json`/`ordered_json` (via -[`materialize()`](../api/basic_json_view/materialize.md)), `basic_json_view` itself, `#!cpp std::vector`, and -`#!cpp std::map`/`#!cpp std::unordered_map` with string-like keys. Every other type -- `#!cpp std::list`, -`#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a `from_json()` -- goes through +`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json_view` itself, the standard containers +and other types the library converts (`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::list`, `#!cpp std::array`, +`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, ...), and the types of the `NLOHMANN_DEFINE_TYPE_*` and +`NLOHMANN_JSON_SERIALIZE_ENUM` macros. Types whose `from_json()` is written for `basic_json` only go through [`materialize()`](../api/basic_json_view/materialize.md)`.get()` instead: the subtree is built into a real -`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there. +`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there. See +[Writing generic code](#writing-generic-code) for how to write a `from_json()` that reads views. Two conversions never copy at all: @@ -236,6 +237,93 @@ Two conversions never copy at all: Both results are only valid as long as the view -- and, for a string with no escapes, the borrowed source text -- is. +## Writing generic code + +A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md): +[`json_view`](../api/json_view.md) is `#!cpp basic_json_view`, and +[`json_editable_view`](../api/json_editable_view.md) is `#!cpp basic_json_view`. These are +different types. The only conversion between them turns a view of a read-only document into a view of an editable +document of the same `BasicJsonType`, so a function written for one shortcut accepts few other views: + +```cpp +void print_names(json_view v); // accepts neither an ordered_json_view nor a json_editable_view +void print_names(json_editable_view v); // also accepts a json_view, but no ordered_json_view +``` + +This is the same pitfall as a `to_json`/`from_json` written for `json` instead of any `BasicJsonType`, which then does +not work for `ordered_json`. The shortcuts are for declaring documents and views; a function that takes a view names +the views it accepts with a template: + +```cpp +// views of editable documents only +template +void f(basic_json_view v); + +// views of read-only documents only +template +void f(basic_json_view v); + +// views of either document type +template +void f(basic_json_view v); +``` + +Views are passed by value: they are two pointers, cheap to copy, like a `#!cpp std::string_view`. A `const&` works as +well, but a non-`const` reference does not bind to the views that [`root()`](../api/basic_json_document/root.md), +[`operator[]`](../api/basic_json_view/operator%5B%5D.md), and [`at()`](../api/basic_json_view/at.md) return, so +`#!cpp f(doc.root())` would not compile. + +There is no conversion from an editable view to a read-only one, by design: the mode decides how a view walks the +index. A read-only view reads a container in place, while a view of an editable document follows the links that edits +leave behind, so a read-only view of an edited document would read stale values. The other direction is safe: a +read-only document has no edits, so a view of it [converts](../api/basic_json_view/basic_json_view.md) to an +editable view, which reads it like an editable document before its first edit. A template deduces the mode without +conversions, so the template forms above still accept only the modes they name. Views of both modes compare with +[`operator==`](../api/basic_json_view/operator_eq.md). + +[`is_json_view`](../api/is_json_view.md) tells views from other types, for constraints that cannot be written as a +`basic_json_view<...>` parameter. + +### A `from_json()` that reads views + +[`get()`](../api/basic_json_view/get.md) on a view calls `T`'s `from_json()` with the view itself if that +`from_json()` accepts views, and with [`materialize()`](../api/basic_json_view/materialize.md) otherwise. The +`from_json()` functions of the [`NLOHMANN_DEFINE_TYPE_*`](../api/macros/nlohmann_define_type_intrusive.md), +[`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../api/macros/nlohmann_define_derived_type.md), and +[`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md) macros, and those of the library +for the standard types, accept views, so these types need nothing else. For decoding the statuses of +twitter.json into structs, reading the view is about 5 times faster than `#!cpp materialize().get()`. + +A hand-written `from_json()` reads views if it is constrained to `json` values and views: + +```cpp +template < typename J, typename std::enable_if < + nlohmann::is_json_view::value || std::is_same::value, int >::type = 0 > +void from_json(const J& j, person& p) +{ + p.name = j.at("name").template get(); + p.age = j.at("age").template get(); +} +``` + +Its body may only use what views and `basic_json` have in common: the type checks, `at()`, `operator[]`, `contains()`, +`value()`, `size()`, iteration and `items()`, and `get()`/`get_to()`. An unconstrained +`#!cpp template void from_json(const BasicJsonType& j, person& p)`, as in +[arbitrary type conversions](arbitrary_types.md), is never called with a view, as it might use `basic_json` functions +a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is +converted through `materialize()` as well. + +Two things differ when a `from_json()` reads the view: + +- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member + with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get()` would see the last one. + Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as + [`parse()`](../api/basic_json/parse.md) does. +- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the + value. + +`to_json()` is unaffected: it writes `basic_json` values, never views. + ## Writing a view back [`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a From 21ec9bd801584fce68882c3e9e4a973bb0710e9e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 07:22:49 +0100 Subject: [PATCH 19/63] json_view: fewer allocations when parsing small documents (#5915) - Size the buffer of decoded strings on its first growth from the input (a quarter of it plus 16 bytes, at most 4 KiB) instead of 272 bytes, so that a document with escapes does not grow it twice, with a zero-filled copy each time. - Allocate the node index of documents with an estimate of up to 1024 nodes (was 256) together with the header, which saves one allocation. When the estimate is too low (dense numbers), that block stays unused: at most 16 KiB instead of 4 KiB. shrink_to_fit() cannot release it separately; the documentation says so. Fresh parse of a 2.5 KB minified tweet: 993 to 958 ns (-3.5 %), traverse and select -3 %, with no row consistently slower (Apple M1 Max, 30 rounds, three runs). Dense numeric documents parse about 9 % faster and keep up to 16 KiB more memory. Signed-off-by: Niels Lohmann --- .../api/basic_json_document/shrink_to_fit.md | 4 ++ include/nlohmann/detail/view/builder.hpp | 10 +++- .../nlohmann/detail/view/document_data.hpp | 9 ++- single_include/nlohmann/json_view.hpp | 19 +++++-- tests/src/unit-json_view.cpp | 55 +++++++++++++++++++ 5 files changed, 89 insertions(+), 8 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md b/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md index f8f8a4312..128e7f996 100644 --- a/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md +++ b/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md @@ -23,6 +23,10 @@ Linear in [`node_count()`](node_count.md) plus the length of the decoded strings ## Notes +The node index of a small document (an estimate of up to 1024 nodes, which is up to about 4 KiB of minified text) is +allocated together with the document's header, so that parsing needs one allocation. That part cannot be released +separately: `shrink_to_fit()` only trims a node index that has its own allocation, and the decoded strings. + !!! warning "Invalidates views" Unlike moving the document, `shrink_to_fit()` **invalidates every view taken from this document before the diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index f7b560f50..a0446a1e1 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -543,7 +543,15 @@ run_found: NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n) { const std::size_t used = arena_used(); - m_doc.arena.resize((std::max)(m_doc.arena.size() * 2, used + n + 256)); + std::size_t wanted = used + n + 256; + if (m_doc.arena.empty()) + { + // the first growth: a decoded string is never longer than its source, + // so a fraction of the input (capped, as the buffer is zero-filled) + // covers the escapes of a typical document without regrowing it + wanted = (std::max)(wanted, (std::min)((static_cast(m_end - m_begin) / 4) + 16, std::size_t{4096})); + } + m_doc.arena.resize((std::max)(m_doc.arena.size() * 2, wanted)); m_arena_write = m_doc.arena.data() + used; m_arena_end = m_doc.arena.data() + m_doc.arena.size(); } diff --git a/include/nlohmann/detail/view/document_data.hpp b/include/nlohmann/detail/view/document_data.hpp index afef5a7a2..2c5f97767 100644 --- a/include/nlohmann/detail/view/document_data.hpp +++ b/include/nlohmann/detail/view/document_data.hpp @@ -82,14 +82,17 @@ struct document_data }; std::unique_ptr edits; ///< created by the first edit + /// the most nodes that are allocated together with the header (16 KiB) + static constexpr std::size_t max_inline_nodes = 1024; + /// One allocation for the header and room for `count` nodes, so that a /// small document needs one allocation instead of two. Large documents get /// a separate node array instead (so it can be trimmed). When a document - /// outgrows the inline array, that array stays unused: at most 256 nodes - /// (4 KiB), and only for documents whose estimate was too small. + /// outgrows the inline array, that array stays unused: at most 1024 nodes + /// (16 KiB), and only for documents whose estimate was too small. static document_data* create(std::size_t count) { - count = count <= 256 ? count : 0; + count = count <= max_inline_nodes ? count : 0; void* mem = ::operator new (sizeof(document_data) + (count * sizeof(node))); auto* d = new (mem) document_data(); // NOLINT(cppcoreguidelines-owning-memory): owned by the returned pointer, freed by deleter // (aligned: sizeof is a multiple of the alignment; through void*, as GCC's -Wcast-align wants) diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index e611ff540..51de7d32a 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -410,14 +410,17 @@ struct document_data }; std::unique_ptr edits; ///< created by the first edit + /// the most nodes that are allocated together with the header (16 KiB) + static constexpr std::size_t max_inline_nodes = 1024; + /// One allocation for the header and room for `count` nodes, so that a /// small document needs one allocation instead of two. Large documents get /// a separate node array instead (so it can be trimmed). When a document - /// outgrows the inline array, that array stays unused: at most 256 nodes - /// (4 KiB), and only for documents whose estimate was too small. + /// outgrows the inline array, that array stays unused: at most 1024 nodes + /// (16 KiB), and only for documents whose estimate was too small. static document_data* create(std::size_t count) { - count = count <= 256 ? count : 0; + count = count <= max_inline_nodes ? count : 0; void* mem = ::operator new (sizeof(document_data) + (count * sizeof(node))); auto* d = new (mem) document_data(); // NOLINT(cppcoreguidelines-owning-memory): owned by the returned pointer, freed by deleter // (aligned: sizeof is a multiple of the alignment; through void*, as GCC's -Wcast-align wants) @@ -1747,7 +1750,15 @@ run_found: NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n) { const std::size_t used = arena_used(); - m_doc.arena.resize((std::max)(m_doc.arena.size() * 2, used + n + 256)); + std::size_t wanted = used + n + 256; + if (m_doc.arena.empty()) + { + // the first growth: a decoded string is never longer than its source, + // so a fraction of the input (capped, as the buffer is zero-filled) + // covers the escapes of a typical document without regrowing it + wanted = (std::max)(wanted, (std::min)((static_cast(m_end - m_begin) / 4) + 16, std::size_t{4096})); + } + m_doc.arena.resize((std::max)(m_doc.arena.size() * 2, wanted)); m_arena_write = m_doc.arena.data() + used; m_arena_end = m_doc.arena.data() + m_doc.arena.size(); } diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index e87162d98..ee2300e3e 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -666,6 +666,61 @@ TEST_CASE("json_view") CHECK(small.root().materialize() == json::parse("[1,[2,3],{\"a\":\"b\\n\"}]")); } + SECTION("block sizes") + { + // documents whose node estimate lies around the size that is allocated + // with the header, and whose decoded strings need the first growth of + // the buffer, in all sizes around the points where the policies change + for (const int count : + { + 1, 2, 10, 50, 100, 255, 256, 257, 500, 1000, 1023, 1024, 1025, 2000, 5000 + }) + { + // strings with escapes (decoded text, minified) + std::string escaped = "["; + for (int i = 0; i < count; ++i) + { + escaped += (i != 0 ? ",\"a\\n" : "\"a\\n") + std::to_string(i) + "\\u00e4\""; + } + escaped += ']'; + json_document e = json_document::parse(escaped); + CHECK(e.root().materialize() == json::parse(escaped)); + e.shrink_to_fit(); + CHECK(e.root().materialize() == json::parse(escaped)); + + // dense numbers (more nodes per byte than the estimate expects) + std::string dense = "["; + for (int i = 0; i < count; ++i) + { + dense += (i != 0 ? ",1" : "1"); + } + dense += ']'; + json_document n = json_document::parse(dense); + CHECK(n.node_count() == static_cast(count) + 1); + CHECK(n.root().materialize() == json::parse(dense)); + n.shrink_to_fit(); + CHECK(n.root().size() == static_cast(count)); + + // the same documents, pretty-printed + const std::string pretty = json::parse(escaped).dump(2); + json_document p = json_document::parse(pretty); + CHECK(p.root().materialize() == json::parse(escaped)); + } + + // a large document with a single escape does not need a buffer of the + // size of its input + std::string large = R"(["a\n")"; + for (int i = 0; i < 100000; ++i) + { + large += ",1"; + } + large += ']'; + const json_document l = json_document::parse(large); + CHECK(l.root().size() == 100001); + CHECK(l.root()[0].get_string() == "a\n"); + CHECK(l.root()[100000].get() == 1); + } + SECTION("source offsets") { const std::string text = R"( {"key": "value", "escaped": "a\nb", "n": 42})"; From 2bb2feeef41cb751cd63b6a72a8fc14b154f545a Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 07:27:34 +0100 Subject: [PATCH 20/63] Fix the red CI on develop (json_view pointer tokens, bson, from_json tests, Infer, IWYU) (#5942) * json_view: pointer tokens that cannot be array indices create objects editable_document::set() chose an array for a null parent whenever the token consisted of digits, so "01" threw parse_error.106 and the empty token threw, while basic_json::operator[](json_pointer) has created an object for such tokens since #5358. This broke unit-json_view_edit_values on develop. Use the same RFC 6901 array index test as basic_json. Also re-indent the unflatten test that the astyle check flagged. Signed-off-by: Niels Lohmann * Fix the other red jobs on develop - unit-bson: initialize recorder::events in its declaration (-Weffc++) - unit-json_view_from_json: suppress -Wexit-time-destructors for the serialize-enum tables and -Wfloat-equal for same() - unit-pointer_access: @infer-ignore NULLPTR_DEREFERENCE for the dereferences after get_ptr() that Infer reports - json_view.hpp: IWYU no_include for the detail headers that the view code uses through nlohmann/json.hpp Signed-off-by: Niels Lohmann * Fix GCC warnings in the json_view from_json test GCC (-Weffc++, -Wfloat-equal) rejected unit-json_view_from_json.cpp (ci_test_standards_gcc (20)): initialize the members of shape and with_names, and suppress -Wfloat-equal for GCC like for clang. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- include/nlohmann/json_view.hpp | 10 ++++++++-- single_include/nlohmann/json_view.hpp | 10 ++++++++-- tests/src/unit-bson.cpp | 2 +- tests/src/unit-json_pointer.cpp | 8 ++++---- tests/src/unit-json_view_from_json.cpp | 21 ++++++++++++++++----- tests/src/unit-pointer_access.cpp | 5 +++++ 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 071c14904..d413a55b9 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -54,9 +54,11 @@ // IWYU pragma: no_include // IWYU pragma: no_include "nlohmann/detail/abi_config.hpp" // IWYU pragma: no_include "nlohmann/detail/abi_macros.hpp" +// IWYU pragma: no_include "nlohmann/detail/conversions/from_json.hpp" // IWYU pragma: no_include "nlohmann/detail/input/input_adapters.hpp" // IWYU pragma: no_include "nlohmann/detail/json_pointer.hpp" // IWYU pragma: no_include "nlohmann/detail/meta/cpp_future.hpp" +// IWYU pragma: no_include "nlohmann/detail/meta/type_traits.hpp" // IWYU pragma: no_include "nlohmann/detail/string_concat.hpp" // IWYU pragma: no_include "nlohmann/detail/value_t.hpp" // IWYU pragma: no_include "nlohmann/json.hpp" @@ -1313,7 +1315,7 @@ class basic_json_document /// member is set (added if missing), an array element assigned, and "-" /// or the size of the array appends. A null parent becomes what /// basic_json's operator[](json_pointer) makes of it: an array for "-" - /// and for digits (padded with nulls up to the index), an object otherwise. + /// and for array indices (padded with nulls up to the index), an object otherwise. template view_type set(const json_pointer& ptr, V&& value) { @@ -1329,7 +1331,11 @@ class basic_json_document { return push_back(parent, std::forward(value)); } - const bool digits = std::all_of(token.begin(), token.end(), [](const char c) + // (a token that cannot be an array index, such as "01" or "", names an + // object member, as in basic_json) + const bool digits = !token.empty() + && (token.size() == 1 || token[0] != '0') + && std::all_of(token.begin(), token.end(), [](const char c) { return c >= '0' && c <= '9'; }); diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 51de7d32a..73323e38b 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -54,9 +54,11 @@ // IWYU pragma: no_include // IWYU pragma: no_include "nlohmann/detail/abi_config.hpp" // IWYU pragma: no_include "nlohmann/detail/abi_macros.hpp" +// IWYU pragma: no_include "nlohmann/detail/conversions/from_json.hpp" // IWYU pragma: no_include "nlohmann/detail/input/input_adapters.hpp" // IWYU pragma: no_include "nlohmann/detail/json_pointer.hpp" // IWYU pragma: no_include "nlohmann/detail/meta/cpp_future.hpp" +// IWYU pragma: no_include "nlohmann/detail/meta/type_traits.hpp" // IWYU pragma: no_include "nlohmann/detail/string_concat.hpp" // IWYU pragma: no_include "nlohmann/detail/value_t.hpp" // IWYU pragma: no_include "nlohmann/json.hpp" @@ -9078,7 +9080,7 @@ class basic_json_document /// member is set (added if missing), an array element assigned, and "-" /// or the size of the array appends. A null parent becomes what /// basic_json's operator[](json_pointer) makes of it: an array for "-" - /// and for digits (padded with nulls up to the index), an object otherwise. + /// and for array indices (padded with nulls up to the index), an object otherwise. template view_type set(const json_pointer& ptr, V&& value) { @@ -9094,7 +9096,11 @@ class basic_json_document { return push_back(parent, std::forward(value)); } - const bool digits = std::all_of(token.begin(), token.end(), [](const char c) + // (a token that cannot be an array index, such as "01" or "", names an + // object member, as in basic_json) + const bool digits = !token.empty() + && (token.size() == 1 || token[0] != '0') + && std::all_of(token.begin(), token.end(), [](const char c) { return c >= '0' && c <= '9'; }); diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index b4756c9b5..73b277d56 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1311,7 +1311,7 @@ TEST_CASE("BSON declared document size bounds the content") // records the events that a SAX parser receives struct recorder : nlohmann::json_sax { - std::string events; + std::string events{}; bool null() override { diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 1a4e726ed..b28ac4023 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -1122,17 +1122,17 @@ TEST_CASE("flatten of structured values") // the same for other keys that are not valid array indices CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1a", 2}}).unflatten(), - "[json.exception.out_of_range.404] unresolved reference token '1a'", json::out_of_range&); + "[json.exception.out_of_range.404] unresolved reference token '1a'", json::out_of_range&); CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/-", 2}}).unflatten(), - "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&); + "[json.exception.out_of_range.404] unresolved reference token '-'", json::out_of_range&); } SECTION("unflatten with the empty token among array indices") { CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/", 2}}).unflatten(), - "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); + "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/1", 2}, {"/a/", 3}}).unflatten(), - "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); + "[json.exception.out_of_range.404] unresolved reference token ''", json::out_of_range&); // without an index "0", the empty token is an ordinary object key CHECK(json({{"/a/", 2}}).unflatten() == json({{"a", {{"", 2}}}})); diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index ec296e5db..4a776dd52 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -41,6 +41,13 @@ using nlohmann::ordered_json_document; #include #endif +// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair; same() compares floating-point values on purpose +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + // The from_json() functions of the NLOHMANN_DEFINE_TYPE_* and // NLOHMANN_JSON_SERIALIZE_ENUM macros read views directly; other from_json() // functions get the value of materialize(). @@ -61,10 +68,11 @@ struct point struct shape { - std::string name; - std::vector points; - point center; - std::map named; + // initialized for GCC -Weffc++ + std::string name{}; + std::vector points{}; + point center{}; + std::map named{}; NLOHMANN_DEFINE_TYPE_INTRUSIVE(shape, name, points, center, named) }; @@ -79,7 +87,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(with_defaults, a, b, c) struct with_names { int value = 0; - std::string text; + std::string text{}; // initialized for GCC -Weffc++ }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(with_names, "Value", value, "Text", text) @@ -447,3 +455,6 @@ TEST_CASE("json_view from_json") } #endif } + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-pointer_access.cpp b/tests/src/unit-pointer_access.cpp index 33a4e2047..2b84ceede 100644 --- a/tests/src/unit-pointer_access.cpp +++ b/tests/src/unit-pointer_access.cpp @@ -141,6 +141,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly const test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -170,6 +171,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -199,6 +201,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly const test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -232,6 +235,7 @@ TEST_CASE("pointer access") const test_type* p2 = value.get_ptr(); CHECK(p2 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p2 == value.get()); const test_type* const p3 = value.get_ptr(); @@ -257,6 +261,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly const test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); From a47170346a791bfac8dbf53be3984853b953e62e Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 07:41:42 +0100 Subject: [PATCH 21/63] docs: new design for the documentation site (#5934) * docs: new design for the documentation site Apply the brand to the Material for MkDocs site: Inter and JetBrains Mono served by the site (upright and real italic), brand colours, blue header band with the syntax pattern, wordmark in the header, new home page (hero, cards, animated feature tour), 404 page, polish for tabs, search, footer, focus rings, tables and code blocks, and theme features (code copy, previous/next footer, TOC follow, search suggestions). Draw the number railroad diagram, the parser callback events and the iterator illustrations as inline SVGs that follow the light and dark scheme. The generator scripts are in docs/mkdocs/scripts/figures; the feature slideshow (json-light.svg, json-dark.svg, replacing json.gif on the home page) is built by docs/mkdocs/scripts/slideshow. Decision nodes in five flowcharts become hexagons, which need far less room. Signed-off-by: Niels Lohmann * docs: add figures, table captions and a macro chooser - Figures with numbered captions: SAX stepper, json_view layout and edit stepper, exception map, JSON pointer and patch walkers, QA pipeline, scaling and binary size charts, roadmap lanes, release timeline, integration decision tree and namespace builder - Table captions (nj-table-caption) on 9 pages - Compile times: modules and PCH charts, filesystem section, re-measured Figure 1, links between features/modules.md and compile_times.md - Interactive macro chooser for features/arbitrary_types.md - Link check: exclude URLs that answer the plugin with 400/401/419 or are dead (customers.md) Signed-off-by: Niels Lohmann * docs: macro chooser generates adl_serializer for non-default-constructible types Signed-off-by: Niels Lohmann * docs: cross-link concept pages and API pages in See also sections Add See also links in both directions between the concept pages (features, home, integration) and the API pages, write API links in code font, give every entry a comment after a dash, and show the callback event figure on the parser callbacks page. Signed-off-by: Niels Lohmann * docs: address review comments on the new design - Home: add a "Latest release" button - Sponsors: update the list and group it by tier - Features: list all feature pages on the index page - Releases: add highlights per release, link the 4.0 roadmap, add the first commit to the timeline - Exceptions: prefix all example messages with the exception name - API: no code font for format names in the See also links of the to_/from_ pages - Merge Patch: show the document before, the patch, and the result - Replace "--" with typographic dashes, mark external links with an icon, center Mermaid diagrams Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../docs/api/adl_serializer/from_json.md | 6 +- docs/mkdocs/docs/api/adl_serializer/index.md | 8 +- .../mkdocs/docs/api/adl_serializer/to_json.md | 4 +- docs/mkdocs/docs/api/basic_json/accept.md | 11 +- docs/mkdocs/docs/api/basic_json/array.md | 4 +- docs/mkdocs/docs/api/basic_json/array_t.md | 10 +- .../docs/api/basic_json/as_base_class.md | 3 +- docs/mkdocs/docs/api/basic_json/at.md | 10 +- docs/mkdocs/docs/api/basic_json/back.md | 5 +- docs/mkdocs/docs/api/basic_json/basic_json.md | 24 +- docs/mkdocs/docs/api/basic_json/begin.md | 17 +- docs/mkdocs/docs/api/basic_json/binary.md | 9 +- docs/mkdocs/docs/api/basic_json/binary_t.md | 6 +- docs/mkdocs/docs/api/basic_json/boolean_t.md | 4 +- docs/mkdocs/docs/api/basic_json/cbegin.md | 15 +- .../docs/api/basic_json/cbor_tag_handler_t.md | 5 +- docs/mkdocs/docs/api/basic_json/cend.md | 15 +- docs/mkdocs/docs/api/basic_json/clear.md | 5 +- docs/mkdocs/docs/api/basic_json/contains.md | 7 +- docs/mkdocs/docs/api/basic_json/count.md | 7 +- docs/mkdocs/docs/api/basic_json/crbegin.md | 14 +- docs/mkdocs/docs/api/basic_json/crend.md | 14 +- .../basic_json/default_object_comparator_t.md | 6 +- docs/mkdocs/docs/api/basic_json/diff.md | 11 +- docs/mkdocs/docs/api/basic_json/dump.md | 6 +- docs/mkdocs/docs/api/basic_json/emplace.md | 4 +- .../docs/api/basic_json/emplace_back.md | 4 +- docs/mkdocs/docs/api/basic_json/empty.md | 8 +- docs/mkdocs/docs/api/basic_json/end.md | 15 +- docs/mkdocs/docs/api/basic_json/end_pos.md | 5 +- docs/mkdocs/docs/api/basic_json/erase.md | 4 +- .../docs/api/basic_json/error_handler_t.md | 2 +- docs/mkdocs/docs/api/basic_json/exception.md | 9 +- docs/mkdocs/docs/api/basic_json/find.md | 7 +- docs/mkdocs/docs/api/basic_json/flatten.md | 3 +- docs/mkdocs/docs/api/basic_json/format_as.md | 4 +- .../mkdocs/docs/api/basic_json/from_bjdata.md | 16 +- docs/mkdocs/docs/api/basic_json/from_bon8.md | 15 +- docs/mkdocs/docs/api/basic_json/from_bson.md | 18 +- docs/mkdocs/docs/api/basic_json/from_cbor.md | 16 +- .../docs/api/basic_json/from_msgpack.md | 16 +- .../mkdocs/docs/api/basic_json/from_ubjson.md | 16 +- docs/mkdocs/docs/api/basic_json/front.md | 5 +- docs/mkdocs/docs/api/basic_json/get.md | 11 +- .../docs/api/basic_json/get_allocator.md | 2 +- docs/mkdocs/docs/api/basic_json/get_binary.md | 7 +- docs/mkdocs/docs/api/basic_json/get_ptr.md | 3 +- docs/mkdocs/docs/api/basic_json/get_ref.md | 5 +- docs/mkdocs/docs/api/basic_json/get_to.md | 9 +- docs/mkdocs/docs/api/basic_json/index.md | 13 +- .../docs/api/basic_json/input_format_t.md | 5 +- docs/mkdocs/docs/api/basic_json/insert.md | 9 +- .../docs/api/basic_json/invalid_iterator.md | 17 +- docs/mkdocs/docs/api/basic_json/is_array.md | 11 +- docs/mkdocs/docs/api/basic_json/is_binary.md | 9 +- docs/mkdocs/docs/api/basic_json/is_boolean.md | 7 +- .../docs/api/basic_json/is_discarded.md | 3 +- docs/mkdocs/docs/api/basic_json/is_null.md | 11 +- docs/mkdocs/docs/api/basic_json/is_number.md | 10 +- .../docs/api/basic_json/is_number_float.md | 10 +- .../docs/api/basic_json/is_number_integer.md | 10 +- .../docs/api/basic_json/is_number_unsigned.md | 10 +- docs/mkdocs/docs/api/basic_json/is_object.md | 11 +- .../docs/api/basic_json/is_primitive.md | 15 +- docs/mkdocs/docs/api/basic_json/is_string.md | 9 +- .../docs/api/basic_json/is_structured.md | 9 +- docs/mkdocs/docs/api/basic_json/items.md | 9 +- .../docs/api/basic_json/json_base_class_t.md | 2 +- .../docs/api/basic_json/json_serializer.md | 7 +- docs/mkdocs/docs/api/basic_json/max_size.md | 8 +- docs/mkdocs/docs/api/basic_json/merge_diff.md | 7 +- .../mkdocs/docs/api/basic_json/merge_patch.md | 7 +- docs/mkdocs/docs/api/basic_json/meta.md | 7 +- .../docs/api/basic_json/number_float_t.md | 7 +- .../docs/api/basic_json/number_integer_t.md | 7 +- .../docs/api/basic_json/number_unsigned_t.md | 7 +- .../api/basic_json/object_comparator_t.md | 6 +- docs/mkdocs/docs/api/basic_json/object_t.md | 10 +- docs/mkdocs/docs/api/basic_json/operator+=.md | 5 +- docs/mkdocs/docs/api/basic_json/operator=.md | 5 +- docs/mkdocs/docs/api/basic_json/operator[].md | 12 +- .../docs/api/basic_json/operator_ValueType.md | 2 +- .../mkdocs/docs/api/basic_json/operator_eq.md | 6 +- .../mkdocs/docs/api/basic_json/operator_ge.md | 2 +- .../mkdocs/docs/api/basic_json/operator_gt.md | 2 +- .../mkdocs/docs/api/basic_json/operator_le.md | 2 +- .../mkdocs/docs/api/basic_json/operator_lt.md | 2 +- .../mkdocs/docs/api/basic_json/operator_ne.md | 6 +- .../docs/api/basic_json/operator_spaceship.md | 12 +- .../docs/api/basic_json/operator_value_t.md | 5 +- .../mkdocs/docs/api/basic_json/other_error.md | 17 +- .../docs/api/basic_json/out_of_range.md | 18 +- docs/mkdocs/docs/api/basic_json/parse.md | 12 +- .../mkdocs/docs/api/basic_json/parse_error.md | 17 +- .../docs/api/basic_json/parse_event_t.md | 12 +- .../docs/api/basic_json/parser_callback_t.md | 12 +- docs/mkdocs/docs/api/basic_json/patch.md | 10 +- .../docs/api/basic_json/patch_inplace.md | 9 +- docs/mkdocs/docs/api/basic_json/push_back.md | 4 +- docs/mkdocs/docs/api/basic_json/rbegin.md | 14 +- docs/mkdocs/docs/api/basic_json/rend.md | 14 +- docs/mkdocs/docs/api/basic_json/sax_parse.md | 6 +- docs/mkdocs/docs/api/basic_json/size.md | 8 +- docs/mkdocs/docs/api/basic_json/start_pos.md | 5 +- .../docs/api/basic_json/std_formatter.md | 6 +- docs/mkdocs/docs/api/basic_json/std_hash.md | 3 +- docs/mkdocs/docs/api/basic_json/std_swap.md | 2 +- docs/mkdocs/docs/api/basic_json/string_t.md | 10 +- docs/mkdocs/docs/api/basic_json/swap.md | 6 +- docs/mkdocs/docs/api/basic_json/to_bjdata.md | 15 +- docs/mkdocs/docs/api/basic_json/to_bon8.md | 14 +- docs/mkdocs/docs/api/basic_json/to_bson.md | 15 +- docs/mkdocs/docs/api/basic_json/to_cbor.md | 15 +- docs/mkdocs/docs/api/basic_json/to_msgpack.md | 15 +- docs/mkdocs/docs/api/basic_json/to_string.md | 2 +- docs/mkdocs/docs/api/basic_json/to_ubjson.md | 15 +- docs/mkdocs/docs/api/basic_json/type.md | 9 +- docs/mkdocs/docs/api/basic_json/type_error.md | 17 +- docs/mkdocs/docs/api/basic_json/type_name.md | 9 +- docs/mkdocs/docs/api/basic_json/unflatten.md | 3 +- docs/mkdocs/docs/api/basic_json/update.md | 4 +- docs/mkdocs/docs/api/basic_json/value.md | 8 +- docs/mkdocs/docs/api/basic_json/value_t.md | 14 +- docs/mkdocs/docs/api/basic_json/with_t.md | 5 +- .../mkdocs/docs/api/basic_json/~basic_json.md | 4 +- .../docs/api/basic_json_document/accept.md | 5 +- .../basic_json_document.md | 5 +- .../docs/api/basic_json_document/erase.md | 19 +- .../docs/api/basic_json_document/index.md | 8 +- .../docs/api/basic_json_document/insert.md | 27 +- .../api/basic_json_document/is_discarded.md | 5 +- .../docs/api/basic_json_document/load.md | 26 +- .../api/basic_json_document/memory_usage.md | 7 +- .../api/basic_json_document/node_count.md | 5 +- .../api/basic_json_document/owns_source.md | 7 +- .../docs/api/basic_json_document/parse.md | 22 +- .../api/basic_json_document/parse_copy.md | 5 +- .../docs/api/basic_json_document/push_back.md | 31 +- .../docs/api/basic_json_document/read.md | 9 +- .../docs/api/basic_json_document/root.md | 8 +- .../docs/api/basic_json_document/save.md | 20 +- .../docs/api/basic_json_document/set.md | 40 +- .../api/basic_json_document/shrink_to_fit.md | 7 +- .../docs/api/basic_json_document/source.md | 5 +- docs/mkdocs/docs/api/basic_json_view/at.md | 14 +- docs/mkdocs/docs/api/basic_json_view/back.md | 2 +- .../api/basic_json_view/basic_json_view.md | 7 +- docs/mkdocs/docs/api/basic_json_view/begin.md | 10 +- .../mkdocs/docs/api/basic_json_view/cbegin.md | 6 +- docs/mkdocs/docs/api/basic_json_view/cend.md | 6 +- .../docs/api/basic_json_view/contains.md | 12 +- docs/mkdocs/docs/api/basic_json_view/count.md | 8 +- docs/mkdocs/docs/api/basic_json_view/dump.md | 15 +- docs/mkdocs/docs/api/basic_json_view/empty.md | 4 +- docs/mkdocs/docs/api/basic_json_view/end.md | 6 +- docs/mkdocs/docs/api/basic_json_view/find.md | 10 +- docs/mkdocs/docs/api/basic_json_view/front.md | 2 +- docs/mkdocs/docs/api/basic_json_view/get.md | 35 +- .../docs/api/basic_json_view/get_string.md | 13 +- .../mkdocs/docs/api/basic_json_view/get_to.md | 7 +- docs/mkdocs/docs/api/basic_json_view/index.md | 10 +- .../docs/api/basic_json_view/is_array.md | 6 +- .../docs/api/basic_json_view/is_binary.md | 2 +- .../docs/api/basic_json_view/is_boolean.md | 2 +- .../docs/api/basic_json_view/is_discarded.md | 9 +- .../docs/api/basic_json_view/is_null.md | 2 +- .../docs/api/basic_json_view/is_number.md | 6 +- .../api/basic_json_view/is_number_float.md | 2 +- .../api/basic_json_view/is_number_integer.md | 4 +- .../api/basic_json_view/is_number_unsigned.md | 2 +- .../docs/api/basic_json_view/is_object.md | 6 +- .../docs/api/basic_json_view/is_primitive.md | 2 +- .../docs/api/basic_json_view/is_string.md | 2 +- .../docs/api/basic_json_view/is_structured.md | 4 +- docs/mkdocs/docs/api/basic_json_view/items.md | 10 +- .../docs/api/basic_json_view/materialize.md | 7 +- .../docs/api/basic_json_view/number_format.md | 11 +- .../docs/api/basic_json_view/number_token.md | 15 +- .../docs/api/basic_json_view/operator[].md | 24 +- .../docs/api/basic_json_view/operator_eq.md | 10 +- .../docs/api/basic_json_view/operator_ltlt.md | 11 +- .../docs/api/basic_json_view/operator_ne.md | 10 +- docs/mkdocs/docs/api/basic_json_view/size.md | 6 +- .../docs/api/basic_json_view/source_offset.md | 7 +- docs/mkdocs/docs/api/basic_json_view/type.md | 6 +- .../docs/api/basic_json_view/type_name.md | 6 +- docs/mkdocs/docs/api/basic_json_view/value.md | 20 +- .../byte_container_with_subtype.md | 8 +- .../clear_subtype.md | 7 +- .../has_subtype.md | 7 +- .../api/byte_container_with_subtype/index.md | 6 +- .../operator_eq.md | 9 +- .../operator_ne.md | 7 +- .../set_subtype.md | 7 +- .../byte_container_with_subtype/subtype.md | 7 +- docs/mkdocs/docs/api/json.md | 7 +- docs/mkdocs/docs/api/json_document.md | 5 +- .../mkdocs/docs/api/json_editable_document.md | 10 +- docs/mkdocs/docs/api/json_editable_view.md | 11 +- docs/mkdocs/docs/api/json_pointer/back.md | 8 +- docs/mkdocs/docs/api/json_pointer/empty.md | 6 +- docs/mkdocs/docs/api/json_pointer/front.md | 6 +- docs/mkdocs/docs/api/json_pointer/index.md | 9 +- .../docs/api/json_pointer/json_pointer.md | 2 +- .../docs/api/json_pointer/operator_eq.md | 4 +- .../docs/api/json_pointer/operator_ne.md | 4 +- .../docs/api/json_pointer/operator_slash.md | 5 +- .../docs/api/json_pointer/operator_slasheq.md | 5 +- .../api/json_pointer/operator_spaceship.md | 4 +- .../api/json_pointer/operator_string_t.md | 4 +- .../docs/api/json_pointer/parent_pointer.md | 4 +- docs/mkdocs/docs/api/json_pointer/pop_back.md | 6 +- .../mkdocs/docs/api/json_pointer/pop_front.md | 4 +- .../mkdocs/docs/api/json_pointer/push_back.md | 8 +- .../docs/api/json_pointer/push_front.md | 4 +- docs/mkdocs/docs/api/json_pointer/string_t.md | 4 +- .../mkdocs/docs/api/json_pointer/to_string.md | 4 +- docs/mkdocs/docs/api/json_sax/binary.md | 2 +- docs/mkdocs/docs/api/json_sax/boolean.md | 2 +- docs/mkdocs/docs/api/json_sax/end_array.md | 5 +- docs/mkdocs/docs/api/json_sax/end_object.md | 7 +- docs/mkdocs/docs/api/json_sax/index.md | 9 +- docs/mkdocs/docs/api/json_sax/key.md | 7 +- docs/mkdocs/docs/api/json_sax/null.md | 2 +- docs/mkdocs/docs/api/json_sax/number_float.md | 7 +- .../docs/api/json_sax/number_integer.md | 7 +- .../docs/api/json_sax/number_unsigned.md | 7 +- docs/mkdocs/docs/api/json_sax/parse_error.md | 3 +- docs/mkdocs/docs/api/json_sax/start_array.md | 5 +- docs/mkdocs/docs/api/json_sax/start_object.md | 7 +- docs/mkdocs/docs/api/json_sax/string.md | 2 +- docs/mkdocs/docs/api/json_view.md | 5 +- docs/mkdocs/docs/api/macros/index.md | 8 +- docs/mkdocs/docs/api/macros/json_assert.md | 1 + .../macros/json_brace_init_copy_semantics.md | 6 +- .../json_delete_deprecated_functions.md | 2 + .../api/macros/json_diagnostic_positions.md | 3 +- .../docs/api/macros/json_diagnostics.md | 6 +- .../macros/json_disable_enum_serialization.md | 1 + .../macros/json_disable_stream_bulk_scan.md | 14 +- ...json_disable_tuple_reference_conversion.md | 4 +- .../macros/json_disable_unlocked_file_io.md | 10 +- .../mkdocs/docs/api/macros/json_has_cpp_11.md | 9 +- .../docs/api/macros/json_has_filesystem.md | 6 +- .../macros/json_has_range_view_conversion.md | 4 +- .../mkdocs/docs/api/macros/json_has_ranges.md | 9 +- .../docs/api/macros/json_has_static_rtti.md | 4 +- .../docs/api/macros/json_has_std_format.md | 2 + .../macros/json_has_three_way_comparison.md | 4 +- .../docs/api/macros/json_no_automatic_udls.md | 5 +- docs/mkdocs/docs/api/macros/json_no_io.md | 6 +- docs/mkdocs/docs/api/macros/json_no_simd.md | 14 +- .../docs/api/macros/json_no_thread_local.md | 3 +- .../docs/api/macros/json_noexception.md | 7 +- .../macros/json_precise_stream_position.md | 5 +- .../macros/json_skip_library_version_check.md | 6 +- .../json_skip_unsupported_compiler_check.md | 6 +- .../api/macros/json_strict_binary_utf8.md | 12 +- .../api/macros/json_strict_nul_handling.md | 9 +- .../mkdocs/docs/api/macros/json_throw_user.md | 8 +- .../docs/api/macros/json_use_global_udls.md | 5 +- .../macros/json_use_implicit_conversions.md | 5 +- .../json_use_objects_for_enum_keyed_maps.md | 6 +- .../docs/api/macros/json_use_simdutf.md | 4 +- .../macros/nlohmann_define_derived_type.md | 15 +- .../macros/nlohmann_define_type_intrusive.md | 16 +- .../nlohmann_define_type_non_intrusive.md | 16 +- .../macros/nlohmann_define_type_with_names.md | 16 +- .../api/macros/nlohmann_json_namespace.md | 7 +- .../macros/nlohmann_json_namespace_begin.md | 7 +- .../nlohmann_json_namespace_no_version.md | 8 +- .../macros/nlohmann_json_serialize_enum.md | 10 +- .../nlohmann_json_serialize_enum_strict.md | 11 +- .../api/macros/nlohmann_json_version_major.md | 6 +- docs/mkdocs/docs/api/operator_gtgt.md | 9 +- docs/mkdocs/docs/api/operator_literal_json.md | 2 +- .../docs/api/operator_literal_json_pointer.md | 5 +- docs/mkdocs/docs/api/operator_ltlt.md | 3 +- docs/mkdocs/docs/api/ordered_json.md | 4 +- docs/mkdocs/docs/api/ordered_json_document.md | 7 +- .../api/ordered_json_editable_document.md | 13 +- .../docs/api/ordered_json_editable_view.md | 7 +- docs/mkdocs/docs/api/ordered_json_view.md | 5 +- docs/mkdocs/docs/api/ordered_map.md | 5 +- docs/mkdocs/docs/community/assurance_case.md | 2 + .../docs/community/quality_assurance.md | 9 + docs/mkdocs/docs/community/roadmap.md | 12 + docs/mkdocs/docs/css/brand.css | 803 ++++++++++ docs/mkdocs/docs/css/fig-charts.css | 142 ++ docs/mkdocs/docs/css/fig-exceptions.css | 75 + docs/mkdocs/docs/css/fig-integration.css | 176 +++ docs/mkdocs/docs/css/fig-jsonview.css | 107 ++ docs/mkdocs/docs/css/fig-macros.css | 98 ++ docs/mkdocs/docs/css/fig-pointer.css | 459 ++++++ docs/mkdocs/docs/css/fig-sax.css | 207 +++ docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 | Bin 0 -> 387976 bytes docs/mkdocs/docs/css/fonts/Inter.woff2 | Bin 0 -> 350428 bytes .../docs/css/fonts/JetBrainsMono-Italic.woff2 | Bin 0 -> 95864 bytes .../mkdocs/docs/css/fonts/JetBrainsMono.woff2 | Bin 0 -> 113380 bytes docs/mkdocs/docs/css/pattern-on-blue.svg | 1 + docs/mkdocs/docs/features/arbitrary_types.md | 54 +- docs/mkdocs/docs/features/assertions.md | 6 +- .../docs/features/binary_formats/bjdata.md | 9 + .../docs/features/binary_formats/bon8.md | 9 + .../docs/features/binary_formats/bson.md | 9 + .../docs/features/binary_formats/cbor.md | 10 + .../docs/features/binary_formats/index.md | 27 +- .../features/binary_formats/messagepack.md | 9 + .../docs/features/binary_formats/ubjson.md | 9 + docs/mkdocs/docs/features/binary_values.md | 12 + docs/mkdocs/docs/features/comments.md | 9 +- docs/mkdocs/docs/features/conversions.md | 15 +- docs/mkdocs/docs/features/creating_values.md | 7 +- .../features/element_access/checked_access.md | 10 + .../features/element_access/default_value.md | 12 +- .../docs/features/element_access/index.md | 38 +- .../element_access/unchecked_access.md | 10 + docs/mkdocs/docs/features/enum_conversion.md | 9 + docs/mkdocs/docs/features/index.md | 18 +- docs/mkdocs/docs/features/iterators.md | 22 +- docs/mkdocs/docs/features/json_patch.md | 23 + docs/mkdocs/docs/features/json_pointer.md | 54 +- docs/mkdocs/docs/features/json_view.md | 148 +- docs/mkdocs/docs/features/macros.md | 14 + docs/mkdocs/docs/features/merge_patch.md | 77 +- docs/mkdocs/docs/features/modifying_values.md | 10 +- docs/mkdocs/docs/features/modules.md | 11 + docs/mkdocs/docs/features/namespace.md | 27 + docs/mkdocs/docs/features/no_exceptions.md | 9 +- docs/mkdocs/docs/features/object_order.md | 8 + docs/mkdocs/docs/features/parsing/index.md | 13 +- .../docs/features/parsing/json_lines.md | 9 +- .../parsing/large_and_partial_input.md | 23 + .../docs/features/parsing/parse_exceptions.md | 11 + .../docs/features/parsing/parser_callbacks.md | 19 +- .../docs/features/parsing/sax_interface.md | 38 +- .../docs/features/parsing/untrusted_input.md | 24 +- docs/mkdocs/docs/features/performance.md | 60 +- docs/mkdocs/docs/features/serialization.md | 4 + docs/mkdocs/docs/features/trailing_commas.md | 7 + docs/mkdocs/docs/features/type_recipes.md | 7 +- docs/mkdocs/docs/features/types/index.md | 20 +- .../docs/features/types/number_handling.md | 49 +- .../features/types/template_parameters.md | 70 +- docs/mkdocs/docs/home/architecture.md | 91 +- docs/mkdocs/docs/home/debugging.md | 9 +- docs/mkdocs/docs/home/design_goals.md | 7 + docs/mkdocs/docs/home/exceptions.md | 96 +- docs/mkdocs/docs/home/faq.md | 15 +- docs/mkdocs/docs/home/releases/index.md | 96 +- docs/mkdocs/docs/home/sponsors.md | 27 +- .../docs/images/binary_sizes_canada.svg | 30 + .../docs/images/binary_sizes_citm_catalog.svg | 30 + .../docs/images/binary_sizes_jeopardy.svg | 30 + .../docs/images/binary_sizes_twitter.svg | 30 + docs/mkdocs/docs/images/callback_events.png | Bin 46039 -> 0 bytes docs/mkdocs/docs/images/callback_events.svg | 39 + docs/mkdocs/docs/images/chunk_buffering.svg | 50 + docs/mkdocs/docs/images/compile_times.svg | 60 + .../docs/images/compile_times_modules.svg | 30 + docs/mkdocs/docs/images/compile_times_pch.svg | 30 + docs/mkdocs/docs/images/exception_map.svg | 174 +++ docs/mkdocs/docs/images/json-dark.svg | 1 + docs/mkdocs/docs/images/json-light.svg | 1 + .../docs/images/json_pointer_flatten.svg | 52 + .../mkdocs/docs/images/json_syntax_number.png | Bin 37014 -> 0 bytes .../mkdocs/docs/images/json_syntax_number.svg | 66 + docs/mkdocs/docs/images/json_view_edit_0.svg | 57 + docs/mkdocs/docs/images/json_view_edit_1.svg | 57 + docs/mkdocs/docs/images/json_view_edit_2.svg | 70 + docs/mkdocs/docs/images/json_view_edit_3.svg | 80 + docs/mkdocs/docs/images/json_view_edit_4.svg | 126 ++ docs/mkdocs/docs/images/json_view_layout.svg | 182 +++ .../mkdocs/docs/images/json_view_lifetime.svg | 61 + docs/mkdocs/docs/images/memory_over_time.svg | 20 + .../docs/images/ordered_json_scaling.svg | 31 + docs/mkdocs/docs/images/qa_pipeline.svg | 240 +++ docs/mkdocs/docs/images/range-begin-end.svg | 473 +----- docs/mkdocs/docs/images/range-rbegin-rend.svg | 1296 +---------------- docs/mkdocs/docs/images/release_timeline.svg | 116 ++ docs/mkdocs/docs/images/roadmap_lanes.svg | 98 ++ docs/mkdocs/docs/images/wordmark-header.svg | 1 + docs/mkdocs/docs/index.md | 78 +- docs/mkdocs/docs/integration/cmake.md | 20 + docs/mkdocs/docs/integration/compile_times.md | 239 ++- docs/mkdocs/docs/integration/index.md | 50 +- .../docs/integration/migration_guide.md | 9 + .../docs/integration/package_managers.md | 7 + docs/mkdocs/docs/integration/pkg-config.md | 6 + docs/mkdocs/docs/js/external-links.js | 32 + docs/mkdocs/docs/js/fig-charts.js | 106 ++ docs/mkdocs/docs/js/fig-exceptions.js | 1 + docs/mkdocs/docs/js/fig-integration.js | 128 ++ docs/mkdocs/docs/js/fig-jsonview.js | 78 + docs/mkdocs/docs/js/fig-macros.js | 585 ++++++++ docs/mkdocs/docs/js/fig-pointer.js | 306 ++++ docs/mkdocs/docs/js/fig-sax.js | 248 ++++ docs/mkdocs/docs/js/mermaid-brand.js | 100 ++ docs/mkdocs/mkdocs.yml | 47 +- docs/mkdocs/overrides/404.html | 15 + docs/mkdocs/overrides/home.html | 34 + docs/mkdocs/scripts/figures/charts.py | 280 ++++ .../mkdocs/scripts/figures/chunk_buffering.py | 90 ++ docs/mkdocs/scripts/figures/exception_map.py | 168 +++ docs/mkdocs/scripts/figures/integration.py | 116 ++ .../figures/iterator_callback_figures.py | 103 ++ .../scripts/figures/json_pointer_flatten.py | 110 ++ .../scripts/figures/json_view_layout.py | 391 +++++ .../scripts/figures/memory_over_time.py | 81 ++ docs/mkdocs/scripts/figures/qa_pipeline.py | 196 +++ .../mkdocs/scripts/figures/railroad_number.py | 76 + .../scripts/figures/release_timeline.py | 127 ++ docs/mkdocs/scripts/slideshow/.gitignore | 1 + docs/mkdocs/scripts/slideshow/Makefile | 73 + docs/mkdocs/scripts/slideshow/README.md | 27 + docs/mkdocs/scripts/slideshow/build.py | 461 ++++++ docs/mkdocs/scripts/slideshow/slides.md | 412 ++++++ 417 files changed, 11100 insertions(+), 3041 deletions(-) create mode 100644 docs/mkdocs/docs/css/brand.css create mode 100644 docs/mkdocs/docs/css/fig-charts.css create mode 100644 docs/mkdocs/docs/css/fig-exceptions.css create mode 100644 docs/mkdocs/docs/css/fig-integration.css create mode 100644 docs/mkdocs/docs/css/fig-jsonview.css create mode 100644 docs/mkdocs/docs/css/fig-macros.css create mode 100644 docs/mkdocs/docs/css/fig-pointer.css create mode 100644 docs/mkdocs/docs/css/fig-sax.css create mode 100644 docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 create mode 100644 docs/mkdocs/docs/css/fonts/Inter.woff2 create mode 100644 docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 create mode 100644 docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 create mode 100644 docs/mkdocs/docs/css/pattern-on-blue.svg create mode 100644 docs/mkdocs/docs/images/binary_sizes_canada.svg create mode 100644 docs/mkdocs/docs/images/binary_sizes_citm_catalog.svg create mode 100644 docs/mkdocs/docs/images/binary_sizes_jeopardy.svg create mode 100644 docs/mkdocs/docs/images/binary_sizes_twitter.svg delete mode 100644 docs/mkdocs/docs/images/callback_events.png create mode 100644 docs/mkdocs/docs/images/callback_events.svg create mode 100644 docs/mkdocs/docs/images/chunk_buffering.svg create mode 100644 docs/mkdocs/docs/images/compile_times.svg create mode 100644 docs/mkdocs/docs/images/compile_times_modules.svg create mode 100644 docs/mkdocs/docs/images/compile_times_pch.svg create mode 100644 docs/mkdocs/docs/images/exception_map.svg create mode 100644 docs/mkdocs/docs/images/json-dark.svg create mode 100644 docs/mkdocs/docs/images/json-light.svg create mode 100644 docs/mkdocs/docs/images/json_pointer_flatten.svg delete mode 100644 docs/mkdocs/docs/images/json_syntax_number.png create mode 100644 docs/mkdocs/docs/images/json_syntax_number.svg create mode 100644 docs/mkdocs/docs/images/json_view_edit_0.svg create mode 100644 docs/mkdocs/docs/images/json_view_edit_1.svg create mode 100644 docs/mkdocs/docs/images/json_view_edit_2.svg create mode 100644 docs/mkdocs/docs/images/json_view_edit_3.svg create mode 100644 docs/mkdocs/docs/images/json_view_edit_4.svg create mode 100644 docs/mkdocs/docs/images/json_view_layout.svg create mode 100644 docs/mkdocs/docs/images/json_view_lifetime.svg create mode 100644 docs/mkdocs/docs/images/memory_over_time.svg create mode 100644 docs/mkdocs/docs/images/ordered_json_scaling.svg create mode 100644 docs/mkdocs/docs/images/qa_pipeline.svg create mode 100644 docs/mkdocs/docs/images/release_timeline.svg create mode 100644 docs/mkdocs/docs/images/roadmap_lanes.svg create mode 100644 docs/mkdocs/docs/images/wordmark-header.svg create mode 100644 docs/mkdocs/docs/js/external-links.js create mode 100644 docs/mkdocs/docs/js/fig-charts.js create mode 100644 docs/mkdocs/docs/js/fig-exceptions.js create mode 100644 docs/mkdocs/docs/js/fig-integration.js create mode 100644 docs/mkdocs/docs/js/fig-jsonview.js create mode 100644 docs/mkdocs/docs/js/fig-macros.js create mode 100644 docs/mkdocs/docs/js/fig-pointer.js create mode 100644 docs/mkdocs/docs/js/fig-sax.js create mode 100644 docs/mkdocs/docs/js/mermaid-brand.js create mode 100644 docs/mkdocs/overrides/404.html create mode 100644 docs/mkdocs/overrides/home.html create mode 100644 docs/mkdocs/scripts/figures/charts.py create mode 100644 docs/mkdocs/scripts/figures/chunk_buffering.py create mode 100644 docs/mkdocs/scripts/figures/exception_map.py create mode 100644 docs/mkdocs/scripts/figures/integration.py create mode 100644 docs/mkdocs/scripts/figures/iterator_callback_figures.py create mode 100644 docs/mkdocs/scripts/figures/json_pointer_flatten.py create mode 100644 docs/mkdocs/scripts/figures/json_view_layout.py create mode 100644 docs/mkdocs/scripts/figures/memory_over_time.py create mode 100644 docs/mkdocs/scripts/figures/qa_pipeline.py create mode 100644 docs/mkdocs/scripts/figures/railroad_number.py create mode 100644 docs/mkdocs/scripts/figures/release_timeline.py create mode 100644 docs/mkdocs/scripts/slideshow/.gitignore create mode 100644 docs/mkdocs/scripts/slideshow/Makefile create mode 100644 docs/mkdocs/scripts/slideshow/README.md create mode 100755 docs/mkdocs/scripts/slideshow/build.py create mode 100644 docs/mkdocs/scripts/slideshow/slides.md diff --git a/docs/mkdocs/docs/api/adl_serializer/from_json.md b/docs/mkdocs/docs/api/adl_serializer/from_json.md index 72df961f4..514b96bfd 100644 --- a/docs/mkdocs/docs/api/adl_serializer/from_json.md +++ b/docs/mkdocs/docs/api/adl_serializer/from_json.md @@ -30,7 +30,7 @@ class (either explicitly or via the conversion operators). ## Return value -1. (none) -- the converted value is written to the output parameter `val`. +1. (none) — the converted value is written to the output parameter `val`. 2. the JSON value `j` converted to `TargetType` ## Examples @@ -67,7 +67,9 @@ class (either explicitly or via the conversion operators). ## See also -- [to_json](to_json.md) +- [`to_json`](to_json.md) - convert a C++ value to JSON +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - write your own `from_json` and `to_json` functions +- [Converting values](../../features/conversions.md) - overview of `get`, `get_to` and implicit conversions ## Version history diff --git a/docs/mkdocs/docs/api/adl_serializer/index.md b/docs/mkdocs/docs/api/adl_serializer/index.md index e994a30cf..78f49f74e 100644 --- a/docs/mkdocs/docs/api/adl_serializer/index.md +++ b/docs/mkdocs/docs/api/adl_serializer/index.md @@ -33,10 +33,10 @@ struct adl_serializer { ## See also - [Arbitrary type conversions](../../features/arbitrary_types.md) - how to make your types convertible -- [**get**](../basic_json/get.md) / [**get_to**](../basic_json/get_to.md) - convert a JSON value to a C++ type -- [**basic_json**](../basic_json/basic_json.md) - construct a JSON value from a C++ value -- [**NLOHMANN_DEFINE_TYPE_INTRUSIVE**](../macros/nlohmann_define_type_intrusive.md) - generate the conversion functions for a type -- [Specializing enum conversion](../../features/enum_conversion.md) +- [`get`](../basic_json/get.md) - / [`get_to`](../basic_json/get_to.md) - convert a JSON value to a C++ type +- [`basic_json`](../basic_json/basic_json.md) - construct a JSON value from a C++ value +- [`NLOHMANN_DEFINE_TYPE_INTRUSIVE`](../macros/nlohmann_define_type_intrusive.md) - generate the conversion functions for a type +- [Specializing enum conversion](../../features/enum_conversion.md) - serializing enums as JSON strings ## Version history diff --git a/docs/mkdocs/docs/api/adl_serializer/to_json.md b/docs/mkdocs/docs/api/adl_serializer/to_json.md index da9765186..5dcbc025f 100644 --- a/docs/mkdocs/docs/api/adl_serializer/to_json.md +++ b/docs/mkdocs/docs/api/adl_serializer/to_json.md @@ -36,7 +36,9 @@ This function is usually called by the constructors of the [basic_json](../basic ## See also -- [from_json](from_json.md) +- [`from_json`](from_json.md) - convert JSON to a C++ value +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - write your own `from_json` and `to_json` functions +- [Converting values](../../features/conversions.md) - overview of `get`, `get_to` and implicit conversions ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/accept.md b/docs/mkdocs/docs/api/basic_json/accept.md index 345d0d507..ad46a0ea7 100644 --- a/docs/mkdocs/docs/api/basic_json/accept.md +++ b/docs/mkdocs/docs/api/basic_json/accept.md @@ -148,15 +148,20 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by ## See also -- [parse](parse.md) - deserialize from a compatible input -- [sax_parse](sax_parse.md) - parse input using the SAX interface -- [operator>>](../operator_gtgt.md) - deserialize from stream +- [`parse`](parse.md) - deserialize from a compatible input +- [`sax_parse`](sax_parse.md) - parse input using the SAX interface +- [`operator>>`](../operator_gtgt.md) - deserialize from stream - [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of treating it as end of input - [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time - [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio functions +- [Parsing](../../features/parsing/index.md) - overview of the parsing functions +- [Comments](../../features/comments.md) - ignore `//` and `/* */` comments while parsing +- [Trailing Commas](../../features/trailing_commas.md) - accept trailing commas in arrays and objects +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input +- [Parsing Untrusted Input](../../features/parsing/untrusted_input.md) - limits and checks for input you do not control ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/array.md b/docs/mkdocs/docs/api/basic_json/array.md index cbc5a723e..38540be34 100644 --- a/docs/mkdocs/docs/api/basic_json/array.md +++ b/docs/mkdocs/docs/api/basic_json/array.md @@ -29,9 +29,9 @@ Linear in the size of `init`. This function is only needed to express two edge cases that cannot be realized with the initializer list constructor ([`basic_json(initializer_list_t, bool, value_t)`](basic_json.md)). These cases are: -1. creating an array whose elements are all pairs whose first element is a string -- in this case, the initializer list +1. creating an array whose elements are all pairs whose first element is a string — in this case, the initializer list constructor would create an object, taking the first elements as keys -2. creating an empty array -- passing the empty initializer list to the initializer list constructor yields an empty +2. creating an empty array — passing the empty initializer list to the initializer list constructor yields an empty object ## Examples diff --git a/docs/mkdocs/docs/api/basic_json/array_t.md b/docs/mkdocs/docs/api/basic_json/array_t.md index f621f7b58..f67980416 100644 --- a/docs/mkdocs/docs/api/basic_json/array_t.md +++ b/docs/mkdocs/docs/api/basic_json/array_t.md @@ -69,10 +69,12 @@ Arrays are stored as pointers in a `basic_json` type. That is, for any access to ## See also -- [object_t](object_t.md) the type used to store JSON objects -- [binary_t](binary_t.md) the type used to store binary values -- [is_array](is_array.md) checks whether the JSON value is an array -- [max_size](max_size.md) returns the maximum possible number of elements +- [`object_t`](object_t.md) - the type used to store JSON objects +- [`binary_t`](binary_t.md) - the type used to store binary values +- [`is_array`](is_array.md) - checks whether the JSON value is an array +- [`max_size`](max_size.md) - returns the maximum possible number of elements +- [Types](../../features/types/index.md) - how arrays are stored +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `ArrayType` template parameter ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/as_base_class.md b/docs/mkdocs/docs/api/basic_json/as_base_class.md index 769707140..912976010 100644 --- a/docs/mkdocs/docs/api/basic_json/as_base_class.md +++ b/docs/mkdocs/docs/api/basic_json/as_base_class.md @@ -46,7 +46,8 @@ The function is equivalent to `static_cast(j)` (or `static_c ## See also -- [json_base_class_t](json_base_class_t.md) - type of the custom base class +- [`json_base_class_t`](json_base_class_t.md) - type of the custom base class +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `CustomBaseClass` template parameter ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/at.md b/docs/mkdocs/docs/api/basic_json/at.md index 194b123ce..f707b9835 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -230,10 +230,12 @@ Strong exception safety: if an exception occurs, the original value stays intact ## See also -- documentation on [checked access](../../features/element_access/checked_access.md) -- [`operator[]`](operator%5B%5D.md) for unchecked access by reference -- [`value`](value.md) for access with default value -- [basic_json_view::at](../basic_json_view/at.md) - the same access on a zero-copy view +- [Checked access](../../features/element_access/checked_access.md) - element access with range checking +- [`operator[]`](operator%5B%5D.md) - unchecked access by reference +- [`value`](value.md) - access with default value +- [`basic_json_view::at`](../basic_json_view/at.md) - the same access on a zero-copy view +- [Element Access](../../features/element_access/index.md) - overview of all ways to access elements +- [JSON Pointer](../../features/json_pointer.md) - access nested values with a pointer ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/back.md b/docs/mkdocs/docs/api/basic_json/back.md index 32066d8c7..e7b505ffc 100644 --- a/docs/mkdocs/docs/api/basic_json/back.md +++ b/docs/mkdocs/docs/api/basic_json/back.md @@ -57,8 +57,9 @@ Constant. ## See also -- [front](front.md) to access the first element -- [basic_json_view::back](../basic_json_view/back.md) - the same access on a zero-copy view +- [`front`](front.md) - to access the first element +- [`basic_json_view::back`](../basic_json_view/back.md) - the same access on a zero-copy view +- [Element Access](../../features/element_access/index.md) - overview of all ways to access elements ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md index d3df598ad..dadb2d887 100644 --- a/docs/mkdocs/docs/api/basic_json/basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/basic_json.md @@ -52,7 +52,7 @@ basic_json(basic_json&& other) noexcept; The postcondition of this constructor can be restored by calling [`clear()`](clear.md). 2. Create a `#!json null` JSON value. It either takes a null pointer as parameter (explicitly creating `#!json null`) - or no parameter (implicitly creating `#!json null`). The passed null pointer itself is not read -- it is only used to + or no parameter (implicitly creating `#!json null`). The passed null pointer itself is not read — it is only used to choose the right constructor. 3. This is a "catch all" constructor for all compatible JSON types; that is, types for which a `to_json()` method @@ -113,19 +113,22 @@ basic_json(basic_json&& other) noexcept; ```mermaid flowchart TD - A(["initializer_list init"]) --> B{"empty, or every element is a 2-element
array whose first element is a string?"} - B -->|"yes"| C{"type_deduction"} - B -->|"no"| D{"type_deduction"} + A(["initializer_list init"]) --> B{{"empty, or every element is a 2-element
array whose first element is a string?"}} + B -->|"yes"| C{{"type_deduction"}} + B -->|"no"| D{{"type_deduction"}} C -->|"true"| OBJ["create object"] - C -->|"false"| E{"manual_type"} + C -->|"false"| E{{"manual_type"}} E -->|"object"| OBJ E -->|"array"| ARR["create array"] D -->|"true"| ARR - D -->|"false"| F{"manual_type"} + D -->|"false"| F{{"manual_type"}} F -->|"array"| ARR F -->|"object"| ERR["throw type_error.301"] ``` +

Decision flow for an initializer list: hexagons are checks, edge labels are their + outcomes, and rectangles are the resulting value or exception.

+ The rules aim to create the best fit between a C++ initializer list and JSON values. The rationale is as follows: 1. The empty initializer list is written as `#!cpp {}` which is exactly an empty JSON object. @@ -473,12 +476,13 @@ basic_json(basic_json&& other) noexcept; ## See also -- [array](array.md) create a JSON array value, forcing array creation from an initializer list even when it looks like +- [`array`](array.md) - create a JSON array value, forcing array creation from an initializer list even when it looks like an object -- [object](object.md) create a JSON object value, forcing object creation from an initializer list -- [binary](binary.md) create a JSON binary array value -- [operator=](operator=.md) copy assignment operator +- [`object`](object.md) - create a JSON object value, forcing object creation from an initializer list +- [`binary`](binary.md) - create a JSON binary array value +- [`operator=`](operator=.md) - copy assignment operator - [Creating JSON values](../../features/creating_values.md) - the article on creating JSON values +- [Converting values](../../features/conversions.md) - which C++ types can be converted ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/begin.md b/docs/mkdocs/docs/api/basic_json/begin.md index 72583ce57..15ca5bf32 100644 --- a/docs/mkdocs/docs/api/basic_json/begin.md +++ b/docs/mkdocs/docs/api/basic_json/begin.md @@ -7,7 +7,12 @@ const_iterator begin() const noexcept; Returns an iterator to the first element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
+ +

`begin()` points to the first element and `end()` to the position after the last element; +the dashed cell is the past-the-last position, which must not be dereferenced.

## Return value @@ -39,12 +44,12 @@ Constant. ## See also -- [end](end.md) returns an iterator to one past the last element -- [cbegin](cbegin.md) returns a const iterator to the first element -- [rbegin](rbegin.md) returns a reverse iterator to the last element -- [items](items.md) returns an iteration proxy to access keys and values during range-based for loops +- [`end`](end.md) - returns an iterator to one past the last element +- [`cbegin`](cbegin.md) - returns a const iterator to the first element +- [`rbegin`](rbegin.md) - returns a reverse iterator to the last element +- [`items`](items.md) - returns an iteration proxy to access keys and values during range-based for loops - [Iterators](../../features/iterators.md) - the article on iterators -- [basic_json_view::begin](../basic_json_view/begin.md) - the same iteration on a zero-copy view (in document order, +- [`basic_json_view::begin`](../basic_json_view/begin.md) - the same iteration on a zero-copy view (in document order, not sorted by key) ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/binary.md b/docs/mkdocs/docs/api/basic_json/binary.md index b19b0af27..1871c2c8f 100644 --- a/docs/mkdocs/docs/api/basic_json/binary.md +++ b/docs/mkdocs/docs/api/basic_json/binary.md @@ -63,10 +63,11 @@ initialization of a binary array type, for backwards compatibility and so it doe ## See also -- [binary_t](binary_t.md) type for binary values -- [get_binary](get_binary.md) get a reference to the stored binary value -- [is_binary](is_binary.md) return whether the value is binary -- [byte_container_with_subtype](../byte_container_with_subtype/index.md) container for binary values with subtype +- [Binary formats](../../features/binary_formats/index.md) - formats that can store binary values +- [`binary_t`](binary_t.md) - type for binary values +- [`get_binary`](get_binary.md) - get a reference to the stored binary value +- [`is_binary`](is_binary.md) - return whether the value is binary +- [`byte_container_with_subtype`](../byte_container_with_subtype/index.md) - container for binary values with subtype - [Binary Values](../../features/binary_values.md) - the article on binary values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/binary_t.md b/docs/mkdocs/docs/api/basic_json/binary_t.md index 2e9517429..f673335b1 100644 --- a/docs/mkdocs/docs/api/basic_json/binary_t.md +++ b/docs/mkdocs/docs/api/basic_json/binary_t.md @@ -41,7 +41,7 @@ represent a byte array in modern C++. [`byte_container_with_subtype`](../byte_container_with_subtype/index.md) derives directly from it. Its `value_type` must additionally be exactly one byte wide (e.g., `std::uint8_t`/`char`/`std::byte`): the binary serializers (CBOR, MessagePack, BSON, UBJSON) read and write the container's raw bytes via - `reinterpret_cast`, which is only correct for byte-sized elements -- a container like + `reinterpret_cast`, which is only correct for byte-sized elements — a container like `#!cpp std::vector` will not work as `BinaryType`. The elements must be stored contiguously, and the binary readers additionally require `resize()` and `operator[]`. See [Template Parameter Requirements](../../features/types/template_parameters.md#binarytype) for the full list. @@ -118,7 +118,9 @@ type `#!cpp binary_t*` must be dereferenced. ## See also -- [byte_container_with_subtype](../byte_container_with_subtype/index.md) +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [Binary formats](../../features/binary_formats/index.md) - formats that can store binary values +- [`byte_container_with_subtype`](../byte_container_with_subtype/index.md) - container type used by binary values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/boolean_t.md b/docs/mkdocs/docs/api/basic_json/boolean_t.md index 2fd1beb4e..0690b86d6 100644 --- a/docs/mkdocs/docs/api/basic_json/boolean_t.md +++ b/docs/mkdocs/docs/api/basic_json/boolean_t.md @@ -47,7 +47,9 @@ Boolean values are stored directly inside a `basic_json` type. ## See also -- [is_boolean](is_boolean.md) checks whether the JSON value is a boolean +- [`is_boolean`](is_boolean.md) - checks whether the JSON value is a boolean +- [Types](../../features/types/index.md) - how booleans are stored +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `BooleanType` template parameter ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/cbegin.md b/docs/mkdocs/docs/api/basic_json/cbegin.md index b4c3e588f..6436e9d1a 100644 --- a/docs/mkdocs/docs/api/basic_json/cbegin.md +++ b/docs/mkdocs/docs/api/basic_json/cbegin.md @@ -6,7 +6,12 @@ const_iterator cbegin() const noexcept; Returns an iterator to the first element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
+ +

`cbegin()` points to the first element, like `begin()`, and `end()` to the position after +the last element; the dashed cell is the past-the-last position, which must not be dereferenced.

## Return value @@ -38,11 +43,11 @@ Constant. ## See also -- [begin](begin.md) returns an iterator to the first element -- [cend](cend.md) returns a const iterator to one past the last element -- [crbegin](crbegin.md) returns a const reverse iterator to the last element +- [`begin`](begin.md) - returns an iterator to the first element +- [`cend`](cend.md) - returns a const iterator to one past the last element +- [`crbegin`](crbegin.md) - returns a const reverse iterator to the last element - [Iterators](../../features/iterators.md) - the article on iterators -- [basic_json_view::cbegin](../basic_json_view/cbegin.md) - the same iteration on a zero-copy view +- [`basic_json_view::cbegin`](../basic_json_view/cbegin.md) - the same iteration on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md index 528fb199e..eb92ce03f 100644 --- a/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md @@ -42,8 +42,9 @@ store ## See also -- [from_cbor](from_cbor.md) deserializes a JSON value from CBOR -- [input_format_t](input_format_t.md) the enumeration of supported input formats +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`from_cbor`](from_cbor.md) - deserializes a JSON value from CBOR +- [`input_format_t`](input_format_t.md) - the enumeration of supported input formats - [CBOR](../../features/binary_formats/cbor.md) - the article on the CBOR format ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/cend.md b/docs/mkdocs/docs/api/basic_json/cend.md index fb568762a..ea89f43ce 100644 --- a/docs/mkdocs/docs/api/basic_json/cend.md +++ b/docs/mkdocs/docs/api/basic_json/cend.md @@ -6,7 +6,12 @@ const_iterator cend() const noexcept; Returns an iterator to one past the last element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
+ +

`cend()` points to the position after the last element, like `end()`, and `begin()` to +the first element; the dashed cell is the past-the-last position, which must not be dereferenced.

## Return value @@ -38,11 +43,11 @@ Constant. ## See also -- [end](end.md) returns an iterator to one past the last element -- [cbegin](cbegin.md) returns a const iterator to the first element -- [crend](crend.md) returns a const reverse iterator to one before the first element +- [`end`](end.md) - returns an iterator to one past the last element +- [`cbegin`](cbegin.md) - returns a const iterator to the first element +- [`crend`](crend.md) - returns a const reverse iterator to one before the first element - [Iterators](../../features/iterators.md) - the article on iterators -- [basic_json_view::cend](../basic_json_view/cend.md) - the same iteration on a zero-copy view +- [`basic_json_view::cend`](../basic_json_view/cend.md) - the same iteration on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/clear.md b/docs/mkdocs/docs/api/basic_json/clear.md index 92043e978..087cd83f4 100644 --- a/docs/mkdocs/docs/api/basic_json/clear.md +++ b/docs/mkdocs/docs/api/basic_json/clear.md @@ -54,8 +54,9 @@ All iterators, pointers, and references related to this container are invalidate ## See also -- [erase](erase.md) removes elements from a JSON value -- [empty](empty.md) checks whether the JSON value has no elements +- [`erase`](erase.md) - removes elements from a JSON value +- [`empty`](empty.md) - checks whether the JSON value has no elements +- [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/contains.md b/docs/mkdocs/docs/api/basic_json/contains.md index 5ed088a55..65ccca142 100644 --- a/docs/mkdocs/docs/api/basic_json/contains.md +++ b/docs/mkdocs/docs/api/basic_json/contains.md @@ -141,9 +141,10 @@ Strong exception safety: if an exception occurs, the original value stays intact ## See also -- [find](find.md) find a value in an object -- [count](count.md) returns the number of occurrences of a key -- [basic_json_view::contains](../basic_json_view/contains.md) - the same check on a zero-copy view +- [`find`](find.md) - find a value in an object +- [`count`](count.md) - returns the number of occurrences of a key +- [`basic_json_view::contains`](../basic_json_view/contains.md) - the same check on a zero-copy view +- [Element Access](../../features/element_access/index.md#does-this-key-exist) - how to test whether a key exists ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/count.md b/docs/mkdocs/docs/api/basic_json/count.md index 580b79884..1265fed90 100644 --- a/docs/mkdocs/docs/api/basic_json/count.md +++ b/docs/mkdocs/docs/api/basic_json/count.md @@ -85,9 +85,10 @@ Logarithmic in the size of the JSON object. ## See also -- [find](find.md) find a value in an object -- [contains](contains.md) checks whether a key exists -- [basic_json_view::count](../basic_json_view/count.md) - the same check on a zero-copy view +- [`find`](find.md) - find a value in an object +- [`contains`](contains.md) - checks whether a key exists +- [`basic_json_view::count`](../basic_json_view/count.md) - the same check on a zero-copy view +- [Element Access](../../features/element_access/index.md#does-this-key-exist) - how to test whether a key exists ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/crbegin.md b/docs/mkdocs/docs/api/basic_json/crbegin.md index 545f86044..8365e4d19 100644 --- a/docs/mkdocs/docs/api/basic_json/crbegin.md +++ b/docs/mkdocs/docs/api/basic_json/crbegin.md @@ -6,7 +6,13 @@ const_reverse_iterator crbegin() const noexcept; Returns an iterator to the reverse-beginning; that is, the last element. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
+ +

`crbegin()` points to the last element, like `rbegin()`, and `rend()` to the position +before the first element. The upper row is the reversed sequence and the lower row the forward sequence; dashed arrows +link each reverse iterator to the forward iterator it stores, and dashed cells are past-the-last positions.

## Return value @@ -38,9 +44,9 @@ Constant. ## See also -- [crend](crend.md) returns a const reverse iterator to one before the first element -- [rbegin](rbegin.md) returns a reverse iterator to the last element -- [cbegin](cbegin.md) returns a const iterator to the first element +- [`crend`](crend.md) - returns a const reverse iterator to one before the first element +- [`rbegin`](rbegin.md) - returns a reverse iterator to the last element +- [`cbegin`](cbegin.md) - returns a const iterator to the first element - [Iterators](../../features/iterators.md) - the article on iterators ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/crend.md b/docs/mkdocs/docs/api/basic_json/crend.md index 56cc40c52..12c100d4b 100644 --- a/docs/mkdocs/docs/api/basic_json/crend.md +++ b/docs/mkdocs/docs/api/basic_json/crend.md @@ -7,7 +7,13 @@ const_reverse_iterator crend() const noexcept; Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder, attempting to access it results in undefined behavior. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
+ +

`crend()` points to the position before the first element, like `rend()`, and `rbegin()` +to the last element. The upper row is the reversed sequence and the lower row the forward sequence; dashed arrows link +each reverse iterator to the forward iterator it stores, and dashed cells are past-the-last positions.

## Return value @@ -39,9 +45,9 @@ Constant. ## See also -- [crbegin](crbegin.md) returns a const reverse iterator to the last element -- [rend](rend.md) returns a reverse iterator to one before the first element -- [cend](cend.md) returns a const iterator to one past the last element +- [`crbegin`](crbegin.md) - returns a const reverse iterator to the last element +- [`rend`](rend.md) - returns a reverse iterator to one before the first element +- [`cend`](cend.md) - returns a const iterator to one past the last element - [Iterators](../../features/iterators.md) - the article on iterators ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md b/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md index 2c3a50217..9372a286d 100644 --- a/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md +++ b/docs/mkdocs/docs/api/basic_json/default_object_comparator_t.md @@ -32,8 +32,10 @@ The actual comparator used depends on [`object_t`](object_t.md) and can be obtai ## See also -- [object_comparator_t](object_comparator_t.md) the comparator actually used by `object_t` -- [object_t](object_t.md) the type used to store JSON objects +- [`object_comparator_t`](object_comparator_t.md) - the comparator actually used by `object_t` +- [`object_t`](object_t.md) - the type used to store JSON objects +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `ObjectType` template parameter +- [Object order](../../features/object_order.md) - preserve the insertion order of keys ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/diff.md b/docs/mkdocs/docs/api/basic_json/diff.md index d45a9a094..655261a3d 100644 --- a/docs/mkdocs/docs/api/basic_json/diff.md +++ b/docs/mkdocs/docs/api/basic_json/diff.md @@ -92,11 +92,12 @@ operations times the length of their paths, so a small change deep inside a valu ## See also -- [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) -- [patch](patch.md) applies a JSON Patch -- [patch_inplace](patch_inplace.md) applies a JSON Patch in place -- [merge_patch](merge_patch.md) applies a JSON Merge Patch -- [merge_diff](merge_diff.md) creates a diff as a JSON Merge Patch +- [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - specification of JSON Patch +- [`patch`](patch.md) - applies a JSON Patch +- [`patch_inplace`](patch_inplace.md) - applies a JSON Patch in place +- [`merge_patch`](merge_patch.md) - applies a JSON Merge Patch +- [`merge_diff`](merge_diff.md) - creates a diff as a JSON Merge Patch +- [JSON Patch and Diff](../../features/json_patch.md) - the article on JSON Patch ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/dump.md b/docs/mkdocs/docs/api/basic_json/dump.md index 23c34cd0c..a2fce5536 100644 --- a/docs/mkdocs/docs/api/basic_json/dump.md +++ b/docs/mkdocs/docs/api/basic_json/dump.md @@ -89,9 +89,9 @@ Binary values are serialized as an object containing two keys: ## See also -- [to_string](to_string.md) returns a string representation of a JSON value -- [operator<<](../operator_ltlt.md) serialize to stream -- [`basic_json_view::dump`](../basic_json_view/dump.md) the corresponding function of `basic_json_view`, serializing +- [`to_string`](to_string.md) - returns a string representation of a JSON value +- [`operator<<`](../operator_ltlt.md) - serialize to stream +- [`basic_json_view::dump`](../basic_json_view/dump.md) - the corresponding function of `basic_json_view`, serializing directly from a flat index without building a `basic_json` value - [Serialization](../../features/serialization.md) - the serialization article diff --git a/docs/mkdocs/docs/api/basic_json/emplace.md b/docs/mkdocs/docs/api/basic_json/emplace.md index b1fd856ae..b4c8bb0bb 100644 --- a/docs/mkdocs/docs/api/basic_json/emplace.md +++ b/docs/mkdocs/docs/api/basic_json/emplace.md @@ -63,8 +63,8 @@ Logarithmic in the size of the container, O(log(`size()`)). ## See also -- [emplace_back](emplace_back.md) add a value to an array -- [insert](insert.md) add values to an array/object +- [`emplace_back`](emplace_back.md) - add a value to an array +- [`insert`](insert.md) - add values to an array/object - [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/emplace_back.md b/docs/mkdocs/docs/api/basic_json/emplace_back.md index 38a6d5c3d..d6ea4cd01 100644 --- a/docs/mkdocs/docs/api/basic_json/emplace_back.md +++ b/docs/mkdocs/docs/api/basic_json/emplace_back.md @@ -61,8 +61,8 @@ Amortized constant. ## See also -- [operator+=](operator+=.md) add a value to an array/object -- [push_back](push_back.md) add a value to an array/object +- [`operator+=`](operator+=.md) - add a value to an array/object +- [`push_back`](push_back.md) - add a value to an array/object - [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/empty.md b/docs/mkdocs/docs/api/basic_json/empty.md index 1f096c956..06b51932d 100644 --- a/docs/mkdocs/docs/api/basic_json/empty.md +++ b/docs/mkdocs/docs/api/basic_json/empty.md @@ -41,7 +41,7 @@ bool empty() const noexcept ## Notes -This function does not return whether a string stored as JSON value is empty -- it returns whether the JSON container +This function does not return whether a string stored as JSON value is empty — it returns whether the JSON container itself is empty which is `#!cpp false` in the case of a string. ## Examples @@ -62,9 +62,9 @@ itself is empty which is `#!cpp false` in the case of a string. ## See also -- [size](size.md) returns the number of elements -- [clear](clear.md) clears the content and resets the value to the default value -- [basic_json_view::empty](../basic_json_view/empty.md) - the same check on a zero-copy view +- [`size`](size.md) - returns the number of elements +- [`clear`](clear.md) - clears the content and resets the value to the default value +- [`basic_json_view::empty`](../basic_json_view/empty.md) - the same check on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/end.md b/docs/mkdocs/docs/api/basic_json/end.md index e94ec78a2..13459beb6 100644 --- a/docs/mkdocs/docs/api/basic_json/end.md +++ b/docs/mkdocs/docs/api/basic_json/end.md @@ -7,7 +7,12 @@ const_iterator end() const noexcept; Returns an iterator to one past the last element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
+ +

`end()` points to the position after the last element and `begin()` to the first element; +the dashed cell is the past-the-last position, which must not be dereferenced.

## Return value @@ -39,11 +44,11 @@ Constant. ## See also -- [begin](begin.md) returns an iterator to the first element -- [cend](cend.md) returns a const iterator to one past the last element -- [rend](rend.md) returns a reverse iterator to one before the first element +- [`begin`](begin.md) - returns an iterator to the first element +- [`cend`](cend.md) - returns a const iterator to one past the last element +- [`rend`](rend.md) - returns a reverse iterator to one before the first element - [Iterators](../../features/iterators.md) - the article on iterators -- [basic_json_view::end](../basic_json_view/end.md) - the same iteration on a zero-copy view +- [`basic_json_view::end`](../basic_json_view/end.md) - the same iteration on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/end_pos.md b/docs/mkdocs/docs/api/basic_json/end_pos.md index 95b07297f..7ec88996c 100644 --- a/docs/mkdocs/docs/api/basic_json/end_pos.md +++ b/docs/mkdocs/docs/api/basic_json/end_pos.md @@ -60,8 +60,9 @@ Constant. ## See also -- [start_pos](start_pos.md) to access the start position -- [JSON_DIAGNOSTIC_POSITIONS](../macros/json_diagnostic_positions.md) for an overview of the diagnostic positions +- [`start_pos`](start_pos.md) - to access the start position +- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - overview of the supported conversions +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/erase.md b/docs/mkdocs/docs/api/basic_json/erase.md index 1782b6162..68eccc5a3 100644 --- a/docs/mkdocs/docs/api/basic_json/erase.md +++ b/docs/mkdocs/docs/api/basic_json/erase.md @@ -204,8 +204,8 @@ Strong exception safety: if an exception occurs, the original value stays intact ## See also -- [clear](clear.md) clears the contents -- [insert](insert.md) add values to an array/object +- [`clear`](clear.md) - clears the contents +- [`insert`](insert.md) - add values to an array/object - [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/error_handler_t.md b/docs/mkdocs/docs/api/basic_json/error_handler_t.md index a80f74a55..9dd205e1d 100644 --- a/docs/mkdocs/docs/api/basic_json/error_handler_t.md +++ b/docs/mkdocs/docs/api/basic_json/error_handler_t.md @@ -61,7 +61,7 @@ keep ## See also -- [dump](dump.md) serializes a JSON value, with an `error_handler_t` parameter to configure invalid UTF-8 handling +- [`dump`](dump.md) - serializes a JSON value, with an `error_handler_t` parameter to configure invalid UTF-8 handling - [Handling invalid UTF-8](../../features/serialization.md#handling-invalid-utf-8) - the article on handling invalid UTF-8 ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/exception.md b/docs/mkdocs/docs/api/basic_json/exception.md index 88cf51b1f..7b3209e48 100644 --- a/docs/mkdocs/docs/api/basic_json/exception.md +++ b/docs/mkdocs/docs/api/basic_json/exception.md @@ -40,6 +40,9 @@ classDiagram style json_exception fill:#CCCCFF ``` +

Class hierarchy of the library's exceptions; arrows point from a derived class to its +base class, and the highlighted box is `basic_json::exception`.

+ Subclasses: - [`parse_error`](parse_error.md) for exceptions indicating a parse error @@ -80,7 +83,11 @@ constructor. ## See also -[List of exceptions](../../home/exceptions.md) +- [List of exceptions](../../home/exceptions.md) - overview of all exceptions thrown by the library +- [Working Without Exceptions](../../features/no_exceptions.md) - using the library without exceptions +- [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) - extended exception messages with the path of the failing value +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - parsing alternatives that do not throw +- [Debugging](../../home/debugging.md) - debugging aids ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/find.md b/docs/mkdocs/docs/api/basic_json/find.md index 8883dc398..4b2018569 100644 --- a/docs/mkdocs/docs/api/basic_json/find.md +++ b/docs/mkdocs/docs/api/basic_json/find.md @@ -91,10 +91,11 @@ Logarithmic in the size of the JSON object. ## See also -- [count](count.md) returns the number of occurrences of a key -- [contains](contains.md) checks whether a key exists -- [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view (a duplicate key resolves +- [`count`](count.md) - returns the number of occurrences of a key +- [`contains`](contains.md) - checks whether a key exists +- [`basic_json_view::find`](../basic_json_view/find.md) - the same lookup on a zero-copy view (a duplicate key resolves to the first member, whereas `parse()` keeps the last) +- [Element Access](../../features/element_access/index.md#does-this-key-exist) - how to test whether a key exists ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/flatten.md b/docs/mkdocs/docs/api/basic_json/flatten.md index a232a538a..6c52010b8 100644 --- a/docs/mkdocs/docs/api/basic_json/flatten.md +++ b/docs/mkdocs/docs/api/basic_json/flatten.md @@ -58,7 +58,8 @@ Empty objects and arrays are flattened to `#!json null` and will not be reconstr ## See also -- [unflatten](unflatten.md) the reverse function +- [`unflatten`](unflatten.md) - the reverse function +- [JSON Pointer](../../features/json_pointer.md#flatten-unflatten) - the article on JSON Pointer and flattening ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/format_as.md b/docs/mkdocs/docs/api/basic_json/format_as.md index 1d20416c3..3c01bdf03 100644 --- a/docs/mkdocs/docs/api/basic_json/format_as.md +++ b/docs/mkdocs/docs/api/basic_json/format_as.md @@ -86,8 +86,8 @@ std::string format_as(const BasicJsonType& j) ## See also -- [dump](dump.md) -- [std::formatter](std_formatter.md) - the `std::format` (C++20) equivalent +- [`dump`](dump.md) - serialize a JSON value to a string +- [`std::formatter`](std_formatter.md) - the `std::format` (C++20) equivalent - [Serialization](../../features/serialization.md) - the serialization article ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/from_bjdata.md b/docs/mkdocs/docs/api/basic_json/from_bjdata.md index 2b697711c..db4a56854 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/from_bjdata.md @@ -110,12 +110,16 @@ Linear in the size of the input. ## See also -- [to_bjdata](to_bjdata.md) create a BJData 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_bon8](from_bon8.md) create a JSON value from an input in BON8 format +- [BJData](../../features/binary_formats/bjdata.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Parsing untrusted input](../../features/parsing/untrusted_input.md) - limits and safe handling of hostile data +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`to_bjdata`](to_bjdata.md) - create a BJData 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_bon8`](from_bon8.md) - create a JSON value from an input in BON8 format ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/from_bon8.md b/docs/mkdocs/docs/api/basic_json/from_bon8.md index cd0bac558..c3cf9f18a 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/from_bon8.md @@ -101,12 +101,15 @@ Linear in the size of the input. ## 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 +- [BON8](../../features/binary_formats/bon8.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Parsing untrusted input](../../features/parsing/untrusted_input.md) - limits and safe handling of hostile data +- [`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 diff --git a/docs/mkdocs/docs/api/basic_json/from_bson.md b/docs/mkdocs/docs/api/basic_json/from_bson.md index c04933eef..2f121c987 100644 --- a/docs/mkdocs/docs/api/basic_json/from_bson.md +++ b/docs/mkdocs/docs/api/basic_json/from_bson.md @@ -109,13 +109,17 @@ Linear in the size of the input. ## See also -- [BSON specification](http://bsonspec.org/spec.html) -- [to_bson](to_bson.md) for the analogous serialization -- [from_cbor](from_cbor.md) for the related CBOR format -- [from_msgpack](from_msgpack.md) for the related MessagePack format -- [from_ubjson](from_ubjson.md) for the related UBJSON format -- [from_bjdata](from_bjdata.md) for the related BJData format -- [from_bon8](from_bon8.md) for the related BON8 format +- [BSON](../../features/binary_formats/bson.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Parsing untrusted input](../../features/parsing/untrusted_input.md) - limits and safe handling of hostile data +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [BSON specification](http://bsonspec.org/spec.html) - specification of the BSON format +- [`to_bson`](to_bson.md) - the analogous serialization +- [`from_cbor`](from_cbor.md) - the related CBOR format +- [`from_msgpack`](from_msgpack.md) - the related MessagePack format +- [`from_ubjson`](from_ubjson.md) - the related UBJSON format +- [`from_bjdata`](from_bjdata.md) - the related BJData format +- [`from_bon8`](from_bon8.md) - the related BON8 format ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/from_cbor.md b/docs/mkdocs/docs/api/basic_json/from_cbor.md index 1c14d550d..78dc695fc 100644 --- a/docs/mkdocs/docs/api/basic_json/from_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/from_cbor.md @@ -122,12 +122,16 @@ Linear in the size of the input. ## See also -- [to_cbor](to_cbor.md) create a CBOR serialization of a JSON value -- [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 -- [from_bon8](from_bon8.md) create a JSON value from an input in BON8 format +- [CBOR](../../features/binary_formats/cbor.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Parsing untrusted input](../../features/parsing/untrusted_input.md) - limits and safe handling of hostile data +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`to_cbor`](to_cbor.md) - create a CBOR serialization of a JSON value +- [`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 +- [`from_bon8`](from_bon8.md) - create a JSON value from an input in BON8 format ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/from_msgpack.md b/docs/mkdocs/docs/api/basic_json/from_msgpack.md index 51e23de7b..7ba1a4904 100644 --- a/docs/mkdocs/docs/api/basic_json/from_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/from_msgpack.md @@ -107,12 +107,16 @@ Linear in the size of the input. ## See also -- [to_msgpack](to_msgpack.md) create a MessagePack serialization of a JSON value -- [from_cbor](from_cbor.md) create a JSON value from an input in CBOR 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 -- [from_bon8](from_bon8.md) create a JSON value from an input in BON8 format +- [MessagePack](../../features/binary_formats/messagepack.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Parsing untrusted input](../../features/parsing/untrusted_input.md) - limits and safe handling of hostile data +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`to_msgpack`](to_msgpack.md) - create a MessagePack serialization of a JSON value +- [`from_cbor`](from_cbor.md) - create a JSON value from an input in CBOR 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 +- [`from_bon8`](from_bon8.md) - create a JSON value from an input in BON8 format ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/from_ubjson.md b/docs/mkdocs/docs/api/basic_json/from_ubjson.md index ca947ef05..d3601c63a 100644 --- a/docs/mkdocs/docs/api/basic_json/from_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/from_ubjson.md @@ -114,12 +114,16 @@ Linear in the size of the input. ## See also -- [to_ubjson](to_ubjson.md) create a UBJSON 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_bjdata](from_bjdata.md) create a JSON value from an input in BJData format -- [from_bon8](from_bon8.md) create a JSON value from an input in BON8 format +- [UBJSON](../../features/binary_formats/ubjson.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Parsing untrusted input](../../features/parsing/untrusted_input.md) - limits and safe handling of hostile data +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`to_ubjson`](to_ubjson.md) - create a UBJSON 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_bjdata`](from_bjdata.md) - create a JSON value from an input in BJData format +- [`from_bon8`](from_bon8.md) - create a JSON value from an input in BON8 format ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/front.md b/docs/mkdocs/docs/api/basic_json/front.md index 08c871b9a..d38a5cd34 100644 --- a/docs/mkdocs/docs/api/basic_json/front.md +++ b/docs/mkdocs/docs/api/basic_json/front.md @@ -50,9 +50,10 @@ Constant. ## See also -- [back](back.md) to access the last element -- [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view (document order, not +- [`back`](back.md) - to access the last element +- [`basic_json_view::front`](../basic_json_view/front.md) - the same access on a zero-copy view (document order, not sorted; duplicate keys are visited) +- [Element Access](../../features/element_access/index.md) - overview of all ways to access elements ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get.md b/docs/mkdocs/docs/api/basic_json/get.md index fcb5c595a..016595d5f 100644 --- a/docs/mkdocs/docs/api/basic_json/get.md +++ b/docs/mkdocs/docs/api/basic_json/get.md @@ -178,13 +178,14 @@ overload (3). ## See also -- [get_to](get_to.md) convert and write into a passed value -- [get_ptr](get_ptr.md) get a pointer to the stored value -- [get_ref](get_ref.md) get a reference to the stored value -- [operator ValueType](operator_ValueType.md) get a value via implicit conversion +- [`get_to`](get_to.md) - convert and write into a passed value +- [`get_ptr`](get_ptr.md) - get a pointer to the stored value +- [`get_ref`](get_ref.md) - get a reference to the stored value +- [`operator ValueType`](operator_ValueType.md) - get a value via implicit conversion - [Converting values](../../features/conversions.md) - the type conversions article -- [basic_json_view::get](../basic_json_view/get.md) - the same conversion on a zero-copy view (many types are +- [`basic_json_view::get`](../basic_json_view/get.md) - the same conversion on a zero-copy view (many types are converted without ever building a `basic_json` value) +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - make your own types usable with `get` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get_allocator.md b/docs/mkdocs/docs/api/basic_json/get_allocator.md index 46f71dff6..f3ca2e422 100644 --- a/docs/mkdocs/docs/api/basic_json/get_allocator.md +++ b/docs/mkdocs/docs/api/basic_json/get_allocator.md @@ -36,7 +36,7 @@ Constant. ## See also -- [basic_json](index.md#template-parameters) the class template, with `AllocatorType` as one of its template parameters +- [`basic_json`](index.md#template-parameters) - the class template, with `AllocatorType` as one of its template parameters - [Template Parameter Requirements](../../features/types/template_parameters.md#allocatortype) - the requirements for `AllocatorType` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get_binary.md b/docs/mkdocs/docs/api/basic_json/get_binary.md index 569e72274..5e75c2c49 100644 --- a/docs/mkdocs/docs/api/basic_json/get_binary.md +++ b/docs/mkdocs/docs/api/basic_json/get_binary.md @@ -42,8 +42,11 @@ Constant. ## See also -- [get](get.md) get a value (explicit conversion) -- [get_ref](get_ref.md) get a reference to the stored value +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`binary`](binary.md) - create a binary value +- [`is_binary`](is_binary.md) - check whether a value is binary +- [`get`](get.md) - get a value (explicit conversion) +- [`get_ref`](get_ref.md) - get a reference to the stored value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get_ptr.md b/docs/mkdocs/docs/api/basic_json/get_ptr.md index 1a8d87e9d..8d6943545 100644 --- a/docs/mkdocs/docs/api/basic_json/get_ptr.md +++ b/docs/mkdocs/docs/api/basic_json/get_ptr.md @@ -86,7 +86,8 @@ Constant. ## See also -- [get_ref()](get_ref.md) get a reference value +- [`get_ref()`](get_ref.md) - get a reference value +- [Converting values](../../features/conversions.md) - getting values out, with or without copying ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get_ref.md b/docs/mkdocs/docs/api/basic_json/get_ref.md index 4177e7483..a91da769f 100644 --- a/docs/mkdocs/docs/api/basic_json/get_ref.md +++ b/docs/mkdocs/docs/api/basic_json/get_ref.md @@ -60,9 +60,10 @@ Constant. ## See also -- [get_ptr()](get_ptr.md) get a pointer value -- [basic_json_view::get_string](../basic_json_view/get_string.md) - the closest counterpart on a zero-copy view: a +- [`get_ptr()`](get_ptr.md) - get a pointer value +- [`basic_json_view::get_string`](../basic_json_view/get_string.md) - the closest counterpart on a zero-copy view: a string without a copy, but as a view rather than a reference to a value that must already exist +- [Converting values](../../features/conversions.md) - getting values out, with or without copying ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/get_to.md b/docs/mkdocs/docs/api/basic_json/get_to.md index 233d151b0..24dbfb824 100644 --- a/docs/mkdocs/docs/api/basic_json/get_to.md +++ b/docs/mkdocs/docs/api/basic_json/get_to.md @@ -68,11 +68,12 @@ Depends on the `json_serializer::from_json()` implementation. ## See also -- [get](get.md) get a value (explicit conversion) -- [get_ref](get_ref.md) get a reference to the stored value -- [get_ptr](get_ptr.md) get a pointer to the stored value +- [`get`](get.md) - get a value (explicit conversion) +- [`get_ref`](get_ref.md) - get a reference to the stored value +- [`get_ptr`](get_ptr.md) - get a pointer to the stored value - [Converting values](../../features/conversions.md) - the type conversions article -- [basic_json_view::get_to](../basic_json_view/get_to.md) - the same conversion on a zero-copy view +- [`basic_json_view::get_to`](../basic_json_view/get_to.md) - the same conversion on a zero-copy view +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - make your own types usable with `get_to` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/index.md b/docs/mkdocs/docs/api/basic_json/index.md index 96745922f..9349ca6fb 100644 --- a/docs/mkdocs/docs/api/basic_json/index.md +++ b/docs/mkdocs/docs/api/basic_json/index.md @@ -189,7 +189,7 @@ The class satisfies the following concept requirements: Functions to inspect the type of a JSON value. - [**type**](type.md) - return the type of the JSON value -- [**operator value_t**](operator_value_t.md) - return the type of the JSON value +- [`operator value_t`](operator_value_t.md) - return the type of the JSON value - [**type_name**](type_name.md) - return the type as string - [**is_primitive**](is_primitive.md) - return whether the type is primitive - [**is_structured**](is_structured.md) - return whether the type is structured @@ -218,7 +218,7 @@ Direct access to the stored value of a JSON value. - [**get_to**](get_to.md) - get a value and write it to a destination - [**get_ptr**](get_ptr.md) - get a pointer value - [**get_ref**](get_ref.md) - get a reference value -- [**operator ValueType**](operator_ValueType.md) - get a value +- [`operator ValueType`](operator_ValueType.md) - get a value - [**get_binary**](get_binary.md) - get a binary value - [**as_base_class**](as_base_class.md) - access the custom base class @@ -359,10 +359,15 @@ Access to the JSON value ## See also -- [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc8259) -- [basic_json_document](../basic_json_document/index.md) / [basic_json_view](../basic_json_view/index.md) - parse a JSON +- [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc8259) - + specification of the JSON data interchange format +- [`basic_json_document`](../basic_json_document/index.md) - / [`basic_json_view`](../basic_json_view/index.md) - parse a JSON text into a flat index and read it through a view - [Zero-copy JSON views](../../features/json_view.md) - when to use a document and a view instead of a `basic_json` +- [Types](../../features/types/index.md) - how JSON values are stored and which types exist +- [Converting values](../../features/conversions.md) - get values out of a JSON value and put values in +- [Creating JSON values](../../features/creating_values.md) - all the ways to build a JSON value +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - convert your own types to and from JSON ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/input_format_t.md b/docs/mkdocs/docs/api/basic_json/input_format_t.md index 34a80e3f7..aa3531832 100644 --- a/docs/mkdocs/docs/api/basic_json/input_format_t.md +++ b/docs/mkdocs/docs/api/basic_json/input_format_t.md @@ -53,8 +53,9 @@ bon8 ## See also -- [sax_parse](sax_parse.md) generic SAX parse interface, taking an `input_format_t` to select the input format -- [cbor_tag_handler_t](cbor_tag_handler_t.md) configures how CBOR tags are treated while parsing +- [`sax_parse`](sax_parse.md) - generic SAX parse interface, taking an `input_format_t` to select the input format +- [`cbor_tag_handler_t`](cbor_tag_handler_t.md) - configures how CBOR tags are treated while parsing +- [Binary Formats](../../features/binary_formats/index.md) - CBOR, MessagePack, BSON, and other formats ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/insert.md b/docs/mkdocs/docs/api/basic_json/insert.md index 12009fbaa..12619a2d8 100644 --- a/docs/mkdocs/docs/api/basic_json/insert.md +++ b/docs/mkdocs/docs/api/basic_json/insert.md @@ -185,10 +185,11 @@ Strong exception safety: if an exception occurs, the original value stays intact ## See also -- [emplace](emplace.md) add a value to an object -- [emplace_back](emplace_back.md) add a value to an array -- [push_back](push_back.md) add a value to an array/object -- [update](update.md) merges objects +- [`emplace`](emplace.md) - add a value to an object +- [`emplace_back`](emplace_back.md) - add a value to an array +- [`push_back`](push_back.md) - add a value to an array/object +- [`update`](update.md) - merges objects +- [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/invalid_iterator.md b/docs/mkdocs/docs/api/basic_json/invalid_iterator.md index d757825d8..acf3f207a 100644 --- a/docs/mkdocs/docs/api/basic_json/invalid_iterator.md +++ b/docs/mkdocs/docs/api/basic_json/invalid_iterator.md @@ -40,6 +40,9 @@ classDiagram style json_invalid_iterator fill:#CCCCFF ``` +

Class hierarchy of the library's exceptions; arrows point from a derived class to its +base class, and the highlighted box is `basic_json::invalid_iterator`.

+ ## Member functions - **what** - returns explanatory string @@ -66,12 +69,14 @@ classDiagram ## See also -- [`exception`](exception.md) for the base class of all exceptions thrown by the library -- [List of iterator errors](../../home/exceptions.md#iterator-errors) -- [`parse_error`](parse_error.md) for exceptions indicating a parse error -- [`type_error`](type_error.md) for exceptions indicating executing a member function with a wrong type -- [`out_of_range`](out_of_range.md) for exceptions indicating access out of the defined range -- [`other_error`](other_error.md) for exceptions indicating other library errors +- [`exception`](exception.md) - the base class of all exceptions thrown by the library +- [List of iterator errors](../../home/exceptions.md#iterator-errors) - overview of iterator errors (`invalid_iterator`) +- [`parse_error`](parse_error.md) - exceptions indicating a parse error +- [`type_error`](type_error.md) - exceptions indicating executing a member function with a wrong type +- [`out_of_range`](out_of_range.md) - exceptions indicating access out of the defined range +- [`other_error`](other_error.md) - exceptions indicating other library errors +- [Exceptions](../../home/exceptions.md) - all exceptions and their error codes +- [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) - extended exception messages with the path of the failing value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_array.md b/docs/mkdocs/docs/api/basic_json/is_array.md index 9f2bfc6fc..cc6ca1469 100644 --- a/docs/mkdocs/docs/api/basic_json/is_array.md +++ b/docs/mkdocs/docs/api/basic_json/is_array.md @@ -36,11 +36,12 @@ Constant. ## See also -- [is_object](is_object.md) checks whether the JSON value is an object -- [is_structured](is_structured.md) checks whether the JSON value is structured (array or object) -- [type](type.md) returns the type of the JSON value -- [array_t](array_t.md) the type used to store JSON arrays -- [basic_json_view::is_array](../basic_json_view/is_array.md) - the same check on a zero-copy view +- [`is_object`](is_object.md) - checks whether the JSON value is an object +- [`is_structured`](is_structured.md) - checks whether the JSON value is structured (array or object) +- [`type`](type.md) - returns the type of the JSON value +- [`array_t`](array_t.md) - the type used to store JSON arrays +- [`basic_json_view::is_array`](../basic_json_view/is_array.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_binary.md b/docs/mkdocs/docs/api/basic_json/is_binary.md index 87bb8b66c..ee52d11de 100644 --- a/docs/mkdocs/docs/api/basic_json/is_binary.md +++ b/docs/mkdocs/docs/api/basic_json/is_binary.md @@ -36,10 +36,11 @@ Constant. ## See also -- [is_primitive](is_primitive.md) checks whether the JSON value is primitive -- [binary_t](binary_t.md) the type used to store binary values -- [get_binary](get_binary.md) returns a reference to the stored binary value -- [basic_json_view::is_binary](../basic_json_view/is_binary.md) - the same check on a zero-copy view +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`is_primitive`](is_primitive.md) - checks whether the JSON value is primitive +- [`binary_t`](binary_t.md) - the type used to store binary values +- [`get_binary`](get_binary.md) - returns a reference to the stored binary value +- [`basic_json_view::is_binary`](../basic_json_view/is_binary.md) - the same check on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_boolean.md b/docs/mkdocs/docs/api/basic_json/is_boolean.md index c7585812c..4f349e551 100644 --- a/docs/mkdocs/docs/api/basic_json/is_boolean.md +++ b/docs/mkdocs/docs/api/basic_json/is_boolean.md @@ -36,9 +36,10 @@ Constant. ## See also -- [boolean_t](boolean_t.md) the type used to store JSON booleans -- [is_primitive](is_primitive.md) checks whether the JSON value is primitive -- [basic_json_view::is_boolean](../basic_json_view/is_boolean.md) - the same check on a zero-copy view +- [`boolean_t`](boolean_t.md) - the type used to store JSON booleans +- [`is_primitive`](is_primitive.md) - checks whether the JSON value is primitive +- [`basic_json_view::is_boolean`](../basic_json_view/is_boolean.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_discarded.md b/docs/mkdocs/docs/api/basic_json/is_discarded.md index 3d434f577..6dcd59675 100644 --- a/docs/mkdocs/docs/api/basic_json/is_discarded.md +++ b/docs/mkdocs/docs/api/basic_json/is_discarded.md @@ -87,8 +87,9 @@ with `allow_exceptions` set to `#!cpp false`: a parse error then yields a discar ## See also -- [basic_json_view::is_discarded](../basic_json_view/is_discarded.md) - the corresponding check on a zero-copy view, +- [`basic_json_view::is_discarded`](../basic_json_view/is_discarded.md) - the corresponding check on a zero-copy view, which is `#!cpp true` if the view refers to no value +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_null.md b/docs/mkdocs/docs/api/basic_json/is_null.md index 3823b96dd..6b7647fe0 100644 --- a/docs/mkdocs/docs/api/basic_json/is_null.md +++ b/docs/mkdocs/docs/api/basic_json/is_null.md @@ -36,11 +36,12 @@ Constant. ## See also -- [is_array](is_array.md) checks whether the JSON value is an array -- [is_object](is_object.md) checks whether the JSON value is an object -- [type](type.md) returns the type of the JSON value -- [value_t](value_t.md) the enumeration of JSON types -- [basic_json_view::is_null](../basic_json_view/is_null.md) - the same check on a zero-copy view +- [`is_array`](is_array.md) - checks whether the JSON value is an array +- [`is_object`](is_object.md) - checks whether the JSON value is an object +- [`type`](type.md) - returns the type of the JSON value +- [`value_t`](value_t.md) - the enumeration of JSON types +- [`basic_json_view::is_null`](../basic_json_view/is_null.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_number.md b/docs/mkdocs/docs/api/basic_json/is_number.md index 5db28d3e0..7e4b2fec3 100644 --- a/docs/mkdocs/docs/api/basic_json/is_number.md +++ b/docs/mkdocs/docs/api/basic_json/is_number.md @@ -46,10 +46,12 @@ constexpr bool is_number() const noexcept ## See also -- [is_number_integer()](is_number_integer.md) check if the value is an integer or unsigned integer number -- [is_number_unsigned()](is_number_unsigned.md) check if the value is an unsigned integer number -- [is_number_float()](is_number_float.md) check if the value is a floating-point number -- [basic_json_view::is_number](../basic_json_view/is_number.md) - the same check on a zero-copy view +- [`is_number_integer()`](is_number_integer.md) - check if the value is an integer or unsigned integer number +- [`is_number_unsigned()`](is_number_unsigned.md) - check if the value is an unsigned integer number +- [`is_number_float()`](is_number_float.md) - check if the value is a floating-point number +- [`basic_json_view::is_number`](../basic_json_view/is_number.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored +- [Number Handling](../../features/types/number_handling.md) - how numbers are stored and converted ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_number_float.md b/docs/mkdocs/docs/api/basic_json/is_number_float.md index c94825b41..6aad56803 100644 --- a/docs/mkdocs/docs/api/basic_json/is_number_float.md +++ b/docs/mkdocs/docs/api/basic_json/is_number_float.md @@ -37,10 +37,12 @@ Constant. ## See also -- [is_number()](is_number.md) check if the value is a number -- [is_number_integer()](is_number_integer.md) check if the value is an integer or unsigned integer number -- [is_number_unsigned()](is_number_unsigned.md) check if the value is an unsigned integer number -- [basic_json_view::is_number_float](../basic_json_view/is_number_float.md) - the same check on a zero-copy view +- [`is_number()`](is_number.md) - check if the value is a number +- [`is_number_integer()`](is_number_integer.md) - check if the value is an integer or unsigned integer number +- [`is_number_unsigned()`](is_number_unsigned.md) - check if the value is an unsigned integer number +- [`basic_json_view::is_number_float`](../basic_json_view/is_number_float.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored +- [Number Handling](../../features/types/number_handling.md) - how numbers are stored and converted ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_number_integer.md b/docs/mkdocs/docs/api/basic_json/is_number_integer.md index 71e2aba20..16951e092 100644 --- a/docs/mkdocs/docs/api/basic_json/is_number_integer.md +++ b/docs/mkdocs/docs/api/basic_json/is_number_integer.md @@ -37,10 +37,12 @@ Constant. ## See also -- [is_number()](is_number.md) check if the value is a number -- [is_number_unsigned()](is_number_unsigned.md) check if the value is an unsigned integer number -- [is_number_float()](is_number_float.md) check if the value is a floating-point number -- [basic_json_view::is_number_integer](../basic_json_view/is_number_integer.md) - the same check on a zero-copy view +- [`is_number()`](is_number.md) - check if the value is a number +- [`is_number_unsigned()`](is_number_unsigned.md) - check if the value is an unsigned integer number +- [`is_number_float()`](is_number_float.md) - check if the value is a floating-point number +- [`basic_json_view::is_number_integer`](../basic_json_view/is_number_integer.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored +- [Number Handling](../../features/types/number_handling.md) - how numbers are stored and converted ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_number_unsigned.md b/docs/mkdocs/docs/api/basic_json/is_number_unsigned.md index 2e94d10d6..832f778c2 100644 --- a/docs/mkdocs/docs/api/basic_json/is_number_unsigned.md +++ b/docs/mkdocs/docs/api/basic_json/is_number_unsigned.md @@ -40,10 +40,12 @@ Constant. ## See also -- [is_number()](is_number.md) check if the value is a number -- [is_number_integer()](is_number_integer.md) check if the value is an integer or unsigned integer number -- [is_number_float()](is_number_float.md) check if the value is a floating-point number -- [basic_json_view::is_number_unsigned](../basic_json_view/is_number_unsigned.md) - the same check on a zero-copy view +- [`is_number()`](is_number.md) - check if the value is a number +- [`is_number_integer()`](is_number_integer.md) - check if the value is an integer or unsigned integer number +- [`is_number_float()`](is_number_float.md) - check if the value is a floating-point number +- [`basic_json_view::is_number_unsigned`](../basic_json_view/is_number_unsigned.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored +- [Number Handling](../../features/types/number_handling.md) - how numbers are stored and converted ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_object.md b/docs/mkdocs/docs/api/basic_json/is_object.md index 2105a28a7..7f7cedea4 100644 --- a/docs/mkdocs/docs/api/basic_json/is_object.md +++ b/docs/mkdocs/docs/api/basic_json/is_object.md @@ -36,11 +36,12 @@ Constant. ## See also -- [is_array](is_array.md) checks whether the JSON value is an array -- [is_structured](is_structured.md) checks whether the JSON value is structured (array or object) -- [type](type.md) returns the type of the JSON value -- [object_t](object_t.md) the type used to store JSON objects -- [basic_json_view::is_object](../basic_json_view/is_object.md) - the same check on a zero-copy view +- [`is_array`](is_array.md) - checks whether the JSON value is an array +- [`is_structured`](is_structured.md) - checks whether the JSON value is structured (array or object) +- [`type`](type.md) - returns the type of the JSON value +- [`object_t`](object_t.md) - the type used to store JSON objects +- [`basic_json_view::is_object`](../basic_json_view/is_object.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_primitive.md b/docs/mkdocs/docs/api/basic_json/is_primitive.md index 70836ecdd..0e24c5073 100644 --- a/docs/mkdocs/docs/api/basic_json/is_primitive.md +++ b/docs/mkdocs/docs/api/basic_json/is_primitive.md @@ -56,13 +56,14 @@ This library extends primitive types to binary types, because binary types are r ## See also -- [is_structured()](is_structured.md) returns whether the JSON value is structured -- [is_null()](is_null.md) returns whether the JSON value is `null` -- [is_string()](is_string.md) returns whether the JSON value is a string -- [is_boolean()](is_boolean.md) returns whether the JSON value is a boolean -- [is_number()](is_number.md) returns whether the JSON value is a number -- [is_binary()](is_binary.md) returns whether the JSON value is a binary array -- [basic_json_view::is_primitive](../basic_json_view/is_primitive.md) - the same check on a zero-copy view +- [`is_structured()`](is_structured.md) - returns whether the JSON value is structured +- [`is_null()`](is_null.md) - returns whether the JSON value is `null` +- [`is_string()`](is_string.md) - returns whether the JSON value is a string +- [`is_boolean()`](is_boolean.md) - returns whether the JSON value is a boolean +- [`is_number()`](is_number.md) - returns whether the JSON value is a number +- [`is_binary()`](is_binary.md) - returns whether the JSON value is a binary array +- [`basic_json_view::is_primitive`](../basic_json_view/is_primitive.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_string.md b/docs/mkdocs/docs/api/basic_json/is_string.md index a1d19c14d..e8ba09d1e 100644 --- a/docs/mkdocs/docs/api/basic_json/is_string.md +++ b/docs/mkdocs/docs/api/basic_json/is_string.md @@ -36,10 +36,11 @@ Constant. ## See also -- [is_primitive](is_primitive.md) checks whether the JSON value is primitive -- [type](type.md) returns the type of the JSON value -- [string_t](string_t.md) the type used to store JSON strings -- [basic_json_view::is_string](../basic_json_view/is_string.md) - the same check on a zero-copy view +- [`is_primitive`](is_primitive.md) - checks whether the JSON value is primitive +- [`type`](type.md) - returns the type of the JSON value +- [`string_t`](string_t.md) - the type used to store JSON strings +- [`basic_json_view::is_string`](../basic_json_view/is_string.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/is_structured.md b/docs/mkdocs/docs/api/basic_json/is_structured.md index a67f2778b..0e150e2c7 100644 --- a/docs/mkdocs/docs/api/basic_json/is_structured.md +++ b/docs/mkdocs/docs/api/basic_json/is_structured.md @@ -54,10 +54,11 @@ Note that though strings are containers in C++, they are treated as primitive va ## See also -- [is_primitive()](is_primitive.md) returns whether JSON value is primitive -- [is_array()](is_array.md) returns whether the value is an array -- [is_object()](is_object.md) returns whether the value is an object -- [basic_json_view::is_structured](../basic_json_view/is_structured.md) - the same check on a zero-copy view +- [`is_primitive()`](is_primitive.md) - returns whether JSON value is primitive +- [`is_array()`](is_array.md) - returns whether the value is an array +- [`is_object()`](is_object.md) - returns whether the value is an object +- [`basic_json_view::is_structured`](../basic_json_view/is_structured.md) - the same check on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/items.md b/docs/mkdocs/docs/api/basic_json/items.md index 025002e07..1316826fc 100644 --- a/docs/mkdocs/docs/api/basic_json/items.md +++ b/docs/mkdocs/docs/api/basic_json/items.md @@ -54,7 +54,7 @@ using element_type = decltype(*j_object.items().begin()); ``` The per-element type (`iteration_proxy_value`) lives in the library's internal `detail` namespace and is -intentionally unspecified as a stable, named type -- `decltype` is the supported way to obtain it, but its exact +intentionally unspecified as a stable, named type — `decltype` is the supported way to obtain it, but its exact name/definition may change between versions. ## Return value @@ -97,10 +97,11 @@ When iterating over an array, `key()` will return the index of the element as st ## See also -- [begin](begin.md) returns an iterator to the first element -- [end](end.md) returns an iterator to one past the last element -- [basic_json_view::items](../basic_json_view/items.md) - the same range on a zero-copy view (`#!cpp const auto`, +- [`begin`](begin.md) - returns an iterator to the first element +- [`end`](end.md) - returns an iterator to one past the last element +- [`basic_json_view::items`](../basic_json_view/items.md) - the same range on a zero-copy view (`#!cpp const auto`, not `#!cpp const auto&`: items are produced on the fly; document order, not sorted; duplicate keys are visited) +- [Iterators](../../features/iterators.md#access-object-keys-during-iteration) - the article on iterating over keys and values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md index 2beed5a22..7fa3ca913 100644 --- a/docs/mkdocs/docs/api/basic_json/json_base_class_t.md +++ b/docs/mkdocs/docs/api/basic_json/json_base_class_t.md @@ -57,7 +57,7 @@ same name. Hidden members remain accessible via [`as_base_class`](as_base_class. ## See also -- [as_base_class](as_base_class.md) - access the custom base class +- [`as_base_class`](as_base_class.md) - access the custom base class - [Template Parameter Requirements](../../features/types/template_parameters.md#custombaseclass) - the requirements for `CustomBaseClass` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/json_serializer.md b/docs/mkdocs/docs/api/basic_json/json_serializer.md index 011f7d113..7ded9c5fe 100644 --- a/docs/mkdocs/docs/api/basic_json/json_serializer.md +++ b/docs/mkdocs/docs/api/basic_json/json_serializer.md @@ -44,10 +44,11 @@ deserializes. See [Template Parameter Requirements](../../features/types/templat ## See also -- [adl_serializer](../adl_serializer/index.md) the default `json_serializer` -- [get](get.md) explicit type conversion using the `json_serializer`'s `from_json()` method -- [get_to](get_to.md) explicit type conversion into a variable using the `json_serializer`'s `from_json()` method +- [`adl_serializer`](../adl_serializer/index.md) - the default `json_serializer` +- [`get`](get.md) - explicit type conversion using the `json_serializer`'s `from_json()` method +- [`get_to`](get_to.md) - explicit type conversion into a variable using the `json_serializer`'s `from_json()` method - [Arbitrary Type Conversions](../../features/arbitrary_types.md) - the article on converting between JSON values and arbitrary types +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `JSONSerializer` template parameter ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/max_size.md b/docs/mkdocs/docs/api/basic_json/max_size.md index 5896a2afd..39ca2d3f7 100644 --- a/docs/mkdocs/docs/api/basic_json/max_size.md +++ b/docs/mkdocs/docs/api/basic_json/max_size.md @@ -33,7 +33,7 @@ constant complexity. ## Notes -This function does not return the maximal length of a string stored as JSON value -- it returns the maximal number of +This function does not return the maximal length of a string stored as JSON value — it returns the maximal number of string elements the JSON value can store which is `1`. ## Examples @@ -56,9 +56,9 @@ string elements the JSON value can store which is `1`. ## See also -- [size](size.md) returns the number of elements -- [array_t](array_t.md) the type used to store JSON arrays -- [object_t](object_t.md) the type used to store JSON objects +- [`size`](size.md) - returns the number of elements +- [`array_t`](array_t.md) - the type used to store JSON arrays +- [`object_t`](object_t.md) - the type used to store JSON objects ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/merge_diff.md b/docs/mkdocs/docs/api/basic_json/merge_diff.md index b02ac3e54..776b84fb4 100644 --- a/docs/mkdocs/docs/api/basic_json/merge_diff.md +++ b/docs/mkdocs/docs/api/basic_json/merge_diff.md @@ -70,9 +70,10 @@ object operation (looking up a key, or adding one to the patch): logarithmic for ## See also -- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) -- [merge_patch](merge_patch.md) applies a JSON Merge Patch -- [diff](diff.md) creates a diff as a JSON Patch +- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) - specification of JSON Merge Patch +- [`merge_patch`](merge_patch.md) - applies a JSON Merge Patch +- [`diff`](diff.md) - creates a diff as a JSON Patch +- [JSON Merge Patch](../../features/merge_patch.md) - the article on JSON Merge Patch ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/merge_patch.md b/docs/mkdocs/docs/api/basic_json/merge_patch.md index 923070080..fe83f6cc3 100644 --- a/docs/mkdocs/docs/api/basic_json/merge_patch.md +++ b/docs/mkdocs/docs/api/basic_json/merge_patch.md @@ -85,9 +85,10 @@ cost also includes one copy of `apply_patch` (see the notes below). ## See also -- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) -- [patch](patch.md) apply a JSON patch -- [merge_diff](merge_diff.md) creates a diff as a JSON Merge Patch +- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) - specification of JSON Merge Patch +- [`patch`](patch.md) - apply a JSON patch +- [`merge_diff`](merge_diff.md) - creates a diff as a JSON Merge Patch +- [JSON Merge Patch](../../features/merge_patch.md) - the article on JSON Merge Patch ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/meta.md b/docs/mkdocs/docs/api/basic_json/meta.md index 9b1ac2a4e..3f465826d 100644 --- a/docs/mkdocs/docs/api/basic_json/meta.md +++ b/docs/mkdocs/docs/api/basic_json/meta.md @@ -48,8 +48,11 @@ Constant. ## See also -- [**NLOHMANN_JSON_VERSION_MAJOR**/**NLOHMANN_JSON_VERSION_MINOR**/**NLOHMANN_JSON_VERSION_PATCH**](../macros/nlohmann_json_version_major.md) - \- library version information +- [`NLOHMANN_JSON_VERSION_MAJOR`](../macros/nlohmann_json_version_major.md) / + [`NLOHMANN_JSON_VERSION_MINOR`](../macros/nlohmann_json_version_major.md) / + [`NLOHMANN_JSON_VERSION_PATCH`](../macros/nlohmann_json_version_major.md) - library version information +- [Integration](../../integration/index.md) - integrate the library +- [Supported Macros](../../features/macros.md) - guide to all supported macros ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/number_float_t.md b/docs/mkdocs/docs/api/basic_json/number_float_t.md index cf482d6f0..142f3f023 100644 --- a/docs/mkdocs/docs/api/basic_json/number_float_t.md +++ b/docs/mkdocs/docs/api/basic_json/number_float_t.md @@ -84,10 +84,11 @@ Floating-point number values are stored directly inside a `basic_json` type. ## See also -- [number_integer_t](number_integer_t.md) the type used to store JSON integer numbers -- [number_unsigned_t](number_unsigned_t.md) the type used to store JSON unsigned integer numbers -- [is_number_float](is_number_float.md) checks whether the JSON value is a floating-point number +- [`number_integer_t`](number_integer_t.md) - the type used to store JSON integer numbers +- [`number_unsigned_t`](number_unsigned_t.md) - the type used to store JSON unsigned integer numbers +- [`is_number_float`](is_number_float.md) - checks whether the JSON value is a floating-point number - [Number Handling](../../features/types/number_handling.md) - the article on number handling +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the number template parameters ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/number_integer_t.md b/docs/mkdocs/docs/api/basic_json/number_integer_t.md index 6bd18a74b..e8983473e 100644 --- a/docs/mkdocs/docs/api/basic_json/number_integer_t.md +++ b/docs/mkdocs/docs/api/basic_json/number_integer_t.md @@ -80,10 +80,11 @@ Integer number values are stored directly inside a `basic_json` type. ## See also -- [number_unsigned_t](number_unsigned_t.md) the type used to store JSON unsigned integer numbers -- [number_float_t](number_float_t.md) the type used to store JSON floating-point numbers -- [is_number_integer](is_number_integer.md) checks whether the JSON value is a signed integer number +- [`number_unsigned_t`](number_unsigned_t.md) - the type used to store JSON unsigned integer numbers +- [`number_float_t`](number_float_t.md) - the type used to store JSON floating-point numbers +- [`is_number_integer`](is_number_integer.md) - checks whether the JSON value is a signed integer number - [Number Handling](../../features/types/number_handling.md) - the article on number handling +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the number template parameters ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md b/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md index cd8044bd3..232f9f95b 100644 --- a/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md +++ b/docs/mkdocs/docs/api/basic_json/number_unsigned_t.md @@ -82,10 +82,11 @@ Integer number values are stored directly inside a `basic_json` type. ## See also -- [number_integer_t](number_integer_t.md) the type used to store JSON integer numbers -- [number_float_t](number_float_t.md) the type used to store JSON floating-point numbers -- [is_number_unsigned](is_number_unsigned.md) checks whether the JSON value is an unsigned integer number +- [`number_integer_t`](number_integer_t.md) - the type used to store JSON integer numbers +- [`number_float_t`](number_float_t.md) - the type used to store JSON floating-point numbers +- [`is_number_unsigned`](is_number_unsigned.md) - checks whether the JSON value is an unsigned integer number - [Number Handling](../../features/types/number_handling.md) - the article on number handling +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the number template parameters ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/object_comparator_t.md b/docs/mkdocs/docs/api/basic_json/object_comparator_t.md index 7f2982703..0ff780d7d 100644 --- a/docs/mkdocs/docs/api/basic_json/object_comparator_t.md +++ b/docs/mkdocs/docs/api/basic_json/object_comparator_t.md @@ -27,8 +27,10 @@ and [`default_object_comparator_t`](default_object_comparator_t.md) otherwise. ## See also -- [object_t](object_t.md) the type used to store JSON objects -- [default_object_comparator_t](default_object_comparator_t.md) the fallback comparator used when `object_t` has no `key_compare` member type +- [`object_t`](object_t.md) - the type used to store JSON objects +- [`default_object_comparator_t`](default_object_comparator_t.md) - the fallback comparator used when `object_t` has no `key_compare` member type +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `ObjectType` template parameter +- [Object order](../../features/object_order.md) - preserve the insertion order of keys ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/object_t.md b/docs/mkdocs/docs/api/basic_json/object_t.md index 66fa9c110..835d7b185 100644 --- a/docs/mkdocs/docs/api/basic_json/object_t.md +++ b/docs/mkdocs/docs/api/basic_json/object_t.md @@ -20,7 +20,7 @@ To store objects in C++, a type is defined by the template parameters described `ObjectType` : the container to store objects. Its template parameters must have the same order and meaning as those of `std::map`; in particular, the third parameter is a comparator. `#!cpp std::unordered_map`, whose third parameter - is a hash function, therefore needs an adapter -- see + is a hash function, therefore needs an adapter — see [Template Parameter Requirements](../../features/types/template_parameters.md#objecttype) for the full list of requirements, an adapter example, and the containers that are known to work. @@ -125,10 +125,12 @@ the object is silently converted as an array of key-value pairs, which is incorr ## See also -- [array_t](array_t.md) the type used to store JSON arrays -- [string_t](string_t.md) the type used to store JSON strings -- [object_comparator_t](object_comparator_t.md) the comparator used to order object keys +- [`array_t`](array_t.md) - the type used to store JSON arrays +- [`string_t`](string_t.md) - the type used to store JSON strings +- [`object_comparator_t`](object_comparator_t.md) - the comparator used to order object keys - [Object Order](../../features/object_order.md) - the article on object key ordering +- [Types](../../features/types/index.md) - how objects are stored +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `ObjectType` template parameter ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator+=.md b/docs/mkdocs/docs/api/basic_json/operator+=.md index 3aee3f22d..73dc0e260 100644 --- a/docs/mkdocs/docs/api/basic_json/operator+=.md +++ b/docs/mkdocs/docs/api/basic_json/operator+=.md @@ -123,8 +123,9 @@ interpreted as `object_t::value_type` or `std::initializer_list`, se ## See also -- [emplace_back](emplace_back.md) add a value to an array -- [push_back](push_back.md) add a value to an array/object +- [`emplace_back`](emplace_back.md) - add a value to an array +- [`push_back`](push_back.md) - add a value to an array/object +- [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator=.md b/docs/mkdocs/docs/api/basic_json/operator=.md index 4639783de..6b9fed682 100644 --- a/docs/mkdocs/docs/api/basic_json/operator=.md +++ b/docs/mkdocs/docs/api/basic_json/operator=.md @@ -45,8 +45,9 @@ Linear. ## See also -- [basic_json](basic_json.md) create a JSON value -- [swap](swap.md) exchanges the contents of two JSON values +- [`basic_json`](basic_json.md) - create a JSON value +- [`swap`](swap.md) - exchanges the contents of two JSON values +- [Creating JSON values](../../features/creating_values.md) - ways to create and assign JSON values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index a4872dc39..59e48d984 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -274,13 +274,15 @@ Strong exception safety: if an exception occurs, the original value stays intact ## See also -- documentation on [unchecked access](../../features/element_access/unchecked_access.md) -- documentation on [runtime assertions](../../features/assertions.md) -- see [`at`](at.md) for access by reference with range checking -- see [`value`](value.md) for access with default value -- [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (a duplicate +- [Unchecked access](../../features/element_access/unchecked_access.md) - element access without range checking +- [Runtime assertions](../../features/assertions.md) - assertions guarding against undefined behavior +- [`at`](at.md) - access by reference with range checking +- [`value`](value.md) - access with default value +- [`basic_json_view::operator[]`](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (a duplicate key resolves to the first member, whereas `parse()` keeps the last; a missing key gives a discarded view instead of undefined behavior) +- [Element Access](../../features/element_access/index.md) - overview of all ways to access elements +- [JSON Pointer](../../features/json_pointer.md) - access nested values with a pointer ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_ValueType.md b/docs/mkdocs/docs/api/basic_json/operator_ValueType.md index dca5ebfd1..945ac22fc 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ValueType.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ValueType.md @@ -84,7 +84,7 @@ Linear in the size of the JSON value. ## See also -- [get](get.md) get a value (explicit conversion) +- [`get`](get.md) - get a value (explicit conversion) - [Converting values](../../features/conversions.md) - the type conversions article ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_eq.md b/docs/mkdocs/docs/api/basic_json/operator_eq.md index 85af76ff6..007d8b0c9 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json/operator_eq.md @@ -168,9 +168,9 @@ Linear. ## See also -- [operator!=](operator_ne.md) compare for inequality -- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) -- [basic_json_view::operator==](../basic_json_view/operator_eq.md) - the same comparison on a zero-copy view, without +- [`operator!=`](operator_ne.md) - compare for inequality +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way (C++20) +- [`basic_json_view::operator==`](../basic_json_view/operator_eq.md) - the same comparison on a zero-copy view, without building a `basic_json` value for it ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_ge.md b/docs/mkdocs/docs/api/basic_json/operator_ge.md index 99949f226..54383c1e8 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ge.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ge.md @@ -93,7 +93,7 @@ Linear. ## See also -- [**operator<=>**](operator_spaceship.md) comparison: 3-way +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_gt.md b/docs/mkdocs/docs/api/basic_json/operator_gt.md index 3fcb339da..dea5ef76c 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_gt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_gt.md @@ -83,7 +83,7 @@ Linear. ## See also -- [**operator<=>**](operator_spaceship.md) comparison: 3-way +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_le.md b/docs/mkdocs/docs/api/basic_json/operator_le.md index db193d0db..8818b221a 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_le.md +++ b/docs/mkdocs/docs/api/basic_json/operator_le.md @@ -94,7 +94,7 @@ Linear. ## See also -- [**operator<=>**](operator_spaceship.md) comparison: 3-way +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_lt.md b/docs/mkdocs/docs/api/basic_json/operator_lt.md index baa04964c..e18d591ca 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_lt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_lt.md @@ -94,7 +94,7 @@ Linear. ## See also -- [**operator<=>**](operator_spaceship.md) comparison: 3-way +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_ne.md b/docs/mkdocs/docs/api/basic_json/operator_ne.md index e759a6327..03d4b4fc0 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ne.md @@ -93,9 +93,9 @@ Linear. ## See also -- [operator==](operator_eq.md) comparison: equal -- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) -- [basic_json_view::operator!=](../basic_json_view/operator_ne.md) - the same comparison on a zero-copy view, without +- [`operator==`](operator_eq.md) - comparison: equal +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way (C++20) +- [`basic_json_view::operator!=`](../basic_json_view/operator_ne.md) - the same comparison on a zero-copy view, without building a `basic_json` value for it ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_spaceship.md b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md index 731372f97..7aed091ef 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_spaceship.md +++ b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md @@ -88,12 +88,12 @@ Linear. ## See also -- [**operator==**](operator_eq.md) - comparison: equal -- [**operator!=**](operator_ne.md) - comparison: not equal -- [**operator<**](operator_lt.md) - comparison: less than -- [**operator<=**](operator_le.md) - comparison: less than or equal -- [**operator>**](operator_gt.md) - comparison: greater than -- [**operator>=**](operator_ge.md) - comparison: greater than or equal +- [`operator==`](operator_eq.md) - comparison: equal +- [`operator!=`](operator_ne.md) - comparison: not equal +- [`operator<`](operator_lt.md) - comparison: less than +- [`operator<=`](operator_le.md) - comparison: less than or equal +- [`operator>`](operator_gt.md) - comparison: greater than +- [`operator>=`](operator_ge.md) - comparison: greater than or equal ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/operator_value_t.md b/docs/mkdocs/docs/api/basic_json/operator_value_t.md index 04915d935..607603c66 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_value_t.md +++ b/docs/mkdocs/docs/api/basic_json/operator_value_t.md @@ -49,8 +49,9 @@ Constant. ## See also -- [type](type.md) named member function equivalent to this implicit conversion operator -- [value_t](value_t.md) the enumeration of JSON types +- [`type`](type.md) - named member function equivalent to this implicit conversion operator +- [`value_t`](value_t.md) - the enumeration of JSON types +- [Types](../../features/types/index.md) - the JSON value types ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/other_error.md b/docs/mkdocs/docs/api/basic_json/other_error.md index 6149cc3fb..515344feb 100644 --- a/docs/mkdocs/docs/api/basic_json/other_error.md +++ b/docs/mkdocs/docs/api/basic_json/other_error.md @@ -40,6 +40,9 @@ classDiagram style json_other_error fill:#CCCCFF ``` +

Class hierarchy of the library's exceptions; arrows point from a derived class to its +base class, and the highlighted box is `basic_json::other_error`.

+ ## Member functions - **what** - returns explanatory string @@ -66,12 +69,14 @@ classDiagram ## See also -- [`exception`](exception.md) for the base class of all exceptions thrown by the library -- [List of other errors](../../home/exceptions.md#further-exceptions) -- [`parse_error`](parse_error.md) for exceptions indicating a parse error -- [`invalid_iterator`](invalid_iterator.md) for exceptions indicating errors with iterators -- [`type_error`](type_error.md) for exceptions indicating executing a member function with a wrong type -- [`out_of_range`](out_of_range.md) for exceptions indicating access out of the defined range +- [`exception`](exception.md) - the base class of all exceptions thrown by the library +- [List of other errors](../../home/exceptions.md#further-exceptions) - overview of further errors (`other_error`) +- [`parse_error`](parse_error.md) - exceptions indicating a parse error +- [`invalid_iterator`](invalid_iterator.md) - exceptions indicating errors with iterators +- [`type_error`](type_error.md) - exceptions indicating executing a member function with a wrong type +- [`out_of_range`](out_of_range.md) - exceptions indicating access out of the defined range +- [Exceptions](../../home/exceptions.md) - all exceptions and their error codes +- [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) - extended exception messages with the path of the failing value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/out_of_range.md b/docs/mkdocs/docs/api/basic_json/out_of_range.md index be579b35d..8e489f3cd 100644 --- a/docs/mkdocs/docs/api/basic_json/out_of_range.md +++ b/docs/mkdocs/docs/api/basic_json/out_of_range.md @@ -41,6 +41,9 @@ classDiagram style json_out_of_range fill:#CCCCFF ``` +

Class hierarchy of the library's exceptions; arrows point from a derived class to its +base class, and the highlighted box is `basic_json::out_of_range`.

+ ## Member functions - **what** - returns explanatory string @@ -67,12 +70,15 @@ classDiagram ## See also -- [`exception`](exception.md) for the base class of all exceptions thrown by the library -- [List of out-of-range errors](../../home/exceptions.md#out-of-range) -- [`parse_error`](parse_error.md) for exceptions indicating a parse error -- [`invalid_iterator`](invalid_iterator.md) for exceptions indicating errors with iterators -- [`type_error`](type_error.md) for exceptions indicating executing a member function with a wrong type -- [`other_error`](other_error.md) for exceptions indicating other library errors +- [`exception`](exception.md) - the base class of all exceptions thrown by the library +- [List of out-of-range errors](../../home/exceptions.md#out-of-range) - overview of out-of-range errors + (`out_of_range`) +- [`parse_error`](parse_error.md) - exceptions indicating a parse error +- [`invalid_iterator`](invalid_iterator.md) - exceptions indicating errors with iterators +- [`type_error`](type_error.md) - exceptions indicating executing a member function with a wrong type +- [`other_error`](other_error.md) - exceptions indicating other library errors +- [Exceptions](../../home/exceptions.md) - all exceptions and their error codes +- [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) - extended exception messages with the path of the failing value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/parse.md b/docs/mkdocs/docs/api/basic_json/parse.md index a069f2086..cf7a29d95 100644 --- a/docs/mkdocs/docs/api/basic_json/parse.md +++ b/docs/mkdocs/docs/api/basic_json/parse.md @@ -282,15 +282,21 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by ## See also -- [accept](accept.md) - check if the input is valid JSON -- [sax_parse](sax_parse.md) - parse input using the SAX interface -- [operator>>](../operator_gtgt.md) - deserialize from stream +- [`accept`](accept.md) - check if the input is valid JSON +- [`sax_parse`](sax_parse.md) - parse input using the SAX interface +- [`operator>>`](../operator_gtgt.md) - deserialize from stream - [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of treating it as end of input - [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time - [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio functions +- [Parsing](../../features/parsing/index.md) - overview of the parsing functions +- [Comments](../../features/comments.md) - ignore `//` and `/* */` comments while parsing +- [Trailing Commas](../../features/trailing_commas.md) - accept trailing commas in arrays and objects +- [Parser Callbacks](../../features/parsing/parser_callbacks.md) - filter or inspect values while parsing +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input +- [Parsing Untrusted Input](../../features/parsing/untrusted_input.md) - limits and checks for input you do not control ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/parse_error.md b/docs/mkdocs/docs/api/basic_json/parse_error.md index 0daae8de2..e9b8b1980 100644 --- a/docs/mkdocs/docs/api/basic_json/parse_error.md +++ b/docs/mkdocs/docs/api/basic_json/parse_error.md @@ -43,6 +43,9 @@ classDiagram style json_parse_error fill:#CCCCFF ``` +

Class hierarchy of the library's exceptions; arrows point from a derived class to its +base class, and the highlighted box is `basic_json::parse_error`.

+ ## Member functions - **what** - returns explanatory string @@ -75,12 +78,14 @@ or the end of file. This also holds true when reading a byte vector for binary f ## See also -- [`exception`](exception.md) for the base class of all exceptions thrown by the library -- [List of parse errors](../../home/exceptions.md#parse-errors) -- [`invalid_iterator`](invalid_iterator.md) for exceptions indicating errors with iterators -- [`type_error`](type_error.md) for exceptions indicating executing a member function with a wrong type -- [`out_of_range`](out_of_range.md) for exceptions indicating access out of the defined range -- [`other_error`](other_error.md) for exceptions indicating other library errors +- [`exception`](exception.md) - the base class of all exceptions thrown by the library +- [List of parse errors](../../home/exceptions.md#parse-errors) - overview of parse errors (`parse_error`) +- [`invalid_iterator`](invalid_iterator.md) - exceptions indicating errors with iterators +- [`type_error`](type_error.md) - exceptions indicating executing a member function with a wrong type +- [`out_of_range`](out_of_range.md) - exceptions indicating access out of the defined range +- [`other_error`](other_error.md) - exceptions indicating other library errors +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input +- [Working Without Exceptions](../../features/no_exceptions.md) - error handling when exceptions are disabled ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/parse_event_t.md b/docs/mkdocs/docs/api/basic_json/parse_event_t.md index f8c12eff8..44c372a89 100644 --- a/docs/mkdocs/docs/api/basic_json/parse_event_t.md +++ b/docs/mkdocs/docs/api/basic_json/parse_event_t.md @@ -22,7 +22,12 @@ The parser callback distinguishes the following events: ## Examples -![Example when certain parse events are triggered](../../images/callback_events.png) +
+--8<-- "images/callback_events.svg" +
+ +

Each coloured dot marks where the parser reports a `parse_event_t` event while reading +the JSON text on the left; the legend on the right names the event per colour.

??? example @@ -41,8 +46,9 @@ The parser callback distinguishes the following events: ## See also -- [parser_callback_t](parser_callback_t.md) callback function type for the parser -- [parse](parse.md) deserialize from a compatible input +- [`parser_callback_t`](parser_callback_t.md) - callback function type for the parser +- [`parse`](parse.md) - deserialize from a compatible input +- [Parser Callbacks](../../features/parsing/parser_callbacks.md) - filter or inspect values while parsing ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md index 727905e5a..a12634b9d 100644 --- a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md +++ b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md @@ -23,7 +23,12 @@ table describes the values of the parameters `depth`, `event`, and `parsed`. | `parse_event_t::array_end` | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array | | `parse_event_t::value` | the parser finished reading a JSON value | depth of the value | the parsed JSON value | -![Example when certain parse events are triggered](../../images/callback_events.png) +
+--8<-- "images/callback_events.svg" +
+ +

Parse events for a small JSON text: each coloured dot marks the position at which the +parser calls the callback, and the legend on the right names the event per colour.

Discarding a value (i.e., returning `#!cpp false`) has different effects depending on the context in which function was called: @@ -92,8 +97,9 @@ the latter case, it is skipped completely, or replaced by `null` if it is the to ## See also -- [parse](parse.md) deserialize from a compatible input -- [parse_event_t](parse_event_t.md) enumeration of parser events +- [`parse`](parse.md) - deserialize from a compatible input +- [`parse_event_t`](parse_event_t.md) - enumeration of parser events +- [Parser Callbacks](../../features/parsing/parser_callbacks.md) - filter or inspect values while parsing ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/patch.md b/docs/mkdocs/docs/api/basic_json/patch.md index 1166c9a71..cd553553e 100644 --- a/docs/mkdocs/docs/api/basic_json/patch.md +++ b/docs/mkdocs/docs/api/basic_json/patch.md @@ -105,10 +105,12 @@ is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": ## See also -- [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) -- [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) -- [patch_inplace](patch_inplace.md) applies a JSON Patch without creating a copy of the document -- [merge_patch](merge_patch.md) applies a JSON Merge Patch +- [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - specification of JSON Patch +- [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) - specification of JSON Pointer +- [`patch_inplace`](patch_inplace.md) - applies a JSON Patch without creating a copy of the document +- [`merge_patch`](merge_patch.md) - applies a JSON Merge Patch +- [JSON Patch and Diff](../../features/json_patch.md) - the article on JSON Patch +- [JSON Pointer](../../features/json_pointer.md) - the addressing scheme used for patch paths ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 571ca9c12..67c6e8814 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -103,10 +103,11 @@ is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": ## See also -- [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) -- [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) -- [patch](patch.md) applies a JSON Patch -- [merge_patch](merge_patch.md) applies a JSON Merge Patch +- [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - specification of JSON Patch +- [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) - specification of JSON Pointer +- [`patch`](patch.md) - applies a JSON Patch +- [`merge_patch`](merge_patch.md) - applies a JSON Merge Patch +- [JSON Patch and Diff](../../features/json_patch.md) - the article on JSON Patch ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/push_back.md b/docs/mkdocs/docs/api/basic_json/push_back.md index f6ed6a426..4bc885277 100644 --- a/docs/mkdocs/docs/api/basic_json/push_back.md +++ b/docs/mkdocs/docs/api/basic_json/push_back.md @@ -119,8 +119,8 @@ throws. ## See also -- [emplace_back](emplace_back.md) add a value to an array -- [operator+=](operator+=.md) add a value to an array/object +- [`emplace_back`](emplace_back.md) - add a value to an array +- [`operator+=`](operator+=.md) - add a value to an array/object - [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/rbegin.md b/docs/mkdocs/docs/api/basic_json/rbegin.md index 544b8a643..ea3be945c 100644 --- a/docs/mkdocs/docs/api/basic_json/rbegin.md +++ b/docs/mkdocs/docs/api/basic_json/rbegin.md @@ -7,7 +7,13 @@ const_reverse_iterator rbegin() const noexcept; Returns an iterator to the reverse-beginning; that is, the last element. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
+ +

`rbegin()` points to the last element and `rend()` to the position before the first +element. The upper row is the reversed sequence and the lower row the forward sequence; dashed arrows link each reverse +iterator to the forward iterator it stores, and dashed cells are past-the-last positions.

## Return value @@ -39,9 +45,9 @@ Constant. ## See also -- [rend](rend.md) returns a reverse iterator to one before the first element -- [crbegin](crbegin.md) returns a const reverse iterator to the last element -- [begin](begin.md) returns an iterator to the first element +- [`rend`](rend.md) - returns a reverse iterator to one before the first element +- [`crbegin`](crbegin.md) - returns a const reverse iterator to the last element +- [`begin`](begin.md) - returns an iterator to the first element - [Iterators](../../features/iterators.md) - the article on iterators ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/rend.md b/docs/mkdocs/docs/api/basic_json/rend.md index 12f68e787..90026fa6c 100644 --- a/docs/mkdocs/docs/api/basic_json/rend.md +++ b/docs/mkdocs/docs/api/basic_json/rend.md @@ -8,7 +8,13 @@ const_reverse_iterator rend() const noexcept; Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder, attempting to access it results in undefined behavior. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
+ +

`rend()` points to the position before the first element and `rbegin()` to the last +element. The upper row is the reversed sequence and the lower row the forward sequence; dashed arrows link each reverse +iterator to the forward iterator it stores, and dashed cells are past-the-last positions.

## Return value @@ -40,9 +46,9 @@ Constant. ## See also -- [rbegin](rbegin.md) returns a reverse iterator to the last element -- [crend](crend.md) returns a const reverse iterator to one before the first element -- [end](end.md) returns an iterator to one past the last element +- [`rbegin`](rbegin.md) - returns a reverse iterator to the last element +- [`crend`](crend.md) - returns a const reverse iterator to one before the first element +- [`end`](end.md) - returns an iterator to one past the last element - [Iterators](../../features/iterators.md) - the article on iterators ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/sax_parse.md b/docs/mkdocs/docs/api/basic_json/sax_parse.md index 0b0f1dee2..af2480e53 100644 --- a/docs/mkdocs/docs/api/basic_json/sax_parse.md +++ b/docs/mkdocs/docs/api/basic_json/sax_parse.md @@ -147,12 +147,14 @@ use the same file with stdio functions, but it must not wait for another thread ## See also -- [parse](parse.md) - deserialize from a compatible input -- [accept](accept.md) - check if the input is valid JSON +- [`parse`](parse.md) - deserialize from a compatible input +- [`accept`](accept.md) - check if the input is valid JSON - [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time - [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio functions +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing +- [Large, Streamed, and Partial Input](../../features/parsing/large_and_partial_input.md) - chunked, concatenated, and very large input ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/size.md b/docs/mkdocs/docs/api/basic_json/size.md index 8fe553bd3..5c70d11d0 100644 --- a/docs/mkdocs/docs/api/basic_json/size.md +++ b/docs/mkdocs/docs/api/basic_json/size.md @@ -32,7 +32,7 @@ constant complexity. ## Notes -This function does not return the length of a string stored as JSON value -- it returns the number of elements in the +This function does not return the length of a string stored as JSON value — it returns the number of elements in the JSON value which is `1` in the case of a string. ## Examples @@ -53,9 +53,9 @@ JSON value which is `1` in the case of a string. ## See also -- [empty](empty.md) checks whether the JSON value has no elements -- [max_size](max_size.md) returns the maximum possible number of elements -- [basic_json_view::size](../basic_json_view/size.md) - the same function on a zero-copy view +- [`empty`](empty.md) - checks whether the JSON value has no elements +- [`max_size`](max_size.md) - returns the maximum possible number of elements +- [`basic_json_view::size`](../basic_json_view/size.md) - the same function on a zero-copy view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/start_pos.md b/docs/mkdocs/docs/api/basic_json/start_pos.md index 19a31df15..fb43fb52b 100644 --- a/docs/mkdocs/docs/api/basic_json/start_pos.md +++ b/docs/mkdocs/docs/api/basic_json/start_pos.md @@ -60,8 +60,9 @@ Constant. ## See also -- [end_pos](end_pos.md) to access the end position -- [JSON_DIAGNOSTIC_POSITIONS](../macros/json_diagnostic_positions.md) for an overview of the diagnostic positions +- [`end_pos`](end_pos.md) - to access the end position +- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - overview of the supported conversions +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/std_formatter.md b/docs/mkdocs/docs/api/basic_json/std_formatter.md index e7e889aee..5afe221f0 100644 --- a/docs/mkdocs/docs/api/basic_json/std_formatter.md +++ b/docs/mkdocs/docs/api/basic_json/std_formatter.md @@ -47,9 +47,9 @@ provides ``, controlled by the [`JSON_HAS_STD_FORMAT`](../macros/json_ha ## See also -- [dump](dump.md) - serialization -- [operator<<(std::ostream&)](../operator_ltlt.md) - serialize to stream -- [format_as](format_as.md) - customization point used by `fmt::format` (fmtlib) +- [`dump`](dump.md) - serialization +- [`operator<<(std::ostream&)`](../operator_ltlt.md) - serialize to stream +- [`format_as`](format_as.md) - customization point used by `fmt::format` (fmtlib) - [Serialization](../../features/serialization.md) - the serialization article ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/std_hash.md b/docs/mkdocs/docs/api/basic_json/std_hash.md index 7be7772cc..6f5453e22 100644 --- a/docs/mkdocs/docs/api/basic_json/std_hash.md +++ b/docs/mkdocs/docs/api/basic_json/std_hash.md @@ -37,7 +37,8 @@ large integers that convert to the same `number_float_t`. ## See also -- [operator==](operator_eq.md) compares two JSON values for equality, consistent with equal hash values +- [`operator==`](operator_eq.md) - compares two JSON values for equality, consistent with equal hash values +- [Types](../../features/types/index.md) - the JSON value types and how they compare ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/std_swap.md b/docs/mkdocs/docs/api/basic_json/std_swap.md index f93830ff4..995d08af6 100644 --- a/docs/mkdocs/docs/api/basic_json/std_swap.md +++ b/docs/mkdocs/docs/api/basic_json/std_swap.md @@ -51,7 +51,7 @@ void swap(nlohmann::basic_json& j1, nlohmann::basic_json& j2) ## See also -- [swap](swap.md) +- [`swap`](swap.md) - exchange the contents of two JSON values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/string_t.md b/docs/mkdocs/docs/api/basic_json/string_t.md index b65e95508..1e3a4b2b5 100644 --- a/docs/mkdocs/docs/api/basic_json/string_t.md +++ b/docs/mkdocs/docs/api/basic_json/string_t.md @@ -20,7 +20,7 @@ JSON class into byte-sized characters during deserialization. `StringType` must have a `char`-compatible `value_type`: the library relies on UTF-8/`char`-based storage and processing internally, so `std::wstring`, `std::u16string`, and `std::u32string` are **not** valid choices for - `StringType`. To work with wide-character data, convert it to/from UTF-8 at the boundary instead -- see the + `StringType`. To work with wide-character data, convert it to/from UTF-8 at the boundary instead — see the FAQ's [wide string handling](../../home/faq.md#wide-string-handling) section for a conversion recipe. Beyond the character type, the library expects a substantial part of the `#!cpp std::string` interface (contiguous @@ -82,9 +82,11 @@ and an example. ## See also -- [object_t](object_t.md) the type used to store JSON objects (and their keys, which are also `string_t`) -- [binary_t](binary_t.md) the type used to store binary values -- [get_ptr](get_ptr.md) returns a pointer to the stored string value +- [`object_t`](object_t.md) - the type used to store JSON objects (and their keys, which are also `string_t`) +- [`binary_t`](binary_t.md) - the type used to store binary values +- [`get_ptr`](get_ptr.md) - returns a pointer to the stored string value +- [Types](../../features/types/index.md) - how strings are stored +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the `StringType` template parameter ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/swap.md b/docs/mkdocs/docs/api/basic_json/swap.md index 4bc36b125..8da743d1f 100644 --- a/docs/mkdocs/docs/api/basic_json/swap.md +++ b/docs/mkdocs/docs/api/basic_json/swap.md @@ -176,9 +176,9 @@ Constant. ## See also -- [std::swap](std_swap.md) -- [operator=](operator=.md) copy assignment -- [basic_json](basic_json.md) create a JSON value +- [`std::swap`](std_swap.md) - specialization of `std::swap` for JSON values +- [`operator=`](operator=.md) - copy assignment +- [`basic_json`](basic_json.md) - create a JSON value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/to_bjdata.md b/docs/mkdocs/docs/api/basic_json/to_bjdata.md index e0728cfdd..14ead0612 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bjdata.md +++ b/docs/mkdocs/docs/api/basic_json/to_bjdata.md @@ -111,12 +111,15 @@ Linear in the size of the JSON value `j`. ## See also -- [from_bjdata](from_bjdata.md) create a JSON value from an input in BJData 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_bon8](to_bon8.md) create a BON8 serialization of a JSON value +- [BJData](../../features/binary_formats/bjdata.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`from_bjdata`](from_bjdata.md) - create a JSON value from an input in BJData 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_bon8`](to_bon8.md) - create a BON8 serialization of a JSON value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/to_bon8.md b/docs/mkdocs/docs/api/basic_json/to_bon8.md index debbb6ef0..6b09bb0d4 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bon8.md +++ b/docs/mkdocs/docs/api/basic_json/to_bon8.md @@ -82,12 +82,14 @@ Linear in the size of the JSON value `j`. ## 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 +- [BON8](../../features/binary_formats/bon8.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [`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 diff --git a/docs/mkdocs/docs/api/basic_json/to_bson.md b/docs/mkdocs/docs/api/basic_json/to_bson.md index b3b346266..9c81cdce4 100644 --- a/docs/mkdocs/docs/api/basic_json/to_bson.md +++ b/docs/mkdocs/docs/api/basic_json/to_bson.md @@ -104,12 +104,15 @@ pass before anything is written. ## See also -- [from_bson](from_bson.md) create a JSON value from an input in BSON 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_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 -- [to_bon8](to_bon8.md) create a BON8 serialization of a JSON value +- [BSON](../../features/binary_formats/bson.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`from_bson`](from_bson.md) - create a JSON value from an input in BSON 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_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 +- [`to_bon8`](to_bon8.md) - create a BON8 serialization of a JSON value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/to_cbor.md b/docs/mkdocs/docs/api/basic_json/to_cbor.md index f0be593f0..483259f85 100644 --- a/docs/mkdocs/docs/api/basic_json/to_cbor.md +++ b/docs/mkdocs/docs/api/basic_json/to_cbor.md @@ -78,12 +78,15 @@ Linear in the size of the JSON value `j`. ## See also -- [from_cbor](from_cbor.md) create a JSON value from an input in CBOR format -- [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 -- [to_bon8](to_bon8.md) create a BON8 serialization of a JSON value +- [CBOR](../../features/binary_formats/cbor.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`from_cbor`](from_cbor.md) - create a JSON value from an input in CBOR format +- [`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 +- [`to_bon8`](to_bon8.md) - create a BON8 serialization of a JSON value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/to_msgpack.md b/docs/mkdocs/docs/api/basic_json/to_msgpack.md index 181948ef1..155db8e20 100644 --- a/docs/mkdocs/docs/api/basic_json/to_msgpack.md +++ b/docs/mkdocs/docs/api/basic_json/to_msgpack.md @@ -97,12 +97,15 @@ Linear in the size of the JSON value `j`. ## See also -- [from_msgpack](from_msgpack.md) create a JSON value from an input in MessagePack format -- [to_cbor](to_cbor.md) create a CBOR 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 -- [to_bon8](to_bon8.md) create a BON8 serialization of a JSON value +- [MessagePack](../../features/binary_formats/messagepack.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`from_msgpack`](from_msgpack.md) - create a JSON value from an input in MessagePack format +- [`to_cbor`](to_cbor.md) - create a CBOR 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 +- [`to_bon8`](to_bon8.md) - create a BON8 serialization of a JSON value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/to_string.md b/docs/mkdocs/docs/api/basic_json/to_string.md index 8c75db84c..a4e7bf090 100644 --- a/docs/mkdocs/docs/api/basic_json/to_string.md +++ b/docs/mkdocs/docs/api/basic_json/to_string.md @@ -59,7 +59,7 @@ std::string to_string(const BasicJsonType& j) ## See also -- [dump](dump.md) +- [`dump`](dump.md) - serialize a JSON value to a string - [Serialization](../../features/serialization.md) - the serialization article ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/to_ubjson.md b/docs/mkdocs/docs/api/basic_json/to_ubjson.md index 6aad86683..345743e03 100644 --- a/docs/mkdocs/docs/api/basic_json/to_ubjson.md +++ b/docs/mkdocs/docs/api/basic_json/to_ubjson.md @@ -101,12 +101,15 @@ Linear in the size of the JSON value `j`. ## See also -- [from_ubjson](from_ubjson.md) create a JSON value from an input in UBJSON 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_bjdata](to_bjdata.md) create a BJData serialization of a JSON value -- [to_bon8](to_bon8.md) create a BON8 serialization of a JSON value +- [UBJSON](../../features/binary_formats/ubjson.md) - the format, its byte layout and how values are mapped +- [Binary formats](../../features/binary_formats/index.md) - overview and comparison of the supported binary formats +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`from_ubjson`](from_ubjson.md) - create a JSON value from an input in UBJSON 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_bjdata`](to_bjdata.md) - create a BJData serialization of a JSON value +- [`to_bon8`](to_bon8.md) - create a BON8 serialization of a JSON value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/type.md b/docs/mkdocs/docs/api/basic_json/type.md index 6ed2aaeda..0ae61926c 100644 --- a/docs/mkdocs/docs/api/basic_json/type.md +++ b/docs/mkdocs/docs/api/basic_json/type.md @@ -49,10 +49,11 @@ Constant. ## See also -- [operator value_t](operator_value_t.md) implicit conversion operator equivalent to this named member function -- [type_name](type_name.md) returns the type as a string, for use in error messages -- [value_t](value_t.md) the enumeration of JSON types -- [basic_json_view::type](../basic_json_view/type.md) - the same function on a zero-copy view +- [`operator value_t`](operator_value_t.md) - implicit conversion operator equivalent to this named member function +- [`type_name`](type_name.md) - returns the type as a string, for use in error messages +- [`value_t`](value_t.md) - the enumeration of JSON types +- [`basic_json_view::type`](../basic_json_view/type.md) - the same function on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/type_error.md b/docs/mkdocs/docs/api/basic_json/type_error.md index dcc30b29a..1dbb543f8 100644 --- a/docs/mkdocs/docs/api/basic_json/type_error.md +++ b/docs/mkdocs/docs/api/basic_json/type_error.md @@ -41,6 +41,9 @@ classDiagram style json_type_error fill:#CCCCFF ``` +

Class hierarchy of the library's exceptions; arrows point from a derived class to its +base class, and the highlighted box is `basic_json::type_error`.

+ ## Member functions - **what** - returns explanatory string @@ -67,12 +70,14 @@ classDiagram ## See also -- [`exception`](exception.md) for the base class of all exceptions thrown by the library -- [List of type errors](../../home/exceptions.md#type-errors) -- [`parse_error`](parse_error.md) for exceptions indicating a parse error -- [`invalid_iterator`](invalid_iterator.md) for exceptions indicating errors with iterators -- [`out_of_range`](out_of_range.md) for exceptions indicating access out of the defined range -- [`other_error`](other_error.md) for exceptions indicating other library errors +- [`exception`](exception.md) - the base class of all exceptions thrown by the library +- [List of type errors](../../home/exceptions.md#type-errors) - overview of type errors (`type_error`) +- [`parse_error`](parse_error.md) - exceptions indicating a parse error +- [`invalid_iterator`](invalid_iterator.md) - exceptions indicating errors with iterators +- [`out_of_range`](out_of_range.md) - exceptions indicating access out of the defined range +- [`other_error`](other_error.md) - exceptions indicating other library errors +- [Exceptions](../../home/exceptions.md) - all exceptions and their error codes +- [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) - extended exception messages with the path of the failing value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/type_name.md b/docs/mkdocs/docs/api/basic_json/type_name.md index d43ed4d71..7af96acfa 100644 --- a/docs/mkdocs/docs/api/basic_json/type_name.md +++ b/docs/mkdocs/docs/api/basic_json/type_name.md @@ -4,7 +4,7 @@ const char* type_name() const noexcept; ``` -Returns the type name as string to be used in error messages -- usually to indicate that a function was called on a +Returns the type name as string to be used in error messages — usually to indicate that a function was called on a wrong JSON type. ## Return value @@ -54,9 +54,10 @@ Constant. ## See also -- [type](type.md) returns the type of the JSON value -- [value_t](value_t.md) the enumeration of JSON types -- [basic_json_view::type_name](../basic_json_view/type_name.md) - the same function on a zero-copy view +- [`type`](type.md) - returns the type of the JSON value +- [`value_t`](value_t.md) - the enumeration of JSON types +- [`basic_json_view::type_name`](../basic_json_view/type_name.md) - the same function on a zero-copy view +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/unflatten.md b/docs/mkdocs/docs/api/basic_json/unflatten.md index 1ae81dd15..8b9f2cbc1 100644 --- a/docs/mkdocs/docs/api/basic_json/unflatten.md +++ b/docs/mkdocs/docs/api/basic_json/unflatten.md @@ -80,7 +80,8 @@ Apart from these three cases, for a JSON value `j`, the following is always true ## See also -- [flatten](flatten.md) the reverse function +- [`flatten`](flatten.md) - the reverse function +- [JSON Pointer](../../features/json_pointer.md#flatten-unflatten) - the article on JSON Pointer and flattening ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/update.md b/docs/mkdocs/docs/api/basic_json/update.md index d641b38d5..698c68399 100644 --- a/docs/mkdocs/docs/api/basic_json/update.md +++ b/docs/mkdocs/docs/api/basic_json/update.md @@ -153,8 +153,8 @@ contained in `#!cpp *this` (for example, a subobject returned by `#!cpp (*this)[ ## See also -- [insert](insert.md) add values to an array/object -- [merge_patch](merge_patch.md) applies a JSON Merge Patch +- [`insert`](insert.md) - add values to an array/object +- [`merge_patch`](merge_patch.md) - applies a JSON Merge Patch - [Modifying values](../../features/modifying_values.md) - the article on modifying values ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/value.md b/docs/mkdocs/docs/api/basic_json/value.md index 5aa056c92..1f0bb2a97 100644 --- a/docs/mkdocs/docs/api/basic_json/value.md +++ b/docs/mkdocs/docs/api/basic_json/value.md @@ -225,9 +225,11 @@ changes to any JSON value. ## See also -- see [`at`](at.md) for access by reference with range checking -- see [`operator[]`](operator%5B%5D.md) for unchecked access by reference -- [basic_json_view::value](../basic_json_view/value.md) - the same access on a zero-copy view +- [`at`](at.md) - access by reference with range checking +- [`operator[]`](operator%5B%5D.md) - unchecked access by reference +- [`basic_json_view::value`](../basic_json_view/value.md) - the same access on a zero-copy view +- [Access with default value: value](../../features/element_access/default_value.md) - the article on `value` +- [JSON Pointer](../../features/json_pointer.md) - access nested values with a pointer ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/value_t.md b/docs/mkdocs/docs/api/basic_json/value_t.md index 0d63bfdac..e38a27370 100644 --- a/docs/mkdocs/docs/api/basic_json/value_t.md +++ b/docs/mkdocs/docs/api/basic_json/value_t.md @@ -48,6 +48,9 @@ functions [`is_null`](is_null.md), [`is_object`](is_object.md), [`is_array`](is_ F --> G[binary] ``` +

Arrows point from a type to the next greater type in the ordering used by + `operator<`.

+ !!! note "Types of numbers" There are three enumerators for numbers (`number_integer`, `number_unsigned`, and `number_float`) to distinguish @@ -86,11 +89,12 @@ functions [`is_null`](is_null.md), [`is_object`](is_object.md), [`is_array`](is_ ## See also -- [type](type.md) return the type of the JSON value -- [type_name](type_name.md) return the type as string -- [operator value_t](operator_value_t.md) return the type of the JSON value -- [is_primitive](is_primitive.md) return whether the type is primitive -- [is_structured](is_structured.md) return whether the type is structured +- [`type`](type.md) - return the type of the JSON value +- [`type_name`](type_name.md) - return the type as string +- [`operator value_t`](operator_value_t.md) - return the type of the JSON value +- [`is_primitive`](is_primitive.md) - return whether the type is primitive +- [`is_structured`](is_structured.md) - return whether the type is structured +- [Types](../../features/types/index.md) - the JSON value types and how they are stored ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/with_t.md b/docs/mkdocs/docs/api/basic_json/with_t.md index 2e8fee663..f98b071d3 100644 --- a/docs/mkdocs/docs/api/basic_json/with_t.md +++ b/docs/mkdocs/docs/api/basic_json/with_t.md @@ -128,8 +128,9 @@ matter. For instance, `nlohmann::json::with_object_t` is ## See also -- [basic_json](index.md#template-parameters) - the template parameters that can be replaced -- [json_base_class_t](json_base_class_t.md) - the type used for `CustomBaseClass` +- [`basic_json`](index.md#template-parameters) - the template parameters that can be replaced +- [`json_base_class_t`](json_base_class_t.md) - the type used for `CustomBaseClass` +- [Template Parameter Requirements](../../features/types/template_parameters.md) - requirements on the template parameters ## Version history diff --git a/docs/mkdocs/docs/api/basic_json/~basic_json.md b/docs/mkdocs/docs/api/basic_json/~basic_json.md index e5b957d51..e10a16b85 100644 --- a/docs/mkdocs/docs/api/basic_json/~basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/~basic_json.md @@ -18,8 +18,8 @@ Linear. ## See also -- [basic_json](basic_json.md) constructs a JSON value -- [clear](clear.md) clears the content of a JSON value without destroying it +- [`basic_json`](basic_json.md) - constructs a JSON value +- [`clear`](clear.md) - clears the content of a JSON value without destroying it ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/accept.md b/docs/mkdocs/docs/api/basic_json_document/accept.md index d2eb3862a..1f1070d51 100644 --- a/docs/mkdocs/docs/api/basic_json_document/accept.md +++ b/docs/mkdocs/docs/api/basic_json_document/accept.md @@ -9,7 +9,7 @@ static bool accept(InputType&& input, Checks whether the input is valid JSON, accepting and rejecting exactly what [`BasicJsonType::accept()`](../basic_json/accept.md) does, with the same options. Unlike [`parse()`](parse.md), this -function never throws an exception for invalid input, and the returned `#!cpp bool` is the only result -- no document +function never throws an exception for invalid input, and the returned `#!cpp bool` is the only result — no document is returned. The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This @@ -69,8 +69,9 @@ compile; see [`parse`](parse.md#notes). ## See also -- [parse](parse.md) - deserialize from a compatible input +- [`parse`](parse.md) - deserialize from a compatible input - [`BasicJsonType::accept`](../basic_json/accept.md) - the corresponding function of `basic_json` +- [Zero-copy JSON views](../../features/json_view.md#what-is-the-same-as-parse) - how parsing a document differs from, and matches, basic_json::parse ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md b/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md index c4bf1c3b6..c0cd5d0ad 100644 --- a/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md +++ b/docs/mkdocs/docs/api/basic_json_document/basic_json_document.md @@ -50,8 +50,9 @@ Constant. ## See also -- [parse](parse.md) - deserialize from a compatible input -- [is_discarded](is_discarded.md) - return whether the last parse failed +- [`parse`](parse.md) - deserialize from a compatible input +- [`is_discarded`](is_discarded.md) - return whether the last parse failed +- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - who owns the source text and for how long ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/erase.md b/docs/mkdocs/docs/api/basic_json_document/erase.md index 7d453addd..db6dc523b 100644 --- a/docs/mkdocs/docs/api/basic_json_document/erase.md +++ b/docs/mkdocs/docs/api/basic_json_document/erase.md @@ -54,7 +54,7 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (` ## Exceptions -1. Throws [`type_error.307`](../../home/exceptions.md#jsonexceptiontype_error307) if `object` is not an object -- the +1. Throws [`type_error.307`](../../home/exceptions.md#jsonexceptiontype_error307) if `object` is not an object — the same message [`BasicJsonType::erase`](../basic_json/erase.md) throws for the same type. 2. Throws `type_error.307` if `array` is not an array. Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is negative, or if @@ -86,12 +86,12 @@ always starts from this document's own [`root()`](root.md). !!! info "Duplicate keys" - Overload 1. removes *every* member with `key`, not just the first -- unlike [`set`](set.md), which assigns the + Overload 1. removes *every* member with `key`, not just the first — unlike [`set`](set.md), which assigns the first occurrence and drops the rest. This is why it returns a count rather than a single view: there may be more than one member removed, or none. Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still -referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no +referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) — it just no longer appears when `array`/`object` is read, dumped, or iterated. What does change is the container's sequence of *links* to its elements: removing an element of `array` (2.) shifts the links to the elements after it by one, as [`insert`](insert.md) does in the other direction. *Every* iterator already taken over `array`/`object` is invalidated @@ -101,8 +101,8 @@ by an erase, not only those at or after the removed element, since the container ??? example - The example below drops a deprecated field and a decommissioned entry from a configuration document -- using all - three overloads -- and shows what stays intact that would not with a plain `json`/`ordered_json` value: the order + The example below drops a deprecated field and a decommissioned entry from a configuration document — using all + three overloads — and shows what stays intact that would not with a plain `json`/`ordered_json` value: the order of the fields around the ones removed, and the exact spelling of a number that was never touched. ```cpp @@ -117,12 +117,13 @@ by an erase, not only those at or after the removed element, since the container ## See also -- [insert](insert.md) - insert an element into an array -- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to -- [push_back](push_back.md) - append to an array -- [root](root.md) - the view of the root value, the starting point of overload 3 +- [`insert`](insert.md) - insert an element into an array +- [`set`](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to +- [`push_back`](push_back.md) - append to an array +- [`root`](root.md) - the view of the root value, the starting point of overload 3 - [`BasicJsonType::erase`](../basic_json/erase.md) - the corresponding function of `basic_json` - [Edits](index.md#edits) - what an edit guarantees, for every overload +- [Zero-copy JSON views](../../features/json_view.md#editing-a-document) - what an edit guarantees and costs ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/index.md b/docs/mkdocs/docs/api/basic_json_document/index.md index b0743e4ec..b38105623 100644 --- a/docs/mkdocs/docs/api/basic_json_document/index.md +++ b/docs/mkdocs/docs/api/basic_json_document/index.md @@ -109,7 +109,7 @@ points apply to every edit: anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...). - [`dump()`](../basic_json_view/dump.md) writes an edited document with members in document order, new members at the end, and, with [`number_format::source`](../basic_json_view/number_format.md), keeps the spelling of every - number that was not itself edited -- see [Editing a document](../../features/json_view.md#editing-a-document) for + number that was not itself edited — see [Editing a document](../../features/json_view.md#editing-a-document) for why this matters. - [`read()`](read.md) discards all edits, [`shrink_to_fit()`](shrink_to_fit.md) does not move the node index once there are edits, and [`memory_usage()`](memory_usage.md) includes the memory edits use. @@ -119,9 +119,9 @@ points apply to every edit: ## See also - [Zero-copy JSON views](../../features/json_view.md) - guide to documents and views -- [**basic_json_view**](../basic_json_view/index.md) - the non-owning handle to a value in a document -- [**json_document**](../json_document.md) and [**json_editable_document**](../json_editable_document.md) - the default specializations -- [**materialize**](../basic_json_view/materialize.md) - build a `basic_json` value from a view +- [`basic_json_view`](../basic_json_view/index.md) - the non-owning handle to a value in a document +- [`json_document`](../json_document.md) - and [`json_editable_document`](../json_editable_document.md) - the default specializations +- [`materialize`](../basic_json_view/materialize.md) - build a `basic_json` value from a view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/insert.md b/docs/mkdocs/docs/api/basic_json_document/insert.md index 883acabda..207e7bae8 100644 --- a/docs/mkdocs/docs/api/basic_json_document/insert.md +++ b/docs/mkdocs/docs/api/basic_json_document/insert.md @@ -13,8 +13,8 @@ Inserts `value` into `array` as a new element before position `idx`, which must [`push_back`](push_back.md), a [null](../basic_json_view/is_null.md) `array` does *not* first become an empty array: `array` must already be an array. -`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document -- read-only or -editable, and it does not have to be `array`'s own document -- which is copied so that nothing is shared with the +`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document — read-only or +editable, and it does not have to be `array`'s own document — which is copied so that nothing is shared with the source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...). @@ -44,16 +44,16 @@ a view of the new element, now holding `value` ## Exception safety -Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document +Basic exception safety: `value` is fully encoded — including the checks below — into storage owned by the document before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated -for the encoding that is not reclaimed. A failure of a later allocation -- while `array` switches from its parsed -layout to a growable block, or while that block grows, see [Notes](#notes) -- can still leave `array` already +for the encoding that is not reclaimed. A failure of a later allocation — while `array` switches from its parsed +layout to a growable block, or while that block grows, see [Notes](#notes) — can still leave `array` already switched to that layout even though `value` itself was not inserted. ## Exceptions -Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if `array` is not an array -- the same +Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if `array` is not an array — the same message [`BasicJsonType::insert`](../basic_json/insert.md) throws for the same type; a null `array` throws this too (see above). Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is negative, or if `#!cpp idx > array.size()`. Throws @@ -62,7 +62,7 @@ document") if `array` is a [discarded](../basic_json_view/is_discarded.md) view Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a [discarded](../basic_json_view/is_discarded.md) view or a [discarded](../basic_json/is_discarded.md) `BasicJsonType` value, and [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or contains) a -binary value -- `BasicJsonType` can hold one, but a `json_document` cannot. Throws +binary value — `BasicJsonType` can hold one, but a `json_document` cannot. Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `value` is (or contains) a string that is not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string. @@ -76,8 +76,8 @@ grows in amortized constant time; inserting before the end within that block sti ## Notes -Like [`set`](set.md) on a member or an element, `insert` never moves an existing *element's value* -- only where -`array`'s *links* to its elements live -- so a view of an existing element of `array` stays valid across an +Like [`set`](set.md) on a member or an element, `insert` never moves an existing *element's value* — only where +`array`'s *links* to its elements live — so a view of an existing element of `array` stays valid across an `insert`, and keeps referring to the same element even though its index shifts. Any iterator already taken over `array` is invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across an edit in general. @@ -88,7 +88,7 @@ an edit in general. The example below inserts a step into the middle of a deployment plan, without touching the steps that come after it, and shows that a view taken before the insert keeps referring to the same element even though its - index shifts -- something a plain `json`/`ordered_json` array, or its `std::vector`-based storage, has no + index shifts — something a plain `json`/`ordered_json` array, or its `std::vector`-based storage, has no equivalent for. ```cpp @@ -103,11 +103,12 @@ an edit in general. ## See also -- [push_back](push_back.md) - append to an array -- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to -- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to +- [`push_back`](push_back.md) - append to an array +- [`erase`](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to +- [`set`](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to - [`BasicJsonType::insert`](../basic_json/insert.md) - the corresponding function of `basic_json` - [Edits](index.md#edits) - what an edit guarantees, for every overload +- [Zero-copy JSON views](../../features/json_view.md#editing-a-document) - what an edit guarantees and costs ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/is_discarded.md b/docs/mkdocs/docs/api/basic_json_document/is_discarded.md index 3c062dddd..4e86a9748 100644 --- a/docs/mkdocs/docs/api/basic_json_document/is_discarded.md +++ b/docs/mkdocs/docs/api/basic_json_document/is_discarded.md @@ -41,8 +41,9 @@ When the document is discarded, [`root()`](root.md) returns a discarded view (it ## See also -- [parse](parse.md) - deserialize from a compatible input -- [is_discarded (basic_json_view)](../basic_json_view/is_discarded.md) - return whether a view is invalid +- [`parse`](parse.md) - deserialize from a compatible input +- [`basic_json_view::is_discarded`](../basic_json_view/is_discarded.md) - return whether a view is invalid +- [Zero-copy JSON views](../../features/json_view.md#what-is-the-same-as-parse) - how a discarded document arises ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/load.md b/docs/mkdocs/docs/api/basic_json_document/load.md index f389c0581..21edbaf87 100644 --- a/docs/mkdocs/docs/api/basic_json_document/load.md +++ b/docs/mkdocs/docs/api/basic_json_document/load.md @@ -16,14 +16,14 @@ static basic_json_document load(std::vector&& image, 1. Reads an image [`save()`](save.md) wrote, from a pointer and a byte count. The image is **borrowed**: `image` must stay alive and unchanged for as long as the returned document, and any view taken from it, is used. -2. Reads an image from a `#!cpp std::vector`. Also **borrowed** -- equivalent to overload 1 called with +2. Reads an image from a `#!cpp std::vector`. Also **borrowed** — equivalent to overload 1 called with `#!cpp image.data()` and `#!cpp image.size()`. 3. Reads an image, keeping the vector instead of copying it: `image` is moved into the document (no copy), which then owns it for as long as it needs the text and the decoded strings. [`owns_source()`](owns_source.md) is `#!cpp true` afterward. -In every overload, the node index is copied into storage the document itself owns -- so that it is properly aligned, -and, for an [editable](index.md#edits) document, can be edited -- while the text and the decoded strings stay in +In every overload, the node index is copied into storage the document itself owns — so that it is properly aligned, +and, for an [editable](index.md#edits) document, can be edited — while the text and the decoded strings stay in `image`. The hash indexes [large objects](../../features/json_view.md) use for lookup are rebuilt, exactly as after parsing. @@ -49,7 +49,7 @@ Overloads 1 and 2 give the strong guarantee: `image` is only read, never written caller's buffer untouched. Overload 3 moves `image` into the document *before* validating it, so that a good image is kept without a copy. If -loading then fails, the partially built document -- and the vector now inside it -- is discarded along with the +loading then fails, the partially built document — and the vector now inside it — is discarded along with the exception, and `image` itself is left **empty**, not restored to what was passed in. Move a copy in instead, or validate with overload 2 first, if the original vector must survive a failed load. @@ -63,7 +63,7 @@ Throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) | `too short` | `image` is `#!cpp nullptr`, or `size` is smaller than the 64-byte header | | `unknown format` | the header's magic bytes or version do not match, or a reserved header field is not zero | | `sizes out of range` | the node count, text size, or decoded-string size the header describes does not fit `size`, or the `#!cpp '\0'` after the text or after the decoded strings is missing | -| `the check failed` | `check` is not `#!cpp image_check::none`, and the image fails it -- see [`image_check`](#image_check) | +| `the check failed` | `check` is not `#!cpp image_check::none`, and the image fails it — see [`image_check`](#image_check) | !!! failure "Example messages" @@ -116,7 +116,7 @@ How thoroughly `load()` validates `image` before trusting it. token is well-formed and matches the value stored for it. Strings and float tokens may share a range only if the ranges are identical (as nodes that share a value do), and never overlap otherwise. Reading and serializing a checked image is safe and always produces valid JSON, exactly as for a parsed document. This is the right choice for - an image from anything you do not fully control -- a file, a cache shared with other processes, a peer on the + an image from anything you do not fully control — a file, a cache shared with other processes, a peer on the network. `bounds` @@ -136,7 +136,7 @@ How thoroughly `load()` validates `image` before trusting it. your own memory. **Lifetime.** Overloads 1 and 2 borrow `image`: it must stay alive and byte-for-byte unchanged for as long as the -returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document +returned document, and any [view](../basic_json_view/index.md) taken from it, is used — exactly like a document [`parse()`](parse.md) borrowed its input for. Overload 3 avoids this by keeping the vector itself; see [`owns_source`](owns_source.md). @@ -145,7 +145,7 @@ returned document, and any [view](../basic_json_view/index.md) taken from it, is The image format is versioned but not yet stable, and may change in an incompatible way before it is declared stable; `load()` already rejects an image written by a different format version with `parse_error.116` ("unknown format"). Use images to cache a document within one build of the library, or to hand one to another - process running the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. + process running the *same* build on the *same* (little-endian) machine — not as a long-term storage format. ## Examples @@ -154,8 +154,8 @@ returned document, and any [view](../basic_json_view/index.md) taken from it, is The example below saves a parsed document as an image, checks that `load()` reproduces the original [`dump()`](../basic_json_view/dump.md) without parsing, and shows the difference between `load(std::move(image))` (owned) and `load(image)` (borrowed). It then damages one byte of the image and shows - `image_check::full` rejecting it with `parse_error.116`, while `image_check::bounds` -- meant for a cache the - process already trusts -- still reads it without going out of bounds. + `image_check::full` rejecting it with `parse_error.116`, while `image_check::bounds` — meant for a cache the + process already trusts — still reads it without going out of bounds. ```cpp --8<-- "examples/basic_json_document__load.cpp" @@ -169,9 +169,9 @@ returned document, and any [view](../basic_json_view/index.md) taken from it, is ## See also -- [save](save.md) - write the document as an image -- [owns_source](owns_source.md) - return whether the document holds its own copy of the text -- [parse](parse.md) - deserialize from JSON text instead of an image +- [`save`](save.md) - write the document as an image +- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text +- [`parse`](parse.md) - deserialize from JSON text instead of an image - [Images](../../features/json_view.md#images) - why and when to use images ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/memory_usage.md b/docs/mkdocs/docs/api/basic_json_document/memory_usage.md index 5ad25911b..d265d623c 100644 --- a/docs/mkdocs/docs/api/basic_json_document/memory_usage.md +++ b/docs/mkdocs/docs/api/basic_json_document/memory_usage.md @@ -23,7 +23,7 @@ Constant. The exact value depends on the platform, the allocator, and the library's own layout, and may change between versions; do not rely on it being a specific number, and do not compare it across different builds or platforms. -Compare it for the same document over time, or between documents built with the same binary, instead -- for instance +Compare it for the same document over time, or between documents built with the same binary, instead — for instance to observe the effect of [`shrink_to_fit()`](shrink_to_fit.md). ## Examples @@ -42,8 +42,9 @@ to observe the effect of [`shrink_to_fit()`](shrink_to_fit.md). ## See also -- [node_count](node_count.md) - the number of index entries -- [shrink_to_fit](shrink_to_fit.md) - release unused index capacity +- [`node_count`](node_count.md) - the number of index entries +- [`shrink_to_fit`](shrink_to_fit.md) - release unused index capacity +- [Zero-copy JSON views](../../features/json_view.md#the-idea) - the flat node index and its memory ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/node_count.md b/docs/mkdocs/docs/api/basic_json_document/node_count.md index 61923f8cd..5bb63a40d 100644 --- a/docs/mkdocs/docs/api/basic_json_document/node_count.md +++ b/docs/mkdocs/docs/api/basic_json_document/node_count.md @@ -41,8 +41,9 @@ strings and, for an owned document, the text). ## See also -- [memory_usage](memory_usage.md) - the number of bytes held by the document -- [shrink_to_fit](shrink_to_fit.md) - release unused index capacity +- [`memory_usage`](memory_usage.md) - the number of bytes held by the document +- [`shrink_to_fit`](shrink_to_fit.md) - release unused index capacity +- [Zero-copy JSON views](../../features/json_view.md#the-idea) - the flat node index and its memory ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/owns_source.md b/docs/mkdocs/docs/api/basic_json_document/owns_source.md index ede04e369..f4068cab5 100644 --- a/docs/mkdocs/docs/api/basic_json_document/owns_source.md +++ b/docs/mkdocs/docs/api/basic_json_document/owns_source.md @@ -40,9 +40,10 @@ document (`#!cpp owns_source() == false`) is only valid while the buffer it was ## See also -- [parse](parse.md) - deserialize from a compatible input -- [parse_copy](parse_copy.md) - deserialize a copy of a compatible input, always owned -- [source](source.md) - the parsed text +- [`parse`](parse.md) - deserialize from a compatible input +- [`parse_copy`](parse_copy.md) - deserialize a copy of a compatible input, always owned +- [`source`](source.md) - the parsed text +- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - who owns the source text and for how long ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/parse.md b/docs/mkdocs/docs/api/basic_json_document/parse.md index 3f39f3485..e458e621d 100644 --- a/docs/mkdocs/docs/api/basic_json_document/parse.md +++ b/docs/mkdocs/docs/api/basic_json_document/parse.md @@ -24,7 +24,7 @@ Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_ the input) instead of a tree of `BasicJsonType` values. They accept every input `BasicJsonType::parse()` takes, and in addition a pointer to a null-terminated wide string (which `BasicJsonType::parse()` rejects). Contiguous inputs of single-byte characters that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, which -must be converted to UTF-8, and non-contiguous iterators) are read into a copy that the document owns -- see the +must be converted to UTF-8, and non-contiguous iterators) are read into a copy that the document owns — see the Notes. Use [`parse_copy()`](parse_copy.md) to get an owned copy of a borrowable input as well. @@ -82,8 +82,8 @@ discarded; see [`is_discarded`](is_discarded.md). ## Exceptions -Throws the same exception [`BasicJsonType::parse()`](../basic_json/parse.md) throws for the same input and options -- -the same exception id, message, and position -- because on a failing input the library's own parser is run on the +Throws the same exception [`BasicJsonType::parse()`](../basic_json/parse.md) throws for the same input and options — +the same exception id, message, and position — because on a failing input the library's own parser is run on the same bytes to produce the diagnostic. This replay runs only for a failing input, and costs about a lexing pass up to the error (its SAX handler builds no values); with `allow_exceptions` set to `#!cpp false`, it is skipped and a discarded document is returned instead. Additionally throws @@ -100,7 +100,7 @@ Linear in the length of the input. | `input` | ownership | |--------------------------------------------------------------------------------------|--------------------------------------------------------------| -| lvalue byte container (`std::string`, `std::vector`, ...), `std::string_view`, C string, character array | **borrowed** -- `input` must outlive the document | +| lvalue byte container (`std::string`, `std::vector`, ...), `std::string_view`, C string, character array | **borrowed** — `input` must outlive the document | | non-const rvalue `#!cpp std::string` | **owned**, moved in without a copy | | other rvalue byte container (including a `#!cpp const` rvalue `#!cpp std::string`) | **owned**, copied | | stream, `#!cpp FILE*`, wide characters, or anything else read through the general input adapter | **owned**, read into a buffer (a stream is read to its end) | @@ -117,8 +117,8 @@ See [`owns_source`](owns_source.md) to check which happened after a call, and th **Numbers.** As for [`BasicJsonType::parse()`](../basic_json/parse.md), an integer literal too large for the 64-bit integer type becomes a floating-point value. -**No lengths.** An integer argument that is not a `#!cpp bool` where the flags are expected -- for example -`#!cpp parse(ptr, len)` -- does not compile (the overload is deleted). Such a call would convert `len` to +**No lengths.** An integer argument that is not a `#!cpp bool` where the flags are expected — for example +`#!cpp parse(ptr, len)` — does not compile (the overload is deleted). Such a call would convert `len` to `allow_exceptions` and read `ptr` as a null-terminated string, past the end of a buffer that has none. To parse a buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)`. The same holds for [`parse_copy`](parse_copy.md), [`accept`](accept.md), and [`read`](read.md). @@ -151,11 +151,11 @@ buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)` ## See also -- [parse_copy](parse_copy.md) - deserialize a copy of a compatible input -- [accept](accept.md) - check whether the input is valid JSON -- [read](read.md) - (re-)parse into this document, reusing its memory -- [owns_source](owns_source.md) - return whether the document holds its own copy of the text -- [load](load.md) - read a document from an image instead of parsing JSON text +- [`parse_copy`](parse_copy.md) - deserialize a copy of a compatible input +- [`accept`](accept.md) - check whether the input is valid JSON +- [`read`](read.md) - (re-)parse into this document, reusing its memory +- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text +- [`load`](load.md) - read a document from an image instead of parsing JSON text - [`BasicJsonType::parse`](../basic_json/parse.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/parse_copy.md b/docs/mkdocs/docs/api/basic_json_document/parse_copy.md index 7cb51ad02..f9110207b 100644 --- a/docs/mkdocs/docs/api/basic_json_document/parse_copy.md +++ b/docs/mkdocs/docs/api/basic_json_document/parse_copy.md @@ -72,8 +72,9 @@ compile; see [`parse`](parse.md#notes). ## See also -- [parse](parse.md) - deserialize from a compatible input, borrowing it where possible -- [owns_source](owns_source.md) - return whether the document holds its own copy of the text +- [`parse`](parse.md) - deserialize from a compatible input, borrowing it where possible +- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text +- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - borrowing or copying the source text ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/push_back.md b/docs/mkdocs/docs/api/basic_json_document/push_back.md index 96e3f113a..f433fc7db 100644 --- a/docs/mkdocs/docs/api/basic_json_document/push_back.md +++ b/docs/mkdocs/docs/api/basic_json_document/push_back.md @@ -8,8 +8,8 @@ view_type push_back(view_type array, V&& value); Appends `value` as a new last element of `array`. A [null](../basic_json_view/is_null.md) `array` first becomes an empty array, the same way [`set`](set.md) turns a null `object` into an empty object. -`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document -- read-only or -editable, and it does not have to be `array`'s own document -- which is copied so that nothing is shared with the +`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document — read-only or +editable, and it does not have to be `array`'s own document — which is copied so that nothing is shared with the source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...). @@ -35,23 +35,23 @@ a view of the new last element of `array`, now holding `value` ## Exception safety -Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document +Basic exception safety: `value` is fully encoded — including the checks below — into storage owned by the document before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated -for the encoding that is not reclaimed. A failure of a later allocation -- while `array` switches from its parsed -layout to a growable block, or while that block grows, see [Notes](#notes) -- can still leave a partial effect, such +for the encoding that is not reclaimed. A failure of a later allocation — while `array` switches from its parsed +layout to a growable block, or while that block grows, see [Notes](#notes) — can still leave a partial effect, such as a null `array` argument already turned into an empty array even though `value` itself was not appended. ## Exceptions Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) if `array` is neither an array nor -null -- the same message [`BasicJsonType::push_back`](../basic_json/push_back.md) throws for the same type. Throws +null — the same message [`BasicJsonType::push_back`](../basic_json/push_back.md) throws for the same type. Throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this document") if `array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a *different* document. Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a [discarded](../basic_json_view/is_discarded.md) view or a [discarded](../basic_json/is_discarded.md) `BasicJsonType` value, and [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or contains) a -binary value -- `BasicJsonType` can hold one, but a `json_document` cannot. Throws +binary value — `BasicJsonType` can hold one, but a `json_document` cannot. Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `value` is (or contains) a string that is not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string. @@ -59,14 +59,14 @@ not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/d Amortized constant, plus time linear in the size of `value` to encode it into the document's storage (constant for a scalar, linear in the number of nested values for an array or object): the elements of `array` move to a growable -block of links the first time it is appended to (or [`set`](set.md) on), and that block itself grows -- doubling its -capacity, so the cost of growing it amortizes to constant per element -- only once it runs out of room. See +block of links the first time it is appended to (or [`set`](set.md) on), and that block itself grows — doubling its +capacity, so the cost of growing it amortizes to constant per element — only once it runs out of room. See [Notes](#notes). ## Notes -Like [`set`](set.md) on a member or an element, `push_back` never moves an existing element itself -- only where -`array`'s *links* to its elements live -- so a view of an existing element of `array` stays valid across a +Like [`set`](set.md) on a member or an element, `push_back` never moves an existing element itself — only where +`array`'s *links* to its elements live — so a view of an existing element of `array` stays valid across a `push_back`, but any iterator already taken over `array` is invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across an edit in general. @@ -90,12 +90,13 @@ Like [`set`](set.md) on a member or an element, `push_back` never moves an exist ## See also -- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to -- [insert](insert.md) - insert an element into an array before a given position -- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to -- [root](root.md) - the view of the root value +- [`set`](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to +- [`insert`](insert.md) - insert an element into an array before a given position +- [`erase`](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to +- [`root`](root.md) - the view of the root value - [`BasicJsonType::push_back`](../basic_json/push_back.md) - the corresponding function of `basic_json` - [Edits](index.md#edits) - what an edit guarantees, for every overload +- [Zero-copy JSON views](../../features/json_view.md#editing-a-document) - what an edit guarantees and costs ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/read.md b/docs/mkdocs/docs/api/basic_json_document/read.md index 174fcc477..63915b1c2 100644 --- a/docs/mkdocs/docs/api/basic_json_document/read.md +++ b/docs/mkdocs/docs/api/basic_json_document/read.md @@ -43,7 +43,7 @@ Linear in the length of the input. ## Notes -Every view taken from `#!cpp *this` before the call -- including the previous [`root()`](root.md) -- is invalidated, +Every view taken from `#!cpp *this` before the call — including the previous [`root()`](root.md) — is invalidated, whether or not the new parse succeeds; take fresh views from [`root()`](root.md) afterward. `input` is borrowed or owned by the same rules as [`parse()`](parse.md#notes); a document can borrow on one call and @@ -78,9 +78,10 @@ compile; see [`parse`](parse.md#notes). ## See also -- [parse](parse.md) - deserialize from a compatible input -- [root](root.md) - the view of the root value -- [load](load.md) - read a document from an image instead of parsing JSON text +- [`parse`](parse.md) - deserialize from a compatible input +- [`root`](root.md) - the view of the root value +- [`load`](load.md) - read a document from an image instead of parsing JSON text +- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - reusing a document and what it invalidates ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/root.md b/docs/mkdocs/docs/api/basic_json_document/root.md index a8f0c0ded..14e4514c2 100644 --- a/docs/mkdocs/docs/api/basic_json_document/root.md +++ b/docs/mkdocs/docs/api/basic_json_document/root.md @@ -37,8 +37,8 @@ auto v = doc.root(); ``` `root()` is a cheap handle into the document's index, not a copy of anything; call it as often as needed. The -returned view is valid under the same conditions as any other view of the document -- see -[Object inspection](../basic_json_view/index.md) -- in particular, it is invalidated by the next +returned view is valid under the same conditions as any other view of the document — see +[Object inspection](../basic_json_view/index.md) — in particular, it is invalidated by the next [`read()`](read.md) or [`shrink_to_fit()`](shrink_to_fit.md) on this document. A view can dangle, like a `#!cpp std::string_view`; see [Ownership and lifetime](../../features/json_view.md#ownership-and-lifetime). @@ -58,8 +58,8 @@ returned view is valid under the same conditions as any other view of the docume ## See also -- [is_discarded](is_discarded.md) - return whether the last parse failed -- [materialize](../basic_json_view/materialize.md) - build the `BasicJsonType` value of a subtree +- [`is_discarded`](is_discarded.md) - return whether the last parse failed +- [`materialize`](../basic_json_view/materialize.md) - build the `BasicJsonType` value of a subtree ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/save.md b/docs/mkdocs/docs/api/basic_json_document/save.md index c87d90f07..466a15ea5 100644 --- a/docs/mkdocs/docs/api/basic_json_document/save.md +++ b/docs/mkdocs/docs/api/basic_json_document/save.md @@ -6,13 +6,13 @@ std::vector save() const; Writes the document as an *image*: a byte buffer that [`load`](load.md) reads back without parsing. The image holds the node index, the source text (plus, for an edited document, the number tokens edits wrote), and the decoded -strings (plus the strings edits wrote) -- everything [`root()`](root.md) needs, with nothing left to parse. +strings (plus the strings edits wrote) — everything [`root()`](root.md) needs, with nothing left to parse. An edited document is written in its *current* state, with its values in document order, the way the library's own parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an [`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity) becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to -the same bytes -- also across `BasicJsonType` and the [`view_mode`](../view_mode.md), since the image reflects document order and +the same bytes — also across `BasicJsonType` and the [`view_mode`](../view_mode.md), since the image reflects document order and values only, not which specialization produced them. ## Return value @@ -28,11 +28,11 @@ exactly as it was, and the partially built image is discarded with the exception ## Exceptions Throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) if the document is -[discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/ +[discarded](is_discarded.md) — a default-constructed document, or one a failed [`parse()`](parse.md)/ [`read()`](read.md) with `allow_exceptions == false` left discarded. Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or -the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets +the decoded strings of the image would individually reach 4 GiB — the same 32-bit offsets [`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to. !!! failure "Example messages" @@ -55,13 +55,13 @@ up in the image. and the sizes of the text and the decoded strings, all little-endian), followed by the nodes ([16 bytes each](../../home/architecture.md#node-index-of-json-views)), the text and a `#!cpp '\0'`, and the decoded strings and a `#!cpp '\0'`. [`load`](load.md) checks the header, and the sizes it describes, before reading anything -else -- see [`load`'s Exceptions](load.md#exceptions). +else — see [`load`'s Exceptions](load.md#exceptions). !!! warning "Experimental" The image format is versioned but not yet stable: it may change in an incompatible way before it is declared stable. Use images to cache a document within one build of the library, or to hand one to another process running - the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original + the *same* build on the *same* (little-endian) machine — not as a long-term storage format. Keep the original JSON text if you need to read a saved document back with a future library version. **Little-endian targets only.** The image is written as raw little-endian bytes, with no byte-swapping. On a @@ -72,8 +72,8 @@ big-endian reader could not interpret correctly. ??? example "Caching a parsed document as an image" - The example below saves a parsed configuration as an image -- the way a service might cache one to answer later - requests without parsing the text again -- and confirms that loading it back gives exactly the same result as + The example below saves a parsed configuration as an image — the way a service might cache one to answer later + requests without parsing the text again — and confirms that loading it back gives exactly the same result as parsing did, and that saving is deterministic. ```cpp @@ -88,8 +88,8 @@ big-endian reader could not interpret correctly. ## See also -- [load](load.md) - read an image written by `save()` -- [owns_source](owns_source.md) - return whether the document holds its own copy of the text +- [`load`](load.md) - read an image written by `save()` +- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text - [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document to JSON text instead of an image - [Images](../../features/json_view.md#images) - why and when to use images diff --git a/docs/mkdocs/docs/api/basic_json_document/set.md b/docs/mkdocs/docs/api/basic_json_document/set.md index dd5d99611..e3f05b151 100644 --- a/docs/mkdocs/docs/api/basic_json_document/set.md +++ b/docs/mkdocs/docs/api/basic_json_document/set.md @@ -23,8 +23,8 @@ has `set`; calling it on a read-only `basic_json_document` fails to compile (`#! 1. Replaces the value `target` refers to with `value`. 2. Sets the member `key` of the object `object` to `value`: assigns it if `object` already has a member with this - key -- the first one, should the key occur more than once, and the later duplicates are then dropped (see the - [Notes](#notes) below) -- or appends a new member at the end otherwise. A [null](../basic_json_view/is_null.md) + key — the first one, should the key occur more than once, and the later duplicates are then dropped (see the + [Notes](#notes) below) — or appends a new member at the end otherwise. A [null](../basic_json_view/is_null.md) `object` first becomes an empty object. 3. Assigns `value` to the element at index `idx` of the array `array`, which must already exist (`#!cpp idx < array.size()`). @@ -37,7 +37,7 @@ has `set`; calling it on a read-only `basic_json_document` fails to compile (`#! null values up to that index), an object otherwise. An empty `ptr` sets [`root()`](root.md) itself, as in 1. In every overload, `value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* -document -- read-only or editable, and it does not have to be `target`'s/`object`'s/`array`'s own document -- which +document — read-only or editable, and it does not have to be `target`'s/`object`'s/`array`'s own document — which is copied so that nothing is shared with the source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...). @@ -82,11 +82,11 @@ is copied so that nothing is shared with the source document afterward; a `Basic ## Exception safety -Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document +Basic exception safety: `value` is fully encoded — including the checks below — into storage owned by the document before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated -for the encoding that is not reclaimed. A failure of a later allocation -- while an edited array or object switches -from its parsed layout to a growable block, see [Notes](#notes) -- can still leave a partial effect, such as a +for the encoding that is not reclaimed. A failure of a later allocation — while an edited array or object switches +from its parsed layout to a growable block, see [Notes](#notes) — can still leave a partial effect, such as a [null](../basic_json_view/is_null.md) `object`/`array` argument already turned into an empty object/array even though `value` itself was not linked in. Likewise, a failure of `value` in 4. leaves a null parent that is set with an index beyond 0 already filled with the null values before the index. @@ -95,14 +95,14 @@ index beyond 0 already filled with the null values before the index. 1. Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a [discarded](../basic_json_view/is_discarded.md) view, or a [discarded](../basic_json/is_discarded.md) - `BasicJsonType` value (e.g. `#!cpp BasicJsonType(value_t::discarded)`) -- an object or array `value`, of either + `BasicJsonType` value (e.g. `#!cpp BasicJsonType(value_t::discarded)`) — an object or array `value`, of either kind, is fine and is encoded as a whole subtree. 2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if `object` is neither an object - nor null -- the same message [`operator[]`](../basic_json_view/operator%5B%5D.md) throws for a string argument on + nor null — the same message [`operator[]`](../basic_json_view/operator%5B%5D.md) throws for a string argument on such a value. Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `key` is not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string. Also throws what 1. throws for `value`. -3. Throws `type_error.305` if `array` is not an array -- the same message `operator[]` throws for a numeric argument +3. Throws `type_error.305` if `array` is not an array — the same message `operator[]` throws for a numeric argument on such a value. Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is negative, or if `#!cpp idx >= array.size()`. Also throws what 1. throws for `value`. 4. Throws what [`at`](../basic_json_view/at.md) throws (overload 3) for resolving `ptr`'s parent, except that a @@ -121,7 +121,7 @@ index beyond 0 already filled with the null values before the index. Also throws what 1. throws for `value`. Every overload also throws [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or -contains) a binary value -- `BasicJsonType` can hold one, but a `json_document` cannot -- and +contains) a binary value — `BasicJsonType` can hold one, but a `json_document` cannot — and [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a *different* document (overloads 1-3 only; overload 4 always starts from this document's own [`root()`](root.md)). @@ -132,7 +132,7 @@ document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_d number of nested values for an array or object. If `target` is itself an array or object that spans more than one node in its parent's original, unedited layout, and `value` is a scalar, replacing it additionally costs time linear in the size of the document, the *first* time (the parent of `target` is looked up from - [`root()`](root.md), and then switches to links) -- see [Notes](#notes). Once the parent has links, `target` is + [`root()`](root.md), and then switches to links) — see [Notes](#notes). Once the parent has links, `target` is replaced in constant time: setting every element of a large array one after the other is linear overall. To avoid the lookup altogether, use 3. (or 2. for an object), which know the parent. 2. Linear in the number of members of `object`, to find an existing member with `key`, plus the complexity of 1. for @@ -147,7 +147,7 @@ document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_d !!! info "Duplicate keys" If `object` already has more than one member with `key` (2.), the *first* one is assigned `value` and every - later member with the same key is removed -- so that a lookup, an iteration, and + later member with the same key is removed — so that a lookup, an iteration, and [`materialize()`](../basic_json_view/materialize.md) of `object` afterward all agree on a single value for `key`, the same way [`operator[]`](../basic_json_view/operator%5B%5D.md) already picks the first occurrence of a duplicate key for reading. See the [Notes on duplicate keys](../basic_json_view/operator%5B%5D.md#notes) of @@ -156,8 +156,8 @@ document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_d Setting a member (2.) or an element (3., through 4.) of an array or object whose elements have not been edited before switches it from its parsed layout to a growable block holding links to its elements; a later [`push_back`](push_back.md) or `set` on the same container reuses that block, growing it (amortized constant time) -only once it runs out of room. This never moves an element itself -- only where the container's *links* to its -elements live -- so a view of an element stays valid, but any iterator already taken over the container is +only once it runs out of room. This never moves an element itself — only where the container's *links* to its +elements live — so a view of an element stays valid, but any iterator already taken over the container is invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across an edit in general. @@ -170,8 +170,8 @@ one-node scalar. ??? example "Example: (1)/(2)/(3)/(4) replace a value, set a member, assign an element, set via a JSON pointer" - The example below edits a small configuration document -- replacing a value, adding an object member, assigning - an array element, and reaching a field through a JSON pointer -- and shows what + The example below edits a small configuration document — replacing a value, adding an object member, assigning + an array element, and reaching a field through a JSON pointer — and shows what [`dump()`](../basic_json_view/dump.md) preserves that is lost once the same edits are made on a `BasicJsonType` value instead: the order object members were written in, and the exact spelling of a number that was never touched. @@ -188,10 +188,10 @@ one-node scalar. ## See also -- [push_back](push_back.md) - append to an array -- [insert](insert.md) - insert an element into an array -- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to -- [root](root.md) - the view of the root value, the starting point of overload 4 +- [`push_back`](push_back.md) - append to an array +- [`insert`](insert.md) - insert an element into an array +- [`erase`](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to +- [`root`](root.md) - the view of the root value, the starting point of overload 4 - [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document, keeping an untouched number's spelling with `#!cpp number_format::source` - [Edits](index.md#edits) - what an edit guarantees, for every overload diff --git a/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md b/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md index 128e7f996..b4ad53fba 100644 --- a/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md +++ b/docs/mkdocs/docs/api/basic_json_document/shrink_to_fit.md @@ -53,9 +53,10 @@ possible would be an internal optimization only, not a guarantee to rely on. ## See also -- [node_count](node_count.md) - the number of index entries -- [memory_usage](memory_usage.md) - the number of bytes held by the document -- [root](root.md) - the view of the root value +- [`node_count`](node_count.md) - the number of index entries +- [`memory_usage`](memory_usage.md) - the number of bytes held by the document +- [`root`](root.md) - the view of the root value +- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - what releasing memory invalidates ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_document/source.md b/docs/mkdocs/docs/api/basic_json_document/source.md index dcefb88a9..688bb6bf3 100644 --- a/docs/mkdocs/docs/api/basic_json_document/source.md +++ b/docs/mkdocs/docs/api/basic_json_document/source.md @@ -40,8 +40,9 @@ is; see [`owns_source`](owns_source.md). ## See also -- [owns_source](owns_source.md) - return whether the document holds its own copy of the text -- [source_offset](../basic_json_view/source_offset.md) - byte offset of a value in the source text +- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text +- [`source_offset`](../basic_json_view/source_offset.md) - byte offset of a value in the source text +- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - who owns the source text and for how long ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/at.md b/docs/mkdocs/docs/api/basic_json_view/at.md index afbf81620..747b46299 100644 --- a/docs/mkdocs/docs/api/basic_json_view/at.md +++ b/docs/mkdocs/docs/api/basic_json_view/at.md @@ -15,7 +15,7 @@ basic_json_view at(IntegerType idx) const; basic_json_view at(const json_pointer& ptr) const; ``` -1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see +1. Returns the value of the object member with key `key` — the first one, should the key occur more than once (see [Notes on duplicate keys](operator[].md#notes)). 2. Returns the array element at index `idx`. The template accepts every integer type except `#!cpp bool` and `#!cpp std::size_t` and forwards to the `size_type` overload, as for [`operator[]`](operator[].md); a negative @@ -62,7 +62,7 @@ Strong guarantee: there are no changes to the view or the document it refers to. - Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index in `ptr` is out of range. - Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if a reference token is - `#!cpp "-"` at an array -- `at` never inserts an element, so `#!cpp "-"` is always invalid. + `#!cpp "-"` at an array — `at` never inserts an element, so `#!cpp "-"` is always invalid. - Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a reference token names an object member that does not exist. - Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved @@ -76,7 +76,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another, in document order, stopping at the first match. Each comparison first checks the - key's length -- already known from the index, without reading the key bytes -- before comparing its content. + key's length — already known from the index, without reading the key bytes — before comparing its content. Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes constant time on average. 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the @@ -95,7 +95,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics the last value. Unlike [`operator[]`](operator[].md), which returns a [discarded](is_discarded.md) view for a missing key or an -out-of-range index, `at` always throws -- exactly as `BasicJsonType::at` does, and with the same messages, so +out-of-range index, `at` always throws — exactly as `BasicJsonType::at` does, and with the same messages, so existing error handling written against `BasicJsonType::at` keeps working unchanged when switched to a view. This also holds for overload 3: unlike [`operator[]`](operator[].md) with a JSON pointer, which returns a discarded view for a missing key or an out-of-range index, `at` throws for those too (`out_of_range.403`/`out_of_range.401`). @@ -105,7 +105,7 @@ for a missing key or an out-of-range index, `at` throws for those too (`out_of_r ??? example "Example: (1)/(2) access specified element with bounds checking" The example below reads required fields out of a service configuration with `at`, and shows that the exceptions - it throws -- for a wrong type and for a missing key -- carry the same messages + it throws — for a wrong type and for a missing key — carry the same messages [`BasicJsonType::at`](../basic_json/at.md) would produce for the same JSON text. ```cpp @@ -135,8 +135,8 @@ for a missing key or an out-of-range index, `at` throws for those too (`out_of_r ## See also -- [operator[]](operator[].md) - access specified element (returns a discarded view instead of throwing) -- [front](front.md), [back](back.md) - access the first or last element +- [`operator[]`](operator[].md) - access specified element (returns a discarded view instead of throwing) +- [`front`](front.md), [`back`](back.md) - access the first or last element - [`BasicJsonType::at`](../basic_json/at.md) - the corresponding function of `basic_json` - [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3 diff --git a/docs/mkdocs/docs/api/basic_json_view/back.md b/docs/mkdocs/docs/api/basic_json_view/back.md index acb6ebd65..473502f0a 100644 --- a/docs/mkdocs/docs/api/basic_json_view/back.md +++ b/docs/mkdocs/docs/api/basic_json_view/back.md @@ -56,7 +56,7 @@ Unlike [`BasicJsonType::back()`](../basic_json/back.md), which has undefined beh ## See also -- [front](front.md) - access the first element +- [`front`](front.md) - access the first element - [`BasicJsonType::back`](../basic_json/back.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md b/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md index 7ae259287..b4aa4db00 100644 --- a/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md +++ b/docs/mkdocs/docs/api/basic_json_view/basic_json_view.md @@ -55,9 +55,10 @@ placeholder for "no value yet" and later be assigned a real view. ## See also -- [is_discarded](is_discarded.md) - return whether the view is invalid -- [root](../basic_json_document/root.md) - the view of a document's root value -- [view_mode](../view_mode.md) - whether a document and its views can be edited +- [`is_discarded`](is_discarded.md) - return whether the view is invalid +- [`root`](../basic_json_document/root.md) - the view of a document's root value +- [`view_mode`](../view_mode.md) - whether a document and its views can be edited +- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - how long a view stays valid ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/begin.md b/docs/mkdocs/docs/api/basic_json_view/begin.md index a294fd46d..9c9974693 100644 --- a/docs/mkdocs/docs/api/basic_json_view/begin.md +++ b/docs/mkdocs/docs/api/basic_json_view/begin.md @@ -5,7 +5,7 @@ iterator begin() const noexcept; ``` Returns an iterator to the first element of an array, or the first member value of an object, in **document order** --- the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one +— the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one element (itself), and a `#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range. ## Return value @@ -22,7 +22,7 @@ Constant. ## Notes -For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike +For an object, iteration visits **every** member, including all occurrences of a duplicate key — unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and [`contains`](contains.md), which all resolve to the *first* member with a given key. See the [Notes on duplicate keys](operator[].md#notes) of `operator[]`. @@ -55,9 +55,9 @@ the duplicates. ## See also -- [end](end.md) - returns an iterator to one past the last element -- [cbegin](cbegin.md) - returns a const iterator to the first element -- [items](items.md) - access iterator member functions in range-based for +- [`end`](end.md) - returns an iterator to one past the last element +- [`cbegin`](cbegin.md) - returns a const iterator to the first element +- [`items`](items.md) - access iterator member functions in range-based for - [`BasicJsonType::begin`](../basic_json/begin.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/cbegin.md b/docs/mkdocs/docs/api/basic_json_view/cbegin.md index 25d17bdad..bd6674230 100644 --- a/docs/mkdocs/docs/api/basic_json_view/cbegin.md +++ b/docs/mkdocs/docs/api/basic_json_view/cbegin.md @@ -25,7 +25,7 @@ Constant. ??? example The example below sums many measurements with `std::accumulate`, using `cbegin()`/[`cend()`](cend.md) as the - range -- the same way it would for any standard container -- without ever materializing the whole array into a + range — the same way it would for any standard container — without ever materializing the whole array into a `BasicJsonType` value. ```cpp @@ -40,8 +40,8 @@ Constant. ## See also -- [begin](begin.md) - returns an iterator to the first element -- [cend](cend.md) - returns a const iterator to one past the last element +- [`begin`](begin.md) - returns an iterator to the first element +- [`cend`](cend.md) - returns a const iterator to one past the last element - [`BasicJsonType::cbegin`](../basic_json/cbegin.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/cend.md b/docs/mkdocs/docs/api/basic_json_view/cend.md index 6d19530ce..cd7def05c 100644 --- a/docs/mkdocs/docs/api/basic_json_view/cend.md +++ b/docs/mkdocs/docs/api/basic_json_view/cend.md @@ -25,7 +25,7 @@ Constant. ??? example The example below checks that every record of a batch is an object with `std::all_of`, using - [`cbegin()`](cbegin.md)/`cend()` as the range -- the same way it would for any standard container -- before + [`cbegin()`](cbegin.md)/`cend()` as the range — the same way it would for any standard container — before materializing any record of the batch. ```cpp @@ -40,8 +40,8 @@ Constant. ## See also -- [end](end.md) - returns an iterator to one past the last element -- [cbegin](cbegin.md) - returns a const iterator to the first element +- [`end`](end.md) - returns an iterator to one past the last element +- [`cbegin`](cbegin.md) - returns a const iterator to the first element - [`BasicJsonType::cend`](../basic_json/cend.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/contains.md b/docs/mkdocs/docs/api/basic_json_view/contains.md index 5f15adf11..dc1208125 100644 --- a/docs/mkdocs/docs/api/basic_json_view/contains.md +++ b/docs/mkdocs/docs/api/basic_json_view/contains.md @@ -34,11 +34,11 @@ No-throw guarantee: this function never throws exceptions. 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another, in document order, stopping at the first match. Each comparison first checks the - key's length -- already known from the index, without reading the key bytes -- before comparing its content. + key's length — already known from the index, without reading the key bytes — before comparing its content. Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes constant time on average. 2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at - that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and + that level or the index into the array — as for [`operator[]`](operator[].md#complexity) and [`at`](at.md#complexity) with a JSON pointer. ## Notes @@ -50,7 +50,7 @@ No-throw guarantee: this function never throws exceptions. `operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get the last value. -Overload 1 always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md) +Overload 1 always returns `#!cpp false` when the value is not an object — including a [discarded](is_discarded.md) view. !!! info "Postconditions" @@ -101,9 +101,9 @@ view. ## See also -- [find](find.md) - find a value in an object -- [count](count.md) - returns the number of occurrences of a key -- [at](at.md), [operator[]](operator[].md) - resolve a JSON pointer and throw, or return a discarded view +- [`find`](find.md) - find a value in an object +- [`count`](count.md) - returns the number of occurrences of a key +- [`at`](at.md), [`operator[]`](operator[].md) - resolve a JSON pointer and throw, or return a discarded view - [`BasicJsonType::contains`](../basic_json/contains.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/count.md b/docs/mkdocs/docs/api/basic_json_view/count.md index 7d6769e0d..81d12915c 100644 --- a/docs/mkdocs/docs/api/basic_json_view/count.md +++ b/docs/mkdocs/docs/api/basic_json_view/count.md @@ -27,13 +27,13 @@ No-throw guarantee: this function never throws exceptions. Linear in the number of members: unlike the lookup functions, `count()` does not stop at the first match, because it has to count every member with the key. Members are compared one after another, in document order. Each comparison -first checks the key's length -- already known from the index, without reading the key bytes -- before comparing its +first checks the key's length — already known from the index, without reading the key bytes — before comparing its content. The hash index of the keys of objects with 128 or more members does not help here, since every member is visited. ## Notes -This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md) +This method always returns `#!cpp 0` when the value is not an object — including a [discarded](is_discarded.md) view. If the source text repeats a key, `count()` returns more than `#!cpp 1` for it, even though the @@ -62,8 +62,8 @@ If the source text repeats a key, `count()` returns more than `#!cpp 1` for it, ## See also -- [find](find.md) - find a value in an object -- [contains](contains.md) - checks whether a key exists +- [`find`](find.md) - find a value in an object +- [`contains`](contains.md) - checks whether a key exists - [`BasicJsonType::count`](../basic_json/count.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/dump.md b/docs/mkdocs/docs/api/basic_json_view/dump.md index aa9a023bb..a47ca6085 100644 --- a/docs/mkdocs/docs/api/basic_json_view/dump.md +++ b/docs/mkdocs/docs/api/basic_json_view/dump.md @@ -11,7 +11,7 @@ Serializes this value (and its subtree) directly from the flat index, without ev first. With the default `#!cpp numbers == number_format::shortest`, the result is the same string [`BasicJsonType::dump`](../basic_json/dump.md) would produce for the value [`BasicJsonType::parse()`](../basic_json/parse.md) builds from the same source text, called with the same `indent`, -`indent_char`, and `ensure_ascii` -- except that members of an object appear in document order rather than sorted by +`indent_char`, and `ensure_ascii` — except that members of an object appear in document order rather than sorted by key, and *every* occurrence of a repeated key is written rather than only the last one (see [Notes on duplicate keys](operator[].md#notes)). For a `json_view` (whose `BasicJsonType` is not ordered), this means `dump()` can print an object's members in a different order than [`materialize()`](materialize.md)`.dump()` of the @@ -58,19 +58,19 @@ Linear in the size of the output text. ## Notes The walk over the subtree is iterative, so the nesting depth it can write is limited by available memory only, not by -the call stack -- as for [`materialize()`](materialize.md). +the call stack — as for [`materialize()`](materialize.md). Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md). With `#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion, exactly as [`BasicJsonType::dump`](../basic_json/dump.md) would (e.g. `#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`), and -integers are copied from the source text -- already canonical in JSON, so this matches their shortest form too -- +integers are copied from the source text — already canonical in JSON, so this matches their shortest form too — except that `#!cpp -0` is written as `#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it. `#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception -for `#!cpp -0` -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more +for `#!cpp -0` — `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more digits than any number type holds (such a literal is itself classified as a float, see -[What is different](../../features/json_view.md#what-is-different)) -- something `BasicJsonType` cannot do, since +[What is different](../../features/json_view.md#what-is-different)) — something `BasicJsonType` cannot do, since parsing already reduces every number to its parsed value. ## Examples @@ -95,9 +95,10 @@ parsing already reduces every number to its parsed value. ## See also - [`number_format`](number_format.md) - how `dump()` writes numbers -- [operator<<](operator_ltlt.md) - serialize this value to a stream -- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to use `BasicJsonType::dump`'s `error_handler` +- [`operator<<`](operator_ltlt.md) - serialize this value to a stream +- [`materialize`](materialize.md) - build a `BasicJsonType` value, e.g. to use `BasicJsonType::dump`'s `error_handler` - [`BasicJsonType::dump`](../basic_json/dump.md) - the corresponding function of `basic_json` +- [Zero-copy JSON views](../../features/json_view.md#writing-a-view-back) - writing a view back without building a tree ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/empty.md b/docs/mkdocs/docs/api/basic_json_view/empty.md index 0d57fe9e7..82c1d10e3 100644 --- a/docs/mkdocs/docs/api/basic_json_view/empty.md +++ b/docs/mkdocs/docs/api/basic_json_view/empty.md @@ -31,7 +31,7 @@ Constant. ## Notes -As for [`BasicJsonType::empty()`](../basic_json/empty.md), this does not return whether a string value is empty -- it +As for [`BasicJsonType::empty()`](../basic_json/empty.md), this does not return whether a string value is empty — it is `#!cpp false` for any string, regardless of its length. ## Examples @@ -53,7 +53,7 @@ is `#!cpp false` for any string, regardless of its length. ## See also -- [size](size.md) - return the number of elements +- [`size`](size.md) - return the number of elements - [`BasicJsonType::empty`](../basic_json/empty.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/end.md b/docs/mkdocs/docs/api/basic_json_view/end.md index ceac53b69..adbf33137 100644 --- a/docs/mkdocs/docs/api/basic_json_view/end.md +++ b/docs/mkdocs/docs/api/basic_json_view/end.md @@ -5,7 +5,7 @@ iterator end() const noexcept; ``` Returns an iterator to one past the last element of an array, one past the last member value of an object, in -**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself), and a +**document order** — see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself), and a `#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range (`#!cpp begin() == end()`). ## Return value @@ -44,8 +44,8 @@ way to reach the last element by stepping back from `end()`. Use [`back()`](back ## See also -- [begin](begin.md) - returns an iterator to the first element -- [cend](cend.md) - returns a const iterator to one past the last element +- [`begin`](begin.md) - returns an iterator to the first element +- [`cend`](cend.md) - returns a const iterator to one past the last element - [`BasicJsonType::end`](../basic_json/end.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/find.md b/docs/mkdocs/docs/api/basic_json_view/find.md index 8345dee59..b55741ca5 100644 --- a/docs/mkdocs/docs/api/basic_json_view/find.md +++ b/docs/mkdocs/docs/api/basic_json_view/find.md @@ -6,7 +6,7 @@ iterator find(const char* key) const; iterator find(const string_t& key) const; ``` -Finds a member with key `key` -- the first one, should the key occur more than once (see +Finds a member with key `key` — the first one, should the key occur more than once (see [Notes on duplicate keys](operator[].md#notes)). If the value is not an object, or no member has this key, [`end()`](end.md) is returned. @@ -27,7 +27,7 @@ No-throw guarantee: this function never throws exceptions. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another, in document order, stopping at the first match. Each comparison first checks the key's length --- already known from the index, without reading the key bytes -- before comparing its content. +— already known from the index, without reading the key bytes — before comparing its content. Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes constant time on average. @@ -41,7 +41,7 @@ constant time on average. the last value. Unlike [`BasicJsonType::find`](../basic_json/find.md), which always returns `#!cpp end()` for a non-object type, this -also does so for a [discarded](is_discarded.md) view -- there is no separate "invalid" iterator to return. +also does so for a [discarded](is_discarded.md) view — there is no separate "invalid" iterator to return. ## Examples @@ -63,8 +63,8 @@ also does so for a [discarded](is_discarded.md) view -- there is no separate "in ## See also -- [count](count.md) - returns the number of occurrences of a key -- [contains](contains.md) - checks whether a key exists +- [`count`](count.md) - returns the number of occurrences of a key +- [`contains`](contains.md) - checks whether a key exists - [`BasicJsonType::find`](../basic_json/find.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/front.md b/docs/mkdocs/docs/api/basic_json_view/front.md index e47f5d01e..4cefcbd33 100644 --- a/docs/mkdocs/docs/api/basic_json_view/front.md +++ b/docs/mkdocs/docs/api/basic_json_view/front.md @@ -53,7 +53,7 @@ view. ## See also -- [back](back.md) - access the last element +- [`back`](back.md) - access the last element - [`BasicJsonType::front`](../basic_json/front.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md index 191fabdf5..bb9ac6f46 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -7,26 +7,26 @@ T get() const; Converts the value to `T`. -For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it: +For the types below, the conversion works directly on the flat index — no `BasicJsonType` value is built for it: - `#!cpp bool` - arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for [`BasicJsonType::get()`](../basic_json/get.md)) - `#!cpp std::nullptr_t` -- `#!cpp std::basic_string` (including `string_t`) -- a copy of the string -- [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's +- `#!cpp std::basic_string` (including `string_t`) — a copy of the string +- [`string_view_t`](index.md#member-types) — **no copy**: the returned view points into the document's [`source()`](../basic_json_document/source.md) text, or, for a string that contains escape sequences, into the document's own buffer of decoded strings (see [`get_string()`](get_string.md)) -- `BasicJsonType` -- equivalent to [`materialize()`](materialize.md) -- `basic_json_view` -- returns `#!cpp *this` -- `#!cpp std::vector` of the types above -- from an array, element by element, each converted with +- `BasicJsonType` — equivalent to [`materialize()`](materialize.md) +- `basic_json_view` — returns `#!cpp *this` +- `#!cpp std::vector` of the types above — from an array, element by element, each converted with `#!cpp get()`; `#!cpp std::vector` keeps a view of every element instead of a value - `#!cpp std::map` and `#!cpp std::unordered_map`, if `K` is - constructible from a `#!cpp (const char*, std::size_t)` pair -- views of the members of an object; with a repeated + constructible from a `#!cpp (const char*, std::size_t)` pair — views of the members of an object; with a repeated key, the view of the *last* member is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) keeps its value A default-constructible `T` whose `from_json()` accepts views reads the view directly as well, without building a -`BasicJsonType` value. This covers the library's own conversions of the standard library types -- all other +`BasicJsonType` value. This covers the library's own conversions of the standard library types — all other `#!cpp std::vector`s, `#!cpp std::map`/`#!cpp std::unordered_map` (also with other key types), `#!cpp std::list`, `#!cpp std::deque`, `#!cpp std::set`, `#!cpp std::forward_list`, `#!cpp std::valarray`, `#!cpp std::array`, C arrays, `#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, enumerations, `#!cpp std::filesystem::path`, ... -- @@ -47,8 +47,8 @@ T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not keeps it: for `#!cpp {"x":1,"x":2}`, `#!cpp get()` reads `#!cpp x == 1`. See [Duplicate keys](../../features/json_view.md#duplicate-keys). -Every other `T` -- user types whose `from_json()` is written for `BasicJsonType` only, types without a default -constructor, `#!cpp std::tuple`s holding references, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` +Every other `T` — user types whose `from_json()` is written for `BasicJsonType` only, types without a default +constructor, `#!cpp std::tuple`s holding references, ... — is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` value first (as [`BasicJsonType::parse()`](../basic_json/parse.md) would), and converted from there exactly as [`BasicJsonType::get()`](../basic_json/get.md) would convert it. @@ -69,7 +69,7 @@ Strong guarantee: there are no changes to the view or the document it refers to. - For the directly-converted types listed above (other than `BasicJsonType` and `basic_json_view`, which never throw): throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value's type does not - match `T` -- the same exception, with the same message, that [`BasicJsonType::get()`](../basic_json/get.md) + match `T` — the same exception, with the same message, that [`BasicJsonType::get()`](../basic_json/get.md) throws for the same JSON type and `T`. - For `#!cpp std::vector`: throws `type_error.302` if the value is not an array; otherwise, whatever converting an element to `U` throws. @@ -77,7 +77,7 @@ Strong guarantee: there are no changes to the view or the document it refers to. - For a `T` whose `from_json()` reads the view: whatever that `from_json()` throws; for the library's conversions and the macros, the same exceptions with the same messages as [`BasicJsonType::get()`](../basic_json/get.md) of the same value. -- For every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws -- typically `type_error.302`, +- For every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws — typically `type_error.302`, or whatever a user-provided `from_json()` throws. None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four @@ -132,7 +132,7 @@ document. The example below reads typed fields straight into C++ variables, collects a view of every array element with `#!cpp get>()` instead of a value, and converts a nested object into a user type - through its `from_json()` -- which runs on a `BasicJsonType` value `materialize()` builds for just that one + through its `from_json()` — which runs on a `BasicJsonType` value `materialize()` builds for just that one member. ```cpp @@ -147,11 +147,12 @@ document. ## See also -- [get_to](get_to.md) - convert and write into a passed value -- [get_string](get_string.md) - the string, without a copy -- [number_token](number_token.md) - a number's token text, without a copy -- [materialize](materialize.md) - build the `BasicJsonType` value of this subtree +- [`get_to`](get_to.md) - convert and write into a passed value +- [`get_string`](get_string.md) - the string, without a copy +- [`number_token`](number_token.md) - a number's token text, without a copy +- [`materialize`](materialize.md) - build the `BasicJsonType` value of this subtree - [`BasicJsonType::get`](../basic_json/get.md) - the corresponding function of `basic_json` +- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - which conversions avoid building a basic_json value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/get_string.md b/docs/mkdocs/docs/api/basic_json_view/get_string.md index 7c9aec2a4..7539c540d 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get_string.md +++ b/docs/mkdocs/docs/api/basic_json_view/get_string.md @@ -41,8 +41,8 @@ counterpart, except that it returns a view instead of a reference to a value tha document, the returned [`string_view_t`](index.md#member-types) is valid as long as the caller's buffer; for an owned one, as long as the document. -The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the -[validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's +The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it — see the +[validity rules](index.md) of `basic_json_view` — and, for a string with no escapes, for as long as the document's source text, which for a borrowed document must remain valid **and unmodified** for as long as the document exists, since floating-point values and unescaped strings are read from the source lazily. @@ -52,8 +52,8 @@ since floating-point values and unescaped strings are read from the source lazil The example below pulls one field out of a JSON text that stands in for a large API response, and shows that no `#!cpp std::string` was allocated for it: the returned view still points inside the original buffer. A field that - contains an escape sequence cannot point into the original text -- it was decoded once into the document's own - buffer instead -- but still avoids a per-field allocation. + contains an escape sequence cannot point into the original text — it was decoded once into the document's own + buffer instead — but still avoids a per-field allocation. ```cpp --8<-- "examples/basic_json_view__get_string.cpp" @@ -67,9 +67,10 @@ since floating-point values and unescaped strings are read from the source lazil ## See also -- [get](get.md) - convert the value to a given type (`#!cpp get()` is equivalent to this function) -- [number_token](number_token.md) - a number's token text, without a copy +- [`get`](get.md) - convert the value to a given type (`#!cpp get()` is equivalent to this function) +- [`number_token`](number_token.md) - a number's token text, without a copy - [`BasicJsonType::get_ref`](../basic_json/get_ref.md) - the closest counterpart of `basic_json` +- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - borrowing strings instead of copying them ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/get_to.md b/docs/mkdocs/docs/api/basic_json_view/get_to.md index d11e7fd1a..ae8a8228a 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get_to.md +++ b/docs/mkdocs/docs/api/basic_json_view/get_to.md @@ -7,9 +7,9 @@ T& get_to(T& v) const; Converts the value to `T` and stores it in `v`, in one of three ways: -- For the types that [`get()`](get.md) converts directly -- arithmetic types, `#!cpp bool`, +- For the types that [`get()`](get.md) converts directly — arithmetic types, `#!cpp bool`, `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `#!cpp std::basic_string`, `BasicJsonType`, - `basic_json_view`, vectors of these, and maps of views -- `v` is assigned: `#!cpp v = get()`. + `basic_json_view`, vectors of these, and maps of views — `v` is assigned: `#!cpp v = get()`. - For a type whose `from_json()` accepts views (see [`get()`](get.md)), that `from_json()` is called with the view and the existing `v`, without building a `BasicJsonType` value. - For every other type, the subtree is built into a `BasicJsonType` value and `#!cpp materialize().get_to(v)` is @@ -73,8 +73,9 @@ them with those of a default-constructed `T`. ## See also -- [get](get.md) - convert the value to a given type +- [`get`](get.md) - convert the value to a given type - [`BasicJsonType::get_to`](../basic_json/get_to.md) - the corresponding function of `basic_json` +- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - which conversions avoid building a basic_json value ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/index.md b/docs/mkdocs/docs/api/basic_json_view/index.md index 4a634b8f0..f363d6433 100644 --- a/docs/mkdocs/docs/api/basic_json_view/index.md +++ b/docs/mkdocs/docs/api/basic_json_view/index.md @@ -36,11 +36,11 @@ Moving the document itself does not invalidate its views: the index is heap-allo [`json_pointer`](../json_pointer/index.md). Unlike `basic_json`, there are no implicit conversion operators (no `#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or `#!cpp get_ref()`; see [`get`](get.md). -`basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an +`basic_json_view` itself is always read-only — it never has a `set` or `push_back` of its own. A view of an **editable** document (`#!cpp view_mode::editable`) sees every edit made through [`basic_json_document::set`](../basic_json_document/set.md) and [`basic_json_document::push_back`](../basic_json_document/push_back.md): once a value is changed, every view that -still refers to it -- including ones taken before the change -- reads the new value. See +still refers to it — including ones taken before the change — reads the new value. See [Edits](../basic_json_document/index.md#edits). ## Template parameters @@ -148,9 +148,9 @@ still refers to it -- including ones taken before the change -- reads the new va ## See also - [Zero-copy JSON views](../../features/json_view.md) - guide to documents and views -- [**basic_json_document**](../basic_json_document/index.md) - the document that owns the parsed data -- [**json_view**](../json_view.md) and [**json_editable_view**](../json_editable_view.md) - the default specializations -- [**materialize**](materialize.md) - build a `basic_json` value from a view +- [`basic_json_document`](../basic_json_document/index.md) - the document that owns the parsed data +- [`json_view`](../json_view.md) - and [`json_editable_view`](../json_editable_view.md) - the default specializations +- [`materialize`](materialize.md) - build a `basic_json` value from a view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_array.md b/docs/mkdocs/docs/api/basic_json_view/is_array.md index 1149c80e5..0bfeffc41 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_array.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_array.md @@ -37,9 +37,9 @@ Constant. ## See also -- [is_structured](is_structured.md) - return whether the type is structured -- [size](size.md) - the number of elements -- [empty](empty.md) - whether there are no elements +- [`is_structured`](is_structured.md) - return whether the type is structured +- [`size`](size.md) - the number of elements +- [`empty`](empty.md) - whether there are no elements - [`BasicJsonType::is_array`](../basic_json/is_array.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_binary.md b/docs/mkdocs/docs/api/basic_json_view/is_binary.md index 3122051f3..06a2f0c7c 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_binary.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_binary.md @@ -39,7 +39,7 @@ Constant. ## See also -- [type](type.md) - return the type of the value +- [`type`](type.md) - return the type of the value - [`BasicJsonType::is_binary`](../basic_json/is_binary.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_boolean.md b/docs/mkdocs/docs/api/basic_json_view/is_boolean.md index 148fe2a61..8e909e6d2 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_boolean.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_boolean.md @@ -37,7 +37,7 @@ Constant. ## See also -- [type](type.md) - return the type of the value +- [`type`](type.md) - return the type of the value - [`BasicJsonType::is_boolean`](../basic_json/is_boolean.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_discarded.md b/docs/mkdocs/docs/api/basic_json_view/is_discarded.md index d9a9fc756..e6751c12c 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_discarded.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_discarded.md @@ -6,7 +6,7 @@ bool is_discarded() const noexcept; Returns whether this view is invalid, i.e. does not refer to a value. This is the case for a default-constructed view (see [(constructor)](basic_json_view.md)), and for [`root()`](../basic_json_document/root.md) of a document -that is itself [discarded](../basic_json_document/is_discarded.md) -- in particular, the root of a failed +that is itself [discarded](../basic_json_document/is_discarded.md) — in particular, the root of a failed [`parse()`](../basic_json_document/parse.md) with `allow_exceptions` set to `#!cpp false`. A discarded view is also what [`operator[]`](operator[].md) returns for a missing key, an index out of range, or a @@ -50,10 +50,11 @@ A `basic_json_view` is not convertible to `#!cpp bool`: such a conversion would ## See also -- [operator[]](operator[].md) - access specified element; yields a discarded view where an element is missing -- [(constructor)](basic_json_view.md) - the default constructor creates a discarded view -- [is_discarded (basic_json_document)](../basic_json_document/is_discarded.md) - return whether the last parse failed +- [`operator[]`](operator[].md) - access specified element; yields a discarded view where an element is missing +- [`(constructor)`](basic_json_view.md) - the default constructor creates a discarded view +- [`basic_json_document::is_discarded`](../basic_json_document/is_discarded.md) - return whether the last parse failed - [`BasicJsonType::is_discarded`](../basic_json/is_discarded.md) - the corresponding function of `basic_json` +- [Zero-copy JSON views](../../features/json_view.md#what-is-different) - how views differ from basic_json ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_null.md b/docs/mkdocs/docs/api/basic_json_view/is_null.md index fffaa836b..322526393 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_null.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_null.md @@ -37,7 +37,7 @@ Constant. ## See also -- [type](type.md) - return the type of the value +- [`type`](type.md) - return the type of the value - [`BasicJsonType::is_null`](../basic_json/is_null.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_number.md b/docs/mkdocs/docs/api/basic_json_view/is_number.md index 158734364..31207e34a 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_number.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_number.md @@ -37,9 +37,9 @@ Constant. ## See also -- [is_number_integer](is_number_integer.md) - return whether the value is an integer or unsigned integer number -- [is_number_unsigned](is_number_unsigned.md) - return whether the value is an unsigned integer number -- [is_number_float](is_number_float.md) - return whether the value is a floating-point number +- [`is_number_integer`](is_number_integer.md) - return whether the value is an integer or unsigned integer number +- [`is_number_unsigned`](is_number_unsigned.md) - return whether the value is an unsigned integer number +- [`is_number_float`](is_number_float.md) - return whether the value is a floating-point number - [`BasicJsonType::is_number`](../basic_json/is_number.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_number_float.md b/docs/mkdocs/docs/api/basic_json_view/is_number_float.md index 52bb43f20..19c78ebfe 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_number_float.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_number_float.md @@ -43,7 +43,7 @@ integer-looking token in the source text. ## See also -- [is_number](is_number.md) - return whether the value is a number +- [`is_number`](is_number.md) - return whether the value is a number - [`BasicJsonType::is_number_float`](../basic_json/is_number_float.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_number_integer.md b/docs/mkdocs/docs/api/basic_json_view/is_number_integer.md index fda0c29c9..33dc48b45 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_number_integer.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_number_integer.md @@ -42,8 +42,8 @@ values; use [`is_number_unsigned`](is_number_unsigned.md) to test for those spec ## See also -- [is_number](is_number.md) - return whether the value is a number -- [is_number_unsigned](is_number_unsigned.md) - return whether the value is an unsigned integer number +- [`is_number`](is_number.md) - return whether the value is a number +- [`is_number_unsigned`](is_number_unsigned.md) - return whether the value is an unsigned integer number - [`BasicJsonType::is_number_integer`](../basic_json/is_number_integer.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_number_unsigned.md b/docs/mkdocs/docs/api/basic_json_view/is_number_unsigned.md index d2cf10ebe..4348547ce 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_number_unsigned.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_number_unsigned.md @@ -37,7 +37,7 @@ Constant. ## See also -- [is_number_integer](is_number_integer.md) - return whether the value is an integer or unsigned integer number +- [`is_number_integer`](is_number_integer.md) - return whether the value is an integer or unsigned integer number - [`BasicJsonType::is_number_unsigned`](../basic_json/is_number_unsigned.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_object.md b/docs/mkdocs/docs/api/basic_json_view/is_object.md index 0aea2b55d..6c2ea3906 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_object.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_object.md @@ -37,9 +37,9 @@ Constant. ## See also -- [is_structured](is_structured.md) - return whether the type is structured -- [size](size.md) - the number of elements -- [empty](empty.md) - whether there are no elements +- [`is_structured`](is_structured.md) - return whether the type is structured +- [`size`](size.md) - the number of elements +- [`empty`](empty.md) - whether there are no elements - [`BasicJsonType::is_object`](../basic_json/is_object.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/is_primitive.md b/docs/mkdocs/docs/api/basic_json_view/is_primitive.md index a28b478f0..6203d91df 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_primitive.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_primitive.md @@ -37,7 +37,7 @@ Constant. ## See also -- [is_structured](is_structured.md) - return whether the type is structured (the complement of this function, for a +- [`is_structured`](is_structured.md) - return whether the type is structured (the complement of this function, for a non-discarded view) - [`BasicJsonType::is_primitive`](../basic_json/is_primitive.md) - the corresponding function of `basic_json` diff --git a/docs/mkdocs/docs/api/basic_json_view/is_string.md b/docs/mkdocs/docs/api/basic_json_view/is_string.md index 2425100b2..ff8a10a2c 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_string.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_string.md @@ -37,7 +37,7 @@ Constant. ## See also -- [source_offset](source_offset.md) - byte offset of this value in the source text, which differs for strings: an +- [`source_offset`](source_offset.md) - byte offset of this value in the source text, which differs for strings: an escaped string has none - [`BasicJsonType::is_string`](../basic_json/is_string.md) - the corresponding function of `basic_json` diff --git a/docs/mkdocs/docs/api/basic_json_view/is_structured.md b/docs/mkdocs/docs/api/basic_json_view/is_structured.md index de5dd5f7e..2e0c6f8d4 100644 --- a/docs/mkdocs/docs/api/basic_json_view/is_structured.md +++ b/docs/mkdocs/docs/api/basic_json_view/is_structured.md @@ -37,8 +37,8 @@ Constant. ## See also -- [is_primitive](is_primitive.md) - return whether the type is primitive -- [size](size.md), [empty](empty.md) - the number of elements, and whether there are none +- [`is_primitive`](is_primitive.md) - return whether the type is primitive +- [`size`](size.md), [`empty`](empty.md) - the number of elements, and whether there are none - [`BasicJsonType::is_structured`](../basic_json/is_structured.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/items.md b/docs/mkdocs/docs/api/basic_json_view/items.md index 1e2a1b802..9f1efffef 100644 --- a/docs/mkdocs/docs/api/basic_json_view/items.md +++ b/docs/mkdocs/docs/api/basic_json_view/items.md @@ -4,7 +4,7 @@ /* unspecified */ items() const noexcept; ``` -Returns a range of items -- (key, value) pairs -- for use in range-based for loops. An item's `#!cpp key()` is a +Returns a range of items — (key, value) pairs — for use in range-based for loops. An item's `#!cpp key()` is a `#!cpp string_view_t` (for an array element, its index converted to a string, as for [`BasicJsonType::items()`](../basic_json/items.md)), and its `#!cpp value()` is a `basic_json_view` of the element. @@ -47,7 +47,7 @@ Constant. ## Notes As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all -occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), +occurrences of a duplicate key — unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and [`contains`](contains.md), which resolve to the *first* member with a given key. See the [Notes on duplicate keys](operator[].md#notes) of `operator[]`. @@ -63,8 +63,8 @@ and [`contains`](contains.md), which resolve to the *first* member with a given The example below shows a settings object whose source text records every update to a key as a duplicate member, in the order they happened. `items()` walks all of them, so the update history is visible, while - [`operator[]`](operator[].md) only ever sees the *first* one and [`materialize()`](materialize.md) -- like - [`BasicJsonType::parse()`](../basic_json/parse.md) -- keeps only the *last*. + [`operator[]`](operator[].md) only ever sees the *first* one and [`materialize()`](materialize.md) — like + [`BasicJsonType::parse()`](../basic_json/parse.md) — keeps only the *last*. ```cpp --8<-- "examples/basic_json_view__items.cpp" @@ -78,7 +78,7 @@ and [`contains`](contains.md), which resolve to the *first* member with a given ## See also -- [begin](begin.md), [end](end.md) - the iterators `items()` is built on +- [`begin`](begin.md), [`end`](end.md) - the iterators `items()` is built on - [`BasicJsonType::items`](../basic_json/items.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/materialize.md b/docs/mkdocs/docs/api/basic_json_view/materialize.md index 5ff007b11..f29a25cda 100644 --- a/docs/mkdocs/docs/api/basic_json_view/materialize.md +++ b/docs/mkdocs/docs/api/basic_json_view/materialize.md @@ -28,7 +28,7 @@ Linear in the size of the subtree. ## Notes `materialize()` replays the subtree through the same SAX builder [`BasicJsonType::parse()`](../basic_json/parse.md) -uses, so the result matches it exactly -- including, for an object, that a repeated key keeps only its +uses, so the result matches it exactly — including, for an object, that a repeated key keeps only its last value. The replay is iterative, so it is not limited by the call stack the way a naive recursive conversion would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself. @@ -43,7 +43,7 @@ never caches the result. ??? example - The example below skips messages that are not useful -- a discarded value, or an empty array -- using only + The example below skips messages that are not useful — a discarded value, or an empty array — using only [`is_array()`](is_array.md) and [`empty()`](empty.md), and calls `materialize()` only for the messages that are actually used, so no `BasicJsonType` value (and none of its per-element allocations) is ever built for the skipped ones. @@ -60,10 +60,11 @@ never caches the result. ## See also -- [root](../basic_json_document/root.md) - the view of a document's root value +- [`root`](../basic_json_document/root.md) - the view of a document's root value - [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text - [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values (not produced by `materialize()`) +- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - when to build a basic_json value from a view ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/number_format.md b/docs/mkdocs/docs/api/basic_json_view/number_format.md index 6b8c6805d..01c06193e 100644 --- a/docs/mkdocs/docs/api/basic_json_view/number_format.md +++ b/docs/mkdocs/docs/api/basic_json_view/number_format.md @@ -10,14 +10,14 @@ enum class number_format { This enumeration is used in [`dump`](dump.md) to choose how numbers are written. Two values are differentiated: shortest -: integers are copied from the source text -- already canonical in JSON -- except that `#!cpp -0` becomes +: integers are copied from the source text — already canonical in JSON — except that `#!cpp -0` becomes `#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it; floats are written with the library's shortest round-trip conversion, exactly as [`BasicJsonType::dump()`](../basic_json/dump.md) would (e.g. `#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`) source -: every number is copied exactly as it appears in the source text -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0`, all - digits of an integer literal with more digits than any number type holds -- something `BasicJsonType` cannot do, +: every number is copied exactly as it appears in the source text — `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0`, all + digits of an integer literal with more digits than any number type holds — something `BasicJsonType` cannot do, since parsing already reduces every number to its parsed value ## Examples @@ -41,10 +41,11 @@ source ## See also -- [dump](dump.md) - serialize to a JSON-formatted string -- [number_token](number_token.md) - get a single number's token text without dumping the whole value +- [`dump`](dump.md) - serialize to a JSON-formatted string +- [`number_token`](number_token.md) - get a single number's token text without dumping the whole value - [`BasicJsonType::error_handler_t`](../basic_json/error_handler_t.md) - the analogous enumeration for `BasicJsonType::dump`'s decoding-error behavior +- [Zero-copy JSON views](../../features/json_view.md#writing-a-view-back) - writing numbers as they appeared in the source ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/number_token.md b/docs/mkdocs/docs/api/basic_json_view/number_token.md index d7c56c22b..97d7a2ed2 100644 --- a/docs/mkdocs/docs/api/basic_json_view/number_token.md +++ b/docs/mkdocs/docs/api/basic_json_view/number_token.md @@ -9,7 +9,7 @@ Returns the number exactly as it appears in the source text, without parsing or ## Return value The number's token text, as a [`string_view_t`](index.md#member-types) into the document's -[`source()`](../basic_json_document/source.md) text -- for example `#!cpp "1.50"`, `#!cpp "1E2"`, `#!cpp "-0"`, or an +[`source()`](../basic_json_document/source.md) text — for example `#!cpp "1.50"`, `#!cpp "1E2"`, `#!cpp "-0"`, or an integer literal with more digits than any number type holds (such as a 30-digit integer, which [`get()`](get.md) and [`materialize()`](materialize.md) can only represent approximately, as a `number_float_t`). @@ -35,12 +35,12 @@ Constant. `BasicJsonType` has no counterpart to this function: once a number is parsed into `number_integer_t`, `number_unsigned_t`, or `number_float_t`, its original textual form (leading zeros aside, which are already rejected by the grammar; trailing zeros in the fraction; the case and sign of the exponent; ...) is gone. `number_token()` is -useful precisely where that form must survive -- a price or an identifier that must be reproduced exactly, or a +useful precisely where that form must survive — a price or an identifier that must be reproduced exactly, or a number too large for any of `BasicJsonType`'s number types to hold without loss. The returned [`string_view_t`](index.md#member-types) always points into the document's -[`source()`](../basic_json_document/source.md) text -- numbers are never decoded into the document's separate string -buffer -- and is valid exactly as long as that text is, see the [validity rules](index.md) of `basic_json_view`. +[`source()`](../basic_json_document/source.md) text — numbers are never decoded into the document's separate string +buffer — and is valid exactly as long as that text is, see the [validity rules](index.md) of `basic_json_view`. ## Examples @@ -48,7 +48,7 @@ buffer -- and is valid exactly as long as that text is, see the [validity rules] The example below keeps a price and an order ID exactly as they were written in an incoming order, where converting them with [`get()`](get.md) would lose information: the price picks up floating-point rounding, and - the order ID -- more digits than a 64-bit integer holds -- can only be approximated as a `#!cpp double` once + the order ID — more digits than a 64-bit integer holds — can only be approximated as a `#!cpp double` once `#!cpp materialize()`d. ```cpp @@ -63,11 +63,12 @@ buffer -- and is valid exactly as long as that text is, see the [validity rules] ## See also -- [get](get.md) - convert the value to a given type -- [get_string](get_string.md) - the string, without a copy +- [`get`](get.md) - convert the value to a given type +- [`get_string`](get_string.md) - the string, without a copy - [`BasicJsonType::number_integer_t`](../basic_json/number_integer_t.md), [`number_unsigned_t`](../basic_json/number_unsigned_t.md), [`number_float_t`](../basic_json/number_float_t.md) - the number types `#!cpp get()` and `materialize()` convert into +- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - reading a number exactly as written ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/operator[].md b/docs/mkdocs/docs/api/basic_json_view/operator[].md index 8aacf94c9..9c09a807b 100644 --- a/docs/mkdocs/docs/api/basic_json_view/operator[].md +++ b/docs/mkdocs/docs/api/basic_json_view/operator[].md @@ -15,8 +15,8 @@ basic_json_view operator[](IntegerType idx) const; basic_json_view operator[](const json_pointer& ptr) const; ``` -1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see - the [Notes](#notes) below) -- or a [discarded](is_discarded.md) view if there is no such member. +1. Returns the value of the object member with key `key` — the first one, should the key occur more than once (see + the [Notes](#notes) below) — or a [discarded](is_discarded.md) view if there is no such member. 2. Returns the array element at index `idx`, or a [discarded](is_discarded.md) view if `idx` is out of range. The template accepts every integer type except `#!cpp bool` and `#!cpp std::size_t` (`#!cpp int`, `#!cpp unsigned`, `#!cpp long`, `#!cpp std::int64_t`, ...) and forwards to the `size_type` overload, so that an integer argument is @@ -55,11 +55,11 @@ Strong guarantee: there are no changes to the view or the document it refers to. ## Exceptions 1. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an object and - not [discarded](is_discarded.md) -- + not [discarded](is_discarded.md) — the same exception, with the same message, that the **const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a string argument on a non-object value. 2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an array and - not [discarded](is_discarded.md) -- + not [discarded](is_discarded.md) — the same exception, with the same message, that the **const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a numeric argument on a non-array value. 3. Throws the same exceptions, with the same messages, that the **const** overload of @@ -81,7 +81,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another, in document order, stopping at the first match. Each comparison first checks the - key's length -- already known from the index, without reading the key bytes -- before comparing its content, so a + key's length — already known from the index, without reading the key bytes — before comparing its content, so a key of a different length than `key` is rejected without touching the source text. Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes constant time on average. @@ -97,7 +97,7 @@ Unlike `BasicJsonType::operator[]`, which is undefined behavior (guarded by a [runtime assertion](../../features/assertions.md)) for a missing key on a **const** value, this operator returns a safe, testable result for a missing key or an index out of range: a [discarded](is_discarded.md) view, which is tested with [`is_discarded`](is_discarded.md). -There is also no non-const overload that inserts a missing key or extends an array -- a view never modifies the +There is also no non-const overload that inserts a missing key or extends an array — a view never modifies the document. !!! info "Chained access" @@ -126,8 +126,8 @@ document. Overload 3 walks `ptr` one reference token at a time, starting at this value, the same way [`at`](at.md) and [`contains`](contains.md) do. It only ever returns a discarded view where the **const** overload of - `BasicJsonType::operator[]` would be undefined behavior for the same pointer -- a missing object member or an - out-of-range array index -- and still throws for every other way `ptr` can fail to resolve. See + `BasicJsonType::operator[]` would be undefined behavior for the same pointer — a missing object member or an + out-of-range array index — and still throws for every other way `ptr` can fail to resolve. See [`at`](at.md#exceptions) for the checked version, which throws in every case instead, and [`contains`](contains.md) for a version that never throws. @@ -137,7 +137,7 @@ document. The example below reads a couple of fields out of a batch of user records without ever materializing a full `BasicJsonType` value for the batch. `operator[]` is used both to look up an optional object member and to index - into an array -- in both cases, a missing value comes back as a discarded view that can be tested with a plain + into an array — in both cases, a missing value comes back as a discarded view that can be tested with a plain `#!cpp if`, instead of relying on undefined behavior. ```cpp @@ -168,9 +168,9 @@ document. ## See also -- [at](at.md) - access specified element with bounds checking (throws instead of returning a discarded view) -- [front](front.md), [back](back.md) - access the first or last element -- [find](find.md), [contains](contains.md) - look up a member without throwing +- [`at`](at.md) - access specified element with bounds checking (throws instead of returning a discarded view) +- [`front`](front.md), [`back`](back.md) - access the first or last element +- [`find`](find.md), [`contains`](contains.md) - look up a member without throwing - [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) - the corresponding function of `basic_json` - [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3 diff --git a/docs/mkdocs/docs/api/basic_json_view/operator_eq.md b/docs/mkdocs/docs/api/basic_json_view/operator_eq.md index b43f5c633..26ff303f6 100644 --- a/docs/mkdocs/docs/api/basic_json_view/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json_view/operator_eq.md @@ -17,7 +17,7 @@ bool operator==(const BasicJsonType& lhs, const basic_json_view& rhs); Neither overload builds a `BasicJsonType` value for a view to do the comparison (see [Notes](#notes) below). Numbers compare by value across their types (`#!cpp 1 == 1.0`), and an object compares by its members, with duplicate keys -resolved exactly as `parse()` resolves them -- the last value, at the position of the first occurrence of the key. +resolved exactly as `parse()` resolves them — the last value, at the position of the first occurrence of the key. This is not what [`operator[]`](operator[].md) and the other lookups return for a duplicate key: they find the *first* member (see [Duplicate keys](../../features/json_view.md#duplicate-keys)). @@ -55,7 +55,7 @@ compared. Two arrays of different [`size()`](size.md) are rejected without visit ## Notes Only a single number, boolean, or `#!cpp null` value is ever materialized into a `BasicJsonType`, to reuse its -`operator==` -- for numbers, so that values written differently in the source text but equal in value (e.g. an +`operator==` — for numbers, so that values written differently in the source text but equal in value (e.g. an integer and a floating-point literal) still compare equal, following the same rules `BasicJsonType` does for special values such as `#!cpp NaN`. Constructing one of these scalars never allocates. Strings are compared directly, without allocating, either from the source text on both sides or, for overload 2, against `BasicJsonType`'s own string. @@ -85,7 +85,7 @@ the way to get a `BasicJsonType` value that supports it. ??? example The example below checks whether a newly received configuration differs from the previous one, and whether a - received document matches what a test expects -- directly on views, without ever materializing a `BasicJsonType` + received document matches what a test expects — directly on views, without ever materializing a `BasicJsonType` value for either side. ```cpp @@ -100,8 +100,8 @@ the way to get a `BasicJsonType` value that supports it. ## See also -- [operator!=](operator_ne.md) - compare for inequality -- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone +- [`operator!=`](operator_ne.md) - compare for inequality +- [`materialize`](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone - [`BasicJsonType::operator==`](../basic_json/operator_eq.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/operator_ltlt.md b/docs/mkdocs/docs/api/basic_json_view/operator_ltlt.md index c2735b360..3ee5961fb 100644 --- a/docs/mkdocs/docs/api/basic_json_view/operator_ltlt.md +++ b/docs/mkdocs/docs/api/basic_json_view/operator_ltlt.md @@ -6,7 +6,7 @@ std::ostream& operator<<(std::ostream& o, const basic_json_view& v); Not available when [`JSON_NO_IO`](../macros/json_no_io.md) is defined. -Serializes the given view `v` to the output stream `o`, using [`dump`](dump.md) -- exactly as +Serializes the given view `v` to the output stream `o`, using [`dump`](dump.md) — exactly as `#!cpp operator<<(std::ostream&, const basic_json&)` does for a `basic_json` value. - The indentation of the output can be controlled with the member variable `width` of the output stream `o`. For @@ -38,7 +38,7 @@ the stream `o` May throw `#!cpp std::bad_alloc`, propagated from [`dump`](dump.md#exceptions). Unlike `#!cpp operator<<(std::ostream&, const basic_json&)`, there is no UTF-8 decoding step that could throw -[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316), and no `error_handler` to choose between -- +[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316), and no `error_handler` to choose between — see the [Exceptions](dump.md#exceptions) of `dump`. ## Complexity @@ -49,8 +49,8 @@ Linear, as [`dump`](dump.md#complexity). ??? example - The example below writes one record out of a larger batch straight to a log stream -- compact for a one-line - entry, and pretty-printed with `std::setw`/`std::setfill` for a readable dump -- without ever building a + The example below writes one record out of a larger batch straight to a log stream — compact for a one-line + entry, and pretty-printed with `std::setw`/`std::setfill` for a readable dump — without ever building a `BasicJsonType` value for the record, or for the rest of the batch. ```cpp @@ -65,9 +65,10 @@ Linear, as [`dump`](dump.md#complexity). ## See also -- [dump](dump.md) - serialize to a JSON-formatted string +- [`dump`](dump.md) - serialize to a JSON-formatted string - [`operator<<(std::ostream&)`](../operator_ltlt.md) - the corresponding operator for `basic_json` - [`JSON_NO_IO`](../macros/json_no_io.md) - switch off functions relying on certain C++ I/O headers +- [Zero-copy JSON views](../../features/json_view.md#writing-a-view-back) - writing a view back without building a tree ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/operator_ne.md b/docs/mkdocs/docs/api/basic_json_view/operator_ne.md index 2e85f8abe..d4bb32f34 100644 --- a/docs/mkdocs/docs/api/basic_json_view/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json_view/operator_ne.md @@ -15,7 +15,7 @@ bool operator!=(const BasicJsonType& lhs, const basic_json_view& rhs); Since `operator!=` is defined as the negation of [`operator==`](operator_eq.md), it follows the same rules for special cases: for instance, since a [discarded](is_discarded.md) view is never equal to anything by default (see -[operator=='s Notes](operator_eq.md#notes)), it is never *unequal* to anything either -- `#!cpp discarded != discarded` +[operator=='s Notes](operator_eq.md#notes)), it is never *unequal* to anything either — `#!cpp discarded != discarded` is also `#!cpp false`, exactly as for a discarded `BasicJsonType` value. ## Parameters @@ -46,7 +46,7 @@ Linear, as [`operator==`](operator_eq.md#complexity). ## Notes -See the [Notes](operator_eq.md#notes) of `operator==` -- in particular for how an object's members are compared +See the [Notes](operator_eq.md#notes) of `operator==` — in particular for how an object's members are compared (order matters for [`ordered_json_view`](../ordered_json_view.md) but not for [`json_view`](../json_view.md)) and for how discarded views compare. @@ -58,7 +58,7 @@ the way to get a `BasicJsonType` value that supports it. ??? example The example below asserts, as a test would, that a received document differs from an unwanted value, and shows - that -- as for [`json`](../json.md)/[`ordered_json`](../ordered_json.md) -- reordering an object's members is + that — as for [`json`](../json.md)/[`ordered_json`](../ordered_json.md) — reordering an object's members is detected as a difference for an `ordered_json_view` but not for a `json_view`. ```cpp @@ -73,8 +73,8 @@ the way to get a `BasicJsonType` value that supports it. ## See also -- [operator==](operator_eq.md) - compare for equality -- [materialize](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone +- [`operator==`](operator_eq.md) - compare for equality +- [`materialize`](materialize.md) - build a `BasicJsonType` value, e.g. to keep comparing after the document is gone - [`BasicJsonType::operator!=`](../basic_json/operator_ne.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/size.md b/docs/mkdocs/docs/api/basic_json_view/size.md index 466cbd704..98473fccd 100644 --- a/docs/mkdocs/docs/api/basic_json_view/size.md +++ b/docs/mkdocs/docs/api/basic_json_view/size.md @@ -30,10 +30,10 @@ Constant: for an object or array, the element count is stored in the index, not ## Notes -As for [`BasicJsonType::size()`](../basic_json/size.md), this does not return the length of a string value -- it is +As for [`BasicJsonType::size()`](../basic_json/size.md), this does not return the length of a string value — it is `1` for a string, regardless of its length. -If the source text has an object with a duplicate key, every occurrence counts towards its `size()` -- unlike +If the source text has an object with a duplicate key, every occurrence counts towards its `size()` — unlike [`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which keeps only the last value for a repeated key. This means `#!cpp v.size()` can be larger than `#!cpp v.materialize().size()`. See the [Notes on duplicate keys](operator[].md#notes) of `operator[]` for why lookups and iteration disagree on how many @@ -58,7 +58,7 @@ members there are. ## See also -- [empty](empty.md) - return whether the value has no elements +- [`empty`](empty.md) - return whether the value has no elements - [`BasicJsonType::size`](../basic_json/size.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/source_offset.md b/docs/mkdocs/docs/api/basic_json_view/source_offset.md index 5f7e06221..fb24caff3 100644 --- a/docs/mkdocs/docs/api/basic_json_view/source_offset.md +++ b/docs/mkdocs/docs/api/basic_json_view/source_offset.md @@ -13,7 +13,7 @@ materializing anything. byte offset of the first byte of the value's token (for a string: the first byte after the opening quote) in [`source()`](../basic_json_document/source.md). - `#!cpp static_cast(-1)` for a [discarded](is_discarded.md) view, and for a string that contains - escapes -- such a string was decoded once into the document's own buffer, so there is no single byte range in + escapes — such a string was decoded once into the document's own buffer, so there is no single byte range in `source()` left to point at. ## Exception safety @@ -47,8 +47,9 @@ string, whose token length is not the same as its decoded [`size()`](size.md). ## See also -- [source](../basic_json_document/source.md) - the parsed text -- [is_string](is_string.md) - return whether the value is a string +- [`source`](../basic_json_document/source.md) - the parsed text +- [`is_string`](is_string.md) - return whether the value is a string +- [Zero-copy JSON views](../../features/json_view.md#images) - images and the source text ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/type.md b/docs/mkdocs/docs/api/basic_json_view/type.md index 17dbc3c34..b8aba36d8 100644 --- a/docs/mkdocs/docs/api/basic_json_view/type.md +++ b/docs/mkdocs/docs/api/basic_json_view/type.md @@ -5,7 +5,7 @@ value_t type() const noexcept; ``` Returns the type of the value this view refers to, as a value from the [`value_t`](../basic_json/value_t.md) -enumeration -- the same enumeration [`BasicJsonType::type()`](../basic_json/type.md) uses. +enumeration — the same enumeration [`BasicJsonType::type()`](../basic_json/type.md) uses. ## Return value @@ -44,8 +44,8 @@ JSON text has no binary values, so `type()` only distinguishes the eight ordinar ## See also -- [is_null](is_null.md), [is_boolean](is_boolean.md), [is_number](is_number.md), [is_string](is_string.md), - [is_array](is_array.md), [is_object](is_object.md) - type-specific predicates built on `type()` +- [`is_null`](is_null.md), [`is_boolean`](is_boolean.md), [`is_number`](is_number.md), [`is_string`](is_string.md), + [`is_array`](is_array.md), [`is_object`](is_object.md) - type-specific predicates built on `type()` - [`BasicJsonType::type`](../basic_json/type.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/type_name.md b/docs/mkdocs/docs/api/basic_json_view/type_name.md index d30132886..da4d78c03 100644 --- a/docs/mkdocs/docs/api/basic_json_view/type_name.md +++ b/docs/mkdocs/docs/api/basic_json_view/type_name.md @@ -4,7 +4,7 @@ const char* type_name() const noexcept; ``` -Returns the type name as string to be used in error messages -- usually to indicate that a function was called on a +Returns the type name as string to be used in error messages — usually to indicate that a function was called on a wrong JSON type. Identical to [`BasicJsonType::type_name()`](../basic_json/type_name.md), including the extra `#!cpp "discarded"` return value for a [discarded](is_discarded.md) view (`BasicJsonType::type_name()` produces the same string for a discarded `BasicJsonType` value). @@ -39,7 +39,7 @@ Constant. ??? example - The example below reports why some parsed messages were rejected, using only `type_name()` -- no + The example below reports why some parsed messages were rejected, using only `type_name()` — no `BasicJsonType` value is ever built for the ones that are wrong, and the message text matches what [`BasicJsonType::type_name()`](../basic_json/type_name.md) would produce for the same value. @@ -55,7 +55,7 @@ Constant. ## See also -- [type](type.md) - return the type of the value +- [`type`](type.md) - return the type of the value - [`BasicJsonType::type_name`](../basic_json/type_name.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/basic_json_view/value.md b/docs/mkdocs/docs/api/basic_json_view/value.md index 47e7b0bbb..7159325b2 100644 --- a/docs/mkdocs/docs/api/basic_json_view/value.md +++ b/docs/mkdocs/docs/api/basic_json_view/value.md @@ -12,8 +12,8 @@ T value(const json_pointer& ptr, const T& default_value) const; string_t value(const json_pointer& ptr, const char* default_value) const; ``` -1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see - [Notes on duplicate keys](operator[].md#notes)) -- converted to `T`, or `default_value` if there is no such member. +1. Returns the value of the object member with key `key` — the first one, should the key occur more than once (see + [Notes on duplicate keys](operator[].md#notes)) — converted to `T`, or `default_value` if there is no such member. 2. Returns the value a JSON pointer `ptr` refers to, starting at this value, converted to `T`, or `default_value` if `ptr` cannot be resolved. @@ -48,16 +48,16 @@ Strong guarantee: there are no changes to the view or the document it refers to. ## Exceptions -1. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the value is not an object -- +1. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the value is not an object — the same exception, with the same message, that [`BasicJsonType::value`](../basic_json/value.md) throws for the same call. If a member with key `key` is found, throws whatever converting it to `T` throws (typically [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302), with the same message [`BasicJsonType::value`](../basic_json/value.md) throws for the same mismatch); a missing member never throws. -2. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if this value -- not the value `ptr` - resolves to -- is neither an object nor an array. Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) +2. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if this value — not the value `ptr` + resolves to — is neither an object nor an array. Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) or [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if `ptr` contains a malformed array index. If `ptr` resolves to a value, throws whatever converting it to `T` throws. Every other way `ptr` can fail to - resolve -- a missing key, an out-of-range or "`-`" array index, an unresolvable token on a primitive -- yields + resolve — a missing key, an out-of-range or "`-`" array index, an unresolvable token on a primitive — yields `default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches `out_of_range` and returns `default_value`. @@ -73,7 +73,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes constant time on average. 2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at - that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and + that level or the index into the array — as for the [`operator[]`](operator[].md#complexity) and [`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value to `T`. @@ -89,7 +89,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics !!! info "Differences to `at` and `operator[]`" Unlike [`at`](at.md), this function does not throw if `key`/`ptr` resolves to no value. Unlike - [`operator[]`](operator[].md), it never returns a [discarded](is_discarded.md) view -- it always returns a `T` -- + [`operator[]`](operator[].md), it never returns a [discarded](is_discarded.md) view — it always returns a `T` — and it is available on any view, since it never needs to insert a missing element the way the non-const `BasicJsonType::operator[]` would. @@ -131,8 +131,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics ## See also -- [at](at.md) - access specified element with bounds checking (throws instead of returning a default value) -- [operator[]](operator[].md) - access specified element (returns a discarded view instead of a default value) +- [`at`](at.md) - access specified element with bounds checking (throws instead of returning a default value) +- [`operator[]`](operator[].md) - access specified element (returns a discarded view instead of a default value) - [`BasicJsonType::value`](../basic_json/value.md) - the corresponding function of `basic_json` ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md index 62b0e24fc..825999c29 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/byte_container_with_subtype.md @@ -43,10 +43,10 @@ byte_container_with_subtype(container_type&& container, subtype_type subtype); ## See also -- [set_subtype](set_subtype.md) sets the binary subtype -- [subtype](subtype.md) return the binary subtype -- [has_subtype](has_subtype.md) return whether the value has a subtype -- [binary](../basic_json/binary.md) create a binary JSON value +- [`set_subtype`](set_subtype.md) - sets the binary subtype +- [`subtype`](subtype.md) - return the binary subtype +- [`has_subtype`](has_subtype.md) - return whether the value has a subtype +- [`binary`](../basic_json/binary.md) - create a binary JSON value - [Binary Values](../../features/binary_values.md) - the article on binary values ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md index ec2e61760..7045a342f 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/clear_subtype.md @@ -33,9 +33,10 @@ Constant. ## See also -- [set_subtype](set_subtype.md) sets the binary subtype -- [has_subtype](has_subtype.md) return whether the value has a subtype -- [subtype](subtype.md) return the binary subtype +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`set_subtype`](set_subtype.md) - sets the binary subtype +- [`has_subtype`](has_subtype.md) - return whether the value has a subtype +- [`subtype`](subtype.md) - return the binary subtype ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md index e22429f9c..9ffc8c6e0 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/has_subtype.md @@ -36,9 +36,10 @@ Constant. ## See also -- [set_subtype](set_subtype.md) sets the binary subtype -- [clear_subtype](clear_subtype.md) clears the binary subtype -- [subtype](subtype.md) return the binary subtype +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`set_subtype`](set_subtype.md) - sets the binary subtype +- [`clear_subtype`](clear_subtype.md) - clears the binary subtype +- [`subtype`](subtype.md) - return the binary subtype ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/index.md b/docs/mkdocs/docs/api/byte_container_with_subtype/index.md index f3a2cc9a3..55e8cb88f 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/index.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/index.md @@ -32,9 +32,9 @@ specific naming scheme in order to override the binary type. ## See also - [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled -- [**binary**](../basic_json/binary.md) - create a binary array value -- [**get_binary**](../basic_json/get_binary.md) - get a reference to the stored binary value -- [**binary_t**](../basic_json/binary_t.md) - type for binary arrays +- [`binary`](../basic_json/binary.md) - create a binary array value +- [`get_binary`](../basic_json/get_binary.md) - get a reference to the stored binary value +- [`binary_t`](../basic_json/binary_t.md) - type for binary arrays - [Binary formats](../../features/binary_formats/index.md) - formats that can store binary values ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md index 0676ee5e7..085f80a94 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_eq.md @@ -5,7 +5,7 @@ bool operator==(const byte_container_with_subtype& rhs) const; ``` Compares two byte containers for equality by comparing (1) the underlying binary data (the `BinaryType` base, compared -with `BinaryType`'s own `operator==`) and (2) the subtype information -- both containers must either have no subtype, +with `BinaryType`'s own `operator==`) and (2) the subtype information — both containers must either have no subtype, or have a subtype and the same subtype value. ## Parameters @@ -39,9 +39,10 @@ Linear in the size of the compared containers. ## See also -- [operator!=](operator_ne.md) comparison: not equal -- [has_subtype](has_subtype.md) return whether the value has a subtype -- [subtype](subtype.md) return the binary subtype +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`operator!=`](operator_ne.md) - comparison: not equal +- [`has_subtype`](has_subtype.md) - return whether the value has a subtype +- [`subtype`](subtype.md) - return the binary subtype ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md index 5adb5cd39..62c5df792 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/operator_ne.md @@ -38,9 +38,10 @@ Linear in the size of the compared containers. ## See also -- [operator==](operator_eq.md) comparison: equal -- [has_subtype](has_subtype.md) return whether the value has a subtype -- [subtype](subtype.md) return the binary subtype +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`operator==`](operator_eq.md) - comparison: equal +- [`has_subtype`](has_subtype.md) - return whether the value has a subtype +- [`subtype`](subtype.md) - return the binary subtype ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md index 281de5aef..2d4b20733 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/set_subtype.md @@ -38,9 +38,10 @@ Constant. ## See also -- [subtype](subtype.md) return the binary subtype -- [has_subtype](has_subtype.md) return whether the value has a subtype -- [clear_subtype](clear_subtype.md) clears the binary subtype +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`subtype`](subtype.md) - return the binary subtype +- [`has_subtype`](has_subtype.md) - return whether the value has a subtype +- [`clear_subtype`](clear_subtype.md) - clears the binary subtype ## Version history diff --git a/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md b/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md index 0f00cc803..3a91ed8f3 100644 --- a/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md +++ b/docs/mkdocs/docs/api/byte_container_with_subtype/subtype.md @@ -38,9 +38,10 @@ Constant. ## See also -- [set_subtype](set_subtype.md) sets the binary subtype -- [has_subtype](has_subtype.md) return whether the value has a subtype -- [clear_subtype](clear_subtype.md) clears the binary subtype +- [Binary values](../../features/binary_values.md) - how binary values and subtypes are handled +- [`set_subtype`](set_subtype.md) - sets the binary subtype +- [`has_subtype`](has_subtype.md) - return whether the value has a subtype +- [`clear_subtype`](clear_subtype.md) - clears the binary subtype ## Version history diff --git a/docs/mkdocs/docs/api/json.md b/docs/mkdocs/docs/api/json.md index b41b691ac..edc608c47 100644 --- a/docs/mkdocs/docs/api/json.md +++ b/docs/mkdocs/docs/api/json.md @@ -25,8 +25,11 @@ types. ## See also -- [basic_json](basic_json/index.md) - the underlying class template -- [ordered_json](ordered_json.md) - specialization that preserves the insertion order of object keys +- [`basic_json`](basic_json/index.md) - the underlying class template +- [`ordered_json`](ordered_json.md) - specialization that preserves the insertion order of object keys +- [Types](../features/types/index.md) - the JSON value types and how they are stored +- [Converting values](../features/conversions.md) - get values out of a JSON value and put values in +- [Creating JSON values](../features/creating_values.md) - all the ways to build a JSON value ## Version history diff --git a/docs/mkdocs/docs/api/json_document.md b/docs/mkdocs/docs/api/json_document.md index 2da6fe548..2000128ac 100644 --- a/docs/mkdocs/docs/api/json_document.md +++ b/docs/mkdocs/docs/api/json_document.md @@ -27,8 +27,9 @@ specialization. ## See also -- [json_view](json_view.md) - a view of a value of a `json_document` -- [ordered_json_document](ordered_json_document.md) - the corresponding document for `ordered_json` +- [`json_view`](json_view.md) - a view of a value of a `json_document` +- [`ordered_json_document`](ordered_json_document.md) - the corresponding document for `ordered_json` +- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree ## Version history diff --git a/docs/mkdocs/docs/api/json_editable_document.md b/docs/mkdocs/docs/api/json_editable_document.md index 2acea534c..c455c8343 100644 --- a/docs/mkdocs/docs/api/json_editable_document.md +++ b/docs/mkdocs/docs/api/json_editable_document.md @@ -9,14 +9,14 @@ using json_editable_document = basic_json_document; This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default [`json`](json.md) specialization: in addition to everything [`json_document`](json_document.md) offers, [`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) change values after -parsing, without ever rewriting the source text -- see [Edits](basic_json_document/index.md#edits) and +parsing, without ever rewriting the source text — see [Edits](basic_json_document/index.md#edits) and [Editing a document](../features/json_view.md#editing-a-document). ## Examples ??? example - The example below patches two fields of a small configuration document -- changing one and adding another -- + The example below patches two fields of a small configuration document — changing one and adding another — and dumps it back out with the member order and the spelling of the untouched number preserved, something a plain [`json`](json.md) value cannot do. @@ -32,9 +32,9 @@ parsing, without ever rewriting the source text -- see [Edits](basic_json_docume ## See also -- [json_editable_view](json_editable_view.md) - a view of a value of a `json_editable_document` -- [json_document](json_document.md) - the read-only document this type adds edits to -- [ordered_json_editable_document](ordered_json_editable_document.md) - the corresponding editable document for +- [`json_editable_view`](json_editable_view.md) - a view of a value of a `json_editable_document` +- [`json_document`](json_document.md) - the read-only document this type adds edits to +- [`ordered_json_editable_document`](ordered_json_editable_document.md) - the corresponding editable document for `ordered_json` - [Edits](basic_json_document/index.md#edits) - what an edit guarantees diff --git a/docs/mkdocs/docs/api/json_editable_view.md b/docs/mkdocs/docs/api/json_editable_view.md index 37301d3d6..eede89703 100644 --- a/docs/mkdocs/docs/api/json_editable_view.md +++ b/docs/mkdocs/docs/api/json_editable_view.md @@ -8,7 +8,7 @@ using json_editable_view = basic_json_view; This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a [`json_editable_document`](json_editable_document.md). It offers the same read-only interface as -[`json_view`](json_view.md); what is different is what it can be a view *of* -- a value that +[`json_view`](json_view.md); what is different is what it can be a view *of* — a value that [`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) can change, with every view still referring to it seeing the change, see [Edits](basic_json_document/index.md#edits). @@ -17,7 +17,7 @@ still referring to it seeing the change, see [Edits](basic_json_document/index.m ??? example The example below is the same as [`json_editable_document`'s](json_editable_document.md): every view read back - out of the document -- `#!cpp doc.root()` and the views nested under it -- sees the edits made through `set`. + out of the document — `#!cpp doc.root()` and the views nested under it — sees the edits made through `set`. ```cpp --8<-- "examples/json_editable_document.cpp" @@ -31,10 +31,11 @@ still referring to it seeing the change, see [Edits](basic_json_document/index.m ## See also -- [json_editable_document](json_editable_document.md) - the document type this view refers into -- [json_view](json_view.md) - the corresponding read-only view -- [ordered_json_editable_view](ordered_json_editable_view.md) - the corresponding view for +- [`json_editable_document`](json_editable_document.md) - the document type this view refers into +- [`json_view`](json_view.md) - the corresponding read-only view +- [`ordered_json_editable_view`](ordered_json_editable_view.md) - the corresponding view for `ordered_json_editable_document` +- [Zero-copy JSON views](../features/json_view.md#editing-a-document) - editing a document in place and writing it back ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/back.md b/docs/mkdocs/docs/api/json_pointer/back.md index b8f0432af..2cf1d830a 100644 --- a/docs/mkdocs/docs/api/json_pointer/back.md +++ b/docs/mkdocs/docs/api/json_pointer/back.md @@ -40,10 +40,10 @@ Constant. ## See also -- [front](front.md) return first reference token -- [pop_back](pop_back.md) remove the last reference token -- [push_back](push_back.md) append an unescaped token at the end of the pointer -- [parent_pointer](parent_pointer.md) returns the parent of this JSON pointer +- [`front`](front.md) - return first reference token +- [`pop_back`](pop_back.md) - remove the last reference token +- [`push_back`](push_back.md) - append an unescaped token at the end of the pointer +- [`parent_pointer`](parent_pointer.md) - returns the parent of this JSON pointer ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/empty.md b/docs/mkdocs/docs/api/json_pointer/empty.md index 6c8fd542e..84a113b7e 100644 --- a/docs/mkdocs/docs/api/json_pointer/empty.md +++ b/docs/mkdocs/docs/api/json_pointer/empty.md @@ -36,9 +36,9 @@ Constant. ## See also -- [front](front.md) return first reference token -- [back](back.md) return last reference token -- [to_string](to_string.md) return a string representation of the JSON pointer +- [`front`](front.md) - return first reference token +- [`back`](back.md) - return last reference token +- [`to_string`](to_string.md) - return a string representation of the JSON pointer ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/front.md b/docs/mkdocs/docs/api/json_pointer/front.md index 247abd188..a80e26bf2 100644 --- a/docs/mkdocs/docs/api/json_pointer/front.md +++ b/docs/mkdocs/docs/api/json_pointer/front.md @@ -40,9 +40,9 @@ Constant. ## See also -- [back](back.md) return last reference token -- [pop_front](pop_front.md) remove the first reference token -- [push_front](push_front.md) append an unescaped token at the start of the pointer +- [`back`](back.md) - return last reference token +- [`pop_front`](pop_front.md) - remove the first reference token +- [`push_front`](push_front.md) - append an unescaped token at the start of the pointer ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/index.md b/docs/mkdocs/docs/api/json_pointer/index.md index c64543c94..944b949ef 100644 --- a/docs/mkdocs/docs/api/json_pointer/index.md +++ b/docs/mkdocs/docs/api/json_pointer/index.md @@ -36,6 +36,9 @@ flowchart LR parent["parent_pointer() returns /a/b"] -.-> B ``` +

Boxes are the reference tokens in order; dotted arrows show which token each member +function reads, removes, or adds to.

+ The diagram shows the reference tokens of the JSON pointer `/a/b/c`. ## Member types @@ -46,7 +49,7 @@ The diagram shows the reference tokens of the JSON pointer `/a/b/c`. - [(constructor)](json_pointer.md) - [**to_string**](to_string.md) - return a string representation of the JSON pointer -- [**operator string_t**](operator_string_t.md) - return a string representation of the JSON pointer (deprecated) +- [`operator string_t`](operator_string_t.md) - return a string representation of the JSON pointer (deprecated) - [**operator==**](operator_eq.md) - compare: equal - [**operator!=**](operator_ne.md) - compare: not equal - [**operator<=>**](operator_spaceship.md) - compare: 3-way (C++20) @@ -67,7 +70,9 @@ The diagram shows the reference tokens of the JSON pointer `/a/b/c`. ## See also -- [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) +- [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) - specification of JSON Pointer +- [JSON Pointer](../../features/json_pointer.md) - the article on JSON Pointer support +- [JSON Patch and Diff](../../features/json_patch.md) - patch paths are JSON pointers ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/json_pointer.md b/docs/mkdocs/docs/api/json_pointer/json_pointer.md index 0fce6f43c..d667da15d 100644 --- a/docs/mkdocs/docs/api/json_pointer/json_pointer.md +++ b/docs/mkdocs/docs/api/json_pointer/json_pointer.md @@ -46,7 +46,7 @@ Linear in the length of `s`. ## See also - [JSON Pointer](../../features/json_pointer.md) - the article on JSON Pointer support -- [operator""_json_pointer](../operator_literal_json_pointer.md) user-defined string literal for JSON pointers +- [`operator""_json_pointer`](../operator_literal_json_pointer.md) - user-defined string literal for JSON pointers ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/operator_eq.md b/docs/mkdocs/docs/api/json_pointer/operator_eq.md index 44cda4354..d7e45c773 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_eq.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_eq.md @@ -109,8 +109,8 @@ tokens. ## See also -- [operator!=](operator_ne.md) compare for inequality -- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) +- [`operator!=`](operator_ne.md) - compare for inequality +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way (C++20) ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/operator_ne.md b/docs/mkdocs/docs/api/json_pointer/operator_ne.md index 77e6b92c8..827c58e3e 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_ne.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_ne.md @@ -107,8 +107,8 @@ tokens. ## See also -- [operator==](operator_eq.md) compare for equality -- [operator<=>](operator_spaceship.md) comparison: 3-way (C++20) +- [`operator==`](operator_eq.md) - compare for equality +- [`operator<=>`](operator_spaceship.md) - comparison: 3-way (C++20) ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/operator_slash.md b/docs/mkdocs/docs/api/json_pointer/operator_slash.md index e91f8a119..ae01dd599 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_slash.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_slash.md @@ -64,8 +64,9 @@ a new JSON pointer is built from a copy of `lhs`. ## See also -- [operator/=](operator_slasheq.md) append to the end of the JSON pointer -- [push_back](push_back.md) append an unescaped token at the end of the pointer +- [`operator/=`](operator_slasheq.md) - append to the end of the JSON pointer +- [`push_back`](push_back.md) - append an unescaped token at the end of the pointer +- [JSON Pointer](../../features/json_pointer.md) - the article on JSON Pointer support ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md b/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md index 05e8ccbcf..e0d4560ec 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md @@ -63,8 +63,9 @@ json_pointer& operator/=(std::size_t array_idx) ## See also -- [operator/](operator_slash.md) create JSON Pointer by appending -- [push_back](push_back.md) append an unescaped token at the end of the pointer +- [`operator/`](operator_slash.md) - create JSON Pointer by appending +- [`push_back`](push_back.md) - append an unescaped token at the end of the pointer +- [JSON Pointer](../../features/json_pointer.md) - the article on JSON Pointer support ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/operator_spaceship.md b/docs/mkdocs/docs/api/json_pointer/operator_spaceship.md index ffc3f0e93..1c97ce569 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_spaceship.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_spaceship.md @@ -64,8 +64,8 @@ Linear in the number of reference tokens. ## See also -- [operator==](operator_eq.md) compare: equal -- [operator!=](operator_ne.md) compare: not equal +- [`operator==`](operator_eq.md) - compare: equal +- [`operator!=`](operator_ne.md) - compare: not equal ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/operator_string_t.md b/docs/mkdocs/docs/api/json_pointer/operator_string_t.md index 44dde733d..4818f7c5c 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_string_t.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_string_t.md @@ -46,8 +46,8 @@ operator string_t() const ## See also -- [to_string](to_string.md) return a string representation of the JSON pointer -- [string_t](../basic_json/string_t.md) - type for strings +- [`to_string`](to_string.md) - return a string representation of the JSON pointer +- [`string_t`](../basic_json/string_t.md) - type for strings ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/parent_pointer.md b/docs/mkdocs/docs/api/json_pointer/parent_pointer.md index e8627714d..1cb26c920 100644 --- a/docs/mkdocs/docs/api/json_pointer/parent_pointer.md +++ b/docs/mkdocs/docs/api/json_pointer/parent_pointer.md @@ -37,8 +37,8 @@ Linear in the length of the JSON pointer. ## See also -- [pop_back](pop_back.md) remove the last reference token -- [back](back.md) return the last reference token +- [`pop_back`](pop_back.md) - remove the last reference token +- [`back`](back.md) - return the last reference token ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/pop_back.md b/docs/mkdocs/docs/api/json_pointer/pop_back.md index 6646be44a..07e1f33ce 100644 --- a/docs/mkdocs/docs/api/json_pointer/pop_back.md +++ b/docs/mkdocs/docs/api/json_pointer/pop_back.md @@ -36,9 +36,9 @@ Constant. ## See also -- [back](back.md) return last reference token -- [push_back](push_back.md) append an unescaped token at the end of the pointer -- [parent_pointer](parent_pointer.md) returns the parent of this JSON pointer +- [`back`](back.md) - return last reference token +- [`push_back`](push_back.md) - append an unescaped token at the end of the pointer +- [`parent_pointer`](parent_pointer.md) - returns the parent of this JSON pointer ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/pop_front.md b/docs/mkdocs/docs/api/json_pointer/pop_front.md index 308a3cbdc..ec3d984ec 100644 --- a/docs/mkdocs/docs/api/json_pointer/pop_front.md +++ b/docs/mkdocs/docs/api/json_pointer/pop_front.md @@ -36,8 +36,8 @@ Linear in the number of reference tokens in the `json_pointer`. ## See also -- [front](front.md) return first reference token -- [push_front](push_front.md) append an unescaped token at the start of the pointer +- [`front`](front.md) - return first reference token +- [`push_front`](push_front.md) - append an unescaped token at the start of the pointer ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/push_back.md b/docs/mkdocs/docs/api/json_pointer/push_back.md index f50f10924..c2c950027 100644 --- a/docs/mkdocs/docs/api/json_pointer/push_back.md +++ b/docs/mkdocs/docs/api/json_pointer/push_back.md @@ -39,10 +39,10 @@ Amortized constant. ## See also -- [back](back.md) return last reference token -- [pop_back](pop_back.md) remove the last reference token -- [operator/=](operator_slasheq.md) append to the end of the JSON pointer -- [operator/](operator_slash.md) create JSON Pointer by appending +- [`back`](back.md) - return last reference token +- [`pop_back`](pop_back.md) - remove the last reference token +- [`operator/=`](operator_slasheq.md) - append to the end of the JSON pointer +- [`operator/`](operator_slash.md) - create JSON Pointer by appending ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/push_front.md b/docs/mkdocs/docs/api/json_pointer/push_front.md index 67dd87384..e68516f1c 100644 --- a/docs/mkdocs/docs/api/json_pointer/push_front.md +++ b/docs/mkdocs/docs/api/json_pointer/push_front.md @@ -40,8 +40,8 @@ Linear in the number of reference tokens in the `json_pointer`. ## See also -- [front](front.md) return first reference token -- [pop_front](pop_front.md) remove the first reference token +- [`front`](front.md) - return first reference token +- [`pop_front`](pop_front.md) - remove the first reference token ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/string_t.md b/docs/mkdocs/docs/api/json_pointer/string_t.md index 6808ae540..468c8446a 100644 --- a/docs/mkdocs/docs/api/json_pointer/string_t.md +++ b/docs/mkdocs/docs/api/json_pointer/string_t.md @@ -25,8 +25,8 @@ See [`basic_json::string_t`](../basic_json/string_t.md) for more information. ## See also -- [basic_json::string_t](../basic_json/string_t.md) type used to store JSON strings -- [to_string](to_string.md) return a string representation of the JSON pointer +- [`basic_json::string_t`](../basic_json/string_t.md) - type used to store JSON strings +- [`to_string`](to_string.md) - return a string representation of the JSON pointer ## Version history diff --git a/docs/mkdocs/docs/api/json_pointer/to_string.md b/docs/mkdocs/docs/api/json_pointer/to_string.md index b994ff80e..2a3f7f2db 100644 --- a/docs/mkdocs/docs/api/json_pointer/to_string.md +++ b/docs/mkdocs/docs/api/json_pointer/to_string.md @@ -44,8 +44,8 @@ ptr == json_pointer(ptr.to_string()); ## See also -- [operator string_t](operator_string_t.md) return a string representation of the JSON pointer (deprecated) -- [operator<<](../operator_ltlt.md) write a JSON pointer to a stream +- [`operator string_t`](operator_string_t.md) - return a string representation of the JSON pointer (deprecated) +- [`operator<<`](../operator_ltlt.md) - write a JSON pointer to a stream ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/binary.md b/docs/mkdocs/docs/api/json_sax/binary.md index f5310390e..617fd76fc 100644 --- a/docs/mkdocs/docs/api/json_sax/binary.md +++ b/docs/mkdocs/docs/api/json_sax/binary.md @@ -37,7 +37,7 @@ It is safe to move the passed binary value. ## See also -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser - [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article - [Binary Values](../../features/binary_values.md) - the article on binary values diff --git a/docs/mkdocs/docs/api/json_sax/boolean.md b/docs/mkdocs/docs/api/json_sax/boolean.md index 89abb67ba..abac5dd38 100644 --- a/docs/mkdocs/docs/api/json_sax/boolean.md +++ b/docs/mkdocs/docs/api/json_sax/boolean.md @@ -33,7 +33,7 @@ Whether parsing should proceed. ## See also -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser - [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/end_array.md b/docs/mkdocs/docs/api/json_sax/end_array.md index 7010e7259..599f951e5 100644 --- a/docs/mkdocs/docs/api/json_sax/end_array.md +++ b/docs/mkdocs/docs/api/json_sax/end_array.md @@ -28,8 +28,9 @@ Whether parsing should proceed. ## See also -- [start_array](start_array.md) - the beginning of an array was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`start_array`](start_array.md) - the beginning of an array was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/end_object.md b/docs/mkdocs/docs/api/json_sax/end_object.md index aaaec6d22..b5f020dc7 100644 --- a/docs/mkdocs/docs/api/json_sax/end_object.md +++ b/docs/mkdocs/docs/api/json_sax/end_object.md @@ -28,9 +28,10 @@ Whether parsing should proceed. ## See also -- [start_object](start_object.md) - the beginning of an object was read -- [key](key.md) - an object key was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`start_object`](start_object.md) - the beginning of an object was read +- [`key`](key.md) - an object key was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/index.md b/docs/mkdocs/docs/api/json_sax/index.md index fec1e0489..c63fadaa1 100644 --- a/docs/mkdocs/docs/api/json_sax/index.md +++ b/docs/mkdocs/docs/api/json_sax/index.md @@ -25,6 +25,9 @@ sequenceDiagram P->>H: end_object() ``` +

Each arrow is one call from the parser to the SAX handler, from top to bottom in the +order the calls happen.

+ Note `elements` is passed as `#!cpp std::numeric_limits::max()` (i.e., "unknown") for JSON text input; only binary formats such as CBOR or MessagePack may report the actual number of elements in `start_object`/ `start_array`. Also note that `1` is reported via `number_unsigned` rather than `number_integer` because it has no @@ -62,9 +65,9 @@ leading `-` sign. ## See also - [SAX interface](../../features/parsing/sax_interface.md) - guide to event-based parsing -- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events from an input -- [**parse**](../basic_json/parse.md) - DOM parsing, with optional [parser callbacks](../../features/parsing/parser_callbacks.md) -- [Parse exceptions](../../features/parsing/parse_exceptions.md) +- [`sax_parse`](../basic_json/sax_parse.md) - generate SAX events from an input +- [`parse`](../basic_json/parse.md) - DOM parsing, with optional [parser callbacks](../../features/parsing/parser_callbacks.md) +- [Parse exceptions](../../features/parsing/parse_exceptions.md) - how parse errors are reported ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/key.md b/docs/mkdocs/docs/api/json_sax/key.md index 5ecea20f9..591f590e0 100644 --- a/docs/mkdocs/docs/api/json_sax/key.md +++ b/docs/mkdocs/docs/api/json_sax/key.md @@ -37,9 +37,10 @@ It is safe to move the passed object key value. ## See also -- [start_object](start_object.md) - the beginning of an object was read -- [end_object](end_object.md) - the end of an object was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`start_object`](start_object.md) - the beginning of an object was read +- [`end_object`](end_object.md) - the end of an object was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/null.md b/docs/mkdocs/docs/api/json_sax/null.md index ef5771094..8b049d26b 100644 --- a/docs/mkdocs/docs/api/json_sax/null.md +++ b/docs/mkdocs/docs/api/json_sax/null.md @@ -28,7 +28,7 @@ Whether parsing should proceed. ## See also -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser - [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/number_float.md b/docs/mkdocs/docs/api/json_sax/number_float.md index d9c37c257..fa28a64b8 100644 --- a/docs/mkdocs/docs/api/json_sax/number_float.md +++ b/docs/mkdocs/docs/api/json_sax/number_float.md @@ -36,9 +36,10 @@ Whether parsing should proceed. ## See also -- [number_integer](number_integer.md) - an integer number was read -- [number_unsigned](number_unsigned.md) - an unsigned integer number was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`number_integer`](number_integer.md) - an integer number was read +- [`number_unsigned`](number_unsigned.md) - an unsigned integer number was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/number_integer.md b/docs/mkdocs/docs/api/json_sax/number_integer.md index c67f9a410..6e7426016 100644 --- a/docs/mkdocs/docs/api/json_sax/number_integer.md +++ b/docs/mkdocs/docs/api/json_sax/number_integer.md @@ -33,9 +33,10 @@ Whether parsing should proceed. ## See also -- [number_unsigned](number_unsigned.md) - an unsigned integer number was read -- [number_float](number_float.md) - a floating-point number was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`number_unsigned`](number_unsigned.md) - an unsigned integer number was read +- [`number_float`](number_float.md) - a floating-point number was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/number_unsigned.md b/docs/mkdocs/docs/api/json_sax/number_unsigned.md index edb40d402..730bf73d6 100644 --- a/docs/mkdocs/docs/api/json_sax/number_unsigned.md +++ b/docs/mkdocs/docs/api/json_sax/number_unsigned.md @@ -33,9 +33,10 @@ Whether parsing should proceed. ## See also -- [number_integer](number_integer.md) - an integer number was read -- [number_float](number_float.md) - a floating-point number was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`number_integer`](number_integer.md) - an integer number was read +- [`number_float`](number_float.md) - a floating-point number was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/parse_error.md b/docs/mkdocs/docs/api/json_sax/parse_error.md index ce90f4448..cad7b5a91 100644 --- a/docs/mkdocs/docs/api/json_sax/parse_error.md +++ b/docs/mkdocs/docs/api/json_sax/parse_error.md @@ -41,9 +41,10 @@ Whether parsing should proceed (**must return `#!cpp false`**). ## See also -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser - [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without exceptions +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/start_array.md b/docs/mkdocs/docs/api/json_sax/start_array.md index 48557fa48..c470a330f 100644 --- a/docs/mkdocs/docs/api/json_sax/start_array.md +++ b/docs/mkdocs/docs/api/json_sax/start_array.md @@ -37,8 +37,9 @@ Binary formats may report the number of elements. ## See also -- [end_array](end_array.md) - the end of an array was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`end_array`](end_array.md) - the end of an array was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/start_object.md b/docs/mkdocs/docs/api/json_sax/start_object.md index 3a569878f..0f1c89554 100644 --- a/docs/mkdocs/docs/api/json_sax/start_object.md +++ b/docs/mkdocs/docs/api/json_sax/start_object.md @@ -37,9 +37,10 @@ Binary formats may report the number of elements. ## See also -- [end_object](end_object.md) - the end of an object was read -- [key](key.md) - an object key was read -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`end_object`](end_object.md) - the end of an object was read +- [`key`](key.md) - an object key was read +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser +- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing ## Version history diff --git a/docs/mkdocs/docs/api/json_sax/string.md b/docs/mkdocs/docs/api/json_sax/string.md index 7e742ebcb..6c9575443 100644 --- a/docs/mkdocs/docs/api/json_sax/string.md +++ b/docs/mkdocs/docs/api/json_sax/string.md @@ -37,7 +37,7 @@ It is safe to move the passed string value. ## See also -- [sax_parse](../basic_json/sax_parse.md) - SAX parser +- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser - [SAX Interface](../../features/parsing/sax_interface.md) - the SAX interface article ## Version history diff --git a/docs/mkdocs/docs/api/json_view.md b/docs/mkdocs/docs/api/json_view.md index 0998549d9..408214fe4 100644 --- a/docs/mkdocs/docs/api/json_view.md +++ b/docs/mkdocs/docs/api/json_view.md @@ -26,8 +26,9 @@ This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a [`j ## See also -- [json_document](json_document.md) - the document type this view refers into -- [ordered_json_view](ordered_json_view.md) - the corresponding view for `ordered_json_document` +- [`json_document`](json_document.md) - the document type this view refers into +- [`ordered_json_view`](ordered_json_view.md) - the corresponding view for `ordered_json_document` +- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree ## Version history diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index 9f76133f8..2ddf6f245 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -140,8 +140,12 @@ header. See also the [macro overview page](../../features/macros.md). ## See also - [Macros](../../features/macros.md) - guide to the supported macros -- [Runtime assertions](../../features/assertions.md) +- [Runtime assertions](../../features/assertions.md) - assertions guarding against undefined behavior - [Arbitrary type conversions](../../features/arbitrary_types.md) - using the `NLOHMANN_DEFINE_TYPE_*` macros - [Enum conversion](../../features/enum_conversion.md) - using `NLOHMANN_JSON_SERIALIZE_ENUM` -- [The `nlohmann` namespace](../../features/namespace.md) +- [The `nlohmann` namespace](../../features/namespace.md) - how the library's namespace is defined and versioned - [CMake integration](../../integration/cmake.md) - setting macros from the build system +- [Integration](../../integration/index.md) - integrate the library and set macros +- [Modules](../../features/modules.md) - macros and C++20 modules +- [Working Without Exceptions](../../features/no_exceptions.md) - using the library without exceptions +- [Architecture](../../home/architecture.md) - how the library is put together diff --git a/docs/mkdocs/docs/api/macros/json_assert.md b/docs/mkdocs/docs/api/macros/json_assert.md index a8f198a7a..158fe7a68 100644 --- a/docs/mkdocs/docs/api/macros/json_assert.md +++ b/docs/mkdocs/docs/api/macros/json_assert.md @@ -82,6 +82,7 @@ Therefore, assertions can be switched off by defining `NDEBUG`. ## See also - [Runtime Assertions](../../features/assertions.md) - overview documentation +- [Supported Macros](../../features/macros.md) - guide to all supported macros ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md index adab0769a..796b20c6b 100644 --- a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md +++ b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md @@ -111,8 +111,10 @@ The default value is `0` (disabled — existing behavior is preserved). ## See also -- [FAQ: Brace initialization yields arrays](../../home/faq.md#brace-initialization-yields-arrays) -- [**basic_json(initializer_list_t)**](../basic_json/basic_json.md) - the affected constructor +- [FAQ: Brace initialization yields arrays](../../home/faq.md#brace-initialization-yields-arrays) - why brace + initialization may create arrays +- [`basic_json(initializer_list_t)`](../basic_json/basic_json.md) - the affected constructor +- [Creating JSON values](../../features/creating_values.md) - initializer lists and braces ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_delete_deprecated_functions.md b/docs/mkdocs/docs/api/macros/json_delete_deprecated_functions.md index bae89d52a..122f40116 100644 --- a/docs/mkdocs/docs/api/macros/json_delete_deprecated_functions.md +++ b/docs/mkdocs/docs/api/macros/json_delete_deprecated_functions.md @@ -88,6 +88,8 @@ The default value is `0` (disabled, the deprecated functions can still be called deprecated functions and the version they were deprecated in - [Migration guide: replace deprecated functions](../../integration/migration_guide.md#replace-deprecated-functions) - how to replace each deprecated function +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [CMake](../../integration/cmake.md) - the corresponding CMake option ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md b/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md index 486f3f34e..56886ba86 100644 --- a/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md +++ b/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md @@ -112,7 +112,8 @@ When the macro is not defined, the library will define it to its default value. ## See also - [:simple-cmake: JSON_Diagnostic_Positions](../../integration/cmake.md#json_diagnostic_positions) - CMake option to control the macro -- [JSON_DIAGNOSTICS](json_diagnostics.md) - macro to control extended diagnostics +- [`JSON_DIAGNOSTICS`](json_diagnostics.md) - macro to control extended diagnostics +- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_diagnostics.md b/docs/mkdocs/docs/api/macros/json_diagnostics.md index c68b4380b..a900574d6 100644 --- a/docs/mkdocs/docs/api/macros/json_diagnostics.md +++ b/docs/mkdocs/docs/api/macros/json_diagnostics.md @@ -87,7 +87,11 @@ When the macro is not defined, the library will define it to its default value. ## See also - [:simple-cmake: JSON_Diagnostics](../../integration/cmake.md#json_diagnostics) - CMake option to control the macro -- [JSON_DIAGNOSTIC_POSITIONS](json_diagnostic_positions.md) - macro to access positions of elements +- [`JSON_DIAGNOSTIC_POSITIONS`](json_diagnostic_positions.md) - macro to access positions of elements +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Exceptions: extended diagnostic messages](../../home/exceptions.md#extended-diagnostic-messages) - how extended diagnostics change the messages +- [Debugging](../../home/debugging.md) - debugging aids +- [`exception`](../basic_json/exception.md) - base class of all exceptions thrown by the library ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md b/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md index a0024d81a..297815e7b 100644 --- a/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md +++ b/docs/mkdocs/docs/api/macros/json_disable_enum_serialization.md @@ -147,6 +147,7 @@ The default value is `0`. - [:simple-cmake: JSON_DisableEnumSerialization](../../integration/cmake.md#json_disableenumserialization) - CMake option to control the macro - [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum +- [Specializing enum conversion](../../features/enum_conversion.md) - map enums to JSON strings instead of integers ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_disable_stream_bulk_scan.md b/docs/mkdocs/docs/api/macros/json_disable_stream_bulk_scan.md index 6b45a0056..7befb6cde 100644 --- a/docs/mkdocs/docs/api/macros/json_disable_stream_bulk_scan.md +++ b/docs/mkdocs/docs/api/macros/json_disable_stream_bulk_scan.md @@ -130,14 +130,16 @@ The default value is `0`: streams are scanned in bulk. ## See also -- [**parse**](../basic_json/parse.md) - deserialize from a compatible input -- [**operator>>**](../operator_gtgt.md) - deserialize from stream -- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events -- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - leave an input stream positioned right after a +- [`parse`](../basic_json/parse.md) - deserialize from a compatible input +- [`operator>>`](../operator_gtgt.md) - deserialize from stream +- [`sax_parse`](../basic_json/sax_parse.md) - generate SAX events +- [`JSON_PRECISE_STREAM_POSITION`](json_precise_stream_position.md) - leave an input stream positioned right after a parsed number -- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio +- [`JSON_DISABLE_UNLOCKED_FILE_IO`](json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio functions -- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) +- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) - how input + sources affect parsing performance +- [Large, Streamed, and Partial Input](../../features/parsing/large_and_partial_input.md) - chunked, concatenated, and very large input ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md index 6d3aa003b..94accb1e8 100644 --- a/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md +++ b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md @@ -105,9 +105,11 @@ The default value is `0` (disabled — existing behavior is preserved). ## See also -- [**basic_json(CompatibleType&&)**](../basic_json/basic_json.md) - the affected constructor +- [`basic_json(CompatibleType&&)`](../basic_json/basic_json.md) - the affected constructor - [:simple-cmake: JSON_DisableTupleReferenceConversion](../../integration/cmake.md#json_disabletuplereferenceconversion) - CMake option to control the macro +- [Converting values](../../features/conversions.md) - getting values out +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - converting your own types ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_disable_unlocked_file_io.md b/docs/mkdocs/docs/api/macros/json_disable_unlocked_file_io.md index f0aa40b65..2c266ba2d 100644 --- a/docs/mkdocs/docs/api/macros/json_disable_unlocked_file_io.md +++ b/docs/mkdocs/docs/api/macros/json_disable_unlocked_file_io.md @@ -104,11 +104,13 @@ The default value is `0`: the file is locked once and read with the unlocked std ## See also -- [**parse**](../basic_json/parse.md) - deserialize from a compatible input -- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events -- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a +- [`parse`](../basic_json/parse.md) - deserialize from a compatible input +- [`sax_parse`](../basic_json/sax_parse.md) - generate SAX events +- [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time -- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) +- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) - how input + sources affect parsing performance +- [Large, Streamed, and Partial Input](../../features/parsing/large_and_partial_input.md) - chunked, concatenated, and very large input ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md index f32fe0277..a2c20900b 100644 --- a/docs/mkdocs/docs/api/macros/json_has_cpp_11.md +++ b/docs/mkdocs/docs/api/macros/json_has_cpp_11.md @@ -42,10 +42,11 @@ The default value is detected based on preprocessor macros such as `#!cpp __cplu ## See also -- [JSON_HAS_FILESYSTEM / JSON_HAS_EXPERIMENTAL_FILESYSTEM](json_has_filesystem.md) - control `std::filesystem` support -- [JSON_HAS_RANGES](json_has_ranges.md) - control `std::ranges` support -- [JSON_HAS_THREE_WAY_COMPARISON](json_has_three_way_comparison.md) - control 3-way comparison support -- [JSON_HAS_STD_FORMAT](json_has_std_format.md) - control `std::format`/`std::formatter` support +- [`JSON_HAS_FILESYSTEM` / `JSON_HAS_EXPERIMENTAL_FILESYSTEM`](json_has_filesystem.md) - control `std::filesystem` support +- [`JSON_HAS_RANGES`](json_has_ranges.md) - control `std::ranges` support +- [`JSON_HAS_THREE_WAY_COMPARISON`](json_has_three_way_comparison.md) - control 3-way comparison support +- [`JSON_HAS_STD_FORMAT`](json_has_std_format.md) - control `std::format`/`std::formatter` support +- [Supported Macros](../../features/macros.md) - guide to all supported macros ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_has_filesystem.md b/docs/mkdocs/docs/api/macros/json_has_filesystem.md index fe843e5c1..1e0255ffa 100644 --- a/docs/mkdocs/docs/api/macros/json_has_filesystem.md +++ b/docs/mkdocs/docs/api/macros/json_has_filesystem.md @@ -54,8 +54,10 @@ The default value is detected based on the preprocessor macros `#!cpp __cpp_lib_ ## See also -- [JSON_HAS_CPP_11 / JSON_HAS_CPP_14 / JSON_HAS_CPP_17 / JSON_HAS_CPP_20 / JSON_HAS_CPP_23 / - JSON_HAS_CPP_26](json_has_cpp_11.md) - set supported C++ standard +- [`JSON_HAS_CPP_11` / `JSON_HAS_CPP_14` / `JSON_HAS_CPP_17` / `JSON_HAS_CPP_20` / `JSON_HAS_CPP_23` / + `JSON_HAS_CPP_26`](json_has_cpp_11.md) - set supported C++ standard +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Converting values](../../features/conversions.md) - conversions of values ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md b/docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md index 6392170c9..4558916aa 100644 --- a/docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md +++ b/docs/mkdocs/docs/api/macros/json_has_range_view_conversion.md @@ -17,7 +17,7 @@ When the macro is not defined, the library will define it to its default value. !!! info "Known compiler/stdlib exclusions" - - **MinGW** -- disabled, because its `std::ranges` support is incomplete ([issue #4916](https://github.com/nlohmann/json/issues/4916)). + - **MinGW** — disabled, because its `std::ranges` support is incomplete ([issue #4916](https://github.com/nlohmann/json/issues/4916)). - All toolchains for which [`JSON_HAS_RANGES`](json_has_ranges.md#default-definition) is disabled. ## Examples @@ -35,7 +35,7 @@ When the macro is not defined, the library will define it to its default value. ## See also -- [JSON_HAS_RANGES](json_has_ranges.md) - control `std::ranges` support +- [`JSON_HAS_RANGES`](json_has_ranges.md) - control `std::ranges` support - [Constructing from a C++20 range view](../../features/conversions.md#putting-values-in) - usage of the feature ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_has_ranges.md b/docs/mkdocs/docs/api/macros/json_has_ranges.md index b22e816ad..a8864fb27 100644 --- a/docs/mkdocs/docs/api/macros/json_has_ranges.md +++ b/docs/mkdocs/docs/api/macros/json_has_ranges.md @@ -42,11 +42,12 @@ When the macro is not defined, the library will define it to its default value. ## See also -- [JSON_HAS_CPP_11 / JSON_HAS_CPP_14 / JSON_HAS_CPP_17 / JSON_HAS_CPP_20 / JSON_HAS_CPP_23 / - JSON_HAS_CPP_26](json_has_cpp_11.md) - set supported C++ standard -- [JSON_HAS_RANGE_VIEW_CONVERSION](json_has_range_view_conversion.md) - control construction from `std::ranges` views -- [JSON_HAS_STD_FORMAT](json_has_std_format.md) - a similar feature-detection macro, for `std::format`/`std::formatter` +- [`JSON_HAS_CPP_11` / `JSON_HAS_CPP_14` / `JSON_HAS_CPP_17` / `JSON_HAS_CPP_20` / `JSON_HAS_CPP_23` / + `JSON_HAS_CPP_26`](json_has_cpp_11.md) - set supported C++ standard +- [`JSON_HAS_RANGE_VIEW_CONVERSION`](json_has_range_view_conversion.md) - control construction from `std::ranges` views +- [`JSON_HAS_STD_FORMAT`](json_has_std_format.md) - a similar feature-detection macro, for `std::format`/`std::formatter` support +- [Supported Macros](../../features/macros.md) - guide to all supported macros ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_has_static_rtti.md b/docs/mkdocs/docs/api/macros/json_has_static_rtti.md index a44a7c224..cf5ca4068 100644 --- a/docs/mkdocs/docs/api/macros/json_has_static_rtti.md +++ b/docs/mkdocs/docs/api/macros/json_has_static_rtti.md @@ -28,8 +28,10 @@ When the macro is not defined, the library will define it to its default value. ## See also -- [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit); on C++17, this macro +- [`operator ValueType`](../basic_json/operator_ValueType.md) - get a value (implicit); on C++17, this macro controls whether `std::any` is excluded from its candidate types +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Converting values](../../features/conversions.md) - conversions of values ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_has_std_format.md b/docs/mkdocs/docs/api/macros/json_has_std_format.md index 0691fe3cd..a4a262fe8 100644 --- a/docs/mkdocs/docs/api/macros/json_has_std_format.md +++ b/docs/mkdocs/docs/api/macros/json_has_std_format.md @@ -39,6 +39,8 @@ When the macro is not defined, the library will define it to its default value. ## See also - [`std::formatter`](../basic_json/std_formatter.md) - format JSON values with `std::format` +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Serialization](../../features/serialization.md) - serialize values to text ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md index a1c2cdb58..5876f9982 100644 --- a/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md +++ b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md @@ -29,8 +29,8 @@ When the macro is not defined, the library will define it to its default value. ## See also -- [**operator<=>**](../basic_json/operator_spaceship.md) - 3-way compare JSON values -- [**operator==**](../json_pointer/operator_eq.md) - compare JSON pointers for equality +- [`operator<=>`](../basic_json/operator_spaceship.md) - 3-way compare JSON values +- [`operator==`](../json_pointer/operator_eq.md) - compare JSON pointers for equality ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md b/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md index 1235d634d..9b8a0e8b5 100644 --- a/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md +++ b/docs/mkdocs/docs/api/macros/json_no_automatic_udls.md @@ -59,10 +59,11 @@ By default, `#!cpp JSON_NO_AUTOMATIC_UDLS` is not defined, and `>**](../operator_gtgt.md) - deserialize from stream +- [`operator<<`](../operator_ltlt.md) - serialize to stream +- [`operator>>`](../operator_gtgt.md) - deserialize from stream +- [Serialization](../../features/serialization.md) - pretty-printing, UTF-8 handling, and number output +- [Parsing](../../features/parsing/index.md) - overview of the parsing functions ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_no_simd.md b/docs/mkdocs/docs/api/macros/json_no_simd.md index 3976acf7d..b16202b12 100644 --- a/docs/mkdocs/docs/api/macros/json_no_simd.md +++ b/docs/mkdocs/docs/api/macros/json_no_simd.md @@ -73,12 +73,14 @@ By default, `#!cpp JSON_NO_SIMD` is not defined, and vector instructions are use ## See also -- [**parse**](../basic_json/parse.md) - deserialize from a compatible input -- [**dump**](../basic_json/dump.md) - serialization -- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation -- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time -- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) -- [json_view](../../features/json_view.md) - the zero-copy view, whose parser the macro also affects +- [`parse`](../basic_json/parse.md) - deserialize from a compatible input +- [`dump`](../basic_json/dump.md) - serialization +- [`JSON_USE_SIMDUTF`](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation +- [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time +- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) - how input + sources affect parsing performance +- [Serialization](../../features/serialization.md) - pretty-printing, UTF-8 handling, and number output +- [Zero-copy JSON views](../../features/json_view.md) - the zero-copy view, whose parser the macro also affects ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_no_thread_local.md b/docs/mkdocs/docs/api/macros/json_no_thread_local.md index f4fb28dbb..400d8fab7 100644 --- a/docs/mkdocs/docs/api/macros/json_no_thread_local.md +++ b/docs/mkdocs/docs/api/macros/json_no_thread_local.md @@ -45,7 +45,8 @@ Copying and comparing fall back to working without the call stack there, as they ## See also -- [FAQ: Thread safety](../../home/faq.md#thread-safety) +- [FAQ: Thread safety](../../home/faq.md#thread-safety) - thread-safety guarantees of the library +- [Supported Macros](../../features/macros.md) - guide to all supported macros ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_noexception.md b/docs/mkdocs/docs/api/macros/json_noexception.md index c801b8567..69af6b740 100644 --- a/docs/mkdocs/docs/api/macros/json_noexception.md +++ b/docs/mkdocs/docs/api/macros/json_noexception.md @@ -38,7 +38,12 @@ available for MSVC if exceptions are disabled, see [#2824](https://github.com/nl ## See also -- [Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for more information how to switch off exceptions +- [Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) - more information how to switch off + exceptions +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Working Without Exceptions](../../features/no_exceptions.md) - using the library without exceptions +- [Exceptions](../../home/exceptions.md) - all exceptions and their error codes +- [`exception`](../basic_json/exception.md) - base class of all exceptions thrown by the library ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_precise_stream_position.md b/docs/mkdocs/docs/api/macros/json_precise_stream_position.md index f641a737a..2292ef36c 100644 --- a/docs/mkdocs/docs/api/macros/json_precise_stream_position.md +++ b/docs/mkdocs/docs/api/macros/json_precise_stream_position.md @@ -128,8 +128,9 @@ The default value is `0` (disabled — existing behavior is preserved). ## See also -- [**operator>>**](../operator_gtgt.md) - deserialize from stream -- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events +- [`operator>>`](../operator_gtgt.md) - deserialize from stream +- [`sax_parse`](../basic_json/sax_parse.md) - generate SAX events +- [Large, Streamed, and Partial Input](../../features/parsing/large_and_partial_input.md) - chunked, concatenated, and very large input ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md b/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md index 88f272334..271d2c596 100644 --- a/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md +++ b/docs/mkdocs/docs/api/macros/json_skip_library_version_check.md @@ -45,8 +45,10 @@ By default, the macro is not defined. ## See also -- [NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, - NLOHMANN_JSON_VERSION_PATCH](nlohmann_json_version_major.md) - library version information +- [`NLOHMANN_JSON_VERSION_MAJOR` / `NLOHMANN_JSON_VERSION_MINOR` / + `NLOHMANN_JSON_VERSION_PATCH`](nlohmann_json_version_major.md) - library version information +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Integration](../../integration/index.md) - integrate the library ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md b/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md index f5702a454..63f156385 100644 --- a/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md +++ b/docs/mkdocs/docs/api/macros/json_skip_unsupported_compiler_check.md @@ -30,8 +30,10 @@ By default, the macro is not defined. ## See also -- [JSON_HAS_CPP_11 / JSON_HAS_CPP_14 / JSON_HAS_CPP_17 / JSON_HAS_CPP_20 / JSON_HAS_CPP_23 / - JSON_HAS_CPP_26](json_has_cpp_11.md) - set supported C++ standard +- [`JSON_HAS_CPP_11` / `JSON_HAS_CPP_14` / `JSON_HAS_CPP_17` / `JSON_HAS_CPP_20` / `JSON_HAS_CPP_23` / + `JSON_HAS_CPP_26`](json_has_cpp_11.md) - set supported C++ standard +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Integration](../../integration/index.md) - integrate the library ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md index dadcb5fbe..8421801aa 100644 --- a/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md +++ b/docs/mkdocs/docs/api/macros/json_strict_binary_utf8.md @@ -92,11 +92,13 @@ The default value is `0` (disabled, the behavior of version 3.12.0 and earlier i ## See also -- [**to_cbor**](../basic_json/to_cbor.md) - create a CBOR serialization of a JSON value -- [**to_ubjson**](../basic_json/to_ubjson.md) - create a UBJSON serialization of a JSON value -- [**to_bjdata**](../basic_json/to_bjdata.md) - create a BJData serialization of a JSON value -- [**to_bson**](../basic_json/to_bson.md) - create a BSON serialization of a JSON value -- [**error_handler_t**](../basic_json/error_handler_t.md) - how [`dump`](../basic_json/dump.md) treats ill-formed UTF-8 +- [Binary formats](../../features/binary_formats/index.md) - overview of the supported binary formats +- [`from_cbor`](../basic_json/from_cbor.md) - also affected when parsing CBOR strings +- [`to_cbor`](../basic_json/to_cbor.md) - create a CBOR serialization of a JSON value +- [`to_ubjson`](../basic_json/to_ubjson.md) - create a UBJSON serialization of a JSON value +- [`to_bjdata`](../basic_json/to_bjdata.md) - create a BJData serialization of a JSON value +- [`to_bson`](../basic_json/to_bson.md) - create a BSON serialization of a JSON value +- [`error_handler_t`](../basic_json/error_handler_t.md) - how [`dump`](../basic_json/dump.md) treats ill-formed UTF-8 ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md b/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md index 806246053..044893aa3 100644 --- a/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md +++ b/docs/mkdocs/docs/api/macros/json_strict_nul_handling.md @@ -125,10 +125,11 @@ The default value is `0` (disabled — existing behavior is preserved). ## See also - [:simple-cmake: JSON_StrictNulHandling](../../integration/cmake.md#json_strictnulhandling) - CMake option to control the macro -- [FAQ: NUL bytes in the input](../../home/faq.md#nul-bytes-in-the-input) -- [**parse**](../basic_json/parse.md) - deserialize from a compatible input -- [**accept**](../basic_json/accept.md) - check if the input is valid JSON -- [**operator>>**](../operator_gtgt.md) - deserialize from stream +- [FAQ: NUL bytes in the input](../../home/faq.md#nul-bytes-in-the-input) - how NUL bytes in the input are handled +- [`parse`](../basic_json/parse.md) - deserialize from a compatible input +- [`accept`](../basic_json/accept.md) - check if the input is valid JSON +- [`operator>>`](../operator_gtgt.md) - deserialize from stream +- [Parsing Untrusted Input](../../features/parsing/untrusted_input.md) - limits and checks for input you do not control ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_throw_user.md b/docs/mkdocs/docs/api/macros/json_throw_user.md index 7f4fb6a8d..994a57cea 100644 --- a/docs/mkdocs/docs/api/macros/json_throw_user.md +++ b/docs/mkdocs/docs/api/macros/json_throw_user.md @@ -69,8 +69,12 @@ replaced by calling [`std::abort`](https://en.cppreference.com/w/cpp/utility/pro ## See also -- [Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) for more information how to switch off exceptions -- [JSON_NOEXCEPTION](json_noexception.md) - switch off exceptions +- [Switch off exceptions](../../home/exceptions.md#switch-off-exceptions) - more information how to switch off + exceptions +- [`JSON_NOEXCEPTION`](json_noexception.md) - switch off exceptions +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Working Without Exceptions](../../features/no_exceptions.md) - using the library without exceptions +- [`exception`](../basic_json/exception.md) - base class of all exceptions thrown by the library ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_use_global_udls.md b/docs/mkdocs/docs/api/macros/json_use_global_udls.md index 6b1c2c7d8..192aec31b 100644 --- a/docs/mkdocs/docs/api/macros/json_use_global_udls.md +++ b/docs/mkdocs/docs/api/macros/json_use_global_udls.md @@ -97,10 +97,11 @@ When the macro is not defined, the library behaves as if it were defined to its ## See also -- [`operator""_json`](../operator_literal_json.md) -- [`operator""_json_pointer`](../operator_literal_json_pointer.md) +- [`operator""_json`](../operator_literal_json.md) - user-defined string literal for JSON values +- [`operator""_json_pointer`](../operator_literal_json_pointer.md) - user-defined string literal for JSON pointers - [`JSON_NO_AUTOMATIC_UDLS`](json_no_automatic_udls.md) - do not include the user-defined string literals automatically - [:simple-cmake: JSON_GlobalUDLs](../../integration/cmake.md#json_globaludls) - CMake option to control the macro +- [Creating JSON values](../../features/creating_values.md) - the article on creating JSON values ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md index 2e7781378..a1942a533 100644 --- a/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md +++ b/docs/mkdocs/docs/api/macros/json_use_implicit_conversions.md @@ -82,9 +82,10 @@ By default, implicit conversions are enabled. ## See also -- [**operator ValueType**](../basic_json/operator_ValueType.md) - get a value (implicit) -- [**get**](../basic_json/get.md) - get a value (explicit) +- [`operator ValueType`](../basic_json/operator_ValueType.md) - get a value (implicit) +- [`get`](../basic_json/get.md) - get a value (explicit) - [:simple-cmake: JSON_ImplicitConversions](../../integration/cmake.md#json_implicitconversions) - CMake option to control the macro +- [Converting values](../../features/conversions.md) - implicit conversions and how to disable them ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md index 8fdf47982..7cba42ef2 100644 --- a/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md +++ b/docs/mkdocs/docs/api/macros/json_use_objects_for_enum_keyed_maps.md @@ -129,9 +129,9 @@ The default value is `0` (disabled — existing behavior is preserved). ## See also -- [Specializing enum conversion](../../features/enum_conversion.md) -- [**NLOHMANN_JSON_SERIALIZE_ENUM**](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum -- [**NLOHMANN_JSON_SERIALIZE_ENUM_STRICT**](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with +- [Specializing enum conversion](../../features/enum_conversion.md) - serializing enums as JSON strings +- [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum +- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with exceptions ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_use_simdutf.md b/docs/mkdocs/docs/api/macros/json_use_simdutf.md index 5502a77f9..b6cf73ec8 100644 --- a/docs/mkdocs/docs/api/macros/json_use_simdutf.md +++ b/docs/mkdocs/docs/api/macros/json_use_simdutf.md @@ -24,7 +24,7 @@ simdutf library. When it is not defined, no simdutf header is included and there simdutf requires C++17 and its header rejects older standards with an `#!cpp #error`. The backend is therefore only compiled in from C++17 on. In C++11 and C++14 the macro has no effect and the scalar validator is used, which - accepts and rejects exactly the same input -- only throughput differs. Setting the macro project-wide is therefore + accepts and rejects exactly the same input — only throughput differs. Setting the macro project-wide is therefore safe even when some translation units are built with an older standard. !!! warning "Define consistently" @@ -73,6 +73,8 @@ By default, `#!cpp JSON_USE_SIMDUTF` is not defined and the portable C++11 scala - [:simple-cmake: JSON_TestSimdutf](../../integration/cmake.md#json_testsimdutf) - CMake option to build the unit tests against the simdutf backend +- [Parsing](../../features/parsing/index.md) - overview of the parsing functions +- [Serialization](../../features/serialization.md) - pretty-printing, UTF-8 handling, and number output ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md index ef0a8c441..ec5f4ec21 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_derived_type.md @@ -181,13 +181,14 @@ void to_json(BasicJsonType& j, const B& b) { ## See also -- [NLOHMANN_DEFINE_TYPE_INTRUSIVE / NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT / - NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_intrusive.md) - for similar macros that can be defined _inside_ a non-derived type. -- [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE / NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT / - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_non_intrusive.md) - for similar macros that can be defined _outside_ a non-derived type. -- [Arbitrary Type Conversions](../../features/arbitrary_types.md) for an overview. +- [`NLOHMANN_DEFINE_TYPE_INTRUSIVE` / `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_type_intrusive.md) - similar macros that can be + defined _inside_ a non-derived type +- [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` / `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_type_non_intrusive.md) - similar macros that can + be defined _outside_ a non-derived type +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - overview of the supported conversions +- [Recipes for Common Types](../../features/type_recipes.md) - class hierarchies ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md index b50022e05..99ceffd2e 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_intrusive.md @@ -177,15 +177,15 @@ See the examples below for the concrete generated code. ## See also -- [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_non_intrusive.md) - for a similar macro that can be defined _outside_ the type. -- [NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, - NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_derived_type.md) for similar macros for +- [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` / `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_type_non_intrusive.md) - a similar macro that can + be defined _outside_ the type +- [`NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE` / `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE` / `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE` / + `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_derived_type.md) - similar macros for derived types -- [Arbitrary Type Conversions](../../features/arbitrary_types.md) for an overview. +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - overview of the supported conversions ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md index 373af300f..3d1030eed 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_non_intrusive.md @@ -176,15 +176,15 @@ See the examples below for the concrete generated code. ## See also -- [NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_intrusive.md) - for a similar macro that can be defined _inside_ the type. -- [NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, - NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_derived_type.md) for similar macros for +- [`NLOHMANN_DEFINE_TYPE_INTRUSIVE` / `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_type_intrusive.md) - a similar macro that can be + defined _inside_ the type +- [`NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE` / `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE` / `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE` / + `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_derived_type.md) - similar macros for derived types -- [Arbitrary Type Conversions](../../features/arbitrary_types.md) for an overview. +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - overview of the supported conversions ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md b/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md index 9ee23c6dd..580305d6a 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_define_type_with_names.md @@ -75,16 +75,16 @@ For further information please refer to the corresponding macros without `WITH_N ## See also -- [NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_intrusive.md) - the macros these variants add +- [`NLOHMANN_DEFINE_TYPE_INTRUSIVE` / `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_type_intrusive.md) - the macros these variants add custom JSON key names to -- [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_type_non_intrusive.md) - the macros these +- [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` / `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_type_non_intrusive.md) - the macros these variants add custom JSON key names to -- [NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, - NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, - NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE](nlohmann_define_derived_type.md) - similar macros for +- [`NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE` / `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE` / `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE` / + `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT` / + `NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE`](nlohmann_define_derived_type.md) - similar macros for derived types, also available with custom names - [Arbitrary Type Conversions](../../features/arbitrary_types.md) - overview of type conversion mechanisms diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md index 85ee0e8e5..83bad6efc 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md @@ -33,8 +33,11 @@ the library. ## See also -- [`NLOHMANN_JSON_NAMESPACE_BEGIN, NLOHMANN_JSON_NAMESPACE_END`](nlohmann_json_namespace_begin.md) -- [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](nlohmann_json_namespace_no_version.md) +- [`NLOHMANN_JSON_NAMESPACE_BEGIN, NLOHMANN_JSON_NAMESPACE_END`](nlohmann_json_namespace_begin.md) - macros to open and + close the library namespace +- [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](nlohmann_json_namespace_no_version.md) - disable the inline versioned namespace +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [`nlohmann` Namespace](../../features/namespace.md) - the library namespace ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md index 00911fb89..094c00ab9 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_begin.md @@ -52,9 +52,10 @@ When these macros are not defined, the library will define them to their default ## See also -- [`nlohmann` Namespace](../../features/namespace.md) -- [NLOHMANN_JSON_NAMESPACE](nlohmann_json_namespace.md) -- [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](nlohmann_json_namespace_no_version.md) +- [`nlohmann` Namespace](../../features/namespace.md) - how the library's namespace is defined and versioned +- [`NLOHMANN_JSON_NAMESPACE`](nlohmann_json_namespace.md) - macro expanding to the library namespace +- [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](nlohmann_json_namespace_no_version.md) - disable the inline versioned namespace +- [Supported Macros](../../features/macros.md) - guide to all supported macros ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_no_version.md b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_no_version.md index 9e2a52d04..a4ac0b50d 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_no_version.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace_no_version.md @@ -36,9 +36,11 @@ When the macro is not defined, the library will define it to its default value. ## See also -- [`nlohmann` Namespace](../../features/namespace.md) -- [`NLOHMANN_JSON_NAMESPACE`](nlohmann_json_namespace.md) -- [`NLOHMANN_JSON_NAMESPACE_BEGIN, NLOHMANN_JSON_NAMESPACE_END`](nlohmann_json_namespace_begin.md) +- [`nlohmann` Namespace](../../features/namespace.md) - how the library's namespace is defined and versioned +- [`NLOHMANN_JSON_NAMESPACE`](nlohmann_json_namespace.md) - macro expanding to the library namespace +- [`NLOHMANN_JSON_NAMESPACE_BEGIN, NLOHMANN_JSON_NAMESPACE_END`](nlohmann_json_namespace_begin.md) - macros to open and + close the library namespace +- [Supported Macros](../../features/macros.md) - guide to all supported macros ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md index 6382996ba..d5cf8f53a 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum.md @@ -86,10 +86,12 @@ inline void from_json(const BasicJsonType& j, type& e); ## See also -- [Specializing enum conversion](../../features/enum_conversion.md) -- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md) -- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md) -- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) +- [Specializing enum conversion](../../features/enum_conversion.md) - serializing enums as JSON strings +- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md) - strict variant that throws on + unmapped values +- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md) - disable the default enum conversion +- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) - serialize maps with enum keys as + objects ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md index d1513f4e7..a6dfaa90c 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_serialize_enum_strict.md @@ -105,10 +105,13 @@ inline void from_json(const BasicJsonType& j, type& e); ## See also -- [Specializing enum conversion](../../features/enum_conversion.md) -- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md) -- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md) -- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) +- [Specializing enum conversion](../../features/enum_conversion.md) - serializing enums as JSON strings +- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md) - non-strict variant that uses the first mapping + as default +- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md) - disable the default enum conversion +- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) - serialize maps with enum keys as + objects +- [Arbitrary Type Conversions](../../features/arbitrary_types.md) - converting your own types ## Version history diff --git a/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md b/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md index d7a314276..84fbe6891 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_version_major.md @@ -32,8 +32,10 @@ The macros are defined according to the current library version. ## See also -- [meta](../basic_json/meta.md) - returns version information on the library -- [JSON_SKIP_LIBRARY_VERSION_CHECK](json_skip_library_version_check.md) - skip library version check +- [`meta`](../basic_json/meta.md) - returns version information on the library +- [`JSON_SKIP_LIBRARY_VERSION_CHECK`](json_skip_library_version_check.md) - skip library version check +- [Supported Macros](../../features/macros.md) - guide to all supported macros +- [Integration](../../integration/index.md) - integrate the library ## Version history diff --git a/docs/mkdocs/docs/api/operator_gtgt.md b/docs/mkdocs/docs/api/operator_gtgt.md index 741183f2f..1263eee20 100644 --- a/docs/mkdocs/docs/api/operator_gtgt.md +++ b/docs/mkdocs/docs/api/operator_gtgt.md @@ -81,7 +81,7 @@ input >> j2; // parses the next value needed. This is tracked in [#5340](https://github.com/nlohmann/json/issues/5340). Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md) -(newline-delimited JSON) input -- see that page for why and for the recommended alternative. +(newline-delimited JSON) input — see that page for why and for the recommended alternative. By default, a `'\0'` (NUL) byte encountered while reading a value is treated as end of input, rather than as an ordinary (and, outside of a string, invalid) byte; see the [FAQ entry](../home/faq.md#nul-bytes-in-the-input) for @@ -116,12 +116,15 @@ being read. ## See also -- [accept](basic_json/accept.md) - check if the input is valid JSON -- [parse](basic_json/parse.md) - deserialize from a compatible input +- [`accept`](basic_json/accept.md) - check if the input is valid JSON +- [`parse`](basic_json/parse.md) - deserialize from a compatible input - [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of treating it as end of input - [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) - opt in to leaving the stream positioned right after a number - [`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) - read the stream one character at a time +- [Parsing](../features/parsing/index.md) - overview of the parsing functions +- [JSON Lines](../features/parsing/json_lines.md) - newline-delimited JSON +- [Large, Streamed, and Partial Input](../features/parsing/large_and_partial_input.md) - chunked, concatenated, and very large input ## Version history diff --git a/docs/mkdocs/docs/api/operator_literal_json.md b/docs/mkdocs/docs/api/operator_literal_json.md index 4e4002aa3..dfb7ca101 100644 --- a/docs/mkdocs/docs/api/operator_literal_json.md +++ b/docs/mkdocs/docs/api/operator_literal_json.md @@ -62,7 +62,7 @@ Linear. ## See also - [Creating JSON values](../features/creating_values.md) - the article on creating JSON values -- [JSON_NO_AUTOMATIC_UDLS](macros/json_no_automatic_udls.md) - do not include the user-defined string literals +- [`JSON_NO_AUTOMATIC_UDLS`](macros/json_no_automatic_udls.md) - do not include the user-defined string literals automatically ## Version history diff --git a/docs/mkdocs/docs/api/operator_literal_json_pointer.md b/docs/mkdocs/docs/api/operator_literal_json_pointer.md index be36b24ed..db3aa1df0 100644 --- a/docs/mkdocs/docs/api/operator_literal_json_pointer.md +++ b/docs/mkdocs/docs/api/operator_literal_json_pointer.md @@ -60,9 +60,10 @@ Linear. ## See also -- [json_pointer](json_pointer/index.md) - type to represent JSON Pointers -- [JSON_NO_AUTOMATIC_UDLS](macros/json_no_automatic_udls.md) - do not include the user-defined string literals +- [`json_pointer`](json_pointer/index.md) - type to represent JSON Pointers +- [`JSON_NO_AUTOMATIC_UDLS`](macros/json_no_automatic_udls.md) - do not include the user-defined string literals automatically +- [JSON Pointer](../features/json_pointer.md) - the article on JSON Pointer support ## Version history diff --git a/docs/mkdocs/docs/api/operator_ltlt.md b/docs/mkdocs/docs/api/operator_ltlt.md index 6bcb160a2..813409abd 100644 --- a/docs/mkdocs/docs/api/operator_ltlt.md +++ b/docs/mkdocs/docs/api/operator_ltlt.md @@ -86,10 +86,11 @@ Linear. ## See also -- [dump](basic_json/dump.md) - serialize to a JSON-formatted string +- [`dump`](basic_json/dump.md) - serialize to a JSON-formatted string - [`basic_json_view::operator<<`](basic_json_view/operator_ltlt.md) - the corresponding operator for `basic_json_view` - [Serialization](../features/serialization.md) - the serialization article +- [`JSON_NO_IO`](macros/json_no_io.md) - remove stream and file I/O ## Version history diff --git a/docs/mkdocs/docs/api/ordered_json.md b/docs/mkdocs/docs/api/ordered_json.md index 30f295fed..5e9776a41 100644 --- a/docs/mkdocs/docs/api/ordered_json.md +++ b/docs/mkdocs/docs/api/ordered_json.md @@ -37,8 +37,8 @@ parsing an object of `n` keys costs O(n²) rather than O(n log n). See ## See also -- [ordered_map](ordered_map.md) -- [Object Order](../features/object_order.md) +- [`ordered_map`](ordered_map.md) - ordered map type used by `ordered_json` +- [Object Order](../features/object_order.md) - preserving the insertion order of object keys ## Version history diff --git a/docs/mkdocs/docs/api/ordered_json_document.md b/docs/mkdocs/docs/api/ordered_json_document.md index 40039774d..23bde281a 100644 --- a/docs/mkdocs/docs/api/ordered_json_document.md +++ b/docs/mkdocs/docs/api/ordered_json_document.md @@ -29,9 +29,10 @@ of object keys, instead of sorting them like [`json_document`](json_document.md) ## See also -- [ordered_json_view](ordered_json_view.md) - a view of a value of an `ordered_json_document` -- [json_document](json_document.md) - the corresponding document for the default `json` specialization -- [Object Order](../features/object_order.md) +- [`ordered_json_view`](ordered_json_view.md) - a view of a value of an `ordered_json_document` +- [`json_document`](json_document.md) - the corresponding document for the default `json` specialization +- [Object Order](../features/object_order.md) - preserving the insertion order of object keys +- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree ## Version history diff --git a/docs/mkdocs/docs/api/ordered_json_editable_document.md b/docs/mkdocs/docs/api/ordered_json_editable_document.md index b47dfc3c2..de0d041ab 100644 --- a/docs/mkdocs/docs/api/ordered_json_editable_document.md +++ b/docs/mkdocs/docs/api/ordered_json_editable_document.md @@ -10,8 +10,8 @@ This type is an **editable** [`basic_json_document`](basic_json_document/index.m [`ordered_json`](ordered_json.md) specialization: [`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) change values after parsing, as for [`json_editable_document`](json_editable_document.md), and -[`materialize()`](basic_json_view/materialize.md) preserves the document order of object members -- including -members [`set`](basic_json_document/set.md) added -- instead of sorting them like +[`materialize()`](basic_json_view/materialize.md) preserves the document order of object members — including +members [`set`](basic_json_document/set.md) added — instead of sorting them like [`json_editable_document`](json_editable_document.md) does. ## Examples @@ -34,13 +34,14 @@ members [`set`](basic_json_document/set.md) added -- instead of sorting them lik ## See also -- [ordered_json_editable_view](ordered_json_editable_view.md) - a view of a value of an +- [`ordered_json_editable_view`](ordered_json_editable_view.md) - a view of a value of an `ordered_json_editable_document` -- [ordered_json_document](ordered_json_document.md) - the read-only document this type adds edits to -- [json_editable_document](json_editable_document.md) - the corresponding editable document for the default `json` +- [`ordered_json_document`](ordered_json_document.md) - the read-only document this type adds edits to +- [`json_editable_document`](json_editable_document.md) - the corresponding editable document for the default `json` specialization -- [Object Order](../features/object_order.md) +- [Object Order](../features/object_order.md) - preserving the insertion order of object keys - [Edits](basic_json_document/index.md#edits) - what an edit guarantees +- [Zero-copy JSON views](../features/json_view.md#editing-a-document) - editing a document in place and writing it back ## Version history diff --git a/docs/mkdocs/docs/api/ordered_json_editable_view.md b/docs/mkdocs/docs/api/ordered_json_editable_view.md index a42ca42f5..e87cb9bb5 100644 --- a/docs/mkdocs/docs/api/ordered_json_editable_view.md +++ b/docs/mkdocs/docs/api/ordered_json_editable_view.md @@ -31,9 +31,10 @@ This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an ## See also -- [ordered_json_editable_document](ordered_json_editable_document.md) - the document type this view refers into -- [ordered_json_view](ordered_json_view.md) - the corresponding read-only view -- [json_editable_view](json_editable_view.md) - the corresponding view for `json_editable_document` +- [`ordered_json_editable_document`](ordered_json_editable_document.md) - the document type this view refers into +- [`ordered_json_view`](ordered_json_view.md) - the corresponding read-only view +- [`json_editable_view`](json_editable_view.md) - the corresponding view for `json_editable_document` +- [Zero-copy JSON views](../features/json_view.md#editing-a-document) - editing a document in place and writing it back ## Version history diff --git a/docs/mkdocs/docs/api/ordered_json_view.md b/docs/mkdocs/docs/api/ordered_json_view.md index 017f2e9a8..7ca5356d6 100644 --- a/docs/mkdocs/docs/api/ordered_json_view.md +++ b/docs/mkdocs/docs/api/ordered_json_view.md @@ -27,8 +27,9 @@ This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an ## See also -- [ordered_json_document](ordered_json_document.md) - the document type this view refers into -- [json_view](json_view.md) - the corresponding view for `json_document` +- [`ordered_json_document`](ordered_json_document.md) - the document type this view refers into +- [`json_view`](json_view.md) - the corresponding view for `json_document` +- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree ## Version history diff --git a/docs/mkdocs/docs/api/ordered_map.md b/docs/mkdocs/docs/api/ordered_map.md index a5e07e60a..efdaa9f7e 100644 --- a/docs/mkdocs/docs/api/ordered_map.md +++ b/docs/mkdocs/docs/api/ordered_map.md @@ -105,7 +105,7 @@ This differs from `#!cpp std::map`, where the same operations are O(log n). If key order matters for objects of that size, consider a container with a lookup index, such as [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) - ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)), as the object type -- see + ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)), as the object type — see [object order](../features/object_order.md). ## Examples @@ -126,7 +126,8 @@ This differs from `#!cpp std::map`, where the same operations are O(log n). ## See also -- [ordered_json](ordered_json.md) +- [`ordered_json`](ordered_json.md) - JSON specialization preserving insertion order +- [Object Order](../features/object_order.md) - the article on object key order ## Version history diff --git a/docs/mkdocs/docs/community/assurance_case.md b/docs/mkdocs/docs/community/assurance_case.md index 54da5c04c..02dc6d7d7 100644 --- a/docs/mkdocs/docs/community/assurance_case.md +++ b/docs/mkdocs/docs/community/assurance_case.md @@ -41,6 +41,8 @@ flowchart LR E --> F[Trusted caller] ``` +

The trust boundary: untrusted input enters on the left through the parser, the SAX interface, or the binary readers, and only a value tree reaches the trusted caller on the right.

+ ## Secure design - **Strict parsing.** The parser accepts exactly the JSON grammar of [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259). diff --git a/docs/mkdocs/docs/community/quality_assurance.md b/docs/mkdocs/docs/community/quality_assurance.md index 6c8046964..61ffdff85 100644 --- a/docs/mkdocs/docs/community/quality_assurance.md +++ b/docs/mkdocs/docs/community/quality_assurance.md @@ -4,6 +4,15 @@ Ensuring quality is paramount for this project, particularly because [numerous o depend on it. Each commit to the library undergoes rigorous checks against the following requirements, and any violations will result in a failed build. +The overview shows the checks a change passes on its way from a push or pull request to a release; the sections below +describe each of them. + +
+--8<-- "images/qa_pipeline.svg" +
+ +

The checks grouped by the stage at which they run, from push or pull request through the merge into develop to a release. The badge on each card says whether the check runs on every push or PR, on a schedule, or continuously.

+ ## C++ language compliance and compiler compatibility !!! success "Requirement: Compiler support" diff --git a/docs/mkdocs/docs/community/roadmap.md b/docs/mkdocs/docs/community/roadmap.md index d294bd5f7..343846466 100644 --- a/docs/mkdocs/docs/community/roadmap.md +++ b/docs/mkdocs/docs/community/roadmap.md @@ -91,6 +91,14 @@ Version 4.0 is therefore the sum of these macros: you can try it on the 3.x rele to its 4.0 value and fixing what no longer compiles or behaves differently. Once your code works with all of them, it is ready for version 4.0. +Each macro has a default value in 3.x and a different value in version 4.0. Define the macro to the 4.0 value to try it on the 3.x release train today; version 4.0 makes that value the default: + +
+--8<-- "images/roadmap_lanes.svg" +
+ +

Each row is one macro: the 3.x release that added it, its default in 3.x, and its value in version 4.0 (highlighted) or "removed". Versions on the 3.x lane are not to scale.

+ The following macros guard changes that are planned to become the default in version 4.0: | Macro | 3.x default | 4.0 behavior | CMake option | Added | @@ -105,6 +113,8 @@ The following macros guard changes that are planned to become the default in ver | [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) | `0` | `1`: a `basic_json` value can no longer be created from a one-element tuple of a reference to it, such as `std::forward_as_tuple(j)`| [`JSON_DisableTupleReferenceConversion`](../integration/cmake.md#json_disabletuplereferenceconversion) | 3.13.0 | | [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) | `0` | removed: the deprecated functions are removed (see below); the `from_*(ptr, len)` overloads stay deleted | [`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) | 3.13.0 | +

Opt-in macros for the version 4.0 behavior. "3.x default" is the value the library uses when the macro is not defined, "Added" is the release that introduced the macro, and – means there is no CMake option.

+ For example, the following makes a 3.x release behave like version 4.0 with respect to these changes: ```cpp @@ -146,6 +156,8 @@ to the `strict` parameter. | Comparing a [`json_pointer`](../api/json_pointer/index.md) with a string via [`operator==`](../api/json_pointer/operator_eq.md) or [`operator!=`](../api/json_pointer/operator_ne.md) | 3.11.2 | [JSON Pointers](../integration/migration_guide.md#json-pointers) | | [`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) with `(ptr, len)` | 3.13.0 | [Parsing](../integration/migration_guide.md#parsing) | +

Deprecated functions, with the release that deprecated each one and the section of the migration guide that shows its replacement.

+ The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above. New breaking changes will follow the same path: they are added to these tables when they land in a 3.x release. diff --git a/docs/mkdocs/docs/css/brand.css b/docs/mkdocs/docs/css/brand.css new file mode 100644 index 000000000..8f181f78b --- /dev/null +++ b/docs/mkdocs/docs/css/brand.css @@ -0,0 +1,803 @@ +/* nlohmann/json brand for Material for MkDocs (values from brand/kit/tokens.css) */ + +@font-face { + font-family: "Inter"; + src: url("fonts/Inter.woff2") format("woff2"); + font-weight: 100 900; + font-display: swap; +} +@font-face { + font-family: "Inter"; + src: url("fonts/Inter-Italic.woff2") format("woff2"); + font-style: italic; + font-weight: 100 900; + font-display: swap; +} +@font-face { + font-family: "JetBrains Mono"; + src: url("fonts/JetBrainsMono.woff2") format("woff2"); + font-weight: 100 800; + font-display: swap; +} +@font-face { + font-family: "JetBrains Mono"; + src: url("fonts/JetBrainsMono-Italic.woff2") format("woff2"); + font-style: italic; + font-weight: 400; + font-display: swap; +} + +:root { + --md-text-font: "Inter"; + --md-code-font: "JetBrains Mono"; +} + +/* light */ +[data-md-color-scheme="default"] { + --nj-blue: #1747a6; + --nj-blue-deep: #10357d; + --nj-blue-soft: #eaf1fd; + --nj-band: #1747a6; + --nj-red: #d93a3f; + --nj-red-soft: #fdeced; + --nj-string: #b4322a; + --nj-comment: #646d7a; + + --md-primary-fg-color: var(--nj-band); + --md-primary-fg-color--light: #1d55c4; + --md-primary-fg-color--dark: #10357d; + --md-accent-fg-color: var(--nj-blue-deep); + --md-accent-fg-color--transparent: rgba(23, 71, 166, .1); + --md-typeset-a-color: var(--nj-blue); + --md-default-fg-color: #0f1419; + --md-default-fg-color--light: #4a515c; + --md-default-fg-color--lighter: #8a919c; + --md-default-fg-color--lightest: #e3e6eb; + --md-typeset-color: #0f1419; + --md-code-bg-color: #f5f7fa; + --md-code-fg-color: #0f1419; +} + +/* dark */ +[data-md-color-scheme="slate"] { + --nj-blue: #7aa7f5; + --nj-blue-deep: #a9c6f8; + --nj-blue-soft: #16233b; + --nj-band: #173e8c; + --nj-red: #f06a6e; + --nj-red-soft: #3a1c1e; + --nj-string: #f08a7e; + --nj-comment: #8b929e; + + --md-hue: 220; + --md-primary-fg-color: var(--nj-band); + --md-primary-fg-color--light: #163f93; + --md-primary-fg-color--dark: #0c2556; + --md-accent-fg-color: var(--nj-blue-deep); + --md-accent-fg-color--transparent: rgba(122, 167, 245, .12); + --md-typeset-a-color: var(--nj-blue); + --md-default-bg-color: #0f1218; + --md-default-fg-color: #f2f4f7; + --md-default-fg-color--light: #b3b9c4; + --md-default-fg-color--lighter: #7b828e; + --md-default-fg-color--lightest: #272c36; + --md-typeset-color: #f2f4f7; + --md-code-bg-color: #171b23; + --md-code-fg-color: #f2f4f7; + --md-footer-bg-color: #0b0e13; + --md-footer-bg-color--dark: #08090d; +} + +/* syntax colours (all at least 4.5:1 on the code panel in both schemes) */ +[data-md-color-scheme="default"] { + --nj-hl-keyword: #1747a6; + --nj-hl-type: #0d6e72; + --nj-hl-function: #6b3fb8; + --nj-hl-string: #23793a; + --nj-hl-escape: #0f5d2a; + --nj-hl-key: #10357d; + --nj-hl-number: #b04a07; + --nj-hl-preproc: #a3247a; +} +[data-md-color-scheme="slate"] { + --nj-hl-keyword: #7aa7f5; + --nj-hl-type: #4fd1c5; + --nj-hl-function: #c4a3ff; + --nj-hl-string: #7ed491; + --nj-hl-escape: #b2ecc0; + --nj-hl-key: #a9c6f8; + --nj-hl-number: #f5a65b; + --nj-hl-preproc: #f48fc8; +} +.md-typeset .highlight :is(.k, .kd, .kn, .kr, .kp, .kv, .ow) { color: var(--nj-hl-keyword); font-weight: 600; } +.md-typeset .highlight :is(.kt, .nc, .nn, .na) { color: var(--nj-hl-type); } +.md-typeset .highlight :is(.nf, .fm, .nb, .bp) { color: var(--nj-hl-function); } +.md-typeset .highlight :is(.s, .s1, .s2, .sb, .sc, .sd, .sh, .sx, .sr, .ss, .sa, .dl, .cpf) { color: var(--nj-hl-string); } +.md-typeset .highlight :is(.se, .si) { color: var(--nj-hl-escape); font-weight: 600; } +.md-typeset .highlight .nt { color: var(--nj-hl-key); } +.md-typeset .highlight :is(.m, .mi, .mf, .mh, .mo, .mb, .il, .kc) { color: var(--nj-hl-number); } +.md-typeset .highlight .kc { font-weight: 500; } +.md-typeset .highlight .cp { color: var(--nj-hl-preproc); } +.md-typeset .highlight :is(.c, .c1, .cm, .ch, .cs) { color: var(--nj-comment); font-style: italic; } +.md-typeset .highlight :is(.o, .p) { color: var(--md-default-fg-color--light); } +.md-typeset .highlight :is(.n, .nx, .nv, .no, .ni, .nl, .py) { color: var(--md-code-fg-color); } +.md-typeset .highlight .err { color: inherit; background: none; } + +/* typography: Inter with cv11/ss03, bold headlines with negative tracking */ +body { + font-feature-settings: "cv11", "ss03"; +} +.md-typeset { + font-size: .85rem; /* 17 px body */ + line-height: 1.65; +} +.md-typeset h1 { + color: var(--md-typeset-color); + font-weight: 800; + letter-spacing: -.035em; + line-height: 1.1; +} +.md-typeset h2 { + font-weight: 800; + letter-spacing: -.025em; + line-height: 1.15; +} +.md-typeset h3 { + font-weight: 700; + letter-spacing: -.015em; +} +.md-typeset table:not([class]) { + font-variant-numeric: tabular-nums; +} + +/* header and tabs: one blue band with the faint syntax pattern; wordmark instead of the site name. + A veil over the tile keeps the glyphs from running through the navigation text; the hero shows the pattern at full strength. */ +.md-header, .md-tabs { + --nj-veil: linear-gradient(color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent), color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent)); + background-color: var(--md-primary-fg-color); + background-image: var(--nj-veil), url("pattern-on-blue.svg"); + background-size: auto, 160px 160px; + background-position: 0 0, 0 0; +} +.md-tabs { + background-position: 0 0, 0 -48px; /* continue the header's tile */ +} +.md-header__button.md-logo img { + width: auto; + height: 1.5rem; +} +.md-header__topic:first-child .md-ellipsis { /* the wordmark replaces the site name at every width */ + display: none; +} +@media screen and (max-width: 76.234375em) { /* Material hides the header logo here: show the wordmark on phones, too */ + .md-header__button.md-logo { + display: block; + margin: 0 0 0 .2rem; + padding: .4rem .2rem; + } + .md-header__button[for="__drawer"] { + order: -1; + } +} +.md-nav__button.md-logo img { + width: auto; + height: 1.4rem; +} + +/* shapes: 6 px corners on blocks, 4 px on inline code */ +.md-typeset pre > code, .md-typeset .highlight, .md-typeset .highlighttable, +.md-typeset .admonition, .md-typeset details, .md-typeset .tabbed-set, +.md-typeset .grid.cards > ul > li, .md-typeset .md-button { + border-radius: 6px; +} +.md-typeset code { + border-radius: 4px; +} + +/* inline code: the font alone marks it, no grey box; linked code keeps the link colour */ +.md-typeset :not(pre) > code { + padding: 0; + background-color: transparent; + box-shadow: none; + color: inherit; + font-size: .88em; +} +.md-typeset .highlight pre > code, .md-typeset .highlighttable { + border: 1px solid var(--md-default-fg-color--lightest); +} + +/* callouts: 3 px left border; note/info/tip in blue on blue-soft, danger/bug in red on red-soft */ +.md-typeset .admonition, .md-typeset details { + border-width: 0 0 0 3px; + box-shadow: none; + font-size: .8rem; +} +.md-typeset :is(.admonition, details):is(.note, .info, .tip, .abstract, .example, .question) { + border-color: var(--nj-blue); + background-color: var(--nj-blue-soft); +} +.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary) { + background-color: transparent; +} +.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary)::before { + background-color: var(--nj-blue); +} +.md-typeset :is(.admonition, details):is(.danger, .bug, .failure) { + border-color: var(--nj-red); + background-color: var(--nj-red-soft); +} +.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary) { + background-color: transparent; +} +.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary)::before { + background-color: var(--nj-red); +} + +/* buttons */ +.md-typeset .md-button--primary { + background-color: var(--nj-blue); + border-color: var(--nj-blue); +} + +/* home page hero (overrides/home.html): blue band with the syntax pattern, headline, lead, buttons, code panel */ +.nj-hero { + --nj-hero-to: #123a8a; + color: #fff; + background-color: var(--md-primary-fg-color); + background-image: url("pattern-on-blue.svg"), linear-gradient(160deg, var(--md-primary-fg-color), var(--nj-hero-to)); + background-size: 160px 160px, auto; + background-position: 0 -96px, 0 0; /* continue the tile of header and tabs */ +} +[data-md-color-scheme="slate"] .nj-hero { + --nj-hero-to: #0c2556; +} +.nj-hero__inner { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); + gap: 2.4rem; + align-items: center; + padding: 3rem .8rem 3.6rem; +} +.nj-hero__kicker { + display: inline-block; + margin: 0; + padding: .15rem .55rem; + border: 1px solid rgba(255, 255, 255, .35); + border-radius: 4px; + color: #bcd3fb; + font-family: var(--md-code-font-family); + font-size: .65rem; /* 13 px */ + font-weight: 500; + letter-spacing: .02em; +} +.nj-hero__title { + margin: 0 0 .8rem; + color: #fff; + font-size: clamp(1.7rem, 4.2vw, 3.2rem); /* 34-64 px */ + font-weight: 800; + line-height: 1.02; + letter-spacing: -.035em; +} +.nj-hero__title span { + color: #bcd3fb; +} +.nj-hero__lead { + max-width: 22em; + margin: 0 0 2rem; + color: rgba(255, 255, 255, .9); + font-size: clamp(1.1rem, 2.2vw, 1.6rem); /* 22-32 px */ + font-style: italic; + font-weight: 500; + line-height: 1.35; + letter-spacing: -.015em; +} +.nj-hero__actions { + display: flex; + flex-wrap: wrap; + gap: .6rem; + margin: 0; +} +.nj-hero .md-button.nj-hero__button { + margin: 0; + padding: .55em 1.3em; + border: 1px solid rgba(255, 255, 255, .6); + border-radius: 6px; + color: #fff; + font-size: .75rem; + font-weight: 600; +} +.nj-hero .md-button--primary.nj-hero__button { + border-color: #fff; + background-color: #fff; + color: var(--md-primary-fg-color); +} +.nj-hero .md-button.nj-hero__button:is(:hover, :focus) { + border-color: #fff; + background-color: rgba(255, 255, 255, .12); + color: #fff; +} +.nj-hero .md-button--primary.nj-hero__button:is(:hover, :focus) { + background-color: #eaf1fd; + color: var(--md-primary-fg-color); +} +.nj-hero__code { + border-radius: 6px; + background-color: var(--md-code-bg-color); + box-shadow: 0 12px 32px rgba(5, 18, 48, .35); +} +.nj-hero__code pre { + margin: 0; + padding: 1.1rem 1.3rem; + overflow-x: auto; + color: var(--md-code-fg-color); + font-family: var(--md-code-font-family); + font-size: .7rem; /* 14 px */ + line-height: 1.7; +} +.nj-hero__code .nj-k { color: var(--nj-hl-type); } +.nj-hero__code .nj-s { color: var(--nj-hl-string); } +.nj-hero__code .nj-n { color: var(--nj-hl-number); } +.nj-hero__code .nj-c { color: var(--nj-comment); font-style: italic; } +@media screen and (max-width: 59.984375em) { + .nj-hero__inner { + grid-template-columns: minmax(0, 1fr); + gap: 1.8rem; + padding: 2rem .8rem 2.4rem; + } +} +@media screen and (max-width: 29.984375em) { + .nj-hero__code pre { + font-size: .6rem; + } +} + +/* the hero carries the headline: keep the page's h1 for the title and screen readers only */ +body:has(.nj-hero) .md-content__inner > h1 { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; +} + +/* cards: 1 px line border, 6 px corners, blue icon on soft blue, blue border on hover */ +.md-typeset .grid.cards > ul > li { + padding: 1rem 1.1rem; + border: 1px solid var(--md-default-fg-color--lightest); + background-color: var(--md-default-bg-color); + transition: border-color 150ms, box-shadow 150ms, transform 150ms; +} +.md-typeset .grid.cards > ul > li:is(:hover, :focus-within) { + border-color: var(--nj-blue); + box-shadow: 0 6px 18px rgba(23, 71, 166, .12); + transform: translateY(-1px); +} +.md-typeset .grid.cards > ul > li > hr { + display: none; +} +.md-typeset .grid.cards .twemoji.lg { + box-sizing: content-box; + margin-right: .5rem; + padding: .3rem; + border-radius: 6px; + background-color: var(--nj-blue-soft); + color: var(--nj-blue); +} +.md-typeset .grid.cards > ul > li > p:first-child strong { + font-size: .9rem; + font-weight: 700; + letter-spacing: -.01em; +} +.md-typeset .grid.cards > ul > li > p:last-child a { + font-weight: 600; +} +@media (prefers-reduced-motion: reduce) { + .md-typeset .grid.cards > ul > li { transition: none; transform: none !important; } +} + +/* home page: no table of contents, so keep the text at reading width and the cards in three columns */ +body:has(.nj-hero) .md-content { + max-width: 58rem; + margin: 0 auto; +} +@media screen and (min-width: 60em) { + body:has(.nj-hero) .md-typeset .grid.cards { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +/* lists: Material's indent, bullets as small blue squares, numbers in the secondary text colour */ +.md-typeset ul:not(.task-list, .md-annotation-list) > li { + position: relative; + list-style: none; +} +.md-typeset ul:not(.task-list, .md-annotation-list) > li::before { + content: ""; + position: absolute; + top: .65em; /* middle of the first line at line-height 1.65 */ + left: -.95em; + width: .36em; + height: .36em; + background-color: var(--nj-blue); +} +.md-typeset ol > li::marker { + color: var(--md-default-fg-color--light); + font-variant-numeric: tabular-nums; +} +.md-typeset .grid.cards > ul > li::before { + display: none; +} + +/* wider page: Material's grid stops at 61rem (1220 px) */ +.md-grid { + max-width: 72rem; /* 1440 px */ +} + +/* tables: full width, 1 px line border with 6 px corners, header row on the panel colour, 15 px text */ +.md-typeset__table { + display: block; /* Material: inline-block, so tables only grow to their content */ +} +.md-typeset table:not([class]) { + display: table; + width: 100%; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 6px; + font-size: .75rem; + line-height: 1.55; +} +.md-typeset table:not([class]) th { + padding: .65em 1em; + border-bottom: 1px solid var(--md-default-fg-color--lightest); + background-color: var(--md-code-bg-color); + color: var(--md-typeset-color); + font-weight: 600; + vertical-align: bottom; +} +.md-typeset table:not([class]) th:first-child { border-top-left-radius: 5px; } +.md-typeset table:not([class]) th:last-child { border-top-right-radius: 5px; } +.md-typeset table:not([class]) td { + padding: .65em 1em; + border-top: 1px solid var(--md-default-fg-color--lightest); +} +.md-typeset table:not([class]) tbody tr:first-child td { + border-top: 0; +} +.md-typeset table:not([class]) tbody tr:hover { + background-color: var(--nj-blue-soft); + box-shadow: none; +} +.md-typeset table:not([class]) td code { + white-space: nowrap; +} +/* numbers and header code stay on one line; a table wider than the screen scrolls sideways instead */ +.md-typeset table:not([class]) :is(td, th):is([align="right"], [style*="right"]), +.md-typeset table:not([class]) th code { + white-space: nowrap; +} +@media screen and (max-width: 44.984375em) { + .md-typeset table:not([class]) { + font-size: .7rem; + } + .md-typeset table:not([class]) :is(th, td) { + padding: .55em .75em; + } +} + +/* more room before headings (Material: h2 1.6em, h3 1.6em, h4-h6 1-1.25em) and at the end of each page (1.2rem) */ +.md-typeset h2 { margin-top: 2.4em; } +.md-typeset h3 { margin-top: 2.2em; } +.md-typeset h2 + h3 { margin-top: .8em; } +.md-typeset :is(h4, h5, h6) { margin-top: 1.8em; } +.md-content__inner { + margin-bottom: 4.8rem; /* 96 px */ +} + +/* Mermaid diagrams: white boxes with a blue outline, grey lines, Inter; shapes and spacing in js/mermaid-brand.js */ +:root > * { + --md-mermaid-font-family: var(--md-text-font-family), sans-serif; + --md-mermaid-edge-color: var(--md-default-fg-color--lighter); + --md-mermaid-node-bg-color: var(--md-default-bg-color); + --md-mermaid-node-fg-color: var(--nj-blue); + --md-mermaid-label-bg-color: var(--md-default-bg-color); + --md-mermaid-label-fg-color: var(--md-default-fg-color); + --md-mermaid-sequence-actor-bg-color: var(--nj-blue-soft); + --md-mermaid-sequence-actor-line-color: var(--md-default-fg-color--lighter); + --md-mermaid-sequence-note-bg-color: var(--md-code-bg-color); + --md-mermaid-sequence-note-border-color: var(--md-default-fg-color--lightest); + --md-mermaid-sequence-number-fg-color: #fff; +} + +/* a diagram wider than the column scrolls sideways (js/mermaid-brand.js stops shrinking it at 80 %) */ +.md-typeset .mermaid { + overflow-x: auto; +} +/* ... and one that is narrower than the column sits in the middle of it */ +.md-typeset .mermaid { + text-align: center; +} + +/* external links (marked by js/external-links.js) get a small "open in new window" icon (Font Awesome + "external-link-alt") in front of them */ +.md-typeset a.nj-external::before { + content: ""; + display: inline-block; + width: .72em; + height: .72em; + margin-right: .3em; + background-color: currentColor; + opacity: .65; + -webkit-mask: var(--nj-external-icon) no-repeat center / contain; + mask: var(--nj-external-icon) no-repeat center / contain; +} +:root { + --nj-external-icon: url('data:image/svg+xml;utf8,'); +} + + +/* ---- polish pass ---- */ + +/* header: a search field that reads as a field on the band, tabs with an active marker, readable GitHub facts */ +.md-header .md-search__form { + border: 1px solid rgba(255, 255, 255, .22); + border-radius: 6px; + background-color: rgba(255, 255, 255, .12); + transition: background-color 150ms, border-color 150ms; +} +.md-header .md-search__form:hover { + border-color: rgba(255, 255, 255, .4); + background-color: rgba(255, 255, 255, .2); +} +.md-search__output { + border-radius: 0 0 6px 6px; + box-shadow: 0 16px 40px rgba(5, 18, 48, .28); +} +.md-search-result__link:focus-visible, .md-search-result__more summary:focus-visible { + outline: 2px solid var(--nj-blue); + outline-offset: -2px; +} +.md-tabs__link { + font-size: .72rem; + font-weight: 500; + opacity: .85; +} +.md-tabs__item--active .md-tabs__link, .md-tabs__link:hover { + opacity: 1; +} +.md-tabs__item--active { + box-shadow: inset 0 -2px 0 #fff; +} +.md-source__facts { + font-size: .62rem; +} + +/* footer: deep brand blue instead of neutral black (the dark scheme keeps its own near-black) */ +[data-md-color-scheme="default"] { + --md-footer-bg-color: #0d2a66; + --md-footer-bg-color--dark: #0a2152; +} +.md-footer-meta .md-copyright, .md-footer-meta .md-copyright a { + color: rgba(255, 255, 255, .72); +} +.md-footer__link { + border-radius: 6px; + transition: background-color 150ms; +} +.md-footer__link:is(:hover, :focus-visible) { + background-color: rgba(255, 255, 255, .08); +} + +/* page title: tighter to the first paragraph; page actions (view/edit source) stay in the background */ +.md-typeset h1 { + margin-bottom: 1rem; +} +.md-content__button { + opacity: .55; + transition: opacity 150ms; +} +.md-content__button:is(:hover, :focus-visible) { + opacity: 1; +} +body:has(.nj-hero) .md-content__button { + display: none; +} + +/* sidebars: bold marker for the current page, rail and marker for the table of contents */ +.md-nav__link--active { + font-weight: 600; +} +.md-nav--secondary > .md-nav__list { + margin-left: -.1rem; + border-left: 1px solid color-mix(in srgb, var(--md-default-fg-color) 16%, transparent); +} +.md-nav--secondary .md-nav__link { + margin-left: -1px; + padding-left: .6rem; + border-left: 2px solid transparent; +} +.md-nav--secondary .md-nav__link--active { + border-left-color: var(--nj-blue); + color: var(--nj-blue); +} +.md-nav--secondary .md-nav__list .md-nav__list { + padding-left: .6rem; +} +.md-nav--secondary .md-nav__title { + font-size: .64rem; + letter-spacing: .04em; + text-transform: uppercase; +} + +/* keyboard focus: one visible ring in the brand blue */ +.md-typeset a:focus-visible, .md-typeset .md-button:focus-visible, .md-typeset summary:focus-visible, +.md-nav__link:focus-visible, .md-tabs__link:focus-visible, .md-top:focus-visible { + outline: 2px solid var(--nj-blue); + outline-offset: 2px; + border-radius: 3px; +} +.md-header :is(a, button, label):focus-visible, .nj-hero a:focus-visible { + outline: 2px solid #fff; + outline-offset: 2px; +} + +/* inline code must not stretch the line box (JetBrains Mono has tall metrics), abbreviations stay quiet */ +.md-typeset :not(pre) > code { + line-height: 1; +} +.md-typeset abbr { + border-bottom-color: var(--md-default-fg-color--lighter); +} +.md-typeset :is(h1, h2, h3, h4, a, .grid.cards > ul > li > p:first-child) abbr { + border-bottom-color: transparent; +} + +/* number railroad diagram: inline SVG (docs/images/json_syntax_number.svg), follows the colour scheme */ +.md-typeset .railroad-wrap, .md-typeset .nj-fig-wrap { + overflow-x: auto; + margin: 1.5rem 0; +} +.md-typeset svg.railroad { + display: block; + width: 100%; + min-width: 36rem; + max-width: 52rem; + margin-inline: auto; + color: var(--md-default-fg-color); + font-family: var(--md-text-font-family), sans-serif; +} +.railroad .rr-t { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, transparent); stroke: var(--md-typeset-a-color); stroke-width: 2; } +.railroad .rr-track, .railroad .rr-n { stroke: color-mix(in srgb, currentColor 60%, var(--md-default-bg-color)); stroke-opacity: 1; } /* opaque, so overlapping segments do not darken */ +.railroad .rr-n { fill: color-mix(in srgb, currentColor 6%, transparent); stroke-width: 2; } +.railroad .rr-bg { fill: var(--md-default-bg-color); } +.railroad .rr-arrow { fill: currentColor; fill-opacity: .6; } +.railroad text { fill: currentColor; text-anchor: middle; } +.railroad .rr-c { font-family: var(--md-code-font-family), monospace; font-size: 16px; font-weight: 500; } +.railroad .rr-b { font-size: 14.5px; font-weight: 500; } +.railroad .rr-l { font-size: 14px; text-anchor: start; fill-opacity: .6; } +.railroad .rr-name { font-size: 17px; font-weight: 700; fill-opacity: 1; } +/* figures drawn as inline SVG (docs/images/callback_events.svg, range-*.svg): they follow the colour scheme */ +.md-typeset svg.nj-fig { + display: block; + width: 100%; + height: auto; + margin-inline: auto; + color: var(--md-default-fg-color); + font-family: var(--md-text-font-family), sans-serif; +} +.md-typeset svg.nj-fig--range { min-width: 30rem; max-width: 40rem; } +.md-typeset svg.nj-fig--cb { max-width: 30rem; } +.nj-fig { --fg-line: color-mix(in srgb, currentColor 55%, var(--md-default-bg-color)); } +.nj-fig .fg-cell { fill: color-mix(in srgb, var(--md-typeset-a-color) 14%, var(--md-default-bg-color)); stroke: var(--fg-line); stroke-width: 1.5; } +.nj-fig .fg-cell--end { fill: none; stroke-dasharray: 4 3; } +.nj-fig .fg-pill { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, var(--md-default-bg-color)); stroke: var(--md-typeset-a-color); stroke-width: 1.5; } +.nj-fig .fg-pilltext { fill: var(--md-typeset-a-color); font-family: var(--md-code-font-family), monospace; font-size: 14px; font-weight: 500; text-anchor: middle; } +.nj-fig .fg-arrow { stroke: var(--fg-line); stroke-width: 1.5; fill: none; } +.nj-fig .fg-arrow--dash { stroke-dasharray: 3 3; } +.nj-fig .fg-head { fill: var(--fg-line); } +.nj-fig .fg-note { fill: currentColor; fill-opacity: .7; font-size: 13.5px; text-anchor: middle; } +.nj-fig .fg-note--r { text-anchor: end; } +.nj-fig .fg-note--l { text-anchor: start; } +.nj-fig .fg-h { fill: currentColor; fill-opacity: .7; font-size: 15px; font-weight: 600; } +.nj-fig .fg-code { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 16px; white-space: pre; } +.nj-fig .fg-leg { font-size: 15px; } +.nj-fig { --cb-os: #4eb05a; --cb-oe: #1a6b34; --cb-k: #e5534b; --cb-as: #f0c000; --cb-ae: #b07d00; --cb-v: #1f6fd1; } +[data-md-color-scheme="slate"] .nj-fig { --cb-os: #7ee787; --cb-oe: #2ea043; --cb-k: #ff7b72; --cb-as: #f2cc60; --cb-ae: #c69026; --cb-v: #58a6ff; } +.nj-fig .cb-os { fill: var(--cb-os); } +.nj-fig .cb-oe { fill: var(--cb-oe); } +.nj-fig .cb-k { fill: var(--cb-k); } +.nj-fig .cb-as { fill: var(--cb-as); } +.nj-fig .cb-ae { fill: var(--cb-ae); } +.nj-fig .cb-v { fill: var(--cb-v); } +.md-typeset img[src*="json-light"], .md-typeset img[src*="json-dark"] { + border-radius: 8px; +} +.md-typeset img { + max-width: 100%; + height: auto; +} + +/* home page: content uses the same column as every other page */ +body:has(.nj-hero) .md-content { + max-width: none; + margin: 0; +} +.md-typeset .grid.cards { + width: auto; +} + +/* 404 page (overrides/404.html) */ +.nj-404 { + max-width: 30rem; + padding: 2rem 0 3rem; +} +.nj-404__code { + margin: 0 0 .4rem; + color: var(--nj-blue); + font-family: var(--md-code-font-family); + font-size: .8rem; + font-weight: 500; +} +.md-typeset .nj-404 h1 { + margin-bottom: .8rem; +} +.nj-404 .md-button { + margin: .4rem .4rem 0 0; +} + +/* touch targets and phones */ +@media screen and (max-width: 59.984375em) { + .md-nav__link { + padding-block: .2rem; + } +} +@media screen and (max-width: 29.984375em) { + .md-typeset h1 { + font-size: 1.7em; + } + .md-typeset .highlight pre > code { + font-size: .68rem; + } +} + +/* figure captions: "Figure N." in bold, close to the figure, set in from the text column; figures and tables are + numbered per page, each with its own counter */ +.md-content__inner { counter-reset: nj-figure nj-table; } +.md-typeset :is(.nj-caption, .nj-charts-caption) { + max-width: none; + margin: .3rem 2rem 1.5rem; + font-size: .75rem; + line-height: 1.5; + text-align: left; + color: var(--md-default-fg-color--light); +} +.md-typeset :is(.nj-fig-wrap, .railroad-wrap, .nj-charts-grid-wrap) + :is(.nj-caption, .nj-charts-caption) { margin-top: -1.1rem; } +.md-typeset .nj-charts-grid-wrap + :is(.nj-caption, .nj-charts-caption) { margin-top: .3rem; } +.md-typeset :is(.nj-caption, .nj-charts-caption)::before { + counter-increment: nj-figure; + content: "Figure " counter(nj-figure) ". "; + font-weight: 700; + color: var(--md-default-fg-color); +} +@media screen and (max-width: 44.9375em) { + .md-typeset :is(.nj-caption, .nj-charts-caption) { margin-inline: 1rem; } +} + +/* table captions: "Table N." in bold, directly below the table, in the figure caption style */ +.md-typeset .nj-table-caption { + max-width: none; + margin: .3rem 2rem 1.5rem; + font-size: .75rem; + line-height: 1.5; + text-align: left; + color: var(--md-default-fg-color--light); +} +.md-typeset :is(.md-typeset__scrollwrap, .md-typeset__table) + .nj-table-caption { margin-top: -.4rem; } +.md-typeset .nj-table-caption::before { + counter-increment: nj-table; + content: "Table " counter(nj-table) ". "; + font-weight: 700; + color: var(--md-default-fg-color); +} +@media screen and (max-width: 44.9375em) { + .md-typeset .nj-table-caption { margin-inline: 1rem; } +} diff --git a/docs/mkdocs/docs/css/fig-charts.css b/docs/mkdocs/docs/css/fig-charts.css new file mode 100644 index 000000000..742a70bb7 --- /dev/null +++ b/docs/mkdocs/docs/css/fig-charts.css @@ -0,0 +1,142 @@ +/* figures: charts (docs/images/compile_times.svg, binary_sizes_*.svg, ordered_json_scaling.svg from + scripts/figures/charts.py) and the number literal explorer on features/types/number_handling.md */ + +/* series colours: categorical slots validated for CVD separation on the light and the slate surface */ +.nj-charts, .nj-charts-key { + --nj-charts-c1: #2a78d6; + --nj-charts-c2: #eb6834; + --nj-charts-c3: #1baf7a; + --nj-charts-c4: #eda100; + --nj-charts-c5: #e87ba4; + --nj-charts-c6: #008300; + --nj-charts-ink2: var(--md-default-fg-color--light); + --nj-charts-grid: color-mix(in srgb, currentColor 12%, var(--md-default-bg-color)); + --nj-charts-base: color-mix(in srgb, currentColor 35%, var(--md-default-bg-color)); +} +[data-md-color-scheme="slate"] :is(.nj-charts, .nj-charts-key) { + --nj-charts-c1: #3987e5; + --nj-charts-c2: #d95926; + --nj-charts-c3: #199e70; + --nj-charts-c4: #c98500; + --nj-charts-c5: #d55181; + --nj-charts-c6: #008300; +} +/* concepts: two compilers, two object types */ +.nj-charts { --nj-charts-clang-c: var(--nj-charts-c1); --nj-charts-gcc-c: var(--nj-charts-c2); + --nj-charts-json-c: var(--nj-charts-c1); --nj-charts-ojson-c: var(--nj-charts-c2); } + +.md-typeset svg.nj-charts--compile, .md-typeset svg.nj-charts--scaling { max-width: 26rem; } +.md-typeset svg.nj-charts--sizes { max-width: 26rem; } + +.nj-charts text { fill: currentColor; font-size: 13px; } +.nj-charts .nj-charts-leg { font-size: 13.5px; } +.nj-charts .nj-charts-rowlabel { font-size: 13.5px; font-weight: 500; } +.nj-charts .nj-charts-code { font-family: var(--md-code-font-family), monospace; font-size: 12.5px; } +.nj-charts .nj-charts-sub { fill: var(--nj-charts-ink2); font-weight: 400; } +.nj-charts .nj-charts-fmt { text-anchor: end; font-weight: 400; } +.nj-charts .nj-charts-best { font-weight: 700; } +.nj-charts .nj-charts-panel { font-family: var(--md-code-font-family), monospace; font-size: 14px; font-weight: 600; } +.nj-charts .nj-charts-tick { fill: var(--nj-charts-ink2); font-size: 12px; text-anchor: middle; font-variant-numeric: tabular-nums; } +.nj-charts .nj-charts-tick--l { text-anchor: start; } +.nj-charts .nj-charts-tick--r { text-anchor: end; } +.nj-charts .nj-charts-val { font-size: 12px; font-variant-numeric: tabular-nums; + paint-order: stroke; stroke: var(--md-default-bg-color); stroke-width: 4px; stroke-linejoin: round; } +.nj-charts .nj-charts-factor { fill: var(--nj-charts-ink2); font-size: 12px; font-weight: 600; text-anchor: middle; } +.nj-charts .nj-charts-note { fill: var(--nj-charts-ink2); font-size: 12px; } + +.nj-charts .nj-charts-grid { stroke: var(--nj-charts-grid); stroke-width: 1; } +.nj-charts .nj-charts-zero, .nj-charts .nj-charts-axis { stroke: var(--nj-charts-base); stroke-width: 1.5; } +.nj-charts .nj-charts-ref { stroke: currentColor; stroke-width: 1.5; } + +.nj-charts .nj-charts-c1 { fill: var(--nj-charts-c1); } +.nj-charts .nj-charts-c2 { fill: var(--nj-charts-c2); } +.nj-charts .nj-charts-c3 { fill: var(--nj-charts-c3); } +.nj-charts .nj-charts-c4 { fill: var(--nj-charts-c4); } +.nj-charts .nj-charts-c5 { fill: var(--nj-charts-c5); } +.nj-charts .nj-charts-c6 { fill: var(--nj-charts-c6); } +.nj-charts .nj-charts-dim { fill-opacity: .45; } +.nj-charts .nj-charts-clang { fill: var(--nj-charts-clang-c); } +.nj-charts .nj-charts-gcc { fill: var(--nj-charts-gcc-c); } +.nj-charts polyline.nj-charts-json, .nj-charts line.nj-charts-json { stroke: var(--nj-charts-json-c); stroke-width: 2; fill: none; } +.nj-charts polyline.nj-charts-ojson, .nj-charts line.nj-charts-ojson { stroke: var(--nj-charts-ojson-c); stroke-width: 2; fill: none; } +.nj-charts .nj-charts-json-dot { fill: var(--nj-charts-json-c); stroke: var(--md-default-bg-color); stroke-width: 2; } +.nj-charts .nj-charts-ojson-dot { fill: var(--nj-charts-ojson-c); stroke: var(--md-default-bg-color); stroke-width: 2; } + +/* single wide charts: larger text on phones, where the SVG is scaled down */ +@media screen and (max-width: 44.9375em) { + svg.nj-charts.nj-charts--compile text, svg.nj-charts.nj-charts--scaling text { font-size: 15px; } + svg.nj-charts.nj-charts--compile .nj-charts-code { font-size: 14px; } + svg.nj-charts:is(.nj-charts--compile, .nj-charts--scaling) :is(.nj-charts-tick, .nj-charts-val, .nj-charts-factor, .nj-charts-note) { font-size: 14px; } +} + +/* small multiples: two panels per row, one on narrow screens */ +.md-typeset .nj-charts-grid-wrap { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); + gap: 1rem 2rem; + margin: 1.5rem 0 .5rem; +} +.md-typeset .nj-charts-grid-wrap > .nj-fig-wrap { margin: 0; } + +/* number literal explorer */ +.md-typeset .nj-charts-num-ui { + margin: 1rem 0 .75rem; + padding: .8rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 8px; +} +.md-typeset .nj-charts-num-ui label { display: block; font-weight: 600; font-size: .75rem; margin-bottom: .3rem; } +.md-typeset .nj-charts-num-input { + width: 100%; + box-sizing: border-box; + min-height: 44px; + padding: .4rem .6rem; + font-family: var(--md-code-font-family), monospace; + font-size: .8rem; + color: var(--md-default-fg-color); + background: var(--md-code-bg-color); + border: 1px solid var(--md-default-fg-color--lighter); + border-radius: 6px; +} +.md-typeset .nj-charts-num-input:focus-visible, .md-typeset .nj-charts-num-chip:focus-visible { + outline: 2px solid var(--md-typeset-a-color); + outline-offset: 2px; +} +.md-typeset .nj-charts-num-ui ul.nj-charts-num-chips:not([hidden]) { display: flex; flex-wrap: wrap; gap: .35rem; margin: .6rem 0; padding: 0; list-style: none; } +.md-typeset .nj-charts-num-ui ul.nj-charts-num-chips > li { margin: 0; padding: 0; list-style: none; } +.md-typeset .nj-charts-num-ui ul.nj-charts-num-chips > li::before, .md-typeset .nj-charts-num-ui ul.nj-charts-num-chips > li::marker { content: none; } +.md-typeset .nj-charts-num-chip { + min-height: 44px; + max-width: 100%; + padding: .25rem .6rem; + overflow-wrap: anywhere; + font-family: var(--md-code-font-family), monospace; + font-size: .7rem; + color: var(--md-typeset-a-color); + background: color-mix(in srgb, var(--md-typeset-a-color) 8%, var(--md-default-bg-color)); + border: 1px solid color-mix(in srgb, var(--md-typeset-a-color) 35%, var(--md-default-bg-color)); + border-radius: 6px; + cursor: pointer; + text-align: left; +} +.md-typeset .nj-charts-num-chip[aria-pressed="true"] { + color: var(--md-default-bg-color); + background: var(--md-typeset-a-color); + border-color: var(--md-typeset-a-color); +} +.md-typeset .nj-charts-num-out { + display: grid; + grid-template-columns: max-content 1fr; + gap: .2rem .8rem; + margin: 0; + font-size: .75rem; + overflow-wrap: anywhere; +} +.md-typeset .nj-charts-num-out dt { color: var(--md-default-fg-color--light); margin: 0; } +.md-typeset .nj-charts-num-out dd { margin: 0; } +.md-typeset .nj-charts-num-miss { font-size: .75rem; color: var(--md-default-fg-color--light); margin: 0; } +.md-typeset .nj-charts-num tr.nj-charts-num-hit td { background: var(--md-accent-fg-color--transparent); } + +/* direct line labels replace the legend */ +.nj-charts .nj-charts-name-json { fill: var(--nj-charts-json-c); font-weight: 600; } +.nj-charts .nj-charts-name-ojson { fill: var(--nj-charts-ojson-c); font-weight: 600; } diff --git a/docs/mkdocs/docs/css/fig-exceptions.css b/docs/mkdocs/docs/css/fig-exceptions.css new file mode 100644 index 000000000..a3562f732 --- /dev/null +++ b/docs/mkdocs/docs/css/fig-exceptions.css @@ -0,0 +1,75 @@ +/* figures: exceptions (home/exceptions.md exception map, community/quality_assurance.md pipeline) */ + +/* the generated SVGs hold a wide and a narrow drawing; show one of them */ +.md-typeset svg.nj-exceptions-map--wide, .md-typeset svg.nj-exceptions-qa--wide { max-width: 47.5rem; } +.md-typeset svg.nj-exceptions-map--narrow, .md-typeset svg.nj-exceptions-qa--narrow { display: none; max-width: 24rem; } +@media screen and (max-width: 37.5em) { + .md-typeset svg.nj-exceptions-map--wide, .md-typeset svg.nj-exceptions-qa--wide { display: none; } + .md-typeset svg.nj-exceptions-map--narrow, .md-typeset svg.nj-exceptions-qa--narrow { display: block; } +} + +/* shared */ +.nj-fig { --nj-exceptions-line: color-mix(in srgb, currentColor 45%, var(--md-default-bg-color)); } +.nj-fig .nj-exceptions-edge { stroke: var(--nj-exceptions-line); stroke-width: 1.5; fill: none; } +.nj-fig .nj-exceptions-tri { fill: var(--md-default-bg-color); stroke: var(--nj-exceptions-line); stroke-width: 1.3; } +.nj-fig .nj-exceptions-box { + fill: color-mix(in srgb, currentColor 4%, var(--md-default-bg-color)); + stroke: color-mix(in srgb, currentColor 30%, var(--md-default-bg-color)); + stroke-width: 1.5; +} +.nj-fig .nj-exceptions-box--fam { + fill: color-mix(in srgb, var(--md-typeset-a-color) 9%, var(--md-default-bg-color)); + stroke: color-mix(in srgb, var(--md-typeset-a-color) 55%, var(--md-default-bg-color)); +} +.nj-fig .nj-exceptions-name { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 14px; font-weight: 600; } +.nj-fig .nj-exceptions-sub { fill: currentColor; fill-opacity: .68; font-size: 12.5px; } + +/* exception map: id chips are links */ +.nj-fig .nj-exceptions-chip rect { + fill: color-mix(in srgb, var(--md-typeset-a-color) 7%, var(--md-default-bg-color)); + stroke: color-mix(in srgb, var(--md-typeset-a-color) 40%, var(--md-default-bg-color)); + stroke-width: 1.2; + transition: fill .15s; +} +.nj-fig .nj-exceptions-chip--named rect { + fill: color-mix(in srgb, var(--md-typeset-a-color) 16%, var(--md-default-bg-color)); + stroke: var(--md-typeset-a-color); +} +.nj-fig .nj-exceptions-id { fill: var(--md-typeset-a-color); font-family: var(--md-code-font-family), monospace; font-size: 13px; font-weight: 600; } +.nj-fig .nj-exceptions-label { fill: currentColor; font-size: 12.5px; font-weight: 500; } +.nj-fig a:hover rect, .nj-fig a:focus-visible rect { + fill: color-mix(in srgb, var(--md-typeset-a-color) 28%, var(--md-default-bg-color)); +} +.nj-fig a:focus { outline: none; } +.nj-fig a:focus-visible rect { stroke: var(--md-typeset-a-color); stroke-width: 2.5; } +.nj-fig a:hover .nj-exceptions-name, .nj-fig a:focus-visible .nj-exceptions-name { text-decoration: underline; } +@media (prefers-reduced-motion: reduce) { + .nj-fig .nj-exceptions-chip rect { transition: none; } +} + +/* quality assurance pipeline */ +.nj-fig { --nj-exceptions-push: var(--md-typeset-a-color); --nj-exceptions-sched: #a86400; --nj-exceptions-cont: #1a7f37; } +[data-md-color-scheme="slate"] .nj-fig { --nj-exceptions-sched: #e3a33b; --nj-exceptions-cont: #56c46e; } +.nj-fig .nj-exceptions-groupbox { + fill: color-mix(in srgb, currentColor 2.5%, var(--md-default-bg-color)); + stroke: color-mix(in srgb, currentColor 22%, var(--md-default-bg-color)); + stroke-width: 1.2; + stroke-dasharray: 5 4; +} +.nj-fig .nj-exceptions-group { fill: currentColor; fill-opacity: .68; font-size: 13px; font-weight: 600; letter-spacing: .02em; } +.nj-fig .nj-exceptions-card .nj-exceptions-box { fill: var(--md-default-bg-color); } +.nj-fig .nj-exceptions-ctitle { fill: currentColor; font-size: 15px; font-weight: 700; } +.nj-fig .nj-exceptions-line { fill: currentColor; fill-opacity: .8; font-size: 12.5px; } +.nj-fig .nj-exceptions-badge { stroke-width: 1.2; } +.nj-fig .nj-exceptions-badgetext { font-size: 11px; font-weight: 600; text-anchor: middle; } +.nj-fig .nj-exceptions-badge--push { fill: color-mix(in srgb, var(--nj-exceptions-push) 12%, var(--md-default-bg-color)); stroke: var(--nj-exceptions-push); } +.nj-fig .nj-exceptions-badge--sched { fill: color-mix(in srgb, var(--nj-exceptions-sched) 12%, var(--md-default-bg-color)); stroke: var(--nj-exceptions-sched); } +.nj-fig .nj-exceptions-badge--cont { fill: color-mix(in srgb, var(--nj-exceptions-cont) 12%, var(--md-default-bg-color)); stroke: var(--nj-exceptions-cont); } +.nj-fig .nj-exceptions-badgetext--push { fill: var(--nj-exceptions-push); } +.nj-fig .nj-exceptions-badgetext--sched { fill: var(--nj-exceptions-sched); } +.nj-fig .nj-exceptions-badgetext--cont { fill: var(--nj-exceptions-cont); } +.nj-fig .nj-exceptions-stage { fill: var(--md-typeset-a-color); } +.nj-fig .nj-exceptions-stagetext { fill: #fff; font-size: 14px; font-weight: 700; text-anchor: middle; } +.nj-fig .nj-exceptions-stagesub { fill: #fff; fill-opacity: .85; font-size: 12px; text-anchor: middle; } +[data-md-color-scheme="slate"] .nj-fig .nj-exceptions-stage { fill: #1747a6; } +.nj-fig .nj-exceptions-qa .fg-head, .nj-exceptions-qa .fg-head { fill: var(--nj-exceptions-line); } diff --git a/docs/mkdocs/docs/css/fig-integration.css b/docs/mkdocs/docs/css/fig-integration.css new file mode 100644 index 000000000..da588e93c --- /dev/null +++ b/docs/mkdocs/docs/css/fig-integration.css @@ -0,0 +1,176 @@ +/* figures: integration */ + +/* namespace name builder (features/namespace.md, js/fig-integration.js) */ +.md-typeset .nj-integration-ns { + --nj-int-line: color-mix(in srgb, currentColor 30%, var(--md-default-bg-color)); + display: block; + width: auto; + max-width: none; + margin: 1.5rem 0; + padding: .9rem 1rem; + text-align: left; + border: 1px solid var(--nj-int-line); + border-radius: 8px; +} +.md-typeset .nj-integration-ns__strip { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: .5rem .15rem; +} +.md-typeset .nj-integration-ns__seg { + display: inline-flex; + flex-direction: column; + align-items: center; + min-width: 0; +} +.md-typeset .nj-integration-ns__seg code { + padding: .25em .5em; + border: 1.5px solid var(--nj-int-line); + border-radius: 6px; + background: var(--md-default-bg-color); + color: var(--md-default-fg-color); + font-size: .8rem; + font-weight: 600; + word-break: normal; +} +.md-typeset .nj-integration-ns__seg small { + max-width: 10rem; + margin-top: .25rem; + color: var(--md-default-fg-color--light); + font-size: .58rem; + line-height: 1.25; + text-align: center; + overflow-wrap: anywhere; +} +.md-typeset .nj-integration-ns__seg--sep code { + border-color: transparent; + padding-inline: .1em; +} +.md-typeset .nj-integration-ns__seg--abi code { + border-color: var(--md-typeset-a-color); + background: color-mix(in srgb, var(--md-typeset-a-color) 12%, var(--md-default-bg-color)); + color: var(--md-typeset-a-color); +} +.md-typeset .nj-integration-ns__seg--tag code { + border-style: dashed; + border-color: var(--md-typeset-a-color); + color: var(--md-typeset-a-color); +} +.md-typeset .nj-integration-ns__seg--ver code { + background: color-mix(in srgb, currentColor 8%, var(--md-default-bg-color)); +} +.md-typeset .nj-integration-ns figcaption { + max-width: none; + margin: .8rem 0 0; + font-size: .75rem; + text-align: left; + color: var(--md-default-fg-color--light); +} +.md-typeset .nj-integration-ns__controls { + margin-top: 1rem; + padding-top: .8rem; + border-top: 1px solid var(--nj-int-line); + font-size: .7rem; +} +.md-typeset .nj-integration-ns__version select { + min-height: 2rem; + margin-left: .3rem; + padding: 0 .4rem; + border: 1px solid var(--nj-int-line); + border-radius: 6px; + background: var(--md-default-bg-color); + color: var(--md-default-fg-color); + font: inherit; +} +.md-typeset .nj-integration-ns__list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(100%, 21rem), 1fr)); + gap: 0 1rem; + margin: .7rem 0 0; + padding: 0; + border: 0; +} +.md-typeset .nj-integration-ns__list legend { + padding: 0; + margin-bottom: .3rem; + font-weight: 600; +} +.md-typeset .nj-integration-ns__opt { + display: grid; + grid-template-columns: 1.1rem 1fr; + align-items: center; + gap: 0 .55rem; + min-height: 2.2rem; + padding: .15rem 0; + cursor: pointer; +} +.md-typeset .nj-integration-ns__meta { + grid-column: 2; + display: flex; + flex-wrap: wrap; + gap: 0 .45rem; +} +.md-typeset .nj-integration-ns__opt input { + grid-row: 1 / span 2; + width: 1.1rem; + height: 1.1rem; + margin: 0; + accent-color: var(--md-typeset-a-color); +} +.md-typeset .nj-integration-ns__opt input:focus-visible { + outline: 2px solid var(--md-accent-fg-color); + outline-offset: 2px; +} +.md-typeset .nj-integration-ns__opt code { + font-size: .64rem; + overflow-wrap: anywhere; +} +.md-typeset .nj-integration-ns__hint { + color: var(--md-typeset-a-color); + font-family: var(--md-code-font-family); + font-size: .64rem; +} +.md-typeset .nj-integration-ns__since { + color: var(--md-default-fg-color--light); + font-size: .6rem; +} +.md-typeset .nj-integration-ns__opt.is-unavailable { + cursor: not-allowed; + opacity: .5; +} +@media screen and (max-width: 44.984375em) { + .md-typeset .nj-integration-ns__opt { + min-height: 2.75rem; + } +} + +/* roadmap lanes and release timeline (scripts/figures/integration.py, release_timeline.py) */ +.md-typeset svg.nj-fig--lanes { min-width: 44rem; max-width: 46rem; } +.md-typeset svg.nj-fig--timeline { min-width: 44rem; max-width: 52rem; } +.nj-fig .nj-int-lane { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, var(--md-default-bg-color)); stroke: var(--md-typeset-a-color); stroke-width: 1.5; } +.nj-fig .nj-int-lane--future { fill: none; stroke-dasharray: 6 4; } +.nj-fig .nj-int-lanetext { fill: var(--md-typeset-a-color); font-size: 15px; font-weight: 700; } +.nj-fig .nj-int-text { fill: currentColor; font-size: 13px; } +.nj-fig .nj-int-mute { fill: currentColor; fill-opacity: .7; font-size: 12.5px; } +.nj-fig .nj-int-macro { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 12.5px; } +.nj-fig .nj-int-val { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 13px; font-weight: 600; } +.nj-fig .nj-int-row { fill: color-mix(in srgb, currentColor 4%, var(--md-default-bg-color)); } +.nj-fig .nj-int-track { fill: color-mix(in srgb, currentColor 14%, var(--md-default-bg-color)); stroke: color-mix(in srgb, currentColor 45%, var(--md-default-bg-color)); stroke-width: 1.2; } +.nj-fig .nj-int-track--on { fill: var(--md-typeset-a-color); stroke: var(--md-typeset-a-color); } +.nj-fig .nj-int-knob { fill: var(--md-default-bg-color); stroke: color-mix(in srgb, currentColor 45%, var(--md-default-bg-color)); stroke-width: 1.2; } +.nj-fig .nj-int-knob--on { stroke: var(--md-typeset-a-color); } +.nj-fig .nj-int-axis { stroke: color-mix(in srgb, currentColor 45%, var(--md-default-bg-color)); stroke-width: 1.5; } +.nj-fig .nj-int-grid { stroke: color-mix(in srgb, currentColor 14%, var(--md-default-bg-color)); stroke-width: 1; } +.nj-fig .nj-int-minor { fill: var(--md-typeset-a-color); stroke: var(--md-default-bg-color); stroke-width: 1.5; } +.nj-fig .nj-int-patch { fill: var(--md-default-bg-color); stroke: var(--md-typeset-a-color); stroke-width: 1.5; } +.nj-fig .nj-int-major { fill: currentColor; fill-opacity: .7; font-size: 13px; font-weight: 600; } +.nj-fig .nj-int-label { fill: currentColor; font-size: 12px; font-weight: 600; text-anchor: middle; } +.nj-fig .nj-int-year { fill: currentColor; fill-opacity: .7; font-size: 12px; text-anchor: middle; } +.nj-fig .nj-int-leader { stroke: color-mix(in srgb, currentColor 30%, var(--md-default-bg-color)); stroke-width: 1; } +.nj-fig .nj-int-hit { fill: transparent; } + +/* roadmap: value pills (3.x default -> 4.0 value) */ +.nj-fig .nj-int-pill { fill: color-mix(in srgb, currentColor 8%, var(--md-default-bg-color)); stroke: color-mix(in srgb, currentColor 35%, var(--md-default-bg-color)); stroke-width: 1.2; } +.nj-fig .nj-int-pill--v4 { fill: var(--md-typeset-a-color); stroke: var(--md-typeset-a-color); } +.nj-fig .nj-int-val--v4 { fill: var(--md-default-bg-color); } diff --git a/docs/mkdocs/docs/css/fig-jsonview.css b/docs/mkdocs/docs/css/fig-jsonview.css new file mode 100644 index 000000000..89cd3bcc9 --- /dev/null +++ b/docs/mkdocs/docs/css/fig-jsonview.css @@ -0,0 +1,107 @@ +/* figures: jsonview (features/json_view.md; SVGs from scripts/figures/json_view_layout.py, stepper in js/fig-jsonview.js) */ + +/* one colour per concept, the same in every figure: structure (object/array, views) blue, keys navy, strings green, + numbers orange, decoded strings purple, owned edit storage teal, dangling/invalid red */ +.nj-jsonview, .nj-jsonview-cmp { + --jv-struct: var(--nj-blue); + --jv-key: var(--nj-hl-key); + --jv-str: var(--nj-hl-string); + --jv-num: var(--nj-hl-number); + --jv-dec: var(--nj-hl-function); + --jv-own: var(--nj-hl-type); + --jv-bad: var(--nj-red); + --jv-line: color-mix(in srgb, currentColor 55%, var(--md-default-bg-color)); +} +.md-typeset svg.nj-jsonview { min-width: 34rem; max-width: 42rem; } +.nj-jsonview .nj-jsonview-c-struct { --c: var(--jv-struct); } +.nj-jsonview .nj-jsonview-c-key { --c: var(--jv-key); } +.nj-jsonview .nj-jsonview-c-str { --c: var(--jv-str); } +.nj-jsonview .nj-jsonview-c-num { --c: var(--jv-num); } +.nj-jsonview .nj-jsonview-c-dec { --c: var(--jv-dec); } +.nj-jsonview .nj-jsonview-c-own { --c: var(--jv-own); } +.nj-jsonview .nj-jsonview-c-bad { --c: var(--jv-bad); } +.nj-jsonview .nj-jsonview-c-line { --c: var(--jv-line); } + +.nj-jsonview text { fill: currentColor; font-size: 13px; } +.nj-jsonview text[class*="nj-jsonview-c-"] { fill: var(--c); } +.nj-jsonview .nj-jsonview-h { font-size: 15px; font-weight: 600; fill-opacity: .85; } +.nj-jsonview .nj-jsonview-sm2 { font-size: 14px; } +.nj-jsonview .nj-jsonview-note { font-size: 13px; fill-opacity: .75; } +.nj-jsonview .nj-jsonview-strong { font-weight: 600; fill-opacity: 1; } +.nj-jsonview .nj-jsonview-tiny { font-size: 10.5px; fill-opacity: .7; } +.nj-jsonview .nj-jsonview-mono { font-family: var(--md-code-font-family), monospace; font-size: 13px; } +.nj-jsonview .nj-jsonview-sm { font-size: 12px; } +.nj-jsonview .nj-jsonview-xs { font-size: 11px; } +.nj-jsonview .nj-jsonview-muted { fill-opacity: .75; } +.nj-jsonview .nj-jsonview-byte { font-size: 12px; } +.nj-jsonview .nj-jsonview-kind { font-size: 12.5px; font-weight: 700; } + +.nj-jsonview .nj-jsonview-box { fill: var(--md-default-bg-color); stroke: var(--jv-line); stroke-width: 1.5; } +.nj-jsonview .nj-jsonview-heap { fill: color-mix(in srgb, currentColor 7%, var(--md-default-bg-color)); stroke: var(--jv-line); stroke-width: 1.5; stroke-dasharray: 5 3; } +.nj-jsonview .nj-jsonview-ribbon { fill: color-mix(in srgb, currentColor 4%, var(--md-default-bg-color)); stroke: var(--jv-line); stroke-width: 1.2; } +.nj-jsonview .nj-jsonview-span { fill: color-mix(in srgb, var(--c) 18%, var(--md-default-bg-color)); } +.nj-jsonview .nj-jsonview-frame { fill: none; stroke: var(--jv-line); stroke-width: 1.2; stroke-dasharray: 6 4; } +.nj-jsonview .nj-jsonview-store { fill: color-mix(in srgb, var(--jv-own) 6%, var(--md-default-bg-color)); stroke: color-mix(in srgb, var(--jv-own) 55%, var(--md-default-bg-color)); stroke-width: 1.2; stroke-dasharray: 6 4; } +.nj-jsonview .nj-jsonview-node { fill: color-mix(in srgb, var(--c) 12%, var(--md-default-bg-color)); stroke: var(--c); stroke-width: 1.5; } +.nj-jsonview .nj-jsonview-node.nj-jsonview-hot { stroke-width: 3; } +.nj-jsonview .nj-jsonview-node.nj-jsonview-gap { fill: var(--md-default-bg-color); stroke: var(--jv-bad); stroke-dasharray: 4 3; } +.nj-jsonview .nj-jsonview-node.nj-jsonview-free { fill: none; stroke-dasharray: 3 3; } +.nj-jsonview .nj-jsonview-sep { stroke: var(--jv-line); stroke-width: 1; fill: none; } +.nj-jsonview .nj-jsonview-arr { stroke: var(--c); stroke-width: 1.5; fill: none; } +.nj-jsonview .nj-jsonview-dash { stroke-dasharray: 4 3; } +.nj-jsonview .nj-jsonview-head { fill: var(--c); } +.nj-jsonview .nj-jsonview-bar { fill: color-mix(in srgb, var(--jv-struct) 30%, var(--md-default-bg-color)); stroke: var(--jv-struct); stroke-width: 1.2; } +.nj-jsonview .nj-jsonview-bad { + fill: color-mix(in srgb, var(--jv-bad) 20%, var(--md-default-bg-color)); + stroke: var(--jv-bad); stroke-width: 1.2; stroke-dasharray: 4 2; +} +.nj-jsonview .nj-jsonview-evt { stroke: var(--jv-bad); stroke-width: 2; fill: none; } +.nj-jsonview .nj-jsonview-evt--ok { stroke: var(--jv-struct); stroke-dasharray: 4 3; } +.nj-jsonview .nj-jsonview-onbad, .nj-jsonview .nj-jsonview-onok { font-size: 11.5px; fill-opacity: 1; } + +/* the edit stepper: without JS all frames are shown one below the other */ +.nj-jsonview-steps { margin: 1.5rem 0; } +.md-typeset .nj-jsonview-step { display: block; width: 100%; max-width: none; margin: 0 0 1.5rem; } +.nj-jsonview-step > figcaption { + margin: .25rem 0 0; font-size: .75rem; color: var(--md-default-fg-color--light); text-align: left; + max-width: none; +} +.nj-jsonview-step > figcaption code { font-size: .95em; } +.nj-jsonview-step .nj-jsonview-scroll { overflow-x: auto; } +.md-typeset .nj-jsonview-steps.is-enhanced .nj-jsonview-step { display: none; margin: 0; } +.md-typeset .nj-jsonview-steps.is-enhanced .nj-jsonview-step.is-current { display: block; } +.nj-jsonview-controls { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 0 0 .6rem; } +.nj-jsonview-controls button { + min-height: 2.2rem; min-width: 2.2rem; padding: 0 .8rem; + font: inherit; font-size: .7rem; font-weight: 600; cursor: pointer; + color: var(--md-typeset-a-color); background: var(--md-default-bg-color); + border: 1.5px solid var(--md-typeset-a-color); border-radius: .4rem; +} +.nj-jsonview-controls button:hover:not(:disabled) { background: color-mix(in srgb, var(--md-typeset-a-color) 10%, var(--md-default-bg-color)); } +.nj-jsonview-controls button:focus-visible { outline: 2px solid var(--md-accent-fg-color); outline-offset: 2px; } +.nj-jsonview-controls button:disabled { opacity: .45; cursor: default; } +.nj-jsonview-controls button[aria-current="step"], .nj-jsonview-controls button[aria-current="step"]:hover { color: var(--md-default-bg-color); background: var(--md-typeset-a-color); } +.nj-jsonview-controls .nj-jsonview-status { font-size: .7rem; color: var(--md-default-fg-color--light); margin-left: .25rem; } +@media (max-width: 44.9375em) { + .nj-jsonview-controls button { min-height: 44px; min-width: 44px; } +} +@media (prefers-reduced-motion: no-preference) { + .nj-jsonview-steps.is-enhanced .nj-jsonview-step.is-current { animation: nj-jsonview-fade .25s ease-out; } +} +@keyframes nj-jsonview-fade { from { opacity: .3; } to { opacity: 1; } } + +/* json vs ordered_json vs editable document */ +.nj-jsonview-cmp { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .6rem; margin: 1.2rem 0; } +.nj-jsonview-cmp > div { + border: 1px solid var(--md-default-fg-color--lightest); border-radius: .4rem; padding: .5rem .6rem .6rem; + background: var(--md-code-bg-color); min-width: 0; +} +.nj-jsonview-cmp > div.nj-jsonview-cmp--best { border-color: var(--jv-struct); } +.nj-jsonview-cmp .nj-jsonview-cmp-h { font-size: .7rem; font-weight: 600; margin: 0 0 .1rem; } +.nj-jsonview-cmp .nj-jsonview-cmp-v { font-size: .64rem; color: var(--md-default-fg-color--light); margin: 0 0 .4rem; } +.md-typeset .nj-jsonview-cmp pre { margin: 0; font-family: var(--md-code-font-family), monospace; font-size: .68rem; line-height: 1.5; white-space: pre; overflow-x: auto; background: none; color: var(--md-code-fg-color); } +.nj-jsonview-cmp .nj-jsonview-bad { color: var(--jv-bad); font-weight: 600; text-decoration: underline wavy; text-underline-offset: 3px; } +.nj-jsonview-cmp .nj-jsonview-ok { color: var(--jv-own); font-weight: 600; } +@media (max-width: 44.9375em) { + .nj-jsonview-cmp { grid-template-columns: minmax(0, 1fr); } +} diff --git a/docs/mkdocs/docs/css/fig-macros.css b/docs/mkdocs/docs/css/fig-macros.css new file mode 100644 index 000000000..07fdccd41 --- /dev/null +++ b/docs/mkdocs/docs/css/fig-macros.css @@ -0,0 +1,98 @@ +/* figures: macro chooser (js/fig-macros.js) on features/arbitrary_types.md; colours come from the brand + variables in brand.css, which switch with the light/slate scheme */ +.nj-macro-chooser { + --nj-mc-line: color-mix(in srgb, currentColor 18%, var(--md-default-bg-color)); + --nj-mc-panel: color-mix(in srgb, currentColor 3%, var(--md-default-bg-color)); + --nj-mc-ink2: var(--md-default-fg-color--light); + margin: 1.2em 0 1.6em; + font-size: .74rem; +} +.md-typeset .nj-macro-chooser .nj-mc-fallback > :first-child { margin-top: 0; } + +.nj-mc-app { border: 1px solid var(--nj-mc-line); border-radius: .3rem; background: var(--nj-mc-panel); + padding: .9rem; display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; } +.nj-mc-form, .nj-mc-out, .nj-mc-editor, .nj-mc-members, .nj-mc-mbody { grid-template-columns: minmax(0, 1fr); } +.nj-mc-form { display: grid; gap: 1rem; margin: 0; } + +/* questions as segmented radio groups */ +.nj-mc-questions { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(14.5rem, 100%), 1fr)); gap: .7rem .9rem; } +.nj-mc-q { border: 0; margin: 0; padding: 0; min-width: 0; } +.nj-mc-q legend { font-weight: 600; padding: 0; margin-bottom: .3rem; font-size: .7rem; } +.nj-mc-seg { display: flex; border: 1px solid var(--nj-mc-line); border-radius: .25rem; overflow: hidden; + background: var(--md-default-bg-color); } +.nj-mc-seg input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; } +.nj-mc-seg label { flex: 1 1 0; display: flex; align-items: center; justify-content: center; text-align: center; + min-height: 2.1rem; padding: .25rem .5rem; cursor: pointer; line-height: 1.25; font-size: .7rem; + color: var(--md-default-fg-color); transition: background-color .15s, color .15s; } +.nj-mc-seg label + input + label { border-left: 1px solid var(--nj-mc-line); } +.nj-mc-seg label:hover { background: var(--nj-blue-soft); } +.nj-mc-seg input:checked + label { background: var(--nj-blue); color: var(--md-default-bg-color); font-weight: 600; } +.nj-mc-seg input:focus-visible + label { outline: 2px solid var(--md-accent-fg-color); outline-offset: -3px; } +.nj-mc-q:disabled .nj-mc-seg { opacity: .45; } +.nj-mc-q:disabled label { cursor: not-allowed; } +.nj-mc-q:disabled label:hover { background: transparent; } +.md-typeset .nj-mc-qnote { margin: .3rem 0 0; font-size: .64rem; line-height: 1.4; color: var(--nj-mc-ink2); } + +/* type editor */ +.nj-mc-editor { display: grid; gap: .6rem; border-top: 1px solid var(--nj-mc-line); padding-top: .9rem; } +.nj-mc-names { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(11rem, 100%), 1fr)); gap: .6rem .9rem; } +.nj-mc-field { display: grid; gap: .2rem; } +.nj-mc-field label { font-weight: 600; font-size: .7rem; } +.nj-mc-field[hidden] { display: none; } +.nj-macro-chooser input[type="text"] { font-family: var(--md-code-font-family), monospace; font-size: .7rem; + min-height: 2.1rem; padding: .2rem .45rem; min-width: 0; width: 100%; box-sizing: border-box; + border: 1px solid var(--nj-mc-line); border-radius: .2rem; background: var(--md-default-bg-color); + color: var(--md-default-fg-color); } +.nj-macro-chooser input[type="text"]:focus { outline: 2px solid var(--md-accent-fg-color); outline-offset: 0; border-color: transparent; } + +.nj-mc-members { display: grid; gap: .35rem; } +.nj-mc-mbody { display: grid; gap: .35rem; } +.nj-mc-mrow { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) 2.2rem; gap: .4rem; align-items: center; } +.nj-mc-custom .nj-mc-mrow { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) minmax(0, 1fr) 2.2rem; } +.nj-mc-mrow .nj-mc-keycol { display: none; } +.nj-mc-custom .nj-mc-mrow .nj-mc-keycol { display: block; } +.nj-mc-mhead { font-weight: 600; font-size: .66rem; color: var(--nj-mc-ink2); } +.nj-mc-mhead .nj-mc-keycol[hidden] { display: none; } + +.nj-macro-chooser button { font: inherit; cursor: pointer; border-radius: .2rem; } +.nj-macro-chooser button:focus-visible { outline: 2px solid var(--md-accent-fg-color); outline-offset: 2px; } +.nj-mc-rm { width: 2.2rem; height: 2.1rem; border: 1px solid var(--nj-mc-line); background: var(--md-default-bg-color); + color: var(--nj-mc-ink2); font-size: 1rem; line-height: 1; padding: 0; } +.nj-mc-rm:hover { color: var(--nj-red); border-color: var(--nj-red); } +.nj-mc-add, .nj-mc-copy { justify-self: start; min-height: 2.1rem; padding: .2rem .8rem; font-size: .7rem; font-weight: 600; + border: 1px solid var(--nj-blue); background: var(--md-default-bg-color); color: var(--nj-blue); } +.nj-mc-add:hover, .nj-mc-copy:hover { background: var(--nj-blue-soft); } + +/* output */ +.nj-mc-out { display: grid; gap: .7rem; } +.nj-mc-result { border-left: .2rem solid var(--nj-blue); background: var(--nj-blue-soft); border-radius: 0 .2rem .2rem 0; + padding: .55rem .8rem; } +.nj-mc-result.nj-mc-bad { border-left-color: var(--nj-red); background: var(--nj-red-soft); } +.md-typeset .nj-mc-result p { margin: 0; } +.md-typeset .nj-mc-result p + p { margin-top: .3rem; } +.md-typeset .nj-mc-label { font-size: .64rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--nj-mc-ink2); } +.md-typeset .nj-mc-macro code { font-size: .8rem; font-weight: 600; background: transparent; padding: 0; + overflow-wrap: anywhere; color: var(--md-default-fg-color); } +.md-typeset .nj-mc-why { line-height: 1.5; } +.md-typeset .nj-mc-hints { margin: 0; padding-left: 1.1rem; font-size: .68rem; color: var(--nj-mc-ink2); } +.md-typeset .nj-mc-hints li { margin: 0; } +.nj-mc-hints[hidden], .nj-mc-code[hidden] { display: none; } + +.nj-mc-codebar { display: flex; align-items: center; gap: .6rem; margin-bottom: .3rem; } +.nj-mc-codetitle { font-weight: 600; font-size: .7rem; margin-right: auto; } +.nj-mc-copystatus { font-size: .66rem; color: var(--nj-mc-ink2); } +.md-typeset .nj-mc-code .highlight { margin: 0; } +.md-typeset .nj-mc-code pre { margin: 0; } +.md-typeset .nj-mc-code pre > code { max-height: 34rem; } + +@media (max-width: 44.9375em) { + .nj-mc-app { padding: .7rem; } + .nj-mc-seg label, .nj-macro-chooser input[type="text"], .nj-mc-add, .nj-mc-copy { min-height: 2.75rem; } + .nj-mc-rm { width: 2.75rem; height: 2.75rem; } + .nj-mc-mrow, .nj-mc-custom .nj-mc-mrow { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 2.75rem; } + .nj-mc-custom .nj-mc-mrow .nj-mc-keycol { grid-column: 1 / 3; } + .nj-mc-custom .nj-mc-mhead { display: none; } +} +@media (prefers-reduced-motion: reduce) { + .nj-mc-seg label { transition: none; } +} diff --git a/docs/mkdocs/docs/css/fig-pointer.css b/docs/mkdocs/docs/css/fig-pointer.css new file mode 100644 index 000000000..4f03eabcc --- /dev/null +++ b/docs/mkdocs/docs/css/fig-pointer.css @@ -0,0 +1,459 @@ +/* figures: pointer (features/json_pointer.md, json_patch.md, merge_patch.md; widgets in js/fig-pointer.js) */ + +/* value types: the colours of the code highlighting (brand.css), so a string looks the same everywhere */ +.nj-pointer-walk, .nj-pointer-fl, .nj-patch { + --nj-pointer-s: var(--nj-hl-string); + --nj-pointer-n: var(--nj-hl-number); + --nj-pointer-b: var(--nj-hl-function); + --nj-pointer-line: color-mix(in srgb, var(--md-default-fg-color) 28%, var(--md-default-bg-color)); + --nj-pointer-ok: #23793a; + --nj-pointer-ok-soft: #e6f4ea; +} +[data-md-color-scheme="slate"] :is(.nj-pointer-walk, .nj-pointer-fl, .nj-patch) { + --nj-pointer-ok: #7ed491; + --nj-pointer-ok-soft: #14301d; +} + +/* ---------- pointer walker ---------- */ +.md-typeset .nj-pointer-walk { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 1rem 1.6rem; + margin: 1.5rem 0; + padding: 1rem 1.1rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 6px; + font-size: .75rem; +} +@media screen and (min-width: 45em) { + .md-typeset .nj-pointer-walk--js { + grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); + } + .md-typeset .nj-pointer-walk--js .nj-pointer-panel { order: 2; } +} +.md-typeset .nj-pointer-static { + margin: 0; + color: var(--md-default-fg-color--light); +} +.md-typeset .nj-pointer-walk code { + font-size: .95em; +} + +/* tree: elbow lines drawn by each item (::before up to its row, ::after down to the next sibling) */ +.md-typeset .nj-pointer-walk ul.nj-pointer-tree, +.md-typeset .nj-pointer-walk .nj-pointer-tree ul { + margin: 0; + padding: 0; + list-style: none; +} +.md-typeset .nj-pointer-walk .nj-pointer-tree ul { + padding-left: 1.5rem; +} +.md-typeset .nj-pointer-walk .nj-pointer-tree li { + position: relative; + margin: 0; + padding: 0; + list-style: none; + --nj-pointer-mid: .95rem; +} +.md-typeset .nj-pointer-walk .nj-pointer-tree li::before, +.md-typeset .nj-pointer-walk .nj-pointer-tree li::after { + content: none; +} +.md-typeset .nj-pointer-walk .nj-pointer-tree ul > li::before { + content: ""; + position: absolute; + top: 0; + left: -1rem; + width: .8rem; + height: var(--nj-pointer-mid); + border: 0 solid var(--nj-pointer-line); + border-width: 0 0 2px 2px; + border-bottom-left-radius: 4px; + background: none; +} +.md-typeset .nj-pointer-walk .nj-pointer-tree ul > li:not(:last-child)::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: -1rem; + border-left: 2px solid var(--nj-pointer-line); +} +.md-typeset .nj-pointer-walk .nj-pointer-tree ul > li.is-on::before { + z-index: 1; + border-color: var(--md-typeset-a-color); +} +.md-typeset .nj-pointer-walk .nj-pointer-tree ul > li.is-pass::after { + z-index: 1; + border-left-color: var(--md-typeset-a-color); +} + +.md-typeset .nj-pointer-walk .nj-pointer-n { + display: inline-block; + margin: .15rem 0; + padding: .2rem .5rem; + border: 1px solid transparent; + border-radius: 6px; + background: none; + color: var(--md-default-fg-color); + font: inherit; + font-family: var(--md-code-font-family), monospace; + font-size: .95em; + line-height: 1.5; + text-align: left; +} +.md-typeset .nj-pointer-walk button.nj-pointer-n { + cursor: pointer; +} +.md-typeset .nj-pointer-walk button.nj-pointer-n:hover { + border-color: var(--md-default-fg-color--lightest); +} +.md-typeset .nj-pointer-walk :is(button):focus-visible { + outline: 2px solid var(--md-typeset-a-color); + outline-offset: 2px; +} +.nj-pointer-k { color: var(--nj-hl-key); } +.nj-pointer-t { + color: var(--md-default-fg-color--light); + font-family: var(--md-text-font-family), sans-serif; + font-size: .88em; + font-style: italic; +} +.nj-pointer-v--s { color: var(--nj-pointer-s); } +.nj-pointer-v--n { color: var(--nj-pointer-n); } +.nj-pointer-v--b { color: var(--nj-pointer-b); } + +.md-typeset .nj-pointer-walk li.is-on > .nj-pointer-n { + border-color: color-mix(in srgb, var(--md-typeset-a-color) 45%, transparent); +} +.md-typeset .nj-pointer-walk li.is-target > .nj-pointer-n { + border-color: var(--md-typeset-a-color); + background-color: var(--nj-blue-soft); + box-shadow: 0 0 0 1px var(--md-typeset-a-color); +} +/* the value a container pointer identifies includes its subtree */ +.md-typeset .nj-pointer-walk li.is-target li > .nj-pointer-n { + background-color: var(--nj-blue-soft); +} +.md-typeset .nj-pointer-walk li.is-miss > .nj-pointer-n { + border-color: var(--nj-red); + border-style: dashed; +} + +/* panel: pointer bar, result, example pointers */ +.nj-pointer-lbl { + margin: 0 0 .35rem; + color: var(--md-default-fg-color--light); + font-size: .62rem; + font-weight: 600; + letter-spacing: .04em; + text-transform: uppercase; +} +.nj-pointer-lbl span { + font-weight: 500; + letter-spacing: 0; + text-transform: none; +} +.nj-pointer-bar { + display: flex; + flex-wrap: wrap; + margin-bottom: .6rem; +} +.md-typeset .nj-pointer-seg { + margin: 0 -1px 0 0; + padding: .3rem .55rem; + border: 1px solid var(--md-typeset-a-color); + background-color: var(--md-default-bg-color); + color: var(--md-typeset-a-color); + cursor: pointer; + font: inherit; +} +.md-typeset .nj-pointer-seg:first-child { border-radius: 6px 0 0 6px; } +.md-typeset .nj-pointer-seg:last-child { border-radius: 0 6px 6px 0; } +.md-typeset .nj-pointer-seg:only-child { border-radius: 6px; } +.md-typeset .nj-pointer-seg code { color: inherit; font-size: 1em; } +.md-typeset .nj-pointer-seg--root code { color: var(--md-default-fg-color--light); } +.md-typeset .nj-pointer-seg.is-after { + border-color: var(--md-default-fg-color--lighter); + color: var(--md-default-fg-color--light); + border-style: dashed; +} +.md-typeset .nj-pointer-seg[aria-pressed="true"] { + position: relative; + background-color: var(--md-typeset-a-color); + color: var(--md-default-bg-color); +} +.md-typeset .nj-pointer-seg--root[aria-pressed="true"] code { color: inherit; } +.nj-pointer-out { + min-height: 3.2em; + margin-bottom: .9rem; + padding: .5rem .7rem; + border-left: 3px solid var(--md-typeset-a-color); + border-radius: 0 6px 6px 0; + background-color: var(--md-code-bg-color); + line-height: 1.6; + overflow-wrap: anywhere; +} +.md-typeset .nj-pointer-out .nj-pointer-val { color: var(--md-typeset-a-color); font-weight: 600; } +.nj-pointer-out .nj-pointer-note { color: var(--md-default-fg-color--light); } +.nj-pointer-out .nj-pointer-err { color: var(--nj-red); font-weight: 600; } +.nj-pointer-presets { + display: flex; + flex-wrap: wrap; + gap: .35rem; +} +.md-typeset .nj-pointer-chip { + padding: .2rem .5rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 6px; + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); + cursor: pointer; + font: inherit; +} +.md-typeset .nj-pointer-chip:hover { border-color: var(--md-typeset-a-color); } +.md-typeset .nj-pointer-chip[aria-pressed="true"] { + border-color: var(--md-typeset-a-color); + background-color: var(--nj-blue-soft); + color: var(--md-typeset-a-color); +} +.md-typeset .nj-pointer-chip code { color: inherit; font-size: 1em; } +@media (pointer: coarse), screen and (max-width: 44.984375em) { + .md-typeset :is(.nj-pointer-seg, .nj-pointer-chip, .nj-patch-btn) { min-height: 44px; min-width: 44px; } + .md-typeset .nj-pointer-walk button.nj-pointer-n { min-height: 40px; } +} + +/* ---------- flatten / unflatten figure (scripts/figures/json_pointer_flatten.py) ---------- */ +.md-typeset svg.nj-pointer-fl { min-width: 28rem; max-width: 44rem; } +.nj-pointer-fl .nj-pointer-fl-edge { fill: none; stroke: var(--fg-line); stroke-width: 1.5; } +.nj-pointer-fl .nj-pointer-fl-link { fill: none; stroke-width: 1.5; stroke-opacity: .75; } +.nj-pointer-fl .nj-pointer-fl-link.nj-pointer-fl--s, .nj-pointer-fl .nj-pointer-fl-dot.nj-pointer-fl--s { stroke: var(--nj-pointer-s); } +.nj-pointer-fl .nj-pointer-fl-link.nj-pointer-fl--n, .nj-pointer-fl .nj-pointer-fl-dot.nj-pointer-fl--n { stroke: var(--nj-pointer-n); } +.nj-pointer-fl .nj-pointer-fl-link.nj-pointer-fl--b, .nj-pointer-fl .nj-pointer-fl-dot.nj-pointer-fl--b { stroke: var(--nj-pointer-b); } +.nj-pointer-fl .nj-pointer-fl-dot { fill: var(--md-default-bg-color); stroke-width: 2; } +.nj-pointer-fl tspan.nj-pointer-fl--s { fill: var(--nj-pointer-s); } +.nj-pointer-fl tspan.nj-pointer-fl--n { fill: var(--nj-pointer-n); } +.nj-pointer-fl tspan.nj-pointer-fl--b { fill: var(--nj-pointer-b); } +.nj-pointer-fl .nj-pointer-fl-k, .nj-pointer-fl .nj-pointer-fl-inner, .nj-pointer-fl .nj-pointer-fl-key { fill: var(--nj-hl-key); } +.nj-pointer-fl .nj-pointer-fl-op { + fill: var(--md-typeset-a-color); + font-family: var(--md-code-font-family), monospace; + font-size: 15px; + font-weight: 600; + text-anchor: middle; +} + +/* ---------- JSON Patch stepper ---------- */ +.md-typeset .nj-patch--js .md-typeset__scrollwrap, +.md-typeset .nj-patch--js > .md-typeset__table, +.md-typeset .nj-patch--js > table { + display: none; +} +.md-typeset .nj-patch-stage { + margin: 1.5rem 0; + padding: 1rem 1.1rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 6px; + font-size: .75rem; +} +.nj-patch-cols { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 1rem 1.4rem; +} +@media screen and (min-width: 50em) { + .nj-patch-cols { grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); } +} +.nj-patch-h { + margin: 0 0 .35rem; + color: var(--md-default-fg-color--light); + font-size: .62rem; + font-weight: 600; + letter-spacing: .04em; + text-transform: uppercase; +} +.nj-patch-doc { + --nj-patch-lh: 1.9rem; + --nj-patch-pad: .5rem; + position: relative; + padding: var(--nj-patch-pad) .6rem var(--nj-patch-pad) 2rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 6px; + background-color: var(--md-code-bg-color); + font-family: var(--md-code-font-family), monospace; + font-size: .95em; +} +.nj-patch-line { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0 .6rem; + min-height: var(--nj-patch-lh); + margin: 0 -.3rem; + padding: 0 .3rem; + border-radius: 4px; + white-space: pre; +} +.nj-patch-code { white-space: pre; } +.nj-patch-k { color: var(--nj-hl-key); } +.nj-patch-s { color: var(--nj-pointer-s); } +.nj-patch-note { + padding: 0 .4rem; + border-radius: 4px; + font-family: var(--md-text-font-family), sans-serif; + font-size: .82em; + font-weight: 600; + white-space: nowrap; +} +.nj-patch-line--add { background-color: var(--nj-pointer-ok-soft); box-shadow: inset 3px 0 0 var(--nj-pointer-ok); } +.nj-patch-line--add .nj-patch-note { color: var(--nj-pointer-ok); } +.nj-patch-line--del { background-color: var(--nj-red-soft); box-shadow: inset 3px 0 0 var(--nj-red); } +.nj-patch-line--del .nj-patch-code { text-decoration: line-through; text-decoration-color: var(--nj-red); text-decoration-thickness: 2px; opacity: .75; } +.nj-patch-line--del .nj-patch-note { color: var(--nj-red); } +.nj-patch-line--mod .nj-patch-note { color: var(--nj-pointer-ok); } +.nj-patch-line--test { background-color: var(--nj-blue-soft); box-shadow: inset 3px 0 0 var(--md-typeset-a-color); } +.nj-patch-line--test .nj-patch-note { color: var(--md-typeset-a-color); } +.nj-patch-ins { padding: .05rem .15rem; border-radius: 3px; background-color: var(--nj-pointer-ok-soft); box-shadow: 0 0 0 1.5px var(--nj-pointer-ok); } +.nj-patch-src { padding: .05rem .15rem; border-radius: 3px; box-shadow: 0 0 0 1.5px var(--md-typeset-a-color); } + +/* move/copy connector in the left gutter: a bracket from the source line to the target line */ +.nj-patch-arc { + position: absolute; + left: .55rem; + width: .8rem; + border: 2px solid var(--md-typeset-a-color); + border-right: 0; + border-radius: 6px 0 0 6px; +} +.nj-patch-arc::after { + content: ""; + position: absolute; + right: -2px; + border: 5px solid transparent; + border-left: 7px solid var(--md-typeset-a-color); +} +.nj-patch-arc--down::after { bottom: -6px; } +.nj-patch-arc--up::after { top: -6px; } + +.md-typeset .nj-patch-ops { + margin: 0; + padding: 0; + list-style: none; + counter-reset: nj-patch; +} +.md-typeset .nj-patch-ops > li { + position: relative; + margin: 0 0 .3rem; + padding: .35rem .5rem .35rem 2rem; + border: 1px solid transparent; + border-radius: 6px; + counter-increment: nj-patch; + line-height: 1.5; +} +.md-typeset .nj-patch-ops > li::before { + content: counter(nj-patch); + position: absolute; + top: .35rem; + left: .6rem; + color: var(--md-default-fg-color--lighter); + font-weight: 600; + font-variant-numeric: tabular-nums; +} +.md-typeset .nj-patch-ops code { font-size: .9em; overflow-wrap: anywhere; } +.md-typeset .nj-patch-ops > li.is-done { opacity: .6; } +.md-typeset .nj-patch-ops > li.is-cur { + border-color: var(--md-typeset-a-color); + background-color: var(--nj-blue-soft); +} +.md-typeset .nj-patch-ops > li.is-cur::before { color: var(--md-typeset-a-color); } +.nj-patch-op { color: var(--nj-hl-keyword); font-weight: 600; } +.nj-patch-path { color: var(--nj-pointer-s); } +.md-typeset .nj-patch-ops > li.is-cur .nj-patch-path { + padding: 0 .15rem; + border-radius: 3px; + background-color: var(--md-typeset-a-color); + color: var(--md-default-bg-color); +} +.nj-patch-ctrl { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: .4rem; + margin-top: .9rem; +} +.md-typeset .nj-patch-btn { + padding: .3rem .9rem; + border: 1px solid var(--md-default-fg-color--lighter); + border-radius: 6px; + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color); + cursor: pointer; + font: inherit; + font-weight: 600; +} +.md-typeset .nj-patch-btn--primary { + border-color: var(--md-typeset-a-color); + background-color: var(--md-typeset-a-color); + color: var(--md-default-bg-color); +} +.md-typeset .nj-patch-btn:disabled { opacity: .45; cursor: default; } +.md-typeset .nj-patch-btn:focus-visible { outline: 2px solid var(--md-typeset-a-color); outline-offset: 2px; } +.nj-patch-count { + margin-left: auto; + color: var(--md-default-fg-color--light); + font-variant-numeric: tabular-nums; +} +.md-typeset .nj-patch-status { + min-height: 3.2em; + margin: .6rem 0 0; + color: var(--md-default-fg-color--light); +} +@media (prefers-reduced-motion: no-preference) { + .nj-patch-doc--anim :is(.nj-patch-line--add, .nj-patch-line--del, .nj-patch-line--test, .nj-patch-ins, .nj-patch-arc) { + animation: nj-patch-in 450ms ease-out; + } +} +@keyframes nj-patch-in { + from { opacity: 0; transform: translateX(-.4rem); } + to { opacity: 1; transform: none; } +} + +/* ---------- merge patch table ---------- */ +.nj-pointer-mpt { + --nj-pointer-mp-set: var(--nj-hl-keyword); + --nj-pointer-mp-add: #23793a; + --nj-pointer-mp-del: var(--nj-red); + --nj-pointer-mp-rec: var(--nj-hl-function); + --nj-pointer-mp-arr: var(--nj-hl-number); + --nj-pointer-mp-keep: var(--md-default-fg-color--light); +} +[data-md-color-scheme="slate"] .nj-pointer-mpt { --nj-pointer-mp-add: #7ed491; } +.md-typeset .nj-pointer-mpt td { vertical-align: top; } +.md-typeset .nj-pointer-mpt td:first-child { white-space: nowrap; } +.nj-pointer-mp { + display: inline-block; + padding: .05rem .5rem; + border: 1px solid currentColor; + border-radius: 999px; + background-color: color-mix(in srgb, currentColor 10%, var(--md-default-bg-color)); + font-size: .92em; + font-weight: 600; + line-height: 1.5; + white-space: nowrap; +} +.nj-pointer-mp--set { color: var(--nj-pointer-mp-set); } +.nj-pointer-mp--add { color: var(--nj-pointer-mp-add); } +.nj-pointer-mp--del { color: var(--nj-pointer-mp-del); } +.nj-pointer-mp--rec { color: var(--nj-pointer-mp-rec); } +.nj-pointer-mp--arr { color: var(--nj-pointer-mp-arr); } +.nj-pointer-mp--keep { color: var(--nj-pointer-mp-keep); } +.nj-pointer-mp-none { color: var(--md-default-fg-color--light); font-style: italic; } +.md-typeset .nj-pointer-mpt table:not([class]) td code { white-space: normal; } +.md-typeset .nj-pointer-mpt table:not([class]) td:nth-child(-n+2) code { white-space: nowrap; } +/* the result cell takes a tint of the rule's colour */ +.md-typeset .nj-pointer-mpt tr:has(.nj-pointer-mp--set) td:last-child { background-color: color-mix(in srgb, var(--nj-pointer-mp-set) 8%, transparent); } +.md-typeset .nj-pointer-mpt tr:has(.nj-pointer-mp--add) td:last-child { background-color: color-mix(in srgb, var(--nj-pointer-mp-add) 8%, transparent); } +.md-typeset .nj-pointer-mpt tr:has(.nj-pointer-mp--del) td:last-child { background-color: color-mix(in srgb, var(--nj-pointer-mp-del) 8%, transparent); } +.md-typeset .nj-pointer-mpt tr:has(.nj-pointer-mp--rec) td:last-child { background-color: color-mix(in srgb, var(--nj-pointer-mp-rec) 8%, transparent); } +.md-typeset .nj-pointer-mpt tr:has(.nj-pointer-mp--arr) td:last-child { background-color: color-mix(in srgb, var(--nj-pointer-mp-arr) 8%, transparent); } diff --git a/docs/mkdocs/docs/css/fig-sax.css b/docs/mkdocs/docs/css/fig-sax.css new file mode 100644 index 000000000..2b8bd645c --- /dev/null +++ b/docs/mkdocs/docs/css/fig-sax.css @@ -0,0 +1,207 @@ +/* figures: sax (SAX event stepper on features/parsing/sax_interface.md, js/fig-sax.js; memory_over_time.svg and + chunk_buffering.svg on features/parsing/large_and_partial_input.md). Event colours are the --cb-* variables of the + parser-callback figure in brand.css, which .nj-fig provides. */ + +/* ---------- stepper ---------- */ +.md-typeset .nj-sax { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: .9rem 1.4rem; + margin: 1.5rem 0; + padding: 1rem 1.1rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 8px; + background: var(--md-default-bg-color); + font-size: .74rem; +} +@media screen and (min-width: 45em) { + .md-typeset .nj-sax { + grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); + grid-template-areas: "ctl ctl" "text text" "ev state" "status status"; + } + .md-typeset .nj-sax > .nj-sax__controls { grid-area: ctl; } + .md-typeset .nj-sax > .nj-sax__lane--text { grid-area: text; } + .md-typeset .nj-sax > .nj-sax__lane--events { grid-area: ev; } + .md-typeset .nj-sax > .nj-sax__lane--state { grid-area: state; } + .md-typeset .nj-sax > .nj-sax__status { grid-area: status; } +} +.md-typeset .nj-sax p { margin: 0; } +.md-typeset .nj-sax .nj-sax__h { + margin: 0 0 .45rem; + color: var(--md-default-fg-color--light); + font-size: .68rem; + font-weight: 600; + letter-spacing: .02em; +} +.md-typeset .nj-sax .nj-sax__h code { font-size: .95em; } +.md-typeset .nj-sax .nj-sax__text code { + display: inline-block; + max-width: 100%; + overflow-x: auto; + padding: .35rem .6rem; + font-size: .95rem; + white-space: pre; + word-break: normal; +} +.nj-sax .nj-sax-unread { color: var(--md-default-fg-color--light); } +.nj-sax .nj-sax-unread--never { color: var(--md-default-fg-color--lighter); text-decoration: line-through; text-decoration-color: var(--md-default-fg-color--lighter); } +.nj-sax .nj-sax-read { color: var(--md-code-fg-color); } +.nj-sax .nj-sax-tok { border-radius: 3px; box-shadow: inset 0 -3px 0 var(--nj-sax-ev, var(--cb-v)); background: color-mix(in srgb, var(--nj-sax-ev, var(--cb-v)) 22%, transparent); } +.nj-sax .nj-sax-tok--os, .nj-sax .nj-sax-ev--os { --nj-sax-ev: var(--cb-os); } +.nj-sax .nj-sax-tok--oe, .nj-sax .nj-sax-ev--oe { --nj-sax-ev: var(--cb-oe); } +.nj-sax .nj-sax-tok--k, .nj-sax .nj-sax-ev--k { --nj-sax-ev: var(--cb-k); } +.nj-sax .nj-sax-tok--as, .nj-sax .nj-sax-ev--as { --nj-sax-ev: var(--cb-as); } +.nj-sax .nj-sax-tok--ae, .nj-sax .nj-sax-ev--ae { --nj-sax-ev: var(--cb-ae); } +.nj-sax .nj-sax-tok--v, .nj-sax .nj-sax-ev--v { --nj-sax-ev: var(--cb-v); } + +/* event ticker */ +.md-typeset .nj-sax ol.nj-sax__events { + margin: 0; + padding: 0; + list-style: none; + counter-reset: nj-sax; +} +.md-typeset .nj-sax ol.nj-sax__events > li { + position: relative; + margin: 0 0 .2rem; + padding: .12rem .4rem .12rem 2.6rem; + border-radius: 5px; + counter-increment: nj-sax; + line-height: 1.5; +} +.md-typeset .nj-sax ol.nj-sax__events > li::before { + content: counter(nj-sax); + position: absolute; + left: .35rem; + top: .2rem; + width: 1.1rem; + color: var(--md-default-fg-color--lighter); + font-size: .64rem; + font-variant-numeric: tabular-nums; + text-align: right; +} +.md-typeset .nj-sax ol.nj-sax__events > li::after { + content: ""; + position: absolute; + left: 1.75rem; + top: 50%; + width: .55rem; + height: .55rem; + margin-top: -.275rem; + border-radius: 50%; + background: var(--nj-sax-ev); +} +.md-typeset .nj-sax ol.nj-sax__events code { background: none; padding: 0; font-size: .8rem; } +.nj-sax .nj-sax-ev--todo { opacity: .42; } +.nj-sax .nj-sax-ev--skip { opacity: .42; text-decoration: line-through; } +.nj-sax .nj-sax-ev--skip::after { background: var(--md-default-fg-color--lighter) !important; } +.nj-sax .nj-sax-ev--cur { background: color-mix(in srgb, var(--nj-sax-ev) 16%, transparent); } +.md-typeset .nj-sax .nj-sax-ev--false > code::after { + content: " \2192 false"; + color: var(--nj-red); + font-weight: 600; +} + +/* handler state */ +.md-typeset .nj-sax pre.nj-sax__tree { margin: 0 0 .6rem; } +.md-typeset .nj-sax pre.nj-sax__tree > code { padding: .5rem .7rem; font-size: .76rem; line-height: 1.45; } +.nj-sax .nj-sax-hit { border-radius: 3px; background: color-mix(in srgb, var(--nj-blue) 22%, transparent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--nj-blue) 55%, transparent); } +.nj-sax .nj-sax-dim { color: var(--md-default-fg-color--lighter); } +.md-typeset .nj-sax .nj-sax__stackh { margin: 0 0 .3rem; color: var(--md-default-fg-color--light); font-size: .66rem; font-weight: 600; } +.md-typeset .nj-sax ol.nj-sax__stack { + display: flex; + flex-direction: column-reverse; + gap: .25rem; + margin: 0 0 .6rem; + padding: 0; + list-style: none; +} +.md-typeset .nj-sax ol.nj-sax__stack > li { + margin: 0; + padding: .2rem .5rem; + border: 1px solid var(--md-default-fg-color--lightest); + border-left: 3px solid var(--nj-blue); + border-radius: 4px; +} +.md-typeset .nj-sax ol.nj-sax__stack > li.nj-sax-dim { border-left-color: var(--md-default-fg-color--lightest); } +.md-typeset .nj-sax ol.nj-sax__stack code { background: none; padding: 0; } +.md-typeset .nj-sax .nj-sax__note { color: var(--md-default-fg-color--light); font-size: .75rem; line-height: 1.5; } +.md-typeset .nj-sax dl.nj-sax__kv { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: .35rem .8rem; + margin: 0 0 .6rem; +} +.md-typeset .nj-sax dl.nj-sax__kv dt { color: var(--md-default-fg-color--light); font-weight: 600; } +.md-typeset .nj-sax dl.nj-sax__kv dd { margin: 0; } + +/* controls */ +.nj-sax .nj-sax__controls { display: flex; flex-wrap: wrap; gap: .5rem 1rem; align-items: center; } +.nj-sax .nj-sax__btns, .nj-sax .nj-sax__modes { display: flex; flex-wrap: wrap; gap: .35rem; } +.nj-sax .nj-sax__modes { margin-left: auto; } +.md-typeset .nj-sax .nj-sax-btn { + min-height: 2.2rem; + padding: .3rem .8rem; + border: 1px solid color-mix(in srgb, var(--nj-blue) 55%, var(--md-default-bg-color)); + border-radius: 6px; + background: var(--md-default-bg-color); + color: var(--nj-blue); + font: inherit; + font-weight: 600; + cursor: pointer; +} +.md-typeset .nj-sax .nj-sax-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--nj-blue) 10%, var(--md-default-bg-color)); } +.md-typeset .nj-sax .nj-sax-btn--main, .md-typeset .nj-sax .nj-sax-btn[aria-pressed="true"] { + border-color: var(--nj-band); + background: var(--nj-band); + color: #fff; +} +.md-typeset .nj-sax .nj-sax-btn--main:hover:not(:disabled), .md-typeset .nj-sax .nj-sax-btn[aria-pressed="true"]:hover { + background: var(--md-primary-fg-color--light); +} +.md-typeset .nj-sax .nj-sax-btn:disabled { opacity: .4; cursor: default; } +.md-typeset .nj-sax .nj-sax-btn:focus-visible, .nj-sax .nj-sax__stop input:focus-visible { + outline: 2px solid var(--nj-blue); + outline-offset: 2px; +} +.nj-sax .nj-sax__stop { display: inline-flex; gap: .45rem; align-items: center; min-height: 2.2rem; cursor: pointer; } +.nj-sax .nj-sax__stop input { width: 1.05rem; height: 1.05rem; margin: 0; accent-color: var(--nj-red); } +.nj-sax .nj-sax__stop:has(input:disabled) { opacity: .45; cursor: default; } +.md-typeset .nj-sax .nj-sax__status { + padding-top: .6rem; + border-top: 1px solid var(--md-default-fg-color--lightest); + color: var(--md-default-fg-color--light); + font-size: .72rem; + min-height: 2.6em; +} +@media screen and (max-width: 44.984375em) { + .md-typeset .nj-sax { padding: .8rem; } + .md-typeset .nj-sax .nj-sax-btn { min-height: 44px; min-width: 44px; } + .nj-sax .nj-sax__stop { min-height: 44px; } + .nj-sax .nj-sax__modes { margin-left: 0; } +} +@media (prefers-reduced-motion: no-preference) { + .nj-sax .nj-sax__events > li { transition: opacity .2s, background-color .2s; } +} + +/* ---------- static SVG figures ---------- */ +.md-typeset svg.nj-fig--sax-mem { min-width: 34rem; max-width: 40rem; } +.md-typeset svg.nj-fig--sax-buf { min-width: 36rem; max-width: 42rem; } +.nj-fig .nj-sax-axis { fill: none; stroke: var(--fg-line); stroke-width: 1.5; } +.nj-fig .nj-sax-illus { font-style: italic; } +.nj-fig .nj-sax-curve { fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; } +.nj-fig .nj-sax-c-dom { --nj-sax-c: var(--nj-red); } +.nj-fig .nj-sax-c-doc { --nj-sax-c: var(--nj-hl-type); } +.nj-fig .nj-sax-c-cb { --nj-sax-c: var(--nj-hl-number); } +.nj-fig .nj-sax-c-sax { --nj-sax-c: var(--nj-blue); } +.nj-fig .nj-sax-curve.nj-sax-c-doc { stroke-dasharray: 9 5; } +.nj-fig .nj-sax-curve { stroke: var(--nj-sax-c); } +.nj-fig .nj-sax-lab { fill: var(--nj-sax-c); font-size: 14.5px; font-weight: 700; } +.nj-fig .nj-sax-old { fill: color-mix(in srgb, currentColor 10%, var(--md-default-bg-color)); } +.nj-fig .nj-sax-new { fill: color-mix(in srgb, var(--md-typeset-a-color) 18%, var(--md-default-bg-color)); stroke: var(--md-typeset-a-color); stroke-width: 1; } +.nj-fig .nj-sax-buf { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 14px; white-space: pre; } +.nj-fig .nj-sax-sep { stroke: var(--fg-line); stroke-width: 1; stroke-dasharray: 3 3; } +.nj-fig .nj-sax-status { font-size: 14px; font-weight: 600; } +.nj-fig .nj-sax-s-wait { fill: var(--nj-blue); } +.nj-fig .nj-sax-s-ok { fill: var(--nj-hl-string); } +.nj-fig .nj-sax-s-err { fill: var(--nj-red); } diff --git a/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 b/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..b3530f3f5269165314114a9e1c2f129e20fddcbd GIT binary patch literal 387976 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 z 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 z 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[G8ma>d@I@Ptv~HW&n>5!LJ>IqF?wh27o@tW3v1 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

fMhJQRdG4Ojw!ssf-sB)36u*a?b?N2f{oHruFw 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 z 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'ha8ztMsz^(x7 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 z�-rCi)o~V>7?B(%@yY15?;Z}400udy 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

Y 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%0D;A!}Cr1xu%xmh4xtszER(< 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 literal 0 HcmV?d00001 diff --git a/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 b/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..d60c270e8c5e385b9b594d3b99421f5c07379397 GIT binary patch literal 95864 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 kKiR`SaT? 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

=%^{)$hpH*{#q?+x7b9aaCP^1|t$ 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 LKQGCzPybs0j|xdv literal 0 HcmV?d00001 diff --git a/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 b/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..6b604cec624bc9bc3803c02920ed64b5719dd4ae GIT binary patch literal 113380 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

UrCehD^F@%^OV*%P#c7yb`vD z`3})o`$LO?n4W9HA>+imL!mdOs2DY^W-pZ1r}{(NP|IweXS5-0M{au7iih}o%!6P8 zv#s@(K9%wyKBvy&Se8y-jR)-;b<$KM85T2|B|ei&E^d)kuCXmfU$LwGJvkUE(;W9^ zX?V>0+KAHKm?=uq~U@~eI~+5@{XOo1DM9)K|nwNlg1t)A)N5EyAj zWxLlnjeZ-85>17dX*G5*)8ju7WZ*km@EXA^JRn~oZU+O5phQp?ZjvbjY*EiFY%fnncc)Q;|)wYw$LU4+Wf78w#kqCM-FVR%?Q&uhZ*Fd)Mo{I)`oCy z0O~8IL7|Mx*0r~iCEwM025*oUi*HGiib1M@=|6`V;(!AZiY;|e4;w9MEzS@x( zT3`1C#?(b4M#QVEA%R~|YU_Y>ZfytGCSaSjEorl%TkMZzNhbO$!*L<*4ETb_rb@S% z?p~kj{(Hhqn_r%VGOsi0SIMb;ol@^S*K#4%MQF5>!l<1RHsv&I(NIOxTHx8*ypv&8 z*SHJiDAh;;ZZpyIf zr&6?DuEL7aA||F6^F`;VuRpqyBstL`|~8VZ9>&@^djm`T7nW=}V{qi5Je`X=mm?HcmORx(!a?Ud3Sw)FI&TgHnOX`zVS`2={39N*Mr3Y z>z~Lzp=v`5ZicYvTadft-Tn$b>njgYMZZwj8UMCG>)VBmJ)4hvw#I9?@yCAt;NHRN z!RHVGN}L1grLpH-ii>BeijxE0+8s-|v0m)?x;F@WZ{y~Tt^E*6VjPz0S#&C^kK>NF z#kR`Tk)O3)TR)nrm*nGL$RvHIPnoPBpX~b23t@sGjA05#48vFx%reJ3p1cIek|S54 zN_Eu+*T73AJ_Dm7z0eqUAPf#@qwQng{3yS1)r8GfEltg#ZE zI|U7Jz@Ud3bf5gks5%E# z*^?GxX@MFdTVAjw1z1*)m4#bhv<!Dp1JrC6;c zN)|7@H}{9w{c4>>rY~KtV)o)Cj&JYyc)~D8zd;KOn{TcG^DMO3T9v9)tKF;3+15pL zH*Llh=vLQA`dBBguV60;(Y3_BPlbD}H$-jef)Yz_b_420?7`F=+*37c&3^TW)P>BI zyk8&WtYx-FLJ^H_m>j46oP{-t@T1JuXQp1Wbm`G8fELuiEhVW#rwnI@di89NSoKwl zg1Ww|`-k`MTg2$70z-kgN5m6)>#)KDEjTnv0OlKKH2d6ns1P0~BPi+%iK3Mt{jN(~ z-4ALJYW{Rq%1`2C_*RFcuNA2JUaS9vVaQbSw@9(N6=j7*{EFot4IrOMmL|ui)|u!| z#l)5@USjo)H|fh+XS2`qH`uezXPJc^Lqj8dv$&@mp6?dYQtD?TpCJ!`6tixQ2rMX2%CYF-&9)zYO3?sm#X7#p(os5I z2=Ec0*Xvr2KCER~vVE7?^xh@bZM)CC9rsZC)KYDGF0yLNU2c7}g9FLM!;L~qVEKuG zf()P_BI#=xsW9ajgT>(qrbG&vWNl?>K^2Msa~)GCrLjaZE}za8%NZPrf+v6x6oB?i z4*EH_e`+!vqsTFvXPcm|;dR<~Weu!4W4S;EXfL;(`lF zJjXpZQe)j#lbs{<8x%hKy$RohZrzyW=)DHKf zTl8T`L8}W{uTj|kja=)=W$>bHCNFQ;6o3zGG{6@&72pRi0rCgq9HXv%%3z>?2eoGNqj(JKtj`~6Nx)%iX+LOiGUQy z8IU?;Z>8<@DTg=1&YW;aR)sPzj-kUz&?66lK2C-KAqGP{3`S@H#&{eicpj#B0%q6+ zbMi1)kbA(ADgY~TFIdw~!3INNOBjP4dcdCC2M$yfIFbjz>5IOTMeq4s@wo(546cMF zxK&=<(HcCcYVbq`c+pP78^hp(V(>*{@Iwjsw^lbbu10bg)Pz+tjar!2O0PC1wKJ=O zO`QVj;!-#Fdid7sXnpqUH{f7{92#<|VfPwwrBT-!^RRJuX5z5*7be|r%HO8_Faz@| zj;wG`hI`QP1wwzM`Y7$uMq>2FGu*7v=1ezlwFOj*c<++IwG0wL5UhL%KKrh_01OclveKo?BoPr?Vq!Be7&{UY7%3@hGBQ)-o==jmoTV`Mo%*beiiHRFCGlqplG%G6+0wI!(jewoqE)EV7 zPEIjMBrzA4z48YaR|gMIH$J{L2xR>Cjw^tZrR2ciJQ{?8gTtiJ5IBd186yi6wG2~7 zRzTY@dt`^I_Wzg*hwJ#szQZE8jE1A-h~PFFiLRr9>u5AOjtTCgvFJH2c#OuQ?}Xqz znn)if9q86^`k982(}Um0iGGjsI~v>>aByY_8qLDwnf;CxcMj~Ix$pRJ z=V7ESA#dc$INd_Q$ejs#g#3{w! zSUE~1=1F_UrJIfD_B>Uc8yCncy)_=cX)JhF_dsqijw8;q>2UrZHfkmLWs4S+ll< z4IAlo5TpVS=Nb44+`f-Y_`~ggn@|TG6ylWAN}Y99m|JcsciU}+?zp4EU3b;E=blRU z-B;qPub7{HUi^pQW+V!S6Nlkl`?NiK68rWE2M$D1QmnMJCm9)1Ru=O4oT8!yFoq1J zlaR<@s=rM39OX)pD(E+l179T}vlUor$ZQ8zcDUMq%h|8ustPU&Sk1Ut>VE2*xHbgt zW{p4X%yAdD>*A-|MRYy-RO{ET#DD=62F-~xZ(gYd3(i@zsM1|`mAmJjD)-$NZOO8_ zc9?GqL=ap{jQqLW*x#Bs@(+$Z z^lgviWUlLYc)r5NcN0Y5YeFL55fl5Cgv5`eWbTq*svDV1O{0*O(R3!b3N{2T5(#2w z$LHWA;Nm9X;UVVbrQ+kK*PuZ$8qFvmAYV|BQb>qGqX@Mo44G!lbfTil#l_W2ND#p# zI1WIsfOZgP0PPU^7ws_a1hiA=KeW@hGte%g|Ix0)U4V8cDn+{scLiDzDnq-3y8-Qf zRF3ulPJmX7D$pLr-9dXFCDA^>JwW>yg`j$4v zg~D;AlAKEAgj!9|XdKmQS?P4%>-B651|N+^4knW?X0w47ix*a_!8V&$cDo@Chu2Q0 zp)QvF*9=EelhRiv!=NUo&rE>;r0xSyhh#siH^4JVuFb}Og4qyR8zs7Mn9STQe%#}H1^vs(_tq}=t<|* z<+RgoIe*=_zps3_KDqmp&wS>*Z+zo@AAMwnb3H8BF)-%FbtGlV)>KmnN5NP-xJ_WJ zGvPS;m%+_#VZ_L`iiNNrj7@^u2gXA~I043EgZBgDstLVRtJc#eYSJ*6G9}8iX)$KZ zh%;+WqIvTYELae2(V|#Omc(1LCfT}msWxp&vu#JZJqI!!IhO0pnH1Nq<$Lp{0I9y@ zk{Xv?Rq2{*YTb2LT_2e6iiaM0+2_WW@X{;weItY?AV%<^Af{k9K+FYi0b&;H4T$;i zK?nqGNhQYaVZy|Nni_9~xk1d>vK3rA!7hW?e*9P* zI2c_=V-Fc-nAaUC_?aLcJ@%dnCb(phNp5zE@jO#a)z383$aCT(q%(}Y!i9_LbvND_ zfBwb@5Wt{;g1rQB(0EmX1@jUjgh;4RMvXG|muS)ch!NvTV~tlLP8>Dy;wejzKwY9l zDorx>lVr)>HOt{GJ=u7Zj~qEhDN@8+v10y8l+skD+&C2~39S?CKZxstcLj07;rTS; zO>UYr@zAWrEUj84Xft1|1r~_0&_eMRTP(6m9G<(>;jKQ)@HX=-x7=J(QZCxHo1;Sq zCo8NFXr+|`tg=eDopzGzUcvr>_`vuG4myZ*>Llx!>@J8;1@8&sGh#VOm zC-^BK?jF0tC6_$v6TxeM_`}#UKKkfaKM6hz#Givb1_^frV3>2|1>Xn~A%t%rksF(Z zJb7~|P%xV!Mf0j>!fX2Iqni;UT}1jiUlYMMfy8ve3(S~#roaB4ORR{u!s-@8s(&;WR7?PMwXIU)uY_=E< zN1W$e0LTDA5fG#UhSed6a1>R5VLEXfoglQ5q;!fBM$;G^j!Z6BJC7%Y&lf2Wh-Dbv zEUQgONRzOLN>NeqVq#e0;!-6fG)PLymXZ=IEiG9_Mw+aw9633{wR`EMYhHQfGq1gN z+MYct$`~~)juMZjBbd@uw0>h|?rs;3(f!xcpH*^no$F^LlAp+AcPJE>tNI|HyY@oR z>1^%%{>Z>9W*)pV_xQK|h3*@?C1^NkYWX&i=zH4Q{vV0tPsn8dpF;5i5cIlghL8c| z(grTk>HdPju!A65Cez=sSpJdC_6-il*J0Q{Ac!TGtK;!}m(Ta-DC%1RfmSH;TrBo6 zhS@X`LVzGwIC?20u(7Fd7!0&@az5YB zp{Rc?5csD;p-d$5%VM$rh+&MH*}j7R38wu*@B>rh;QxcEc!CQO5_(LS7{!#S0A|cM z*Ie-5!L)w}zF;~a_`hH}a6;11h=`OD6BCh;C?_R_RA%U($jSXug#jETr79{aIn>nj zG&Cw`X`ys<8tCb{85r0Y8C5eeDFB1P%*-lTSYWKI5)cSiQGxyeh{6V<;BYl;Y)Aw` z84}4_yFl*&q8tMhPEG_D7dAJy5*{89FRx5KJ_3GzS*7gT`(GiGy{jT3sQVW*nwQl@ zFvk8Flj%h*!hW%*7UJM<;Z8a6OfAR9-yon6gcB#iKO&}JBvXl`_y=UX7m$BK&Yz>0 z+DU2ZM=Ja^>ZwE;Q)_AQ_vlEBzB9h;vtivMBtau{G%`aYQ#7(bBYQNmL8F0aB#%aF zSOFwuT#3hxEO93XJV*gg(!{IZae5!$_#%Az>({W4J0JNrT__Yb8tnoGqZ5nOfx}_p z@h%DoxhyQKT|`cgynlRqxjfEECSY(k^i!C*2nPqNUZn+sNtZ>arE8Vi%YIm%$ z##I|_a>HSVO*rC&MQ5CGy|({D@Q1-Hb}|9Pix=2(6E+~#er^13u8Eh;v}xNym#(es z+1u7QW6s2jH&cQHP7)=W(H7&ISz;OR7v^NZ=79Mrm;}!K76AY4YQ=TO6t7=8Cu|1cO6fKC%IYu zWl|AQqQE@Pqrv4!S^c8Gu-96^>a{;ueHKjX!4*xrPh>0bYD{xE#o+!~R z<|0R!Y@nf-?#R0#zMjxpp$zn0+pQH5dOVLMk81Z+Ft%CiaJk$YF}{v4Tgfo3)aMFu zLC|#T*qRE%vd@9wfjZy{8qg=1+;a#5C6;{Is4eUuhtt!XnKUMj)VR+zsYc*SwL5CS zxMGiPH#7NBseF-vzz|GOzi<$kLDWz4Gc_MmdrhR2P~&_=Vc<{*hOCRLpt^!l-GAo? zQhHMCW62v^^buiZ&Nz_bBFN{ zB!5v+5cZjYgHIO1Gp4~%+_a9A<~K+_m(bE!U125%725*4IhV_dLOLA3qHe z?iY_`c|O|C63rk6rqNPpL0vomxER+STBpyuG@Ubrz*b@IrFPwYneI`%6NzbAv5oo_ zVF9>n1jj_szaD1@_Dnfs%!(~ys$)o+f}lgIrc?4k5IIL_Vnl^el(|`WAYT|OU@b?> zNTFpvvMjx=n$a#z2;z(ig#-O`6bXdj5)q~p>wE-rrG&FZ+OKv{4xIs01IQCuRh&?l zPAC)w%qVjWxi^6DHj&pb^{3LPmqQ;{wGQ+(h}zLqe=bFqqgp7Th+N03DIT15w9ooz zACcTrAkdUIcf*tP*}h|iaOTpNQSu- zLwT))b=FS!+|Ooe%#1nC7r?UP(MsA}*OFKFD8z~9&pENLs>uu&4HCr^cUlzJ$92xA zeFGuD5RuY$mlh(NM=!W;o+NnTmnj?gJ3o8@6Y6Qc_F$4POF-wsfKf^ zJ$ndX?cIXm(wx&AU%tE**wMEyOGt0+n>}5mex4m)!qRD7PArIwUi-=lWrO0xWPo9$ z01zf1!C83#cjFsXHS}5;$J_ht3N=JhY@JGm_my++n7OBkDqA3}Dv9$;Hq_EjmOe)^k<^Lhkwbd(5 z4mVf>-R~t#JYuatDAU4BQxpZj=+rRKd>EPY`x`R>mu4IJDj#~&L3&K{FDWQE=+$50cG>F?otTKyH-|6kfYRl^xyfgQC|MHpP% zXpafTVxfx&&JXF4dGRo=rxp11PF@)D+&Tk0P}A8`=x|60y5i8p3@u<-iY400eE9Sj ztgN)~${B@|xd#DJ3yF9Nnk?#-l#Cgn6j0Y^JfV;goXY-! zjF==dtP=s666$cFV@ncBo+W*i#p2FU>-}@imZW1$yxk}hxez5uKkWz6e(h;_?fYeU z$BuZar2QdJ+y37P|~MCvOM$Tmi%G$Bt8Q)N<$ znXv(?5fGdiIb|9X5Ftprs;$g-ED=>njHwimm(8rCG$wJ;f{eYzmPlf7$`-i`U0l%f3Th1o5x5by=_ z&)^U=SfGy+9HDb3ol*d--T!)+3jgPyujdcVg5lt@8+!D|e(Tkjp4$4?u&HnTf;-xxbkxyNhtQcm+gKR-MW8t21+t`5w%1bg zHumOIN+xA1pL|$@WXiCD#QC-Y0%qjt5?v1`hy$Jx4Z`26V3%pAXwR1%$%G;y7@tq6 zPQ+w1FqOr;DF(L)O5uEtBT@^m(D8R}xO@SKKl4Di($C^q9~k&xk3{(>G0_DPVJVF-)%KQ2ZN2o;de zi3{`andW@J{?_b*l%tpu@(!2y3LKWuo4Io-P6s|EY9SC&a!m^u!NNSHZZzl|I3Qo@ z1F;f>0RBN#?n?(4Kb4pvTY9}P0ldF0wn-Q(-;A&xoW3F%1Xwe5Utw=J!GF@`F^?Q( zSeOZpItNxYMxo6^b6-0sq$Me6ZZ>iR`cd+Pz`6+oQ-{Xk$#Z+^)NVp|=rfUHc)E}C zNl^)~c%1Nk)YuQjsI{p!L!1gRD8B&>+|M&;eCfo>s0ZlC^rYl4D)g7SS=mN9Bg{g! zSmhB`PqOy>)ahGypa+uID}uHhgd^<24_R32TXTowYl!V|45ZvXe42#=ZWR8)xXLDD z{Abdp&YT|;+w6#JbDFR@(35RbIr(qexzU}`q-H@ud_Y`PDf~e^#-T(7iwY>hdMF0n zBvMH!YRiC6j-ANUMU=scHRUYxn>gvm&W)3_M}dU&@vWS@)oCkDSE4I_)CUuY(+8cT zpOwH)YObqr11NpLEK`*wESYv+{!=a^`OGGV5AojNs{|AZbd~B9^JeoT^*&_Rd^z}^+>n(6Z2=>nhEOOp;B37fb{96awJR{3m%>17! zH#?^%;(o@Cp!?p=G%aP*5`vOsjnNen z6@Av8@T$}%P%Hqx_AA2ACdCpKGs|ZwxpF49rl!bUQX&lZ?+NtWP_)^F6SJY`@L#tBM}+n&GpUtY0lcsl8ygN>W94O!bBncrY~=3d}uzG z(|u6!)vK>=>a?#t%tZ-x=DShaTZ5Atlkui3a4;*pO8# zDoI0(+>4a%_tCsYa zRG?v_awQgt15U%8H!!zic_?J7lcUE`TY_vqp)FhS3USxm3J19ZqC6AYpZ0vFH){E) zxO23d!Q$@m*K^3Uah=j4eMh%uJwLRiM>RqMWCUVTs8`cyk5oR z`4B?p5^rJ*i{D^)hx??Rl4jUh5h6=*8Te}RMSv@i_%arc>6G{>`&Enx`cv6vZA)y4v0Y#-ChK|K7Y)sY-74hoF^7A!B zE~l>mXAY{nH>{Z@+IFCJ0c zvH3!0*YDIril3+iF%#I-wH&z=c5UF!FOK}>Jj_30koEyV8zXUa1%!EQO93*bY#k*1 zrhgLEY1f&lziGnb{C6ywD&ow9I2!@+Rc}0rO zuVbEZtVpwZfyWqTFPBr1u6$~PABc%+qnKvAl3}Wx)yW-5U@JFo8x-J#c!Q7!Q$V(* z#{QmW={S>lO%Hl(aeo%AOX#-<%PVIGii>j98rv!&J-ceJcn67{{5~VztbKX@8NG41 zIhd({5VvFH?PEf#W3*wlY83tx`hE44rbU{F0S)dOwNs>L&a5GeBQZS-9`#;!&;6H9 zc}+o@)NP~;cc8(3oRBmMqYF#K8M$sHMV)waWk<3J>d;uK9Qf!dt%q0Pdhj4%rD>c< z>LCZ6lfW6=*>XMmsP>!cwzCd_jJOq1= zDA*W_{EypZz(8P2%HmGnZ{g#B?wnUaf42PYWQq;1PM+Cw!U({|a9U2k@M?*O_E19=_1 zv96{4JqTQGUklt9)M!&tvf{f5*LfQViIEL)rwajobU695!Koo&uP{O*fo9l%chK6Vd7#6cSV@^^DUa|) z-Wz7vgM@_QIm>HwO z;9Tap&?Z~?x_IjpVJr#y^9lRu>UNB&8Et-$)Q93Wvxm)5o~I?yh;itYXi>HI6Z5VZ zZa6r1u(^aSy_qEQn3=Z{-v}nl?Fu{hEU%LhW%kaK4=#@n0A&cJJ=bw^GUg7=-?O+Y zyWMkf<2FSc(AmevzHB(3Sfkig)A4P?Y!TN=ghqGdbN_+uRU)`Ks!#)~Y6i5>oe90c zjRDpcZW982+#ujPSv>X}Lec1|^;c@I=Os{Xn;=#=3eJ;f*$P9!aoI$h{I6aB27xvh z3OiBlUD!^3!;hjtN7*=2pPL3tVCw7@AOL0NnY@CoQ}Veu6E<>*&%O#C>_Fn5p7Xhy z*J$G5F2xVu?Bs97UcX(&2dmE%H~uK(mc@cS{o}Bd@C>;y%v9fCUqzg96>921tLBvE za@zgo6%<5Yt3VGFJXJ>S$3qEQ5j8OnWY9%PHH0L1)PrOtD}A~vo43ZizR+Gt30;nz z`Wc2=U8$gI&d=Cqdmp5ne<*ojDT6P;& z{X@lbTRcFdyK-w9Gu~BOHP4uwvBl&L(8d65H2nVC?LFjd(f6TDd>ns{~gr?fvT;fzfU5 z!buS9_D+oPu9*D1?t(LWyxmQUpG+%{vvL7imxY5X>gRqqP{#5t$l@+x6X{hr^$T%M z1K`3F0r^`Q8Xy4W`o@|xbAOIX7FJWtVq$nu;CMb5-tv0{^{wGVdH{FiMBbwaUTCB= z1ef?EvB}MgUH4~23~Rd(ub&DbCQS+o`Otv|q#D{X+$*OKgIV54J)UAv4Kr8L$I2^{ zt+uIF|A#HtLs8QDgT?Ba!f6juGF!6b{dPD5JfM1aD0Lm4oL;sNgr+U} zs>k*dD!3xea+#dQWfRzU0TfB)jz;IrIELwaP|Ns7*D$S;1GvG)H6|Yk_(xiZ5-aWj zBYTC;2?f#+T7>1+SEd}DI(uCH#f##hF=XkH7{lXPGPgg7rl(IY zc82H$A6XWO*)^Vtthi?eiNR{e8r@?U8NaUCrymY0Xz?sN>!Nx3WRE4jF$uhJ8r7ayx0xg*HxpNtp28V3xGh$lfynD;%Rd2ZI6pAV23xr@G! zwS_gz6_0vLCYjgWq}KOB$!_xi3e<`F^NPpbn6`8&ap~)&3!rRz$!gOP5jzAVLOYA?Sl)c>H^hn24%IN@FCG{Ta0o4c~(2uE0IFB-2 z7REldcUX+u)vXoxZnnTJh1<0gCotgt6NI-S0XHUd994(O%wFX2%`|BjIj^cpnIvje z2Dx1_9Nd?lZ=K<`zBH%bXfj^tRCbL726V)OmoR1P>dw??Q?m5@|H|@q z*I3hkzJpG==~}mEp5ssVwH4-WJbNg@N0}XP$jww~qC@r68DGYai>)wp4<4g4dfU6< zO(ny2Uz`)Na8uI_hV-<}+gY>?tR zO6@0U-1f@x7lkR6?y9;&b6$NlD!xCln`RBU(LwJ7cZS-~87&Tgcy_ zEwJNtQ(7W16pW~!pU*RVu4gwoIeDni=8QV#<|wGn2A;rLx*<86(@maH+xi2?XAX4G zwcVxy*-)P1`55ft3~_UDfB8!ymN$jC8+Z!LVMJGllYrqX;CEfJA}}R+ZgMumnt|(yiQ-) znoH zhH0L9yf*MTGHFEuL@}wHM37pe8Z65AsL=+RM!S7AM$k>|7}2c|>iRVu)lWR_hL+3D zSXhfv54&5QC7GpU#`D1=7boD0HR*y6xs%PmgpLv8~lN%u{;R*2S zQF<+K%tmfDck)(wf+_d<`2eeZjO)1+DMd{enrgr?M22jq54{zruS=T}Rl_#95gcnH z!wyE@lJ!#cK&Od36%Z0E=y!j}Zc{vu=k4_;mtX%`SpUz>owA9m?ElS8=XCas`>#F_ zAxoNK*1U_(L%iOhJN{7cLn?Ty>lR;fmRL(3!D-UhfZYe1Hoi32^M3+PTXBC2k?t~? z^J6enkkARcX96IxA3WCpC|gS|@)9mFbm-u+@!zbk*K~IVxf;Sbs@O3%E2XwG*XGfG zFcPj3VzD`tn1BW6KMdYBydx85Yi%oV;CU0<`s!v@{~e4QEh*IKcH9o@IM9eq3_uZK zbJ>CHZF1F)FswVcMdn)em@Q{K`eJo)p$;r!tiN1a6k{#F%3_qPw$8~nMU{KpS9t&S z9D{!4545v=p}U!FZO+=N05EPq_6*TbuRm{fe=X-)B-^clc+8vU+fkurD(+#qSeDiq z?X322GdRMvmJ)%nm@LCIM#dA}ud%5)g25mu$sVpO$-P|`Odg2^4syd}S)ERpmM5#* z&55%=M}_h-27=)=y&>A3PA!Tm_5`JD853;O%~*;<3PNGgoZ!wV$eopWY~p4D>FpoR+4Wz{_kw=;%Q^C&Fh}@;SMtc6e?jBl`0~rx*J@eF z?+&`xdu+#d=j6;n_uju()q%vsU!p{J6U5!vV=;%NKh$cud-Zno5x{M_C|Pz|ZIPoF zZD}QueBJps3*0Dlq7o(07VXhX%4(K17W))PW+5FHSWx@+DaVw|9kFf6i21H(Q3BhP zG9j+`Ji(qa24w%!5DM*3Ne<#!*K1*T;z)u@-k0PDqnUGE454Wt4nND*4qd<=1mO=S zLQ{ub8o%!B`h@5M%xA#jpwjFXz^ROmUm<|Fp*DZP<@S`ofeU>n+2L9^wAoOypxw0R zD)fCpgrP#e1SAZ)fp)!&Jg07S{mcE&+5a2&Uv2(lH}A)a{Zd(>Zr*oXS9({QEjwDg z>A{mJx8M;&iL$co&rcY&9NL|6#>7l&c|Jr;RkA4mor)P;9!7ibrZxJ?{#3Luz{~-d zKHsn*FaCduq;5nqox$)6H%%&mS)ylj>1K$#+92C)7_1D0i)Fv3KqZ8!7){2zl4LF* zAX@tvr(i5J@C1C;sf6T#tEFTRd1_e|yB<{VY#gX$f7CX->jcLZDNx_w(PW{dk=MeC zd-qX?AIMMlL5`BVn=fakT6XOgPU-P!4?^uE(hjqQtj_erRNqh1MMy`*Ut!Qr5V|o} zlxosADw5T{T7IY@jfRB|Ybu-56_3}bJZt_uwCzx1vI8A%BrGxOgaY{_5=4_SX@SEg z#qb(E)gX8D3Q`vC`5y4h=eVng-+vfQZcjzr_6Yr_jg8C{AEVQek;3Qk>fGVd;_3*b zbQ9bMg5CE1>%8&@-CyN%p@GW-N4QT%76gW{_KP=hvm^_qEhsfPlE)YSDLJygNhR2q zl~SoT?Ur*ZN=VH^><#|-X%~J(oc%O4?x!(rK7%&T5;{VFC7mE^uePv)dg#6@?-;aRV}??qR||5W$)#{F}Y+$=Tw>I-EuHXMGf=k2Z{d{Ts=^R`%4 z{kOLl(vldpE4C%3KtBJdm&s`$`u`d1aG(_`J(tZ76QX9cNdG=P(=3dFvdKnl-a*&h z52)2~T7ATnS!RGxzmJW0ZLDrPi%gpKOg-m)mrhHj**0xW&H?9wl>kX9;ema3Tb&23rlv;MC|yUm25R-TV{SOmH8(7p ztHPH!r4icg@tSW-W8>F!DGqz4K&1y9nC{?SByTGr@;|EtRk79LUqOk7jM3D@e)r9# zAVK8?=H+6-8b!!!saOLaW61SD*w`pUbyX6E`Fm$>%l!F<1s>H+17F)cSem_K^!_bc z1I?3%IpBN-3c8a9e6jQk+c?eHkdf0Lo_&djA$TJ6b}t@;zSj&J?IYm@n}4Oas%G?6 zW8SpK6#&_{YCY&gp!~j|!WF*%s{Mmj=*x6f28FU3VOKp|+<1=#YIBSXtv3G;m{Z1# zus{jCfIEXt1_w+o{WXNye0{cU<9!aK-FXZjHj9vF2C!jB+NS2gVI*^902fY@YfYM) zkMck|)$L?5o3a?%(hL|4n`GWNA->m}Zs6B9AfW;u{h}WO>h34(;3#v~5WZ=D;osLVtdraYA`J(x|s*wH1VI zAw9e|&@S^+)0RIix)8Acx#3w1*2{C@>c)AGdnK8ZOJ&g}d#P;4wnZ1k^6JX!CeET; zOk>xju@iZ!qRK0L(z0!FBG;CQ(vDs(pi%b&1?0;un8P)O(C4It57n6OqH<&^!SMb= ztt1N7r8_wSMp{%&ZWNy4>Xd>aa;QJ%P03|YPmoDeQL3v8QUZ4PCgQ z?{D|rO+*{yJGmL5uT_{^U$)cm6O<+-2*)$UU!CKX3n^Td4s22=L@7PLAq-4@*A4&d zg*p%5Kqm&E_dM_>=`Eqx2TG#l8hJDLIK~Vke33>0<5H);_1atoG^3+J;u=7?feCx6 zhAAiuA#h+U#E^kta@a^QJsc(#a3#SplswNfGR^@Vv?Q(oVQC3qI%f-l__9XC>$PW~ zc-K9^`}a~n2(%pvNFdJl2LqcI9S}kuDwYMo#SKZW#>W^?#aIHan1Z1aR>7F76&L`> zKb(P6?oWMulCVWj6=$Rj0*>ZBp{VA~X(!99k%Cy$&AT%>$@j2_`hwD&gZ_bpQ2byF zEm-F5c2wK4Ri2KFuOTz5L37Q7I{oj$HbIh5oYEzqk%d*3Ma+rhk?P{vD7i!%Y35ne z1W;5l0?*_~PGH^ABQr}3GWG|!pVX_5o9*=QcSCt_6&TM>GURrBTYHRmXnx1fo_6b zq`a8d;sC2Yw1AC7XjKctS41!77R@bYxir6Yyo_bMrv~S8FYIvV`_rtyquF<-vi8$i z5GGH2e-&qD-S}iE-}if|o?hK>=lMKl3Bbmyv?S$zHv^Zu@nS3~c-g^XZ22$Vj}%M< zq;1Kk(HXF}7M-UZ4hD&W=i6tz@ST~tqwia?{YhLh;av~c&@FZ6q;KiZ1{DM8c4#z` z=TeUJ*|vBeqbGlmF^^1BH7qxYVr5Bt4Q$=XJYf5#3bR|891~y6)?!g*FUq$`QI($m z=I*(w5L$4%diBtdL6kaG%sfWAXh~O@bESFO@LqCxkvM7aHj*XG%jtA7`6#>U?w}_N zcWkxo)K2}6veiE{MS^jpE>30YOlo>7o3`gXILg_o+Aa-~=^IqE)~Bq_4lR`AC$y=j zLe8sQgDfV@?P{=iGeDhQbU;nAf3!1u3XII4%7>>6A_X^D2;M|1t%j`n8vb1 zxyshqaG1u)Z;2bBVH%a(b^4~LkNwT#Sj?^y&W`IwoWq4;Y*P{^`yTtC3~%sYiY^2g zq|a-v3RYF^U@E-}=&UO=en`9T3enpJ}ENyF8gw-F_g#4xnbA$&Q)o zaXLfw7cQbYiStWuTm6H&Xjby&qR%#H7K~>&OckqH!`Ng_8Hdq&tC6)y`ri7y z=PbzJ(H!MfFFH}L3QaU{W`fz=x!fpMwA@XMv{0qUQvdl|#83{x-l3_SGza>8Ho1JE zBvwW}vCgPY{D&Mpn(Tr23qZ^|Yfg?0bu|aurH&TE!anV<vJ;L8WFLxYY{l9@WL8L+Bc~!APD$>1=MF1p$+q9x+fysSY78h>fy?!>LFO?3BLIm+2LFX!u)G zoa311(GS@iliDCGr%D=&`+#wePqd|@Ze}L3H4z1%0UG@|B=e7{Eb{I$RpHg zUrt8-9Z1fhQ}!er9dwsYeeKY-%-u(!zPknbc)|#In#^CMWKi+Fy%z;tQ)ReXk ze$Mf*ACo&LlaJD;;)k=y4Lj)~KDJjt@~b1(M3paW^YPu49e^zm9`T5;nBR z1X7y8q3Ee0q-cXoinL&0Nn+9CfhF&gHV+O__{6P;gA(7TI3v;X5*^IU<_|#hHX-ef zgx~*ydV12+UbI=6La5B7cTR9bgU`~}ic&G(@?gE1u3GdERgdfI* z8vFlo6cJb(FY{AQt|#V|e1~J8lAL?bbA^C&?J-E&TKFyi8`wxNq7OZoJcbWjeS>%7 zw!NXbQoTVQGzvtngH>!9UE-;)mw=bUVEL#rOqmX{uQ(l9+v z6&=wQId*m(*02(OH-oqeEYpgUQH4Vx{S#l#9tRqKUHbHTAFI3ZqhycLZs>DYb`@4a zGR3XYy$bn$LJ9S$9mIA+h487ho%!jcEe!ig6tXD|OfEW-E1b2jvT4D{WsH(56H1+y zLg&@a)%bC~P-o8d`A5ky7Xf^Ub;rpG7edu~18038MuLC7((GtBgcvqTU}m z(WN@aby54rad+kUtX_sST!q@GrFTWQFjop~0+)_~nwFwXJXQ%(QX6rq3t_bBT~^TC z+2h4x|DG|_x>6wXYmqe;?2o_sHI#p14Z}Zvu=-|+?Z|$^7F7zD5oQV=xq;`oVT0cF z${Em}qmCo|D!Nmiz;zg<4SZ4k*xzK1FVcaP@#4Mq2vM2aK=<7816@8BzNH(tnp(u{ z6Eb5wA(3e-2MiUXU8cf!Xt`gUC|7W`KeJ#^y_6*{@Ru8dHP%ZVH+GEmOF*s77_jNw z+lI5Q*KP3BYE)%Atzs$#rCdV00|)Xf?T}u9ngfkkYAf*ewQgZOrzVP;O;P3Gdh6%U zUbHI0`&v190IEPFx$wQZe2ifAVqxLHWs1*u+p`&C@@#X>;MWktbZPJ6%S12923Fg+ zjUsUDKS@61(b7?v-0hN`UcEe%2V>P_u0E9+1O8?8cKq}P^ygSvepLK?w=6ECe(R*W zFLSP$=YpL$0DMve68R=!|tSSukfYpM{ImgL|zxMTOTB3*Vu797ao>O~lnp_PSc}+T5 z5|tPT818l@xdO?P*(-Ra<4i_c>;T+WsFct`93W32;Qa6RCX;=ZuXo6PU{0q0Zp$KodyGb>EO`csigkQ$ImA=^mI|2Jf{NE0T_VJAW z%Xw8ho!lFK8$t|m_54^ynE}A_mhq1&uXSWI9Cs1;~(K( zVr9_8Wuv>pJdYumdXLniYEH=U6;z7Vff-?k%eu%n&xn~d8q{nY0xA}kRH);q3sc9E z3JgmqRWX#YAehO7VHTGKa|p2*L=}(!j2pOcPDhqJF{PkOSr<2WHusau>rlCN@|(fk zqvI#-K^lDu=ivGltYL5Qw_+|Q#zD;wysd!cbI)+U=R>~>(&LZOgc}eAWxHwcddBYX z)Y;8%Q&qDZy=rsJV)wR>XTb;q>$Sn848hWBUChcDopzI+Kr~tsH-xE1oOZ!5?ZVN3 zoyAOnOA7rjlpjm`r*0m{?Zq4cb$>l9Yk&|o`*lMV#oHy)nLQ5@`OWTQbjIW3qCrvBH!)}Vq2XdCE2Iu=`&!Tc>Wh}s& z05d-Sb>D}CS|x;W&t8r z%bYpg+HRw?xb`kxCPkSn!htNh(F(A5j|UruamD#{`EEBQS|(q#oLn8|NWH0VX6I*3 z%);UdCGWk;SezVSxQyE*Z0b5a>Cvp($)g23>Aq+wJ}-6L07e&JuLxvIZ}6L+w7{C_rowKjpF z+NAvEvuzSD-ee*jW(#RIZ34_VQ~e0$qtG#1b*Z(w;PBUU59VW0G7Q`G3f!O$U^!c~uV^){D z>#MDn*7ceN+Lf#HIB7KDgnku}vB*w^K8`YGuMM#{?MAT_IX+XtR^)3ZjjC`=Znnhb znnnYo#4Q>PYEeQ@bw{xsMbu0WqK&0h;Czkn%$)LOm@AG>wy2_UrS)cH=%><$LORkI zf@qQn4T{qSn6R69g#;||9lW+&hoHvE0L4OMDrMYy#+7Y>F_X~@E)1%O#grHxRagUa zS#R*I3*xdE2z2T1+up%Nl==?*!z;5sXHjF1BqeW* zS?bx3g@X{)TFXW^55L_!K!XNAeHP;2qDO$&Y!j8D}_P&6v&6hXt5GWoE@p`uw4C{DbLlSg1AbRY}|pLe0}6 z?N*ce*8(ORnHh|xyAzGWP3dW$s4sA`tK68^s`Ks>r^V8D!3~AD;m?XI`tDZ08OD#_ zFS5{vnA;U#@CLU4u3x5`KVskb-)?nJO^v-Ki255)5uz9xA{2G5 z+CF}_pB)g3mc5p_#-rw~VaNEu&9XH(ti^?0%sA7Mv8Y&W2|DpWsxPrW?%OxpYxo=A zXZvN)A%tbS1tD5tZ;L`3*p3;*T3izYZJA-F9@XnjB)RUekhn>Y8aPa5IJzs~Kb38Y z0w4b*7TwYp-=Fa93)liqi@G>t(LRR5gccTC8ysA(-kKt8e%R0({<7`Ee*-_u)}hn4 z;kddbuC-QO__JrK;qDQHdcX}0>Fh0?Z)~waU2^a2H`MD8yrHw*K==`l9~IXjH$@gl zQ7!~GBsM9PRK;#DplON9np;mhK}%$uP#MtP>ap1em6~Zv0MIW zdJD^M5;!wqFJxs$sy;5xh?L;7B zm)DQ*JF99Ns;cSQTDrQb0W^)#DHa=UemR?tPfQKXX#=To=6qM}jgEnw*VtWq@Amu( z&dTjvJ|X+zby~_=*I4s34Vpfql220YTh3KFPbA@&lUeCIFZ+%RzO*<1wDvgWZ~Bt_ zB)~kMv$#gg7s_>Z<3VBD6pct@@mUW9+iQQTtoXI|F_&wvsJu3a@|aV6T#J?TMc}m6 zWP*_kSfUIWxKX0JwKw865lgDnEyM$)I^_%uPaFg(@&Q8KQh@qR7>aC0jA=6B8U_a# z{ST^KpowCC?ro8cd>%~g&*;%5>#H-evNF3D>ukTo4vU02)SnC1wtSQ2Cv7`oOq0=S zV))tu$ zBiHu-rjana+;0f&c3bP?HJ9gSgGxb_hYj=os^c}$-Hsl+|M8Mib3GR{kEw-MY12`1 zakF*5Rn$x=^f+CG=NHEhpKBCyt;Iv(k@av_0&$;lsLdyRW zc^df%4i@NdZSxVIi)@UxCuEGw{F=dJ#*sLfKGED*FD6wCk5Pz~Jy8AcSv#f=nLSoV zOG8*prG3`sO0GaGHPlz`-?FZVU=a-tZ#ozS7nSNWWBS`+ouFumSsjh5tk#%R-Jmle zO>ow@eXs-8HMgiRBQ3Xh>_(%V&upNi8VuDOnHD4TCE#(BJsS4BKn-w?*Be1=BlVjm5Hx7~Se#%=hvb6MDV_F2rug{;J!*{L`ahpnVekC5;;C)Gb2 zG@Y)K7OU;L$!3X@a-TdXtGZDV`DG+J-7;%d%T$DzQKJb)5r5M~^6UM^xX;F?j$B@Q2?e$3m+o5tr)<2}J;*C&RZ zb2G_)IKL&{*X*bIVsT`QJ9)WS?(S)9CiTBcrN8R+sHVbAQyWg!JAZZo&PkY2D8oh< z<5K^K1z)N^oI-ch7%w>S4Fd^Qq`fWt*%LJ2E1G4pW`&z^huhjfk`?KgqAo|5Lcj>Z zMPraU?l}(k1MWJ&IS1;vwhQX-{Qrmx^BOW7h56DPNUpaVpBFwP)rd7#pY=dvd+n+6 zieGC#pcZRym6v~A`xxkTa%t&8ZX|DlFKDq%jbKxWqmwEcxsruG7q_D^-V_IKf3N*I zYtC1he~3Gh-vTJT%dm^8AH+2{99yUg_T;L;PpHR+PiR2Wo0hJmIjf52H3mCy&4p`9;BQ2Oa1e;sPS7olN7e9V5644C zQxT&;^q?c`NA3Lk(bzP;9re4ovWeEa;k@N*Mgzs8t&U#5mmMc@+qtT$wyL_Ws;aKq zP5*nei?uC2=88UTr+4xBzp(DmyybI?)3nh|k-z(9EU*4}N^uQ^cJXd;YI6D?+xAXn z9Va`DKN7-Yj@n`4JF>5Rj0eQ>zT#-~Xr3|Kf{YL2c!yrG&aF@q*CQ35VyMC08is$Xes)f16KbCsfnI(-AOX#c*;w-er+mJyv;#CB7&zvcPko zQ5yDy<^7GIjzxB>;rJA$jJBggWyjP=g$*RG)ewl@I~a&m;lck=Ai=FJ30pZkn4h zKaDnlBHE-V#5OgYH8~tG%n!h{4y;_YmfjS1p`O^;kakZ4cs!^nWxCoTs&GGsk@%#l zb1EeoQe8ah$^ka2u*tFQ7-+^R z%`IXl>36Y_7Gf6|JvaSl&+VeBL8G7>pgbPH#L&9n96dr{kxhm@&KhOL}dV zSGy6V=nMCx62%h=bqsT%>bTQz^n}ab;$Y_JOd{f+|JNONL)acn(kLS_zb{lg%2j|3 zEO)pybRg~+7!Mf5*EG7exP8qnjf+CftzM_#vSyLyvf%Yi3G~~nXpy_iW!1z|YV_JT zV=%BrGk?WM_K40#nvA#|WR1~y%~f@E$TKlf#+2s(qpJuiPW?%gfP?>|_$syf`{<=o z_8AP$PZ*TFZHwM>d%3T?H*mr}z?^^8sQAo(?DBC#h=nGUsyK3iN;08@!N3gW8cOI` zoadSgxxr3H{jS!7zdZ;Kewl-dECPRza?;i$khvkgTlT1n zC~7sn*GqmEE(H0=ViM+4kcBkp3SFyyGLk7?)mX@Yy8=7UAF<1XRfw6+)P4S~;&rYb zfIkvWf@#AQb6Jh)5BeW%Kk5em-#Pl=aCKAXFN43X-v*I?0r?55yPRyODyuR2>Jw_m zUe`Zgo8lj0rf9^Lhk&CokYN zhOCmgJF^?HSY^6T#FypGOf4ihgD8@@`QbGc$^7);L96V0IsD-X`1ff@5Lm8YV&MA^z^w6{jU80<^|8{n%ZL&#qLd7 zyH0&nCOfLu>57Jv#RIlbVc8d1S*-O@(H( zyG_YrqJ04;} zPq;g&fey9wQq5?^_cV>p{l1D23CeG2zR@w3b9~^&Z2eVL3uvG@W_|tb*UUEX<<_us3&n15tcXta~u^OtV)i+FXBY!L2arHG|A z{g%~n>lemGny_eu#Fff8x((Hd#gzyWo7Xd!*d98Y}VwBR(ztIM2faZ+|cQdeWS)^8PURgeTS{)jJLqyG*Rr!ULv0 z4r=h=fUVKjv&7IU$!X0X4qaDre?~0n_B#a(p($*7XTeS_H9`Rq>-y1+*#FW;XgV&3 z^PR&0oSx=yljxC+AnR+}^UEwfPO|>cm zJ`|yupl8f4FPSH`H`*id=BRkA87$|yqh_r2AVak*eNh?w)dp})oeXb5(N5crm=h10 z$KJE5;xV<^%!pN#MsE#?v)(h)=h7-v-ICB+ai7B)F3nu9Z^hq6yU%K+BQWVaeDyt* z>YiGS*i9y@%UA1F5Kvvj60~NLSjsAoh1Et+m8(%lxZo6m|JpDZFeBm+fwO$L4{?FZ61IylN-b`X(y+?OE&16 zuYO&?KcUtrR&y9gv#Dw|_+1Z|cp7-YXy*z8m2aVlbc@cU3bY*!8D2#bXPkxj9EKZB~yl7<%B zQUxvDzyJghmsrMSJ+}anyyXLkg~ZkBn02jH9gnM!0{T$?4+a!BhBOu}u=))M0k4Hk zx4}$+zKdVkr|1>>>4h(SJ1sW%fr(KmW9H53+R*3oyn|A#U=)a_}j6xMQZ8j@o zO)B`v#J?PE4&$R*6xU1$u%wxh%Na{F7&V)5^B2AsuVO$gk>8NoB)1#^M_5K%v;TOQ zFc)DZmj6$BTEMQXydQh%8yDPnx~2(Ei*jF1n6tdILdX5)%_aq6v1O|7m7N!#2}svy zI$~5>`(7ChT3VfVHg&8MLzrodhukM@B##}P!Fq^3Nk7DL)PDT2%XY$T7BVS5pW|DA zw_LxG9q#`w{4TnZP5<@p447o^W|IM({#C+b4_W=~YngmyescMky(MDnb??0|pZ)Zo zPbWf3crB{PHu|+$_U}5oWwPV_DS_1cS#?aM91ggZgu3{t--h(%5Dlbc5s9tIfbY{&TdawGlUky;p7S_U8WKn=#GrF5{>;W!-H? zcnw@XzeQwMDn(?=>-vr zrIv>*(#FaN2;0tuU-_U{*rZe|H4fk&ztWSEq1ClP^F1%{;WcpEYPa+;UN`@hGWYr9 zbTuidlT=Hc0-^Dfkf=5n%0)%crfol99C}y#q{=9^~)1t{oKD@5k4^+qc-ulNc5FS7S9h)`B!@FDdh-QGs&&IsZ%F%crKh zb4-B5D4hwC>_mCd4ExT>t zcl`qj7bJ4Xow0~(;2FY3=sY#;#hn^_3-sc1^aX&tA#^1hX(R4N>Zcv(MPG3QnUfFA zrbWBcjY38-aZE1gZ1n=e27fb6L$D9E3R zz4xKw@I%9}n)#8{7SI&AyxAse%&tY%#!!VmUg1*LDvE{H3Sso}dj6-21=6*drFm_G zvQImaFf|ItM=g=>+xZByDbG$0gSxZWz3qpVXYbEeoRD97>d?B97fPD>q;oX^Zt)kE z4s0T-z7q&5wr#?+KX{GZ#)p3&+l*b=3rMkKMLl$KbE=>OU$z=;zI6q+Z6**gnv{F3=G4E9@`UYwN> zQ5w+4(z#`SQX9uNX8-9V)A^X5=rhgK9#R3Xfp2eN70kIY2hOrOL#}6>Hlfk!$C4Li z)#N)s%IX@Po!pU1n%&htG&?2qC7S~>?}uLPK$7tZlS8v_dYWHmW1<}*HVVFz_c1r8 z@yL7;K{im07nolkW0SIHk_8s4Gvs{61yO5c`f*m_WF)MfMf9;Sdb)<82?o9t*9o+S zG9AK9X1M1YZ)4MfHc4D~_Cv3>GXB|lSShN;-UBGK?&3Oj(V1Y>d=@&g_DF>;9$^_D zUj5GPTF^TN{hN~~|NIZ9MCWf#6z&`Obxz42-i;bhIO2Z$+Y=ok@?~sWw-6zx7#BdM z|8nD(_ja9Z5UkQilgYA6)Ly11+PN|Nrl^?+xjIGaaO%}Ck~{{Civ+CtdX~WWb&{o} zPZkwFQCj*$anX}E_CjM9cS6E@2cU_3_#zBkW6<8p(DIXzx3Z}%9c{M09NB{_AMi_} zJ~?NPR*d(ZX00%+m^*%jA z@=!eHCryermTE=&&K63!y7$lOtDXhZ3E7dDQ_LGzNaovWvn(*hZSnq{G5wpt@vAh1uldnmDS~_uK66e&8X2 z8dlrjzXQl;(}>2DV|TyrrITWMb8zOa?53_R6JhU45l^oS+dTX`o^et!II+AEb4#zs=kXY3&wu>kxt0uCTtQmUYZ=tya;IrT;q;VmP4U?(J1;>l~5Xy~uyp^qk zTE{%_AbI_QjsFNLZ>mr;GmG})4-m1)F1ex6fJJ6GDw6fr4H%@b!$zasEEnj|HBSl{Gg05brc*|pK9G`Gtt~QLKxYWF2H1)EpH-wt@3#Q~t9fqo z1TW*jhPql+K&}M{}gWYd{##Iim4%lj& z>E352eu}K&^X~`*cdC;EKD*dRmPhP#@>JHG;_$9=97xDd;z6fbP^5U(R9f>53KZSR z3*cK_&OwhYWYZGH%z9Cggvy_3(~}02_Mvzr;IIht`TTsL5Spj~-1>t|IJr>cv^& zP=0R~*qmMji!?Z${GkKe;dBnUz7$sTc`~7Z&Ev6wXuiT(vy;zW^zUlp#Gazc8$9mM zm4!Q@Wv1%hYW_~A;uMjsx_F&_8T`YZ3T+6p*+Q7M+IxPDNw==JngD|NNCZne98>F{ zLN2cWs#v`r<*Gz3KQjs3x3qX^1ZxU`oIh@deq077WXQ&WN~)AqW{(kWxOsmF%ZdpC|H8gsj~jJc3us}7Qh@xmqD?>%WL@zWbIjJVe8!zI3H zChK=f-A|_-H$Aeg`^J%QH;&}qT04&a%CupPJo^I+8aDay70P`I`r2pR#KztHX$4(B zUC)V?h>J9t_#uQY4lO3z)%pZxkTF5c+yv}jkBoVJcoH?rnqo{KubV`MM%v|mD$9sg z9Q&If!>T;b-dLp+Rfn;ePm{;mGXiDc^N?wXO> zvVXqnR;%#!@V|QZ()Sa5)4!OmyGV(iTu15R;NsGG%tAi^2dgva9^{?KEV0`3a5apuSE51g4cuR_Dzh9sof$0rDLQy zf~$fYDWhxg$yE(R-*G>$tNWbGJG1S1hI>$R0biw;upd3Q^T^H)JGbz?ctE+xf5fM= zxGKB{4iANqsj&bWz9aq^MvOgS%w>UdvBr%&kqe)C(3XbwQ~l@zwn0NVBTkcqlE{q4 zxt_7gQ)A7tcub0Yra^e&;GDlaTAf}Izh0BsKXCz$}I z1vasWfXw4fqk0VY9XM7mYZh?8TRPg~@D$R2Q%;$!}rMMZXzpaB$(Ll@S(Y!8ShI=75^sR5pQA{ zh-TdKJF=-eZU>J95m{&nwh+oO!+XT{7H1TIQ6&h0fPX{BR$d21AYk%$uM@zPX2Yaq zwMXQSWd)M+<;5bfW{6G@9d|Zipj4e2WE}QAE{ji zJ}u^LPa4$+3u-ra#&<#{K2H4ay7z=>P)}rbZ`}<&V$PZ;*ajYI@9!){K?@)6{f}0) z4T~Ht2Een2Q0)^$K&(XVg=$y>Q$N|0kT(DZIc2Q}qh6$=4*>MGSAwqaucdDBUaDUVDCp>?VzkT4XSXaA4jOZrn*{)<0_~fAJB1hJo(zz+b0c7EEp1|P;h6n_jY}$ zA)5kVIrzsea2?3rje)l{>(6pc(9Pl%J$QKU1d10`jRs8+^~w%32I*YeOwqP;+8=Kq zj{fk8v+QAAmc&QBOpZ!@xe301-Af)oVCIa-j)@L46L>BI>}4 z21g!?T`se!B6M#yuH^F>z+LM%)N3{7ihB7wR|UoSXPZ8>wC!zTcG;D?dlq-g6`58W zn<$y;s)zRHR+bEhH+VJ4Z@!HS2eA%v2H)>raYRvMChKUlkQ@Fw;g6-l)OW5zfI}R^}8TN z>Z2X#0d#6JUzgGCQ=wziT$M>rNU(&WLNwn-!>j8w^L)3{Y+@QkW zeZmb|YoR^p%Z|BbOpWBj1%2_+`xbhG5g6h9F1P@)X6Hx$t{nb-u3hqD`FkLiCVX61 z_=KVSXUjCJ_a?w68nK6{RYg}l57U{g1aqSim(rjYxE-hjuV`p$IGm?ja_!v&oitJ^ zd80KekFK-9OT|nVj>}o>Sv>!|pwj$^J7%rEoB!DG90iw*3I-DdH^EFi(`faJ>n}?A z#U@zGj3wWA`_|$%-Nl8I#%5NtXZ8hyU(n>=SnQe^zlTD%!>)a?YU{l~;5<$G=65zb z_ZufCVC5dD;PEOBaJesucs$Wdpm20q>`-@SMu<^7*Ld(rmju=0q*^)x6PLrw=tHd? zpn;L)o%*rJQR44&(Vacv+-09mY?h0q$jLF2bIQ>ik=jZg1CtcrWjh-|Yg7KOODGog z_@ghhwN*nkZ+z9QnN#=(3ZZG(g~cl225mE#xUo)($?I(65-@WYrIir7xxU1Fl$vIXSrfJOu@#|BYIE1d6$T79faX>J zcY1CyR3@^#^O2rsP|*o7hE*p-6`Yh%q9&d34-MOL?yVwBE~<0XFP=-ClT)1(06#q} zbCa5$-}?9`{(b)^Z#%|*1WZ%h*WB=~RdqX)8~Q}%Pm?mnPEfDA-5;lpt0CmY$R{t) ze(|IZQD>zn=34i;L0 zyz=ENTSJ3w>I>0xAVygMDkxgFO)42ERs;yQ=e%d((rUY2JmE3?F&YOm@W0!$y_huT`S`@5)Vrc1NG#Sfdv_;ZJ%wjCL%%*+(AW7y+32v8^2?p6hKaQ{9e zd(mj=TjZ#lMi1tS0_X1fdxy>kT}uhqOp~d{k#?H8T_aZ&S*3IeAh$a0ZHl^sMSt0; z|3?dH|Iyzizb}2dw3^?K-;)6Tt)`t_(^gXxs$n;NbbGPRhi)L3uypDzEtZXzUUUr= zl!E`8pId#Bx>3#b|Hcn1kH&92#sYA!= zkJZ%)Nt??N9elk3MY}TjH2;Q+^w>C1T)una@m}_@AFlD}Inieyl&&d?=au(m>LBjWLrH560|k1CM19wU$m*xsNxW$Zc9DX1(#_Q z9~a^zbrSWf&wC{IP;#nQ+EEcygyxo26+UV%ik|tTF)xlUt(_p2F-m`#{`EBhrmYmb zy@+9P{;vZyxl|p0V}tq;|ab zM+=;;NgF|&c$ZU z!S(f#1B6X&UOPDIGHAa<(THzUg_lsj0~f^m`!^yh*EWX1w@qxfgR zscFL$WUHfxidd^Qbp-hv1Ur1e6Q9hkz1mF59H`i0p%p{GyUm7}g|ji!2hGjnY;76C zbX)5poAi5b#(nG4A;Et+bDUb>D`0>~bL9Z>WA1(T?cmTa!_ZW8!1~5itF`*@oYO#b zuV-NPEaNQoaeMAD_iT2%O?$0-z<>F08iSJyhFS}^%s$P#iGEo5^tJV!2fDA*D2R5A zp#>FVhLm$56tn_&*f9t*2f!8OxpXdG30$Rv1f?)BYG~2qr!h*O5}3%$KSeIDqRl}s zK;)wCb01NC6v0yT-5m8vr6U(GOodwC1;2z2-H}4-qeT!$QkM@+%kL`4%WAiexB}q9hEa` z$}gI3g?R>bHJVIse)mY#x+p|HPZxzcKS6}*PUoBJO3kLQE=f9xy+M*VO>z@ipXG4Q zPsG{Pn&Hg_;J&7%@7E(X7_^I%c=pIJ7BI7`$rjm5q)k7>5%FN@%qO4Dq8N=#d$4Y|Sno;T zq#C&);aKFm4a7sN+;c=CG@92%TD&Pz*?!xQxS!wTMK!up!4-0PFNy-Im)>(|TFSZ+P}x_`6LY{ab7F?P$>{5xEJ zy9{N0AyfWCIa+obOx&^oDc$Tk*DSCFWXZeIWV(xbZ~@P0C)!%l2ee)O=a&aGEiDr< zCf64o+=2OKWTrBAkRA(jX`rLdQ%++f7k%KZdbR#(ZfD@ZtZH4yfP0D|CmE4CFNJrW zXPB=$r=xvZSCn(@i6VPgepe`Bo?n0!7GeeYI8c&uY58g8qf|fc^2F2ij^(#)nptXx zr8F17qu+vsuDb6MeppLNtDgb4Fc(@wO&;u45`0E_f`(RY1kJRLP?kDL^mdU6w_Fsn zKb>QW+Tr+of^)SOl|dbRt_lx#uQW+h5hco$YVPNc68X8i8ZtYg+qzDn^^q8L#LBPr z-Kjhkfn50_3H@6fHA(7wnp417;+0a5djb@^ zmtm;0i1s2Bcf+@U)-UkbyG46@Az6ps5H7EiFjqO7n^{ty^o>5;(b)cg9Q$7S_CEAt zgR-%0dJmM{nvJW1x|s%~SM6pSlmt~XX7rkC;f)n}KaYNMWpts<7W+C5GZ5r~pU3w0 zZ`w-t8srWYJLfmZM3c|s&v}-}*nxS(@%b1&EalgxFR(8o1K%{C4b)k-^6uJUou6C& ztWv%`Zue*7=iS|N{8cU7Kj;6cMSs@Z{ zBvAe5r)urF>fCRy98903zXkx&m}Wvau)LLP7o;RsiYtWXr|w*ke5M62di8=IznT*y z;-)1FA`igh#>(`Bosb7qe6(6;>blb&+a0d4Fz^zhZ1h#;NxGqD|A%e`0)UJdo~O`tP2zPx0z!@*<5? zzWAcky=32RxBs;VyPY?*-XTw(zf5%sM&9~=?wlyCr{38&G92_!pNhFzk)D?E7fJUpvhyj*j_w3D)!C7cxB)g$;E|eqjTne}yjub1l z(`W@@XKZo8IICbrWqMfqfW{)mNNf38g~3<2kbG$ zDYxq+=jbidAb8aaPTow1yOx$up;kL@9mKADbO4GMDgIFg1J-(FmEp6>yw0=q;9tu0 zA3b_lW({sd&4nhE;SD^ZJ&^j_b`-)d}-!m)SGe{KXpJ#aY+48{YY@ijB;%>*yB(MsV88+_A{Y#VddF@ z^+ovF!l3f3&~p%A#XCvN3h#o<;tD39Ar@uP(1`|36kkRIi<=2M2hs+^DSxr@Y`z=VPg+w~~b>4IR zm<9P8z;k`RtaF}NT}mOJk^>e`9)9Jk7ho6RZjSO?3{8N0!++oX0ljl#HCV$-&AUmx zHi=aKG_NJO$d+6H&Rqn03A9_Qfudixzk#r>Yh(`eg2g`>FHlkb5MBTAZt+0LCuSvb za1#Evl6(W8>TcbaY&L@JbUQ|)0Pr{UU9zAeiwnByCDCsj9{;c&OJ8eP14ztc?-NQAI^VOsbLtV&t zEwlLkOxTd^xQ%G)uFC(qX)|5nJyLWyf1dsq`8}4BnTNm*-_uvcJTtgH8|T zicOBeEtzfG+s?F!+lJcS_Dty)p)l4@7Ez&SDZ^8rb)}hJxp+7rzMOU!f*j~eW3ZEMjcwBZsSxRy`x#qw$T(jKl-AnQJBRe^?by`RE0&qd=$~2mm;CgQVn~cV`B>| zaTbjROJV(I>%$ekb;!NIL>E!3BRU;pJ5pzeD3uX|^@yQUFjIu{c(_O;u$V3y?KKeX zowpk(j7C$fl+C#28+___|Gfh!4s#}b*A?sz<5t5KX}uO!z2;`d*|f($_+CBgd;CKO z&AYz*!SC8pzt{e<0G5L50sJA~Xnm*&VZ-=SaRzwcY8<%`!@Zk*MSxuoPkJu6SL|i~ zg=1z5r48)T_gxI24BtpdaF4{~?C;Kyge znj6IMsvd9`f#d>q)N>G|1Cbi*Ga z$YBRb%4mBetrfMcv2|ZpacH`!Xdg=KjYg$oNcIBn8HpT29DF{n%n1 z&IP&<;`^z8F3zvQ88=a{0fUg)vNQ^MI`TDC_`JThhiZY5)EZCOTPLPgOh~J-;E?}qF_2h)E!t! zAmhYb{$wSFvR^r5tcVC$dhGD#^pq`!LO4X$oIO*YJm#dyE?Ill3xb2GTQ;X3)}Vg% zp_Jr1e)oX$@+7S4ttA#O^l%hs(SxN9K#yEJy6};w{`F5kWeH&EA%1gYO9({EPHDNY zE4TAIS{nGc?B2chl3xN%u;8O49??>?v?~c%Uc{1E~b1V6|JVaEP@M&aI>Y1Hl5%T8RrIapzP};B8ns}_hvs0M3TK? zMOH~P1m^>61l4G_kUpbAF2v#>l7gy=Ig!GF(j>Pk6mKmJHKh17jBL4_R8;I2FDRYS zpn9wa*+wak959OIqMU*&&Ke7_c_2;7K-tzrB>%L6w3b-%wAGa2QbLL|<+H)`4N>-_ zNU6>#QJLOAmU}w>qyU&BuC!5Vta&PW1qgB1JR9Yz(D@prKye8+lspYXiBcSg4f$ta zP*f_(X5~Ua+(dB@N0x%UGVYS$29*3C{96rDNznMlNy6@)Yn+TP1kcjnMh@WbDG!+* z=aw)D7zak=6`Ry==oyPAKY#-li?B|>JmT--CMD_`L@T{>a;m#CyE3-;W2?6gMDGl& z9R|;Zo+|%UR{26j`3L$N{iO9A$0(8>Xe{fNa5Z7caC$%m| z83Ofh+*naT^)GKDqU}cXW9>&V(GPzCi~YD;qPSpBJZ#Sd+>QKEt;(_Ps^j5rw050r zxt=2yjo)`W)H&c~szccA-OLUhRgN0$Y%5D0Bp$NqobtN4?)uPe@e4`*}6oCJ4>OKK5yi`H{2SSScGjt9_weG zPCY|XMk{w^=W%J><%VcK`vqOhBA61IaD618&ZnRe!Vk^mV`Wj#n^V%TF^e z^PReV!jcqf`XsVREn@FuQHtoC7wV4^jcTG=BmFKMEhw0C$dCG=FeN3HlG1`gN5MNX zImdk2V^(kO2D$X9|J_Kp;RPZhY_EN&7twLifL|b2(#wQPh%56fpLO(xLca(SG*rrK z?R;&`|DrS+5$;^~PYTw%{5^8@E<;F)`=eQJ(Yg$_yPli6SL7MX-lHl`;~KWU1n(90 z-Zp+M!|xXa!rm$p7J{bcFTDjTd$_@c7Ab^ts8QPdq6}-OD7<}!$^{jtPyIk7>g7K> zw6hbn5%1Nh#gXsaeBLhXh4mSs0oK24zIuH%Q=zF>xu-cMi7846==JRBuVW7_TmqiV zpE&R6MGT!~PqRb4A;%s|{z`F4Zv<#W(rl}WKO>g4?#^}pIt^2K*)wbY$(uWqeQr|K zcAskgDoPGFT+R9>NUl@*=@0U!h$5*s7Si1j5=sCaH zh~ImKi@tBT*B7|nUkb!){C8A8@OXXKCN&^3Nbrk#ylQSOHRoJxj*O%&7G-x*AI&yCG=*GYIA zMT6o!JlLpKj~D`pB)#mL&97I=M0PJ0tz2#4x{af zx*?fTxeltYD{Y>7$y}G~gp5YJHk1F`DA(227*_h*yc4j5xsb1vx_PzcUx!M_$yCa0 z#BDb+(WC%T`MQ{BcJR})BOK1lT<7FZ=a$v+o}3OueV(}{YmZ~Q<8;qllgB$}i{>;^ zz)ey`$gtg@2sX)Jn6V`q4J8VYpx$OtFI5{uugBdcv;vgh5VOlc1*$$*O*2vaWAJUk zl$Y7`XMC>qTY266=V%pja#Xwab#3|8^7Wfl3+B$zrj^;sw031DRNLlVQ&rXNt!eXh z>b@wNp`H}@7^-Sl)pS4?0ne~a1N>dXTTDi;s3D-QiW?@4tw2J9i+*F%)uX|AGvj_mzN z6yj*=Y6;FE?yCAy{CKaqVEKorBDhEU>qn)IH{N0H?3?-v5aWs1cY{apd}nmJ#!ysr zj6CsC?~%gGar6{(q2lSswu+!G*)3TYBxsse)HE^`E%~dEm^vj?_*Y2-5V7pNh-uJ! z%V#{G47;=Y@Cyq4{slXg=8mtA*im|&Lv*C@hy$fFrC_=8Uz}ig73G9Hz1MG6^j{e8 z$Br-o!8CIO7(>2Aq)NG+_n&{J z)7LM74@;FkvBaxTGKL09LzDR@+mqg-QW#Mx;E1Gb?~}IOCQIQ8KlEa*H^dfqq3h>Q zGm$Up@^^&OdK{KXah0B&EYBPZXF5bM4-Ku5-u`oidz=T9m-v)Lvi*;=HnxI0H>j(R zAWg9KhNdR<@hYf<+t<7eCmB*ut>Yk+xReUU_F-2U&<7<~F_^1bPDcYz3jIRkw=VPA z^DZP2epQQnca1lj-F0glb{L%IuWL2kAbQ*0(u~c1mgqw8!MlrghWE>$`Hz`7+ML8{ z&ma(AN=tu$P|$?*uVH_Yu|!3LIw@Duh|d$cv)QDUO1lYHpgG)ERn4S+rzy?`cY^b( zE?N8>76|n7ruF#gNBgGo|6{Dr-KnO-^yXu3Y?5<7=SEHv-3^Afb)(U*p4Urch^F2a zJjrd&bLOto)&V?(I)U3%Dm$ga0q1<5L9@@ldD_{Vav`Pjh12O)t%n`uR;rA4dwxu+ zYp~Jq!=j5Tn>H!xW$<^9eqZh+%UxNO{q^LqjiADeNF>Y@Z*KX=MnyEPIO?q%C3Iy1 zgW}{QcLOjNVLyLQ&gb%!v`i&_r{%u)fuk`#0}rjZ|LLxF2Tb>owg=vVo#oQI2tFE=b7CoF4DPGI)is0LK$015J#RHW4G z6BFV+BC=%L?p-@~VP-pbKC;t}-?i)Ehi&FN9uAfX1c<0nv_~8g?Ne%`qGQ|a_?MKXj?1>?IBa4ixL$3RpC|vRW!LYNoijI`!=4=l zTR}LZ0j92Q&{f(hpPWWDiK{h)L~4C|>j42JD4eP{X((+>4vF=8BLB1-C9ZvG?OL-Hy2qa@B|%T@C*|4ny|-9CO4N-j;| z!@j)6zx`<8>mMZV3N-E)+wZ5)*-&f=F8efj6Ox^t&PcsgJN8Za&F*jTMQJ?rJRh5~Q zwcz+jZsRIP;Ki2~W8udsKYJ&yjnZkR@SMGrliIHrloDyB zCTf+|yAau9S>Z?7DBq|EG)t_{(Y>0h8ubranCcI{-$2it4U>e)L=s@m!ftn#GWP4h zjXx}Dp7FU?KfUK7gWN9oXU-BtWPtjP@y0ht4pwIR}TwnQ@>y7ro;bmk8xEMACmf+hUMwkUAPDy4K&C6WesI17SL3N^a6cJ^z<;Dzy94xIzzgp6+kaCR z$FA1oYyNo(x?w`m3{bI8DG4XuCeI)wyV=+?VX+7Zqtl!vD zzq4MaRy#7&#}T>M5=KYD)TcKKd$#5?doC~g6g54>XH6W8utJn_hLJSTZwy6%+TZ3W>xk@P{TeV>Tlzo@~Le|L{m2wUs# zm6rk#1LK+F97PA z#Me^)NZ53-bi}+!k<{u7%Kymrw*m?ArsV4Wieugi4a@*uiE7WlTfF8+nq8q03-k#o zm4#(oYv-E(Xv8ToD^{IGu~!6+D~F9x6anR-i1)OIFrdznMv)@V)quLcof`^i1j4K* zx~e+kivKuMfOlPw`z3@V!Od7&YNb*hXxt zg>~h%bUfB7^55MCMzvaqKq(xj==A=VQ=ex$t$Pxr9EkHx2okv*Zz0t%F?0@Bxfu6r zA(f;%tbTPH7_O@XIN~+H?d1dU2A5R!U?82Mgr*+v_Y32T(0wDGRx=~6A^ z0f2P~lLtS(t~XNyU#=+b6OrqbK;9#1&*pGDp(85l@Dyr9Xrp&&}=!` znePx=YQEuAU5-ODO?+0W4?6C{QKx>3cqhY62zZ^$aY6)=C2@2`T>SE=gfy3H(4N{7 z6aQ}7;276=T&PA9&W5zgEx>mz<$H~9@ww~{-**-Q{@21DIH^#LZ=to1e4W7m`LUEW#^Dw0 zwi-ADi+Kp4A{DMJLWP&tgsXEzs0)gg^&s^VGqJ2>q*g{@pt3-N9=QIu0Fn6BGFTIIFzIy(?2&VF+d0d0`A~v|rgX9D9olpp>7?ltU2}VKC zE-rRRBQ7sn;q@ot{0HxtO%unb||FD#z)>kMoLi}=>g`j7&wT9E>5 z6K4;OIcsYFPcCAACu*(UzANe_M1xru-vp649i(^QU4L(aW<+l;(sfJdS3of5ACh(! zSk$5VBy4!^GWbhJ-8#6oCq5qtb!T1-h#c4GVh_d9r>_G{+A^ECotn7~{(FOy0rpVF zo_YZg{nU=rN+da^ef}VCsmzlo|Gml}PqHKr9PN0Og@%-Yq8U`h9QTAd`>?Gi~ln>0r4#f<1oA4aCDH-y1Br|2e< z8H>e&hhI~6fKd^zW=g@Z(z7?m+O-g6>x3dZ8#Vf74m`m&n@@sI-B+b(F=Q3iV4W&a zU9C3_PJWLr4qy~#L>Cr26%_{(?IC-x?R)0+^Q08BKM*f`Uwk*efD~t7oixmu6uVX_ z(Q7ZK9u!9~m;D4$QzgSWAq*cHLvIKnSYpYVP>rINH4;AT9|wxblU)qmq1N1o8!rZ! z-cJ87m@gL_f>Y&ZBn6X)zv~AF24iT3W-N{zRgWHReAEjUJR!YpX@+S@6= zEQrHCRvbgiSeF;*U=3M=Cc(vGQN&etCUa4=JEBXUsE84Sn(Ozvxmm;V(L;$|2?xO9 z#NPPu$)9myB2rQ0{@x=!T&@JZk>8a^)fA1`Z$r&52%wKBU03!JzskJ-Y_vVp zF_nfAN!-eB_}z}|`%JeM3D-I(DUl?hI(vyWclL#r>@+0-ERhoAKuYfxpt({0yo$b2 zu^7M}6=EQ+Cb*7rPL+gp$mD?xumyTan!tW|$;6c5L$2fC`TKAUDeW5b z!6#pg8P#`5@o7c);g>(UY5y9$SjOnz#2AzF{5d3kLtzUROIIBX)ZdM+yymOCSzHW)q|g>j<7nb#Y>iYhb-wBslYaGYvx9qNOd?nhitN)&15IL1M|#= z!BX-pj%!u4OCqtVx^YQR9dR}LxUow+7*o!k<^Bi3IS1 z2pspjDGT7BA{c|1aQ%EL?o#f-D{myAEk{!j?m!z2>R5?wmDpf_W_qZEH;WK9ay zG(N`D6g&Khp&QLYL6D_s(IJg$HwceJvP!$d%6N0s6RT_K*nvz{kIP`xm?ddhj1R})w)rU+ihu)z;9=|-5KI-H(EdeS?3q4~#&3N8^u z)riCmJB}Zb3+2x2N~TpZt#KIKr^BSV|7z~=TOD(Z=)yc_*qFmY7G_~b1S&~pdHFh_ zR@8rpuJ{Sf6pwHi;kd3Uc`CfY4MNth6oNAA9$Ea)&_O)io=?kJ7M9W~hoz2RD2NLi z0-^r%0x$3aFWeXtlu|>&F^-jEjk{*>S<|P5PtfiE8MU>2dpt>Ar=sJS!LO@KOQ(%a zGxzNUUf23nP)#qmTb`FDCf3ukJq#{AKEmVw^Z6=YKZ?vb6t~@*Rd-r^ZDWD8!z6l- z9MKuq5nrgNZ6e9|hDfL80&C1Vb6qCz-V6(d7}GnBOAYN6L)VO&ry)8{me(23Lui6DV%5rX~ZIoKL42P8yV> zsdMFuwmbx;NpjHKJz-eF=qyD6)7ZY0-emkiwNsZ9U>Kd>qhMH$#}^T7j-oRMRZv(-$&e*S9{CiZAc|Ipv~oSZ3Zbg63{>$| z%TQ&iGN_teYNKZP7dFWZKEC+68hlW`@FNqpBvWnxUJ}iIqck+0$9I6Tkrha0On99% zP1K3zEU0=D_kG?UjLkm9o;P+7SBl-=n_Gx@9x*@g5!^p|tq~^75hDT*uXVm9XYNWC zUU+5szt5AsUp@ZULF#zL7g5Cv7{mX+De3cNX(gU9wF=wuv5H^lSXBg~vQZleyHMC) za$&Lr&STiH7Z5wh-JZJEzW?uoraEd=#gBNeMG^MOb|YYm;Up~Wiy+%@$&OfcH?UW>Yex9SKke<6@L~jRawE2xo@46B|yjy-f!ta4^q+ERF3rQ+Pobf**U(8Cr;Us60Iw)`SPF)%HAr9 z3A5t9FMXGe8bLYh^1`QGb})`+*pO|#n19N8Uz+tH%+V}fUyJ!Q2W1opHY6rrY71Wy zAggYyCPOmvFi^X<5!6f*ouurNa?oqT_4YL7?A9_~YU{kpqm5yNeT?*HhZ7cZ)o_wz zCG>joVEi$svu@pS_a&PY0O@er?*-53)v4OqO5b*Rw_<7koKK!0$6 zh|@QwYZbL$bXxxmzL)!tT<%YlIC5Vc{2#$cBV(J?*YqpBeGJ^-ZP>JB+j$pVa@iGE zU32g7{u3v&beBEKV|e1JXP$fErB`0N4sRyx8J^oK8q@oG>_6qNXz}kBSN`80^lAr3(}4q{^U($yYAm~x_LR)y%Xh^)=v+;fBbwnWag;; z;faiQ{wHc^8h`%Py>V;9d-R>PKYlm{)O+vyaOlG(o#*h_?@SE2FdD(j(%5jEhk~~O z$rFaaAE#RPD)KJGI;}czF_IuInE#6*^fE#RK_4{iX`_z(*`7Z2*(Y<9^|iUnJd0KGZUAWBl+L zk1ZcO#dQP?`5G@=gfFN}M)I2dRv07$#m$|}LE%ZILb^0$%M5E;FdCC;b*{)+oiVX- zhy^cD@`O~}z!-0df=GhilP8d+8~}rKGSJd9)? ztPk2?=0tZb> zqYo&wjcm#JEXOAYi6Vj0QRKrUI~lFv8)1*Pj0Lv@P*+l4FdEqoyU7SfNnXXqWK?o0 zgjVvQR?|l2ygM{P=qOAp-@%CrKYahBVBEHT2hIihA`F1h+c$53p6Z=!VVqwZ_#v>% zZRU&_?U5}aFtx1-z%)$em8Wl5TR4QDYa>Y1SuF)oK|~ekwRxX=;i1YBzGG~~7&I*k zVAOtC6r00(PORFvxD+7Ui^V&*pjrvmIX*)xkPDGiyUjc_j|@T)1V^ocNK z1ZWJt$6E!9NdeTA9SCt1nE&K$w^0U#5$+^N9y6-T7N9gAi7?Wzr~N=J;V_Q2U;sbZ z00X^gtz^rps?>v5RF&IU?*zUdD$peinlMy_Qw-npKB}YGC7j&iok9$5tMCfUm$Nd| zkcK##M52#u0HV0@4Jv9@4W_3ierWl0%d%88tQ{4iP0Id*%!Tbxmkb&|1+uWa@QjlK2%>iV(maJap*3 zSwp$WSCH(QTfXo1sEq#RuQh|YnG62Z;3n8(#@I9Q6gW3N9ki|cFJmwG>Zu?tSMA&$ zg%@C1a#o|Cqrt2satxoE8*{f&pUEG6=eEY$k22M^*Imn4v*i1$td8V|(0X|iSquG0 zmO($JE%d_+%lEho8K_2n@TFC`nbA}K<=F=wbxX56YqC0$-Ozg3iL8ZQlx5H>w1sxl za&=kiN_LHAW#XAzjx}w`bZu}nBi}b!9mx-&_3|XL7W$DagMLg~=!djiU68tx9}K;y z=9Q)z_N-5#CG?D5R9a3&lyr)cOfikgH4XGrSz`2zXxXdPQIP^m_$KC1wXH46xD*q2 zaG}EGC5s)3B~HhDrTyFYaVK7`Qzw!8Jwiq9INX#I8xbr#)W!K1E28#IaN7NT#spK# zE`l$=CuJ@w`r!Gya#py`!Qk2M7 zMw@{tV`0%Qg;D7S#eLyg^DSdF{<~e0mX)-`9H$2CmGdVw7XMca+<4`&!iRvSRD5pS z5RvfzS6UFS_yW>5`n8HQ&ZBCg?1G$nA)J-3L#s-OP^0{60!d zXub4HR`zD?it`eYv9#l5c5XDger_!3-A|r)(szK{(uIbU?%?I)BzkJ{f8S_hx?=Hv zSAn|X|DVQIl<eMM$137Hahl<3@zr~JAA~7#R^3|0-9sHwx>D_JfxG8i9+(+joF>2%bgfL z&mhkynlTxmV`?xqqr56GjGEg;7^QzIZeX~H5Xs#+_QlGp6||K>z&cv7yGMS2=;DF^ ztMC0!xzr!z?f>VqJ>==Ar$I)WVy*%!ZBnhpQmbvX&oSp+>nPQUAyxhw)n6Z!bXn^> zmwLi~b5IkZBp~#pq#%`PO>eGbDi8A_TNxk|0V+c$>rHQs%{-#A`VqN_a#mEA%EQtH zN_|s1m0GnarHYDb$Sg(q*E|l9U>zTD7aQ>a-eZ(2XwI>r$`RzM7^P z`m9dqlJ00#`}#!RXj2oNo39vW<85*bYq~}?p1~%wM{I3}+sSsPy=l)`?i7csZoTt$ zJx+11Tk0;{(Akcu7Go<)(T*FU5yP0roO?gy;x=&Ca<_AT z=KjrF&Rfsh!Q00>#CwT%oOhb{J3ouB;G6k&KE)s9zXO#(6vRNSP%ktHt%bHhd!Ua6 zIf7||8G>3tSdbEQ3;G2Q3RVd=33dt&3M%Dw4aoSj;xLZM`9wWktLB; zk+ku9_H*%!L^be9@X zK$#SKo7&TCP>K(0!ZisRsb;Na+QeFCt(!JL8>QW)b+!Ao9qqUilEb=rx<$H@?heVL z_v;ybT0c+!y#5vaoBH1j&4wk0HHK#mM-5*ZON>&Z&geAK#%ALjM7EJH$UkTfDniw$870x}Xfw)2=h4ULD!PaMi%r0$ zVRFoXVVD~WVq35c>?8IQ&&H?Wp=%NcM+o$bz9&iT%zPNwsv%hnZiZF1GQ z1>GfXr+e*(e_~Y~-@1JEO!2rq>pT}cn_jFp?0w0{=WFsk6IUZ!4U z`&la+XJ^f&`}%!5AV3JLg5_{8Tngi`3svaj{~j8!MyF99a?t}C#RZmNEhcaehSFe?=szo%f`!>Elh{6fkgNF*;-MH92Srb=l!JVw zky$w+Ss9ag*^r`i)I7DMimIg~WveAhRV}?vubQnzTB~>JvhHckx!-xso#cMqQ{EHr z&%Ync3yy1$A=2z*^?NT*paw&p)dVOqe(BX zlH^;HSDN!2`<$66wJFPUmUB&Wov9(ItkgZZ0?WUxyl7fvT47o@Z9_i%H_3OUyQBxD zv(uLrSpHA{zwH0s|9gNVKnbt`e1SBe6C497K@O+@17IAO2DX6nq2tgO&{vq@7~BJ2 zN5&y)ght9rhk%?w1v+a{9;!o~XhKIO8b)iiWpqFK3(GMI(_>T#reQ2>EiQVjPTUiZ z#4GR(L;*1%&XGih$us0ra*wi;s&riPN%7KJ{lxwCE`Ubh0UAtz<)95_ zA%-*LKrw`22du*Y%)tS4;RaZv_2@)hD2+DI0J&%vn!zWq2Djm9oX1^k;YC=&HcoI0 zk77;~GLVh12}BgcNZQB<36eY!$YSCVX3XCS6>X1dZ=5boFQaT~^08?%?e56#SUa-yqhw5sv(vM)es(asgI)cdb*-AS=QeR0|J&}8d1b!#|3A6j zlh{+;bM|x>rzo82)f@7|LL(Rp83Lq`9e@J_b>GEHvUo4r)^B#CHSa4x)y8E_;PX!m zQkD*$^0KAAps)Phl6@xq`?}JuhK-Z-aI~3D)Q#ua`T~6nFz}+oTc|6)|Dfkj*AGr=Esw95yE%(+yy;OglSP#MV1J0vv~mw zZ#SfH_On@@&Ul-Po^>HZrHwUO7S)P)st=cFgKumKS%ocxB6JVg8ahr@+t9pkjd>+C z__n|(vv9}bdM=8FIWS~p^9*M`iU5W+RcoX*z#`6P)S)&VgBdfnhf;{oF${QkZ?40L zTqQ{?)duT2vRXesQ@|1I^vR1*49Um^-C#S~=>&$c-qJuJMVea38@zG}?SNtQJGqaJ zAQl<~B9~9qcgk^!0SM&~Xkz8Fuy~ey1kJ*xp*mx*olS)u{;A=EH+_3WScedVa<#U7 zE!sT>C!M+~vMCiDk)eXv1F*twU?79x;6W$e+p)L&yceANPU1XLdWT}BAkuOfA-#@) z3auU=MXC_6z|M+`7}$< z>pSL-@nd$ZQd47j_zL~R zdIg2#Wmbca0|2bm$e`pxPzIUgY!^5ZRFwW;uuX&2FI;cE`J38xEn)FBxKt>K)&@jg zWJ_mOt(#*_i>E#$I2O?(bMi7*Nbu|O7n86d1iK!_Y&;3$*R_Wrjvfo^rMm4}wLwMz z(it6j5nKK1h5W#Ovc4Wd(jrSiilhmY(%w+!pkHp^(DKC-K)G%{T(BQ}cjq-AgYwND z00XILuRE3Wj`X51JSFESRHJ?hnSB~b0E_;d@GAA!%ei2Di`6}ru(-k%ZgH(&HMX$} z8Jgris&8+>ejL8qg%S*%7WENwJS$YBvb1O_J={IpJ$wU$17J)WV3&7t)qXO-_Kri zMy4p>eQiGq^#+XM@1C0a3J@~*!H3)vS_`#TNhFZw)PSy>JRsktA=ZTeB}?EnfOcr^)Xww!NVJW2GO~qi>FkZ>{K%X zO?ZM{{HHbCnTBZ?cX%L$5d}^68VGRwHVyU;8lfPIGKO!*evhq0&gf_NIx_+8|Cs~M zJ0iDBjRx}3R6BgebTs7>eh|7Mm>y|Z5_<)(k&RMoSS~kY%qWS)lR(K!mJS#$ro`4| zeMBgYa=0^3wJa-Q-=m`AT6!!Wya!8{tBxWLKE`2WWUD|x8=WP?!_q+1aW23!8ScXR%{24^Y=W5lZjq`M$ zNfxOMQ``#f2$Q1P!;&#+$c{sM8N_kMoj9Oo!gq@--NIb)J11!GP4CVf24wR4AU#Atn-hL$WEy%e|GYuHxW zg)OG4e7v>)BS1y_iDW#S_i?#wFX)kPv?Io(#IL7MTEBVzUC9mzMv~^$xc>N3$5aA-AP=~@b23&0Mj^J7%pHqc7*wnrP zI)Dls+~G^GxyJh?L!b2|Ke`)8Ke6`|D@i~4tW;cN#s)^U24}%*FrP(e1LH7Sg9`na z%FK+Yf*hIcYaGPVmQe5$+wwQ?D5AhplrjdnBw6-Ahn25wwLwh>eeRPasllG_V_<*c3f33@SR^Xd^@+n$=s@ zJm_(bONHNVys~$;xUU->T;Zx{BZM!Ug~32(WD{Q(8dy< zSQKHrB;e7>3cJ|`iK>!aw<>9xn&M3v24O71`miuNjGrZ1&;LpwNT=IdcP~qB8b$9N zk?b)UZ0utCWuz)~ct|@Nx*6Ku7p|gUZeraLrfr{}Q&^9y(Lzno|HinnwzP0ZTR}3=9jiZ@tTnsJl)bC{jQ$nIdYK_x*h}9gBnGRbOogd=+B+;~ zDN-h!J{k{3h3+OQP^P*Bq}oOJ)QSW}RW}ygfqjWyYiA@rPv1LiLd=8_@F3e{++*fy z@)WS9#Jpbhc#R#QO{hz5G}8jSJs-aCNn_mOLckEFO3z4v7>iA{_#QZg;48j7YyK6e z?7r$$7c%RslN*!N$&FXc_hz-a?oOM{LGQJtd&ZsfK>DTEAk#8<>j(5V|2 z2_xW&6&f_?3y_6%ET@@JXM+v;NRbOTEjG;4p|u$4Zvh#*Fj%1g8HZKUQQ{h1?j%fl zSZbzL=6I-BTMjYW2!MM*sMo z-@>05N=n)3zqN~(82To6dH8JNJaD1Orss%00@v0?OR@#65^O*_ zZegpGrbJ;?{A{=|UVOLm%tb@HJ3Hd;6nV{4O4gGu7FII`bWa*bX8?d7yj>wKtHxY; z{>gH&{K@mpdi!5Y+|=LvQ${P*`aydg&-ezMT(x%C|(qgZ54bVjq+JWt_G9hoo! zEmYWEn!&_zPzz-_<)E~k(ZJKAFWW{!+uC1m=J;2Y3lUI!g-6f|bz|e{_nvNiQUes~ zql@$2`MF#H2RLkbj?w2ITsDG`;%GwYo$b2Fv@%v5Z4q2VKfiIn3h8gQ7@I{S;Wx{* zs_c(X5~cBn8mO;S_9KSa&-~%T)^1tR@4NzwuK}rl`QO=ZVe*Gwf`{-WBq8;GTC(`) z?Z_Z7p`FXeV;gdbHgbMd?g%eoTPeG>Vvi|2;2^gv#TgX~ut%lKPZ|th#--%9vmu7s z0Ccv^<5Q63O?~Cl&5UMSpKhY?lb^16Xu9_4CI|lt-=GG?afnYpu7CXJzp7h@0gM0z zpl5dN$gtwqPlUuq+3_sHenx%R60PGmXr=3A9C1!o#abOIrqAlNW73XwS zm`of^Jw4B~eLF9(43_CW!z#9PgA=Nm&U!=C}b=e7dt(8 zA->;~UI4BjUX-b5VzuF_#YPK4MK7PggD+^pkL=_PV;^*!gd%fC;`h+Nk_BaVJ^Dhk?c0sa0cf z7%5$G26^9e!3PAji-H@a>4PUq3<-qI78w`Rm6x{}Bw$2t{rS-ffVqIQ;DXv6BjiRdL2)ieR$$OkbI*K8#D`;Q?^k}+L9 zzU(^!z!+w~u6Ty(@(_Tl!#8_S;e%5ubT&*8B`i#n_aYeaLT1D zzPz`|BifK3vI;u&RnwAKC)?Yh!FG&87!n`*=Q4-{`so#fGw~k6KM*s z_vXupwd!`mw;iz)m7#;GJ4Wd{R~PDop;lB!_jGr{JQ4l+|E9mjS) zc09&hZ1~e12Gi7AtRFk55Fs(HI1s^9br>wHpA>B3SMi{9*F{TvD2(GvOV=L-@9ax1 zuOSgEKaY$1WL==S+SR=Eb;@N`htjV)g%Y?$gVy(918g>xf&gTjHpZsO@>*F^T^3Y! zPG-FO!b~**7fCb^SZ#Kjzi~3rkOW|5HrX16J*g&mJmP~cZ7H+H9&CUsl==l+5Io4> zG1%^rmTPK5lVrNd~?al z6oaC6OTHGx5z*;qwhOErID<4VwtE6)ZbaIfC!ySue0g(oskgM$;F+ek^s;fZWPf1r z+m&QtepkaQcqUF6tU4}Q)9|_$O=wzh2unQF+WS!nAW?)^RD3msV84vneh^Nq>mSv@ zigEWk&%ZmEABU%O%l%3cwKY-i+m8>$VU_9-0929Q3SwctNwDi*|qjs4X)+<@V2{t%cfZt9ShT$Pnz<7+nK-P(C6o(c>XU?%-9`Xv? zHA`&sBqOuk4*?q!JMvKy3fVhdY3brqz99Zy zqxUc&;swTCJ&djX!eFB%<$I7cpgU*y2Ka(%LvHFs)L1r9ZlxU=AbEiEF3PcSJX&}1oTwasVcSDe z-QQf>pQQLaU{tX@9uhj!r|FmW#iIb+xEh=P(PBmy`S~?plskV9Flh5+jdOJA5riS2 z>Oi?5-?mxO5WK{p?#8fT8-=y5Q3S3?nvrqfqHod#;-@}MX&};97WFxsUy4JujAPvJ zua9FMOE~sQO=V35k7y%+0Ek|Cr()XO2L~nS1zmdn8Z?2jTJu*#`BN`Ja?Y%2m5bI4W9b|UZea*M&}APOa4;bR>I5Z`(E#*z5Q$g4_kGLt75fRL#B=> zs6u{So4d{Lm6Q`V5fY!2jb?*;KRXfTGktzU(o`_@2;(mT<6&Yp?|RuvfdIlG%ds}5 zcrl&`@~DccsiQF1V0E+e{8>MlOP#oUJ(Tnfy(+yhCcT`9Q?cT$3~6C!t&Kh!UQ0*~ z#rl@8tyBE?$tL+#tryJ|95lPe9lM)VW?Zm?Ju9zWPEw5MZA=&{Zaib#*Hz6PQpj|J ziIUBz@SRE*425@rVPKbj!snZr%kwOr)_Y-OA+v=m2n)4X0OE?p zscSyoYx7pbzM!v{$i+_46Amp&w185n#=m`dD$VATIwqX}K|sF0pynZMMR0q(r5r3J1KdsG6sNEC)r#_d^<7SHNb@6>7Fh7rZ8jr)xpLD{) z2*Ymdzf;|!xx`p)f6+M(P0fryR+B-Wr!PI%5<-wE!6LivNF30}uRy)3SBQn^u)EsP zrQCP-lcN;lABQAAm~>Rj)$aB~80WLvCny?&&ioErh6j)R9Ks`pE;kA5!5zeLNB+ou zv>wa5p!eTxIiu<6x-kuFV09*N-(tW_3IlH!P=drO5%sJlN0dyqZB?)6ombAT36u}c z0YJHmXHJe(6|+ZavKhAoLR#=jQ zvEQwy+l7KQ`N93vvCx5!PUR3*q3_7_VL#Q+n&|Xql#%zRhKZK-ph%zYz2R5n+Xr#n zX7bChSkG7@-?|#mQlhv*{Yo;yg2$hO&N0KTHo@ss(HqXexu)Pl5DtJ(ndE4VVCA7f zEQ|P)l@%p+%4o?2>(J6MesN#x-Y9w9Bya$C8&jyKqLjir=VNUZl3rKdOIHq1&Ojnc z2Ez;!Y$Py8$s1~9{r>o26#-QaK^uL{gy7J*dngmf;sBTKqPK9I+j|U?Uf_?DlzA=V zDwDW82CwoXV6v2`1{YqULWN<&+~Z6XsGJEOHO}U^Stp;_iqF_gpQQH@Ou;COt8T-- z&orT2JED}~kw)mI3)F4dNb}ZGYa<9@(}~)MYdC)JVHx>6wPt%tjIJIt6BdQ94^j{^ zg2E3cd4B)UY>2_vwF4P~VuWSA^o7{gHxp8PVcNy(d1rCjxGw!AzjeM((2I9H{;o_y zzuDe5_Cx?%G?Vl~yBz3-FY_YAcw>cc<3H_|8?GA^>Dni4*Zy)c>b@`?9&u`FZ}bF6 z(=%eaE`~6Z4z|N^9PiFFV6z;gIW{uO(3+`GPMV5Swv+3OP*hVNg9UR#w;)UtdG4zd zgm3Yxy8BAFF9{5^otZ+6EV1@EM#5MFx-=fl z*^LeyBsp}j);Uv+x5=wmU@o~5Jo5lV%$}NY&N-8YoZ1MH9jxxV758S!kM5>2iOr)o z!qB{^qy;m&2+x&TdWvAXP{99I5R8bOCV@1|k|=~g8OOH15psxQnBRmW^G)(7I^5M+ ztfkZrZOD05Ul5;|jdy63@7S|`x5*D+0u%ATIoq1cU5Fh5BbHFfl|W2K&J|8plm;Oa zC%Fd!y`!tS=y%iNtV)OE(ovYc^S3xVp!UKvLeM!uaxTP1vpQI zS{C~JwQwR7S{1CepqEL5ttaRs^_XkAAqjzcL>sYyzQe+EfdK*-%##ljWI1)n+uc7- z^DehGfeR%!rY7LzKE{5oII)jNQB4Ht+za20sxlJ~y9s0{wbxE&7eOYSq!kM$iTrz~uu`0EP$8(L1N!73dvL9s@9e^PWS2 zavx^GmPk%~D6tuIj1wevs=5eRrFO&IYS&VT&X2*<5j>dqP~sY>m>$XQg?b5+or5Vv zt;R*9C&5___!Rp7DVVieQ)2eHDg%1_o%-5VWq2Dnxzm)b#z-pMhJD1;*qJ5ThOMr(k~1!j+>*V zONAEE-81E6C<=xlr6b;`AC~#%?jN6B(tCNP&VOMo)Bk$=o9k!a{>NJ!2{f=vqw|0s z)ye{K(C>SA7qlZkk{2+7Oj0}vAq_p$Rdz%i60uRYZ3mysyEFjs@9xC@s|6S&IaEs# za;ZJr9Bw{)UIV^g{LV%Hs(;aIHCVyAm~2eklT1etHX0p~PY_uytC5Z3(WAWMpr=<5 zViM|;PJaf?ejxenI$YhmE$^j+PoNEnu?xR&B*wh0n zHDnNK;Efr+&^6F#kZhI|v8fO_!TA>ncf~GXxTbhk3f(2i-RrfkUM;np-4QU#kvE-E zONwdQaZ2|VmRkyX#r*gRJ9~NZ)@Nfg-}c3FyQ*wSM?eH}sStbFlTykx-qzb|^ZJRc zvZfNmmG(5Kw@ps=8QuaHEI)yvm^q?3D~5zZ?YS5_AtE0F%K5b0A@X6P=%`?aBW}f` zyB*Po5vL*0$1dz8;>+J5x zc`S2r>iPsPMRJ{wUcBka7L?K%?G@8#5M}hJRZ>y?-7g`xDLHDhbDs z^+W4~R`jRm`5=IkmER5VAN+&(Mt315%F`zeBl)h|t9*Pne^9K8&3;i-Dd${@Q=|~m z3VG2#;}3PX(=13IubkIW()Y6Wb$3AJfq}!U$ZF)pmHsQo&+y>e5%h-7%)NwvvPLjVd>@o>+||(+ zOIUB^`Ilh6zuiw(6GTCq1&E4jWbeQlX$h!HMpGfP01VM$(r;;|EmyfE*5+q%bL1do z42wd+xAPH))d-rP5oCz}3n}#YM$x*r^t0aezMzSCkv}f5m4NyC(>yotA0Yy)%`u6_ z;jMj-pU`lOCNXqRB`M%lHuG~$s}r0OW__b%cqE!hJYyMM>}!qS6!BCgDVJ2)Y`(v} ztsR`!TO(QOZgXB8o`Ilm}TFMjBSe*p@T0#*OTu2@}QhKTc zEt_CWI$M^f%zWxO^Z>r_MUKXNmDyQXco28h3pp7h$H>f+StOe{%TBu-;_ZLCm7HfA z!aLZMJGL><91q1YB95%tHiV<2(YD_>U>X1^eRF!}LmCL*Je3@R5^u_H7D(mz$?uOA zssDV@B|rM?&vUSEHGu*Y`Qpjp)3g8n%f}}jrJ{ed&mJAT_{;h>_W8tHK+lXf5G-tz z6i`=Oo5D@QDI{#|*lzGB|I9aEE{=dg+@ue$z50hF!vdkLZWUt9SY-29E4-kqKLY!b zy2C&p-1%7?_6I> zEB)2N{p|9NzxoqEI0OC%N7v|Qt%KDNg0LymDTE-p0t!aW%(9-(+*UVcP{rD1UCcXu1gf8LLP-1NN_Q5hgcGA zbGrq4)w#Y6%=1dzLDRPL)W7Pgdfyue?ma?GT^3I;jD-~ZAo@9fRoLi|v-z}REP=g^ zO?9M=SZ`KTud-m78iinUtD^ng{iiQ}N(L19d2jdgR|%QtaUJ+iEl&$tvB}`m&muhAJaB-5X*$WB-_K z>$2U=8M)?*Xg8ZHb}*b@ZYa1+HzLT^qQ>^@irnsVDEf3}Euu4DZ$^pQXsjKdNrC1P z?Fv$5GSTz4Q%h&QL99dFkYWX_-qu7V2t`HKI1v>G>^LTT2fk7cbSD0n$n;)Pxy|}w zeplYJ)qZ#TZ+QNNUU#(b)K}hpq)|To(rJW3S;eOH1iHgL6oW~>$R~JjNzGride!JtA3NiB4$kHhDa;IzM8TXa(XbWrP?`Jh> zpfWUgZWmC%V5Rk8M*+baYl8b8laQ(dm2VwLz5t2QOY!{Dha z2!>zIz}tu0H<5!`4Y0@U?UVFvIp0`sw&^>*Adr%d%C+$aC${uK{&mRTnh&-~`Z(2m zDD)$2gmr~t)QYz#HxwUag7m$xbfNCePG6}{J1I4cP2s)(2`^R1uXP&*a^&4rtw&03 z*4M^m5CU8Xz8G1vQFQB=_`D$)-3zcXV$HJFMf2=_XC%%DDeobv>2Q}G2~r?-M9CZ) zvS~}#m~WaCLU2VmW_P*ptm|i{&s?8D4n`@Ql;xOCC!=B|JSRmHVY>$mj#?lp#u-nrR$adq;XGd zfN>^Y!(OgBLaDBGln95VILPP~t$}5rh81GupLMf)H4FP8d_^i?DkIU4?=A_M#`UFH zkSW_F`eaBFV_I4_y=h*E3xDCDI)sBGFBa%D6tWkvvDNA-L>U)SVVZn2Dd!nJ*mcqj z^cIajIbDdy;$~x7bYiiwda`BPMv+6&3*uv~+073@ent2pS7%V=;va(5DlBLa_lV&o z8qL1|$-t{WrmL&*U9=Hql8{%I%G% zNF?Tn&;UvAM`B`ht7kMO6n$Kd74-^qV>LT#C~}`!NxbuTAshu4S!3u}6Z>9PXSodC z?=_9CpRoI*az}9t)&!V&0F@Kqj7!lKI(q~c{R0xd$`=pk0$#s+;}{g2%|Ay_*Zr!G zHPP|)yeZwLcDj3EPo!95bD#{iAe{vly1a-jGVpvA9W--qz2$Q|#mF5aD`M{TY3 zO4!74nFO1Y_}k;-9R<$z12*{i@%x6HwlJo;vH&~SWpDtL7b-~-E4;ZyvWHw8fz#;x zPu>i7a((5^Ql110j*|_e=zjFp{9<{<{407{T6Cc6TRrh>(kA_zijDip$@xJ zKK~k9Ew$V1qgf<#?8BtBF^~MwX}DT8Dj8~~$w#?s<4;?I55eibwv!q~PhIbjND9u9mSP!k2Z*2ha$hO~&B*HBdRQE`{^u5h%f zeM2aB)?$M7ROIePoBo?il&4p`OKzD_pY*SnraT_$i2m?xePEw(;+QCM% zVdD^^p|EJhj-~OQ9M9A7xXLxG?&qYrDdiy|wGgrOexiU^27?9zG>L%7uB zv})QC>Y5kQS{B8k3#7VIyteKWdm;0DC~5A#0cVg&w5U}7$PCaJjx?h1h5;!$pa*i9 z(*nIMXTtYPF&w5e;jH#{0`+7T`ioJ-e^NrGAkQZ5D$tvfZS$R6V5NWyj_MP%1>RcY_F9ip$d1|K-u%KXpljbkadRITNt>e@tu{`$({N9wfsU< zo)p%2jN7;|AgSAXA97_%KxxL|1tgOJB;Hubt<_Y%I%S<9w~XB9ax=fg&8LI7g_o0f zoT7x1ES9L4N|TilH-%4r%0O(5K zgEI6dD@%g4q|FfIha@Zs`z`3PUxKB=F&2p!A2!8lz?Qb`7-0qQnN9gJk45pwo!W3^ zSSjVqKO$M|vExePSK{5kA1_V^&3n8NoV7ya?e+rIW|1$KaCJI;%t46NBzeSbOUulC zRrL+Z6~9Ag+0vZ0j*^l&KP80Ga)Hj2rjuDN?76I-?lD}%WE97HXuac{jV;MuPI?T~J1dR{%GSJ@KO>Nj ziZVrTTl@P?frpcFevcVAu_kgFrt#y0ScX!UPmuK1b6~A6K~*n@=qmo9*6bLp3Lt^Z zDnz`-Q}3M*;tT9flim~&X{HGX^@in|+@Mdbr}53-n_pA^wz4?S)t;9{se@{3m8mNT ztBd3IAVA8?uNIo6-Yf1m+0Q7@LO6Mg(}-5G5=CAUKmTku&j4y<8CPj9sl(1Y?eU z-E25z@9ey${)2}Ww}|QvUW+5(?79Mrv5T=U$p4m^+!Q9x@|T*`$=jfuj2R=+ZT2Km zFRVgG*V|st6Q0`$m;@&L@OaolUB17Slfj}39B&KQ0|L!J2l3*fGZa{8$h3%~)=ZQG zOdA*BQVWZfd0lFZHF6-tP!$)041WR%3wRI~O0*r9$DL7vZ3w4&^YL2OgNax~3fPUV zbKq}vHwYOIKE_@SC~Swp2F6#7J~?RL|6Ve;)Yp5zS&MHNV(b@~3Wyf~&kFx|xv^rM zDRJxWcttl{-U2Q_{^P(0%wV{&to47@QD#!(4CBmhoIf8vjF4ueId+e*W)OzRfpn$` zWFdPPwqn?X7ZM*|zp4y09#|>ds5lR`jv?Bnm6^_Di96gX9rq_& z1&7J~XgZk%g?|?$o5;8b>605{-SIPzfB(^0n_cN5k3iBKu!5 zGGxqLn{Zt-+Vy1f*QYlWYv@#^ z39)uu!n?59qcuM~D%AIb6NZIwkxxD0LngHrZ6??(JhM*~6c=|-qs^mHzkm&Wcy`%` zbaicIc~tCsRFnCwN5SY*JpN*izr3E|8~#!6{HC1*GnG>KxihOiG0fGrms0*EJ4QR? z9o=^9XUXDUn?w7jvk=7{4p2P*o@;fVqJa~fMG6!$I3}L!V$?Zh(R4ok_kWY0seHXnFKJKvNB9yX?i{k`iA})Uz$nxBLd8HDJknhXq zesh71ZYn$4KHWZg0|ydXfPDG#&7(IjN9gbsut$A8IXm4sQoj5M>yzn2!15HSAO(~` zMx!o#BNo6ynW-zmVAoT01;PxBF%Q9|MCd*d$v7LN^?RjPeP76b*Jsm{+qg|rEu7NK zDF!eDSUIY-jhyx$1zN%5t>@)jq>ci5EIH8liELS{~gGo|a=20lonxxM(R2~HvLNtkxw+oKMh45YBqU_Q^ z&vk65BF>6aT25t{V@6e!)~BBx1^(K5`Ge&zQVrBT_a@lH$V#zN;1M6z6n9|;Y%gpC|uMZ4hr==I5)p+_|4 z?y57NX7=L?A7wc|%c|ZYw7|L5yt$2gijUZnxAD2P`{}9(~SJ@`=0t22w=K@h? z<5?9qz42x7+y10t6>V%e^L@X)Lw|&$X(o0-=dq@RbU&%+PMGo3Nzd}_i5_XJ)ElM3 znq})Wq}L+IYCTf+rEolZ#3S1YTcwBU`uHK#v}xQEg*`UO?y_C?h~Wwkj3|{sZi+Zj9GTe!97|Rmyl-DGRM9lL<1uc-;|rPD zL*5V{gwiT<%vo1^pA%KK-kvvCR3~D6w$qSC(5dI~w!}Im??eX1EiPr}TN{!1giaJp zk}Tap8sA6)*W!fSkW@v5#IfKwHFD zw!*9Uys6_T3x$d$Lmq`|ho=cTTwO0{$%9L}S!aYa~Z+PkTy!hNYtDY+o)Q&84Q9g~ngp8=>k@!@NbG8ZQ@ zb4q)dKn9mR-F&hFe z0TnFBHO@Jw=5o;r9Op1DXOeIx_Gt@>>{orQ`Qv$f%Jee4ejkUL0Y%xt*ycUySr74Q zW|;lZ_N>P($h3~wx^)R!F!m5QO8^Jr+8DMh%^_W-j}_l%p~~*k{%GmM=(Bk}80%~9 z+1QDDR-&bWE^k8Na1lWKJKAJ3k;#Dx7|L;v=}PV^rUGDe#d;1>g}UH^S;e%>RZs1n z4~HlOdO;p(jhl?SETi;7v%(zcIy+B!W}j1b>@rYLRCVhn=HgNP$89~G# zs*b)2UL|$5w145jRF;ip{YlGy1HAh{0!W+>XD2LrtoCFyD`M{e;x|2^83XZ#!lH*&GGpU9m_Vp3gOYd^B8-cw0-h(vfTcCYfgzh=W4Fe0g_<)h zpxACo4YT7FNZaid&rYutsVrb*Hfk&I;N%PVw`aXN{qNT9y6Gk#gM7Pa*YBQPS&jd% z+nT+2Dd&Fh8V7U`cilfk?Vp~2)K61KqRMs&Kcfa@j2n|((j{ex8$zP7{5mcvi=5MP zc~dZefIJj-&dqq3WtL__Pw9mtZTwWsa;$!kciWw}Gr+QpU&bG1D?NmJhE|1h0x=`z zdJ@#sng36u%u)Yc7J#E;><@zrZLSlf@YeKlh*2@vK|RA84-f zFkIsUJ#0>OPgZw$;Vl@PimQ`{7tafR_m{x2cM&CE{B=PI(G(Kmcf3Izt0=Y>&ud=F zAFuT;Gx)yHTzp#glS@%tYW%Il9~^<}+Y+Y!H;{z%kQrlB1-;aBI^t#E!Bps)4bGef zjfl~44&l-;B+f2@a9o5K9!<7}O6|mt6y2HYtP1TC=*A~t(;3H3cY0pK%b^*t;TC7zaN2DsP_COa2K0Z0Q_kS zf2(Ba@3y-;6RZ0#4w{e1}5ma2fiAf*Uo)!)JpRm?Jjz*1~N#yi{{ zF3222Ly(oUmse|X*QvNn?K6F3PyO1(7^itrKXotD-k1p~ zldCQH#7|=F+dpuZpj6_}Yhu40I|n75MhqU>j)MzecPhHrjUkP@UefS{BBt`o@lL(~ zSFVf01J|?Mx%LS85EO)DqXN`Zj{t@?A?QxJQ!xwgwGmo=3)T7~AVAbVB3x}4UV1(w zWWk>xgk6jvnfp~(*f$a)iaCU(moXrQtXbF8Pvs;>n<58IB^)^q=vC-hRPp?fTo(#F zh08g$1T$2vtaS6vJu8ZU(oPUXbMY*4(pb4+=+k*}CnCuA6^~7*J(oM~69^xY5amRT zg{aBTKPz2%Ei~s__Pl=(UVgKD#;M#H<#zLgF7u3`yIj^%hEbTxx&`6KMX>7vHrQ2s zPC{;(nhVd4F=a^;s%Ty%5w~~__0C#=Q;qk`GRe);VzsiFvjEH%aW`!I=oEyM)R=$7 z8}Rtra%tMW%GvXuZ4|zWcvMJf96m`6x~;%$fk(rl!WKJ^-SZf1qpYIQe0@srQ;4om z;;t+$xZoGWR(je#3^Ph?@r?PRylHlQL*lxgb~`pHPp*V9z1Aml7=+MtUv&|0&MaZ` zVBqmEm0GlYX2fM2dV^>Z^E6@m{wVL0IYN0qlJ(^1SfzT_P`Y0b-Z-qaE|+D}Ti%Wj|4D7#M(ojD0%ybdWr}H@EHw=`pM}x?=nCa$9)?T#F*Nh>cP* z#>To?UR8BCvzayyi`apVvY3aQZ;k)>z4!|+7B1udKmY3(`A1I)_;-->uO#WUDEL`h z^47FFj*Cg-uTG@!)V*Vrr2VCCHxJso)NUbN7P*sNHY1M8i{A^URGm1E#>yEyAQ;B( zb0lMhjPR{o{B_v4Hw*%{!8%;S4CrFSU+cT4yLeNdoD%ps;Y-=>$ zb2C${H{ZLy^9O{mduLHJU?Z{Ipr@O>ZN=~@W>kr4TmU-^d7%}HZf!Rfmc^kB!)4ui zw1J|1Ce7l+zJ2RnIF^qcFpTV5x7TvLT2`yjY^^}YgyZ(a_}S#lYB=YK?d33x7^3G0 ztd#mF2$G5)ACQrRkBqSG^z4;P<1lLX|5Iey!~_@MEQx8NbMOD*LWg^gui127X?mz; zDmLq{4RNbENfl@|xXc7zl>BX0uu=-A)aDw3q2oNMr9PQY)%iGFHz93J&$@FWm-hSn zMU5FLX2AVb5Vl(iemu&(>W z_ZQq8zr+r^2U>_I2pejaz!aIGqDqo(BoSR*2(}tC#*SV}eQhOHQdnpBQ<9`LP!+|* z_*zEWVk1fPCAhx=OT*a zPR*CID%8I#G4c82%;H;}I2}Zli$G^b$sb3vgv9+r9s)#}=iR{O&PWBEzzoA@ zo&7Xk=Xu%jkc0oSw-VUZ%3|c!s#k@w2)T8n%8e2xqtl+aq@_E=z0)^a5Bt+DeIo{C z;*d*b#at_H+(^+Bv6fUx2GJDdU zd=V5dM#R@A0AvYIC6*lSj$(p`mrhX6S5|fumkmHt6lP#!@uksMb=MVW)Wqj4;g^!} zdnD23>UAxwx;xiYl70?am~$xaAx!!ttd&O}@-?n+VQuijlj+hD&dmF%+jzfW9jr@- zG@sC6c;A(L=4n7HKjv=SA@1zZ9%(mpCQut!X%(;ss3$+x54|ZJZ`&x|tiC^*TWsyA zDAM@c)}&Y~7e~X3U|=D&>`~rczKaMo2$2cqWu1SPG!q-ogZX@C7ClHp&cH1v@VgO4XrVZuu=A#w4wf-osd&ztbdl@?>~n> z(4SF*z2yb;-h!>*2bqya-fYKxL+?3xDZR7C-m_J}<;te`xwmJ%jEa1-d=@{DfW%g2 zwydn=M``J>q!EZ?g7}$!13H~I?==W1Wf05BhE#rly|6cJ(BeRb-Huh4*I8@lgG8Z& z<0Bcjag5e}%%e$%CR%mno{MC-{`jENS+1%Z&UE=&fAI7g>&cXj@QF#%BJ6RWTpm}% zLp1D#XH*CDT^Kug6p6-;zhkxF9bxUTX~iMKLJFoM54oQb%b#vE{{5q?tM4x_C6mSL zksN)!M&(4047f_Wv81)E93}5om&EOSSRFxwP&6oMVo4fRZjPEQ8V{JHk@+#DFqPG+ z0}}fE2~)Mg{ z_gqJ@vJtyFYSo}fX0&O9dn_^KB8X=*qnSqYgOu+a+a!?c*n!pz;kCmM=+j%fI9nff zOQ#*eccG&D?}y+0rt{%@m?oh)dV#S1rlFLsS z6i4@Yta=ZwLj3keeA)cItI-0bixI*Gq4_{i4sl;>b|)u(MQ2+#e%2bj>D8*f{O265 zXk^>vu_&LvD6YtJpw_#5aBtxDPyN?V7{r17-mFP=_X2%ewTAx2Tvn0~&Qf1x&&O6o z|D1ZWN%CpVfrOO?Y4|pNu5`!UaCLd3dSrH3RfjmLG88T~3c8=!Hjnw`-y@pU@JO$8 zNNHkf9Xs2*`A=<)*w+ZB&$J5El-gT@#c@a#RCW*JTf3NuBXc*nAIb4LY`)144IS`+zIhx{`}=cAHqA!V zTG$4>8;>yH_bf*bukzM-i9VeVFC^tg`A5>dj_~CdH5BVHWFWb!7?p zC14Uj%8~yj`NDX_vR>mpMpzkwMia3v8N-ZZ~5~X7yy1{X`D= z!uY4T7?P4+4RdsMb32F@T4)>^LFYk`7A4sVdoH0JU1bb@xRxfag)*bT%|vpk*|CVT zKpoq|-qzr*G<|e&jrm_+`vL0?x}hV1B}09qWL*JtOrRJnKrUc{nMU=*XNDi`%7;jP zXpk9vyB@^BIAs>!A8>#&rYOzZ&7?N^W&7}a4waGl@J4ps*z>jcM0D@G*OTC3G;P6X z(@7|Z5z^rVy+Lwme$N!gVqr*+f{4j(<u*$GMi(&DWu>_L0)NgH zw`F+mQQ6k&=iwP4BzvJKV7~?t4Ci<3DsW94@^V7c$a61lfO(EqH~iJFh9ChJeVZWc zat7!J^D=BNq3c_ah!YoUI9w~tmW>lKx?)YSEZ>EvAT^@{i4(N)cp{>+lh}boMr-6{ z4}?HJ1_Op=5*6Wy5DtpD*ts3b!C0e;naqgT6n#190r2yp^H+p7b2hHxZY1#YS#>l#V90~PWNEDAUZxJE@5 zTna9cT&H8*6>CMNFpl6IIY?n>PLr&>XO*W)YqIU0Y?`WMjcCMA{;vDDTL{K#7rUmB zsvyxQ-nh8=52ttK5s0mh$T9)?9j z>bwh3lbl7X=}OZ#r6a2oiftNz$3tN0YVt6fhW=}vjN<%F zZq@BpnQP$}o85JXUrmn+(u*J$RPN69x<2gpBn&~$3W!zD4S|HS1AF#ti$8UKbv!#y zpRgX*xP}&89EEE)ykoh8T|)GD(%Hz`@dY=9i6(iTQ%#FqV+w0LTxy7<$z@oNG#^qT zDvHOp#x|v`!FWyx>csZ!>2@GK2DvKW%J&N#Fc5AH11A505oVYGO1XCaeLL@{A>nC& zly!To^tM5L>e}&m>h_XaSxoBWU79*rXr{WNMNy)&ksIABf1 zDIVQ`c*=n~ZYE%k<=4Et-gD;U8mN0^f3{p|HT_`CV2ajCF<0TQYtn?ruthF8MQ`PI zpfxgg;)05ZcS5C)QNw3?pL=ICX4)~N*pXttaTe#Tv)!fa4ACdO zMA4eDNlr*4c|skWOD?4C#=6_-%`^y|$cF?PYMA%_Vs>=%G*~d_A*9&D`y$J_2`}zW zY^w|0bZ`GXf(l2c9_;ZN_+3uoe@KbNe+#XwvD7HnNc%c&CDv-ky7cio@y0gG>G=WM zoD?G6FcF#AkVySq1IsvQj@8Q`Y0uWAKpgi$PeN+=A`mF_A@9D>`gk5kT=q|Ap!GL{5C$XROtO652NRJh5b0 z`VT3f)|V*QJt;BULz*O~3d!4289(f#!(wZuZGA${gmlOS608y+M3M70fk$gZcYVG9 za&aP*1R5^eKAe_lBF@%%1x+SCimfF=OPTi|U8t;*0S4{pIQwX^4MWCQM;HSMGJ=2IMf3+rH;hQo&0t#RVsQ(VE?A3WqdT8Pz-T@6ZVqe+I~$WA z^vJgyY6Pi@#T<4J$<7Up@Ee?r(nib8(Z+hiNI+nQ8p7)<+TSVS5TUwCD^m{C?$jk56xGqB|5}nUVee!P zzvu$jVQaEDbL_yjB)%WU1sc4xNFC}2;sa~Zb1Hxy0JA}YSk&@kJaZtoHBTAI;!s_v ziaf(-b7g5c7(!E(K=NWz2)VNMM`XTK##vbc-*|W%O)|&z9nJSOoE!$+)=2Q=p&~}h zIC|tW+ncUokr&0In2|JSAMvg6%B3Zy|2t*#U)%W3j3g2bf|v$X5-gLvQ zvO9rlLwwBg0GTM;~xry^vQh=;8 z#fnCm=(Q#nV-^L7QI~AHdgnHtE-IiLFe9AxYkWKClSE4;rXT^EAuk}I%nB>Z~ zGHgxd@x0dPo_HXTw9fy9w2m5PT9b zoT*Qbw-49#x0kncfxRuO43XN>pD7aRq(Is(G9!P4v?BWsFfS+tiWk=dy#}-k^?}rk zsDN3Zd%a?=ysBRtFN1hM6n3aJ%4-4`$I&}t{{3Aa4zEl6ibt&Pa5f2tP958Ob>KV_ zH{l?gV^+4blVg+-&&PSwi!l%8>n`6=@tx{~qWt>tW$L+^5 zVI8;fj{=I&Z3~Az3lak!-O71(NPnbc>uN?+Ns6w3R^XwZ&&FYYD61b5ZTk`>ISnBD zP6C(d2%wP>8)51WVa5nQ)}m8iA~%{1>oCSTh+?njf<%h+Q~Uxy#To8s5C+Dq^!2F@ zi(0Ct!@?A#C~_4EBxR|Dp9$i5>_-hh1)0um!JwInw@i|ZHBH%-DH)o0sjkB_;X=@E zDn%8B6gH?IN($1FOi3eFOi&Yj6q()0SZ=kE3fo)EE(*C39JHZ`z87goz;4WiKkGuqz zFKXQc+U^j1iBi1^9o!Fg1LT8Ys^MZm zh)01(x%TJAMjF9_LFglTAhnet<2nq5GanH}W#Qd0Q6LP2&UXN-EYpjSFmKO}#uQwX z;@!zCU9DN^HC!&QalJcw95&*n@(8d(xkAt>VXbYRg7(otyyegs+}ScgGWP;5t7Zj? zm%&~2gagE7;xYjIOV_IJdN2)Qki)R2jTztZmonKKoWj)#noF*`4IC5Zh^NJ5mYnkH zX{uryfg*CFz|~Q!O_x}MOL_#4L3x`*6*q>}5mcf$S_4oa%dpXm@o$IFc21Q#$71rI z3ukj#LwAejg$m*X*HJx8z~bugew!t~7@XLG>+-*y-Wzl0CPcm3yiD?-3DrWNv6YP~1Rf?lm?)pC3Numby&61Tu1~3ATEhc!SUo~NkK&Q-=U=hi&R@XEj86)T+o)1}uCk{2M zd2{>^0NRS+0Z*bL>rMx?LeF#@-etRv3@tD$?8#2zz?xdaTNtGr;Q-ghw0*}zL|Md= z7!HjV{@}Z-2%voJi|%FK4pp`013_LFa-0|BBHE`zi|&P^q|>jNZ1$u|T2Uk$lw6v( z#7vL%sgg_+!n;@BeFMU_&)^uA!%7$5ews9$W^uePIt}s|>Y>w#xTvqA!od9L(*Oke zsqQ})id4k9J+w^FX$Xzhp(yJLb%%9wiXp6y}m(q;|Ir8E+Cp0W)B zRb`lQs2UDCX_6H)2d-y1X`z- zPP`81mI1sQXqd!fK4lbZas(KhGV0Ny$7TsN&-V)I1&r}peyf2enWL}I*4KLPP$36Z zD7!R{(RY_1^9<=Bz<~AR=LSg#3AN8eWfzP+K#1qY8XJTY#661T2+`aWUGCSn(w!!1 z_4Xtf)Q_ry44~S_%3NIg?S*C z8Kt26guveZ@Y75OOUWK-!f=3=<`73xeNk>m(#3N5#M;-&C^Xe#!QN=n8T^hKY$M&| z(93zZ^XuL(2ZRwf{((y;6)w_)f&V_Z6gUY^1I}-IA#Dn>T2EL9I>fP)4}bgr$otu{ zn9R}OqC#Nykhb$D?wi98hwSi!L*mt~9j;MEumX;+cAo4@3aio*95AlK!77LK)(?6R zmH3jw)Jzq72tKwn|6UV9T#!8Snoq!Y2Zx7HUvjK&(iY79>gAsMt$7nT_qBLh^}qjp zxNNl`)fZvIP-jX3Y8u=Ez0IhwgoRPd^Ghw!o$xBS;X~zm^k|D7A77T+xo_~{hr@4< zkMFiab{H>)s9cT|n>MTeU%}Gz-i{P}*uP6y(d{~8&UH$*;$ex>mAq=|!~k4r_|?Dr7s%V64cpv!7v-pZL#bn}fV^Gb^)yO7-ZH}g^Pnk{;nPL4sncauXoJJ~!8 zDI$>JcZOC=*M=}kNjPK6hhoAw@5;Q61hY-#61FLZt-w*TE)yxKl~WSI#sf;Y`hd}Z z;joG{q#lUaN$?pL&FuJGkt32fd>uBH8bS+@mEgQ|BBLAxi4&Z>VNOG#(yUepUardqoi|gIi?AgxH{RRKs@*{b; zH3)qcmZ-7xoDLoy>!0OM6k`?z@x7Z~kKT*Ea(GUjH&0(6xubk+!lEev6OSA)AGQDs z`7Hm#gAdd@c~_a~h^g%z`b4olSF-=+Z>f)C2lxoSxBv!Z4mV$}AgH~tIzUgN*9xH> z_oen}yl>xq9M~*D@J^kQVP4`lNUtymi=5!S?k!wq_w~+Rj(&@N2P)Y%XH7fAdDn;( z2Yf)DHV!BaHsOk>3nbS_3LmVC$^ZM4#~y#|f9?N2^7xbO#%^0$`mZSrxGA?$Ik3hH zxsEWcEtG7#_UkK)fdGvW$IcA8!T_-`MG#N?Yd|ATL(reVsh&Cca2emw{^ zA^jaF+Ec?7OXPJhA5NIU*OW~oJvMw3%aB-zhfUV3gna^-Q22;@GC7t)V+QD?!=Zt(LT+k+4P4b)rXL{Fg)_T^XjUP+KRuSXo;EkGu5 zr^8GQ-o+tHDUyA<(6PmE^HXZzt#hasIj@T`a9NHZpnTyZUFAc!jqP}ZI}j8IcXyLx zRpY?!|9F9HgARQ**-Jblj(Vv%k2h#f18f`wz7IcqJ{5l>3(eA0SZY?cuqx<}DPe)G zkONkUt@XN15xZ1jsafq}z3Qd#O699_-kVZ33@*VtSyh~pcVV9FlQr2l8^s&mvbzRV zvScPw$-Hd!?G+*-&_nwcj#72+s^e3|igugxMcE4CE4E-#K?n$|6prX+lwrDyEeBcV zTu2Zqj1uG=zun=D5m@$xPq`oQwiWp479P{%y3vQJvaDE{?L_Up@q34zq*y;W2Jkuj z;3_u5S{<8XUI6Zuq%soJh?rC&Z1<-rW52?>gZixSFh=+AedW{!(ygUV6}VGzJbr_f z-2?fMFLww1zOS(HD?7?htv*b_5X0MIYL(BW+_1GOD?>tS7kfRfSgUh~*R}oPn}StB z_l-)c#@4FT>lu#fs>lxRE!Cd?&%<|oAG33H>AW1B<9ocg@9WI-^02x-LrH*MjxdZ= z@XLM>v&Ch>gL)fxmVTHP;9N@rM>wk=ANk3aVML{wT(Y0 zwd#s-aZyW{Tup|{+M~x8AC=zOaX;9gMxO(;jF^Wtuk6y>6TVakD4ny#nB^gi7;z8P zyMTx>_eZ0;)DK>6NPt=2|6px=B-v^`3C9W{RZEzxU<7g;lj^m;pv@A{Lgofon2#5< zOE8mg2uac0V|w9i6n0r$`)@#X(b}K%<3*XfBFKaK zOB=3JD@u}US;fROJf!ua5(BCA;R>bvMhTsK;je5xeeu9g;J|Is+AsFCHr*uxphoKT zWGxJ4n5FvLQisu(_{}a0qu44IGxFf7ZyP;w@3!V+}%8l9*&}PIC8Y8HFIuxPn_NpWRrHSZs+qqKUqxt&DGUMUfI-5Om>w+&;(vh z5`Z1kH;5S8<{ZUOMR8`rudXg@ZS&>%*>XKZ+ni$z=Lu+L;CEM-;a0~7hYwGGcX-;? zoRm0EKpE~2#+7FX8@Yush~B=ziO2^GhpZ&5g~%;TrG{F-BAs!K8@2^H(^-sKG#*{N zI5rQs;sLm$m%NMI)?CY!!Ra7zjCc7gazk3$lymoS;D zz+!S@wZynftXY__Ybge1HGbXT!Ez9{!46oTr?$OAvVLSHOheu2O6f6#6{H%rudW~b z=~3PD9)%zf{oo5}GYgqvrz$jyn0+Zgg!fOsoWR=pq#2DVp?RVe`MNAio{d`av_pNL zqUVE98A5v!y6etsUc*j+h#NhyvQ*d*#ba8R+-k1sKCcT;)RdKe&z$dkJSRtOyZObh zZd6uHD1kzXh^~>?JMcN?`94}&55k|gDLcV1T#}w=utzZB2D6@36+p{*$nkUW0oXt6 z^SV(o@XqQk+Ac6v^)zuu!19*pNeefgFOPmQizDT4W#u=5;PrU8OPrTsKjeqP&g$Gb z<>r+LbDN_DOz%O{|J$DU(!qD71HR2e2l;mW+xBlC!?9b}#{=+j0{G#sY`qIr55!;-6ep1v)_);IQ{9hg27?9nm5l`EaSM5MLT_xG3+~DU857@+Xk6K zv!CWOssG(~v_0$r%937XciXL3!iYoCOdag6ziViT^cTn4nEg>d0%cds;3^zlK;RN0SktoS_KfaZQ^XV2foFZ2EPD|+-?7qBZJ?!r3Ojduk5Az3zvD9XU!+P(edR`aw;%Z zslk`Bi9N(X3Ru9Bfu$_wOb4z^0Qvud9U}U}_4e&nJYhR$^M_F;v!BNzng<{8`Ykc5 zbR#~VeKs;Z)Ny`&9~R$zxURpxb;oAeV>qJcQ&>E?_FyC4HokplT=w}LG2h%`+VhVC zowi04nboQtm*iWpe-b?Lx|3f+T{E@YOH3VrZ$>cIzKwJ2hoz6OQJN+X3|t>V z0*ok{kjgwp%s6Mz@}5dwcCb{iB;lAEgKn;CNR=q2u}B~S=2C{VTVn~U#$y|Y5jZMY z&?B#aOgMUBH|%s_Aw|xA;Z_Xrs^bW1eHRX~hHI9YQ<|F`3*`nF9X458g(+D2x(~NQ zb(>)1Bgx7fqYznDo~xnn2hWT30WR`c7+Gb51OEc)!p#nyUEIpbK$sHg$hD z4CA}{mMV(7t&%_Z$N#;Tq^3_~6?0&Q=-$@mu6&E)(YfcKpMCuJq$m?p8hCMOEmWy1 zUq3z{gu%x1IZyht2?B#AyI2Ms^rxGK7I}THy0Z)rdexz`^$4+wFT~MrrpYImj(J5j zp(SiU84b6k!uy#`DmMB0#;0yYk16Ar79-+ z>?SX&_;B+mpVG!eRzJD0I-bsQ$PXTQmBt9;d=xQj)(NU3=QLW=rGx%R(G3sBO%$G| zu_gAZL_9k| z8d`A?*=allH6CU%*Y7f_#@2YfvYV_kqBZSabf?ux@Fm+R!&e$AwF@1M6r;ug@+lm{ zC2c^5AjS`*YYaxUEn^R&%dXGGy(}$fwO`KTuC$8UH%qf+D|}OAZmx32px*e&sz}F? z2tnG+L?k6k+)j>Qv^t8aFjZs}-x65S5ZW}{Cfhq!EB0NZS?Wd&FS!$;(XM+(e)uW3E)ad04yJ(HxsJDvaxdrYTFqgDhT{aEb3qy7N5-`l84^|Qm z^j;cS)sr47Dq=@^9(`>i!`PC0kg_%PD5xn_lp``xo@|ckNH?B82VOVN7&;R{I=Kta z6DUZBL>a#voR-#yVL6OvsW7~h^U36w zLqE?ytNr8d^go?_uxcRiY_|g*GM~&@G&j<>r$Ik$CYPJSMz$&6QSe_{WIlD5LUYAj zdD%1Fr`fcrb9Ls5ywa^1D0BH#b?gS_vaQBx6KE9RRTbSa7ZDCNo6zBQLC_M3*TMJg zHV|@+s6FF-ioH#Uu7VlN}ht*=NYHb8tIz^=1GK9l3RVq zL(1d^Dv3^5r8-%%$oXi&ms%AnR1nVq^>x8dAU)xooD?M!AM6}zINFF8g6|KjtJeO6 z*s%h&c4dHC#aLg!!b9v#j&JDPOCRJ|4_#gbS+~PLz z9OscE19%vtiP*?k#*#pP@pyl?t^Yhg zWuySf){7#kstbXVMGW1>AcBsP^9$j%@YnVv1neshZbT0kR&O)0K=jKc6RuS)IA<-c z`PtA*dmcvY`CzcXo6XsoTV<^Bu`m4p;qC=DZC>a*5b7*mr@)9cK| z@$}b?yCZ*>P~FRRLMvaGP|%Dobs@}})6A7&)e+U4X=1pD+mp9`JvyjEzYUVCDDMh= zt}_PabUD^s$dcHgA;H2V9K5(=mkpA^)DWbrwjX6ZOw z58@)8F0(IVF&NPjHvmYg)@0ynmq&D8+cqsR*PiJ~) zuGTq$wCTYg3^KwM8EX=1T$9nZxO+yL-k*Yp@0R!H&GzPq`Pt?HA}AuXm!^G9JeG;Q zr@s&29;M0s*gV1%J^?D#u)YsiqCko2y=@5+hQwIn91Fn)-}L)+EMjd3ZyKJC z1gufd2Lr{GTZq-7vKG%+^dn7ZbCg80W2||GoFKzUf zWwyl7H z#8%?SB*y(wmo{_EXZwo5xU0di&M0%oe(XcGGxDPnS}so>3}u0q9TuoRwyzNtXwb2) zBW*t#Ye%{QtEOqRbnvAN*u>;91(pqxRg`P$snq+s8K;bM-F~lgn(LYjHR=C=GR?DE z3fjRwK9&k023X2WG(2aq`jcvVxLiTEl)lMx<|(5Q=og7Gmx4oU-5^<%{?hPXZlE`c zM3#kzGjz=^k9Dm?P7LtQ&7=cZenY(hmb5KLu;=w2XWxBb4OGt@~r^ zRa&k-K~>sIDVPxXP9uphoYHvBAnRxYH$}_1ocapLXV37E=V22G`4vD>NxoIpVm892 zXY&`TQ(bL{DR@Pd$4$VOZ3!QtM<1)x_%ro%e;>5=e~o~K#GO=C*sWt46z$91BFQKk0np)zzldEU(EU~|zQg9BRge0B73&;xygRA^19 zjg~r7kr%qq1NLu|MXE?>&>$HekBCUAFo(lV)nNf(3(H_-MPY=^hX?F!>7S2m3_hd` zl~V@rz`*f`)m!WC{5QWm+kkgV-fm&S%cC#eKvOa{&c5vDy;a-Ezs!oS*j{qGp+(4i z0f{41Z=qI-Bu&cRsB|&&=g4pM=hK58pp~MsmRe!5eoHcyY6o~!KaK#I<K&8&>+3~HyEnAfW8!RcZk8zo zw@-U+NOlKo#nfQQwN~<25!*)-x%5rt$?PjEL(Cz~4tph35b3%kr|Q;zi3;t3SPE}O zCt1uO#%Sn00yeB0_s|kH6*!yd${|S!*1UocA^n0tELawzspiYD+kyzHmTsoO?(Z?K8!6L{ zN=Dn8L1Z+f?-Fop)44`X)5#*G?JMEupIPV!C&Q9+=F~j6>tJ(Gl+LqTyHsnMNlKI7 zp+mnlI-+2}`}Wz3{L2G8P#^m;pZV;|++}qVrXbJcrtfYr(W{aWyThdhn)|ld8XVg_ zl1!J}^u1vd9E7;$12Y6GznIOsA-{wQy6^&qBGk1@UXx3Fnav^6S^d#NT(L}eyxK_N zO%yUAM8JY>c%x)9N+8d9)SosQDFh>`4xI#0>3G}*|M{lAGAruS#90Z|t#;|WC^AM( zkq*yM_Zo3o$02yamg|kBX$Hc$iiYlPb72H!7&T+wRGjFXj8!vEgx+*>HlS!HN-`#i zE1MLldg@YZx^$Wgm2$@xTSup`9e)!4igi+9B7C{Qd@Jg-?JTNs3SO&iWe zwxFpVQ4KT#f|3~+srXn7;+LoT$235OYZG#q!kG^>mROEpKt^&{k&T)AD~AjQm&vs8 zQDnO_)c6jqFLFOgIpXtBw0;VJA_p28xw*c^!yOv*^hq{FQvo-sOFF^3khOm$o8s0p zfKJa&LORgX)z(={c;{HavoqQn#s=|z>2Hbo|o zUG&0uR)GPmEyB}kM5lEVW{hZ9g7QbzRI2By)YcB2}lhUl}5+osYe$7VA1V}4G6AsWf` zCi2W8P>ipUIZBR33ogOG1tT~*5xaUmA*;EFpNc2X;63rGBcAP5{4$gl`JIz&Cz-U3 z%wG5dRynPXT*JmGt0yUUrT1sfUgPD{t@NZe+PmI7JYs;qn=HToJw(2hv&&2FJ>Q25 z?JtMt@rJ*)sv?Doss|9rfq3qg;UMO@H6%d)tkT^MFeZ#43$J?_;?~mu7X{M9{P4v} zof4iP9`3hyRgY(kPDV-}8dy@NSV@r%@2^xC-ANgirs>ekBE_fUkv|iov1y!Jk;6^h^ZtEU9K)XIVPwWG& zvFq=NsK2t?2Ibw)6-v67kjHqq<^*$w_AdZrEehu|_-zt9&)yivEhf{ASm}|C%)~Fn zDjPOVPsSONF#;CHd6IJLf-WJlA)yaqqYx)goG-j*J;T4AxDbiNCoheA{cHOA=V=j6 z!;y4FViL~_o&=4a%NY2;IeSPAi`mpdlvoNF>RSa1fylr-+oxV63;L;&nY);AKx5-> zNvUH5+@_vaFvyw`1vjeKd4}yEP-`+CgcmX8oz1QDuI?r8?W^NQ-790DZerg1w7JN} z7q+Q|ykxvegb^5{`H13H39bN(d6^;!;4LPQB8}$>11rOxfqQ7qUCLP}&X(Hm>m<*5 z7BCeaXL2DbAO#2T`eHYL1(4P3Lqn+N+WXANnp_Cwria4wUi#(`!BsFXzIX9AW$ni~ z#6_?z>|>+sxW<+gYYb;uQMvAvG?r{GHorF2A4 zg=H@87C(C%g|qmu=4#b+8b^c26TZ(F`@NSR|GK}`llf0=0j6mT&$Pnz?-G{-5ps6B zUlDoJLcM7Qlm>>OMH|-`121GTjbe7$BD35FM_b(dA|4uSef~WZTWzd&D6kk(;KO|w z2NZ&!w|7TqkbX>d?pFt$GhuRm<%l3l6RKR)yvdOF4{lG-Y=H=uT3mrW*A_Z78x6)o z4xnZcl>iK2{Hj|y|{rZmdLu0>7xX0*YEw0hO> z)x~RGIsW920-qni5R9zeX70LxHMQZU*0$6c4*qV6;eyjy=!A1}_O@>S^~Lik{O$*b z-hRtoF_<@Fh*7p(1=%|C6WOGdNlcjm-(V-Z+d|x~B7eGsk$!6We9GLapcSBHFt#;B zc!W~TJnb$4WE@q}PC0(TNzH|l@^*V6M4J!)u{sQIUJZU+Q7DqQuUGI9c~Y6+S?*2i zg74|cC1a@tYcuxmtS;WlA!cN4-NdP%wf)O-E7n^)1_3skwBwF~w0HsHCtS5{&6m#; z-~7lpPJqa_Uh03>xpy$V-P*TB=yIMn%E8FC9jyg9xvQe6C>+s8XqrO!+;Y@Ig{Hoj z!USK#c_9F+l-xEz^-lRKUkz&XM8Ap{gwCAhS_$~2fjF6soKx^?BNGxwl#&ghb?E(T zVw2gHE%mTtQ9KfDK31cdQ4~dFcV^%e*_kW_FjkUccV2+xZ<`pK#vB=>0{ThE8kl)h z8XfJvglpfeWW`!U2WS(^vb8<}exhkRPXrnzgp^V%i=6d+tB0YtF}Fkx8Ed=`C-aDF zrBJdClu{044ESuPuA|A<7U?nP{bfK<98^L?=caN9gZ*Zt2uwlH; z2^N+Yu=awuibNi7q9o_0rS8VJ6@Ztc%UyXYF5A+4Jiow%XxXqqP?HHX(z0bp-*EPv z+??{%Pfb9<7}V8~S#$1TU|{^9m`bVi8Jqc-PWT=x1VFuKvr@C|#gEt1^~x3#M^)6M zi2!~ZD0JLy)BJJ)hjpUoSMf-btHPx;VR~S!m3@(*c~s}qs!cY~O%uawF^qf~a!QCn zTl?Hx{|Sm!QWxx!uDdQtbBSA16S9zjF0fv(=kkSkg=Z3qGa0JljH@k4>{*yL-!UwX2w8KuQlRgZdUAArhTM!N zk1x*dR6m|zfeFDqiXf6*6y!H{Khof(gRq>laI2M{SrdXDZu_6e!Q06AC;cajmONP^ zDO*yUyn`R z=o^fB){zM?xBLZKW8`?RPzP#d-joH75d@7AI>PM|uE~(nugL00H?H=a$mmUbA8h6$ zLXie*48Jp%@Vk1Wd{MPNlO6+5PAvQ~a=(+APTmsKdzQa({=HeEs-5!=-`LdNV|cKfD*F8E!Gz&6Mu~=4Q%lyonN> zuYHjU)#IYLsTZ=xu1#?&Y^EYzEqcS~|31ndHNFMlr|#`fYO8l@6yA0J_iFw_g8bvj zM~(LPxqt$Vl$b{6Eu8lzMn0)_!%?`0|C~dPmW%Yo zlF;)(P<=BY#0rps802_izlBJ)xG)I*na{T~_^H7Kgy^BNtu^B-B%F ztJ7HBYkkUyN<^cW@u(H#obx_#t&QP-?l4{ptumX14*iTWPih?cA5oe{GH3{ItV+!c zJ%SsbpNVQ-xM>q45|0`^1wXC902lDe`jr2KRJIlL7uj2DnC^kcv;#ko}^${M}jk& zB1BvrELwx~Nu(D%2oK;p@Exs)xr7QW=aQ7hSu#-8@Jau9n@89I*Gsvpgx6^0O#_2F z?s|LuC1K*`itU(pA2IKUY`84;hQt!KyV(tUe#_rnZ~ek+_$n6^UOgJY&Pl|&69jX4cHCN8-DOOJd;)-QYrw5)Jhqt%O2ekl9hIy0N1JrZby(oR<7ydgKVyeKL|WX)b%x@@tpY?+fLG9&Fb~v$?L|;M_b4}uY=&f+iOm(;i4vFxzuMY0$Tgq;vJl^@_0>;4|9pKx zL>kOmYBs0K>3nP5vTe>mm*dih^5r@(I_;4{Q8ZoF#UD5h(Wm@SisIr z;_55uY^9rLB!%~~1S{0g>iqZM->Y|wT)NihwCYCgYEgK7kAEyRQz!XSWB>a?3PdI$ciN+tA|`kdF@& z(dO$q-Al$J4PkVK@w&A#f69FQUvFN!?62}q=d0K5tE#zBj0U-YiT+<|c3G%V=O?9L_l zSk3JUCkI_zr_(oVF8az1TwXiac;+-;x%xl>OL3Ar@#rvJ8YH=8@;UDd1ghTZ#nS?S z2~IlVa&@_VgO!?&-P`3-W|x6&(Jtn-6tHlt76U{Va%_kbd?kSd-F1NVD#f?O8SUez zro0d6=;!e`6Z`l5kEge58vHvKPF6ZJzmv%eI>x(xunWTgbmB-=8_Xc~l)fEfA0FMI z&JB@w&!z#8ac+pK4Kw5bm=KJh^LVHYGmjV8)Bw`%T3!`p0%E%-{PE7`1;j4^!3N;4RPJwi+{`lFTyn`WW{ybwE`_ydd-dy zY^>B%@CpA3_QT1^6MK8UqoVAVDq^MXDz&QG^3VAXM?I+jG3eybVi8l>3kEJqRAtRX z$YvMDszL+8ybK#>puY-pE zgfYofBL3eG;{!xQ3rUB0tn3e^SkBKTl1Tt< zsXx@#s<(1V;feF7J$Xu%?zk8sywFKB#iVIa(d?K6EQYxT5aWSB!#}pJNDHiW;WfRh zi3+K&;e79!8CabYUwgm8PoV~F1Xkj$E=Lb_Ees?Gw9pqs0N@|O%)$@G-%&bzg>gCW z`*!@jT)LRLK=4UVgmxOSy;Jv#$G~P;;{W=K1It9nF#Iomy_aVu%DSY*_1Pe>4Y9!+ zdEpbcJ-sRJd<<_oD>@%1Sd}|Sz{v1w+tRb7nE&}~a&@V{GIApv!w(E^N((Za0Umn> z++PyEa-@@7CsgrY`Q~w+9;MIVekU3B_EQRzd{k**$_tg6t_kZ93P44D5ZiP@zPilp z^Yx7=aD$WFV9SnOpIZw)jriu+c7`nk$8m|BV8FwYY^I8hCAM>KG8;?{jHxS)Yo~H5 zr~VEDPX9*kF!ZcIJ(>FUhnT_exej}norKzpw%Z=$k;J@OA~|p6+&z(my&#c=nSuH! zyXnIscfTOUg3G8_4@%V<*NEbbhU*mD*VSH61*a); zccGmwMz*z0xYZ`-G(yLXCc_3hFAlgL@svkCF*F?P`UJQlr znK(}O<}-^%oF@KpFg{$#6ZSj`%|7q@E{UOuivH9n9S5i^yBan9t-2oStZE*@R=qg1 z=C(l&Ow*v2H5@*U;L~rlu+yTEhQ)f32QX{K??Pjsmer~vYFSMNLTG-+91KZJhdH(s z2h1=_F?!{nvb=geWsF%P*M`8yn}}CKo*xPsi^Wt}&#_p~SqayCzS^x4`O30DWI2@+ zK^>3;br_*~wb1J>jic9y*_S|Lqx@&@`j_|9yKT5cIUJULbn&Uw~5g(07)50IC7HhzxN_Pxij$Rf$UID zdghzR zTbf<#+yCNMN_3=$);!lg@5an*+y0}6Poe|?Lq@MRd!z|h3Zjus=H+@X4YiTh${Q#1 za|<=Wmx^E+s$trkTmR`KxM}_d9jH1L7qj>kEG3SuIOv1BOUnX)a}4He!!uX?VKmy1 z3o5xY^8eW@ifhoox706x2zU~Be?)asjp6>{h}hcxYpMHuP}}n!1wN@?RR;!^ELn#2 z^;N188tU^@w#~Ol<^pQt3f;=F{y$;VL$b7=+w~;qn!;RvI?GMBNxS3RpvrHd%Gd`T z=;m21LcLAaQ5vMqK(87iFc*i&CT_1Cy^b99sv-9$wPr+<^VEIMWtu8nbIw-M%Ccii-ndpMGxe6ln_V$4NhjCDhp~W1@w1X=rIRbx2JNjCXTR&b z-Nklu+1{Ve#(iE2#Fw_pOt2wqpi0j5L7mWd4Ina1SHn>~md!Xdl5dE;BpQaaHFmJz zY8}=hrKkRuHaVlIMswhCf`~t+=Q!4dq-;k%B*TS5@9X=~HO5nt=wfoh_-G9i3C1UK z+Zn{_AL(o@Tz(K_ADQ`&VTLJXsUFvfEIc7FmRb1+9D73->A!GP~VxOLZFgB&yTXAiL#L+H@u+nSZ)X?%X9XLt(Yd@2I?KARXffK;lwC~b;uEJwTAvE)o zw26faX5GOj*|8~MDR5Z_m)hNH7Mg*HFmWPD8rBL^$+6X)t;5wbEa%)vsq>=4^6Dab~k_;*3X!Xf+6@4`Uh=@sCEj11-ei#w+P64^_9O%6o3Q#R4CO-9SsALQJeq^@`R!61m z!F%@>o@g%;*=t}A%qu2u+W-cctU^1Ecwm)7MTv+PHlH$HFb6tSq{Ag;v3rAsL-q56}wQ-Gj?oDH`7Dc9QAB0LX zgpi%NTM4`7au{zkZ8pWI(yY&cH6OLLYE4)*m3;ucQ_01VpgP417G*ZE5t&F;hZUkx zp%d>Js)eCTL%wZT;Dwn>Lt7_Q% zdtR7X4N2#&O?9H9EHQ#joQBm>NG`{*BOi2Ej+8GAy3-g_(vQJhLdPnvgWQ;D7hq#wg%IgB-C=!OECNTP@p0^l4A z0yL%)7?47dH%UM`${rz6Abd>(`B=PX4Y-uZ`%-d3YF)+}`4M`xNb9{DhC;qdqzzzf zsG$ZNSK|>{E*lUOFq>f!vEV>VJ$9DPxU{IK4LJA%^tgX}9p4v0QPl!z2hkq|hBcrS^D>MG|2@3P-?w}3xh90 zdQ#Hdm8HSEEIATXS#oGI%*SFY-g_qjawVR2;Fe=Q%`2rxkyvAZYg%D^PqM`U9quBch1Km`tiRNz z!&d$=O;~e|%(e|{dyZBq27%>!@xi>aV5elHdOh=jRNwtJw6j0c6XuhrWqnF(YOfL8+K#D28(u7 zJw7TaD(yDKqfCAcJ9`{KG`6)izHV(h?EUME1fj7X(newfSy^hX8RZV#2F{qCbrwgH|f5M%BL8_t202 zqaLt$0pPe&jpHFnek%RQx<|~eCRwUP*~th-gkaiHd-yiC)4o$tc$y~4ZcMoBDHX95>uV%AFh#-B zRcDGEo_+G8#;QszY~qjTZRsUtAF7vfVl$ccXgzwsJ{3enkDM|P9YJ2 z#6rF=na!O(MhCtHSd(7yHg`*8QwJ)}jfOT%=YClq0`M%CwQ3%wIEF&0S#m!=g>SwZFuG|3r8w!MO&`~umP8# zf$O;~a+mJm0oR}U0X-lV~qtoN@;gBvl8vy`D z|H^VARpa-}$AV}9%45Y`t)t=&cDe)RN0}xP8n^WMjGkgAL5`fONO~qOIXTki7Z@q8 zMmNs@JkRrS+%E@)trvp7zWk%bW?|N5W3u@|HSQJwgIW|1fB5)niSNoeYl)&ewcF#N z1gh?ydAr#8Z1W*ZdnSkEPA~QZibct~9nRwTC)@}S{p@>6o)CV*$lOLmvQSFA4Nj?8 z^?OO;pCOp?W}17ia&XvQ_yG5}uqZp9F*l!1PiM93O#PvS0Y~hts+ency|LKE>JnHp z*H)`SB4QIS%);_>44P6V0{R*f>(nq)lJ(zQRCNDdlAD;;k5uQRiHlf{co#!NRw~gS zoZZsyfyR>w*crsSbINfo5VY?K%<@EcN}UfZWChe8NNd}dphPqjnAt#vq0gpn?F~Dx z4q*XC%bEZ_IwVQs$g0&*<1Lxl=t0%hMxz7eBEqj6^cHnfh!DyTcU9H;)rr|)3b#}% z*)ZzM1|0}7ifs>^!R9MSRcfqCb!ZPN76Z+PM>sWHXbwJ-#(>h+WRO0<5pC3v4QXbn zR=SFuU_-CQhkVN+X}ZV(N=XKGT&z%ozCQA3c;EItV{|@{b4j50_pf5Xh9BwnL!OFV zqUcW0rfJ2IQdA>G1gT8K4RcE=jt6#l!Gl`Xb3_EJX+pOjOR5h$p>V)0p9pD zN4x$Ibmd;|2)wA{eo0E8vBPJ|1BBsSdUhyAEFj!yfsG)v4Jfq(C5r&@wFcTzA`1NStpMN^rA~$+c3vN`<0l_f zp3JANIxW+f4zbm7MFJjLWky6guB9KrXTq8$1)UYdYhSiwJmKvuycX31EXKhH4ilEN z1U`8)u*eSmHnUPZHJ*rx$md^7py++3M?4_=V5M~oo694|{JWU1WW0(<)-z&Y0>jfk zhW#tu+~9kuZ_wb`*S2~O-|A%mg`agP_@WLqBDTQjp2;w;|jQqWs43FN0i0tElfeYlOAF-191~?69R1Ooyyh} znUR4CMmlXFtA&B3;pq#&o+1^XxO1YctpRGh581{IiDvKN-wyyJ!~}TQIYxrXm@K-g z*rYo$EB=v|cC0(nVA;VHaqv2c+u^M*raF8#agvOvX#;!heUbAa>ZcKEaUevf^K7RO zu;CfjHB`)20D++C1tQ*eOKd(E2>!?+?H1d^_$U%Y#G?Y;y&n=*)ATZuL~_uORM<7E z?^XxGZqM%d@1Vht>3d;Fi47(Q^I31T&&|;A(a_<6lD?wLa)61H*mmC2iad?-CINcZku<8ync-KsKdR|2-;pw9Nw|HJ)-hV-vU z^7I5fzj0a89ve9lHZ9zP=s($eT2GJeYC#|bA87^Zr&IY}c#V&{ma#Ye5yb*Au$}Kz zQWn9TjyEpi2|c%sh;BlkR+tBe`kyXvq6H@1BCMy_$0oW9Bh}&CYAVj$n@ZS{nFs3} z9M1J0m8si9eB#qyW$w;G-&Q79BeAl0KMmxa+Bw$RH_cz_F)X)F%3o8K!rL=|6VKY3 zJRDPXmkn&FZgol!2hZkIGH9g$!qbDx!*AUaCq3FusdRa2IFYVB|LL8&ye04{a; z!@{9(0dFIj(q%J;kgQmzFk-)db#Vh~!&rK*c>H=z%2rdc=)6LjL<-o7uzh#_tCcf; zj#3UM59BZJwHb<~{6!B02an3fk3cn@8V4B^4IQDo=s|M^xJb+%=9Ft#LJ z3TqwdTepVQbXgC+7W<&IBh6oLM=!+4E?=!kv|Y z_-w^3E~-_s*o8B|ihGLp_JM1+al_#*S{J5|XTs;j29D*a7x#S zwkP6vnp|A8{r^Ej=1tuVU4Ms;>0oGBraC}*oE{Ny^zC?7*KT*P^}G&KJElX&7kH zo$MoBa_|Y6hlweuhG$aW?wdlf0MnezFM??r8EvQ82*aOrE2Z~}q`R;(pwauJ8(|}y5J1u= zRxolF9_AzWJqN|sO=dIG>^MaUWd<2X|7+yHr#zqDsO4ePay5E(5@YWcs>U^stnIt`TFI;SmvlTBH5DiO01Cl`G96ehuOG|1WRy*2xlS+GWmz*M+4jQj4bcJhY+0oii zUEf@lu2obNgsIPL{oXoxks8ORamxVhWT3$aI9kReWnkgU>>o#Io~-D*TV%fkEI2&n zNM~DiOy%qmeRhG`&#hiCo1d<}ELuC2G?7gd5VXF|3Qu!H879Oq2iJ#+7EKEVGRCrJ zXW%?`M`|P2X==nmWXsF8vPU>G_cM9e2j)6VXn?3Lr=gy?p}Zmw*h-HR%BgZI?dk7l z)b7>^4#uHmvsx+6zN1mQdrcr-S;6cVabra3dG<_^eFd^-c$(LC_!#6@B(KB9IWC9T z$7x(&*_9e*OQX~nCxC=|wL`5sQ|DqlN~>Ns&_W+>votGGV?8~rP*z4?ROJcVBH0}lr%sj(Ux6&+FOeBQS6i2ki>l_G4>dmogJ z)%>D4{FdKF0-@X`En6?^bNGGrAk>X6+R`~AZlcrNgcF{HXK8V#P~3z7r;*4FfBH=( z^*S*@?kL8rPG9q7M-J*n*fdlzJ>H2-(fY=cf&I99(P98{Uke`PcFQ77jbZ%!j*vc0 zt6`mmr&;j>r;#7Of@|a4ctgy_Sf7n?I;L(MtI| zSjuwwExWxF4(%V%r-xbH5s*?sK6-+ty}VhJpzqwhV=Y4?BmN>=oUCa3!{*FqzbN~G zb$xa6&m8-y3(6u!7l8W(m`;{T{C!l$NFaHFgg0yGWHa=X)if#+Vxdkb|dF|$) zF>E4e?byu03^ZT^|X8$Wr6x|1@XHDC+rEGA$fp_D{*}piO4!JA{yg&P{ zZ04xEX+ufhYQ~=f+~l_ZD}kqZ94frT75cKDCs63<%CHDF?DO8G$2^ua3c;Mohz}3{ z%d<~E{b>E!lg~UGj5}#w5cvLxcO19bx(gr75Y6dUOX3T!7h4%EPDbL>D7^s8LlkdgyG6f7E@MS7jg(F%0_fzf9Pf?qE!9O^;@2$7q ze4y!G`aoDIM!BN_?LYsa+HZXIFU(;FvdV_{Y?>KEw4Y-G=TKD#rJf+uJBz6(#e)%f z&rPg>mRnJvGhWDv2@a$(w6nl~i49*4P*ko`ll$1bz5F{~vUjF{%_sZD$7rR*Ym{i81iWZLbGbqD4MS zq`GGJiW@DeR3fM`L$izH7yM#vy;8`Dn~ThvawQX-nPSz4Or(h{Hxe<1E6Q8E2Zv^k0X*Pt24soG z#%)WY%s)%&P&B5;CpXINM=)tJm_b&h)k>CWN+F(6gqP6vqT~fZ1XqPbiTd1(^G^-| zOl&}AFpPmo-=xK>N?52LJ6V{H=ZQlLYWB zTal|iNiwrJ1~yb4mL%YP#?ek_f@lh`%@c3GscKe zgO9OM39P^`9GY|Rf~}sCv3IyYR2!RSAoE`pU{$)-BM2saY+jJvew#hwNxykGbR6aI z>pzU;d4Vwyup#{%96Fdhl&$~@sL9Zha7Ky;Pr7_C)PCn#JVc5JXA`zyRS}kx&th`w z@>8R&)A&~tpCMVHps;n9K9pQRjUX8Y=u$hf`;#N|v*U-k_Hn3hK{y`CLk3_GHcHw0 z{V!kivE7>q52)mx$Enxjb-C?#e{#z4N4F>E3!Wc89IcI+@(E*_Eg~LzYJ>y)lf)?Y zfOM{xnKA_fj`Pc2R{%iNLx32iSab3=wCnRac;d3G`S8tH)XJLvhElHKnqIHkPt49X zF-w$loyL&vjriA0vfbBfW54UkCs400g!q*)2x+GK!KzFG+QPS&BR38r{Od0ax14P& zm8j{_pLpk)@esn1AvqoVYisp)H?XY#PUxEG>X*nSC&P4G0+Xz&%2rW-G;xfipLhq~ z3|%v1Njy+xAB%peFR1Zz$!XJus0}Nv{B5uMn$_&*_}cP&^q+}_5&NU3Wv>Dc@MNS=OOV0F}Z}{`zu8tT#L4+d7-(I#w(m9GC|`9%GSpwFkpPjQh>- z_5N^9O}Suf9VBO3u_I#kiyv%A96ujutT zUm@FLCUHkyd>)>9SaKQosh2jG;*}2Xk9}go(EGr9B+L%MdCh7id;H`3nazG{W!T_8 z0SSVp`DwQ^4EOlr-i+W^<+;(a#qWdSQSH?Zcq{8&0HuCDFN7+bzf}}5Py6+T)`Zw% zaN494;x(@{wh374Fa4?vajd}vVXNj8?{1z^zlCuuduN4aS*!o7sO-p$X77mju2;$H zXtZV8;@#>c-7w21B1EG?+ec%vxrFK+4o8aI-<_EfJe@4!xW`GHH2(9ce>O_>aA^`% zGYsmwGCsz8oy@L$YcnHi)n}si)6-e75{NW{k4HZUMf1FIxoQWpsEUt$M?|t?#B4O| z*_cBMl`J%vPGcJe883CgV z!%TScAHcCX+l9c1C>U%(cs0N{J}QeywTd(J_S#e0-MtePFsy-ThLWY1-6ILtQcetD zsp)Nn7??~Bn3~Gj=gX{$KS@@JAV{^(t zt}E>dv=o(g30ekKvLXzVFv%#_`f%n?7^Nk)N6U3_I=kHflwmaO-EP>R%zxg86q2(& zZfB?n+A(vM1qEHkTBV{`(WeDLVO(_Y$l#6c9r+C(2|#+?>vt6mQ*HDob5s;n&%bzE zr1ma&AY~Z2K>X4B+vWRTfCMK~=9$s+bGPwBAD_n`sj$Vi4>cv0t=u50c#rX~Tpk)r^^ zkvbX29@2{iFMj4Xt=jo*X{4CgVi*ZnOC2d7;_V2_r>_oL4B$1jE`?9!yp`@E2us7; z`mEo4@yT&5xan)wv!CN1^{K^obG22j1;T3+4L$ZpPre5hKYL=Y&xBJTh=6CbqRpB` zIK|X_){)PQ%=dv=O~9+wTxRU&Gd`iGq0IsU0K9ke*rmtnOXc*8RuFZ2o|r zfv-UlEBX*S<1sG3$MVgMtlF3hk!HqOt-4MjpbNp1K^^5G>odu(rRVb?^c~&Eu60Lq zB9s;_bXTtJ;E;x%F2fe&dM!!m#*Bm95keRr7S41eY#S>0I3xZZ#5T}01?&98PR3?; z?k+RKg0ZYD81+0FP+~j8L8MhDei_>}VY4mQMF=iXzu-5G-jN2TIyf1AOeh(2cwp9~y zwwae$LhvfQ{4-d^pWhm`=b}_TH3t``AN#VZse=fZE32iwPb%= zRze5#rwzJ|_w9canV<)^5~9TFdfcdAh>lH%y3<%)EdL2Co#DrANWnj z5~0~J^IhpWX}22fl}fl-wdGu#grA}6vNY&jr*GMm=^nI zH0$0|@=%Z;&PKCVa4hqy%x5>cUA8@xoOgKeaI+B4tNpCdeeEIrvmWjHGx?%wT;UX=+}kF{0*Vv+3QF(qAx{JGZvlG|#?UHneY{9%Kio{COsqgzwW8{c;b;i6y0WV>^fN<$XnFSlNli5mgv+@Ig#rlK4d*S7` zKJM-hkWXXYjhMOAi&+#g9|roNWH)2`UkptYlY#}lwY9-j|N8Rdna(%oN9!Uec)YM5 zu`q9w7Y7(d!q5|P8C@I!Npt%Rw*n5#crgDvoBgLv%n=skzy-ySvLtmLP5Q+pQ_TbG z*iwzKrBij*B3=;X)zSb2mrN-uJ4NK03Q7iis4tB+y;aIn&^CRa%r}R`<~xw!F+x@% zQbVaIWD!O%+ zwsBRWv%}7JU7S+TrD?g;bQ)Rfy;iS15ar_7kygf!DJy$R{a&Xm%Kl($d$=m_InWx6 zR@xpL)h<|f%&aSB`84?q(})r&2{rd>=<9A?0YsQktfbklTfw*2w{kwRYgx4%*L6JX z`y@(BXN^lbzx9P&-(#jd8sw)pfRO~Nyi=js^Y~nRg%+q0< ze&w64-l<6{GFK*?)P_;hX_n=MH)DBz)2KqZxjE&{FlWJ0toqt;xM;>z^xBQ zI_#yx4LzO+bcNpmg3r7L<}ir#EWZE$UM)XRxvz7F2!=i?68qGXuqXQ8t0cDIu`hAP zK5%n+!ux#__q+i#vGBflPP1HjR9T*|bsyzd1)$b?qB1HoLX%cgp`G2LGA&c;IQj2)@8N-{z>Brw) z`bqNOS367tXjmJsjthKy&)?TSp%w%HF2OtJ2}z;i>jq5JRPkhyiWtA;PKQ0ukfLbr zOtHso<2fY-v}z157m5}rl`P?ONmU2fJ2VjqVbApEfw7_y!*_N$yf3Vv&O-XdLElQGKUaY zbmXzIP7?TLB=>`TyWjHT9S1aV9P;3EGOQy|nQbt>mJ2m>cedRA@qm_*@-+25?M8t= zNe5UZ3!PmLmp4~uuCpWK!yP3vSS-EZs7G}~^~5L+4YKW&tn-vsj87LCSGyc@UXxuY zB0DkE<(%hpsMJ%JT?`2t>w0afNWq3kSa}9IbK!j?yeiVFiX$Kb4$f$ZV4Y%Atm`;v z#X_Bhxe+PZ&$CbMpdK66;IDce?e2CP^x+jSKgwwM#o@m5#J?ud!+gQjvR@X}$MTn7 zrhn8eLmRjBi&2a)qP3t0dai4u4`yRf;=Ia`J{Vu!XbAUUeiZu(Nf?I}f4%ITTNTB6 zoiW4_hlbG6?b3|c*>eb7B<*vwBDPykv?LmpCS5-EYW%nXON$j#$m&O_h*K$QJy}-( zHjgv0cK}!|3dc)$mj|n8#q7sMv8c>-g_BMf=KAW8N!UhPk)yo;zW5n*60xN%?i#13 z^~~ONuM1TzRIe&m#6K)t6%j->U_@11?D@n~S@5zR@8^ztUc-@EY;4}Ne%Q|n7P1N%dSFp*np;(Q?Hs)=PT>T)s&}5>FlegK9)6`cJunw2G;2V`lyp9sXLo*+R(V^&# z>=d`FmaHa>HFj4Tb+26RZ`f{GxF8ZKyE|Z!4G`5=MMfl03mNI5Ouy+$5z46HOo2L< ziUVnbIS2y3KHQJ<80~hCl#LW+q^Xeg;)zPPjBVv>edl zLv8NolK{W-+Bbo}{0mTdj1m)tuOnK;9~y@Ks*>Uj19`uZCEp%qEV0BAODt!HKQIi2 zvx?t19zI||z#DMztD;oue{a~o`FQku03UwwY$5<(4&n_oTGGeg<^!mzKooxzHk!x9 z?;N-}B|*uXRqAb{^(7II8t;(_1w%W&l!x<_xTa8Fv#6lZDT_1{K2=-W zA=iGc9FNCcXCG1^xzmErNj&M&xn?bwe9w?Tj`TOxG|=Rn)<~3sH7nv?xu3$MtPucC zBT=n9SxQ9&nFblr=VE+}&T~Oe3vwO@O2lxf~&9d?;1iq)}QTl?*B+ zyL9xuc#XJQ_KIA08DR<3A�vqVUsV0sTS7X%tC5F&(d3cG2o1W`gza4?{R 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 I{{Ie=F_^aAcK`qY literal 0 HcmV?d00001 diff --git a/docs/mkdocs/docs/css/pattern-on-blue.svg b/docs/mkdocs/docs/css/pattern-on-blue.svg new file mode 100644 index 000000000..1c3c16de3 --- /dev/null +++ b/docs/mkdocs/docs/css/pattern-on-blue.svg @@ -0,0 +1 @@ +{]:[,}:{ diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md index 9d918506f..3c8594281 100644 --- a/docs/mkdocs/docs/features/arbitrary_types.md +++ b/docs/mkdocs/docs/features/arbitrary_types.md @@ -124,26 +124,28 @@ For all the macros, the first parameter is the name of the class/struct. The `DE ```mermaid flowchart TD - A["choosing a NLOHMANN_DEFINE_* macro"] --> B{"adding fields to a base class?"} + A["choosing a NLOHMANN_DEFINE_* macro"] --> B{{"adding fields to a base class?"}} B -->|"yes"| C["...DERIVED_TYPE..."] B -->|"no"| D["...TYPE..."] - C --> E{"need access to private members?"} + C --> E{{"need access to private members?"}} D --> E E -->|"yes"| F["...INTRUSIVE... (used inside the class)"] E -->|"no"| G["...NON_INTRUSIVE... (used in the namespace)"] - F --> H{"only serializing, never parsing back?"} + F --> H{{"only serializing, never parsing back?"}} G --> H H -->|"yes"| I["...ONLY_SERIALIZE"] - H -->|"no"| J{"allow missing keys when parsing?"} + H -->|"no"| J{{"allow missing keys when parsing?"}} J -->|"yes"| K["...WITH_DEFAULT"] J -->|"no"| L["plain (missing keys throw)"] - I --> M{"need custom JSON key names?"} + I --> M{{"need custom JSON key names?"}} K --> M L --> M M -->|"yes"| N["...WITH_NAMES"] M -->|"no"| O["done"] ``` +

Decision tree for choosing a `NLOHMANN_DEFINE_*` macro: answer the questions from top to bottom; each answer picks one part of the macro name, and the parts are combined in this order. The table below lists the resulting macros.

+ | Need access to private members | Need only serialization | Allow missing values when de-serializing | macro | |------------------------------------------------------------------|------------------------------------------------------------------|------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| |
:octicons-check-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_INTRUSIVE**](../api/macros/nlohmann_define_type_intrusive.md) | @@ -153,6 +155,8 @@ flowchart TD |
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_type_non_intrusive.md) | |
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
|
:octicons-skip-fill-24:
| [**NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE**](../api/macros/nlohmann_define_type_non_intrusive.md) | +

Macros for classes and structs without a base class. A green check means yes, a red cross means no, and a grey skip sign means the column does not apply, as a macro that only serializes never reads values.

+ For _derived_ classes and structs, use the following macros | Need access to private members | Need only serialization | Allow missing values when de-serializing | macro | @@ -164,12 +168,33 @@ For _derived_ classes and structs, use the following macros |
:octicons-x-circle-fill-24:
|
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT**](../api/macros/nlohmann_define_derived_type.md) | |
:octicons-x-circle-fill-24:
|
:octicons-check-circle-fill-24:
|
:octicons-skip-fill-24:
| [**NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE**](../api/macros/nlohmann_define_derived_type.md) | +

Macros for derived classes and structs, read as Table 1.

+ !!! info "Implementation limits" - The current macro implementations are limited to at most 63 member variables. If you want to serialize/deserialize types with more than 63 member variables, you need to define the `to_json`/`from_json` functions manually. - For the `WITH_NAMES` variants the limit is halved to 31 member variables. +To pick the macro for your own type and get code to start from, answer the questions below. + +
+
+ +- Members are private: use an `INTRUSIVE` macro inside the class ([reference](../api/macros/nlohmann_define_type_intrusive.md){ data-nj-mc="intrusive" }). +- All members are public, or you cannot edit the class: use a `NON_INTRUSIVE` macro in the class's namespace ([reference](../api/macros/nlohmann_define_type_non_intrusive.md){ data-nj-mc="non_intrusive" }). +- The class adds members to a serializable base class: use a `DERIVED_TYPE` macro with the base class as second argument ([reference](../api/macros/nlohmann_define_derived_type.md){ data-nj-mc="derived" }). +- Missing keys should keep their default values: append `_WITH_DEFAULT`; otherwise a missing key throws. +- You only convert to JSON: append `_ONLY_SERIALIZE` instead. +- JSON keys differ from the member names: append `_WITH_NAMES` and write `"key", member` pairs ([reference](../api/macros/nlohmann_define_type_with_names.md){ data-nj-mc="with_names" }). +- Private members of a class you cannot edit: no macro works; write the conversion by hand ([third-party types](#how-do-i-convert-third-party-types){ data-nj-mc="third_party" }). +- The type is not default constructible and is converted in both directions: no macro works; specialize `adl_serializer` with a `from_json` that returns the value, or append `_ONLY_SERIALIZE` if you only convert to JSON ([non-default-constructible types](#how-can-i-use-get-for-non-default-constructiblenon-copyable-types){ data-nj-mc="non_default" }). + +
+
+ +

Macro chooser: each answer selects one part of the macro name; the generated program defines the type, calls the macro and converts a value to JSON and back. Combinations that no macro supports show the alternative instead of code.

+ ??? example "Example: using the `NLOHMANN_DEFINE_TYPE_*` macros" The `to_json`/`from_json` functions for the `person` struct above can be created with: @@ -250,7 +275,7 @@ For _derived_ classes and structs, use the following macros The `NLOHMANN_DEFINE_TYPE_*`/`NLOHMANN_DEFINE_DERIVED_TYPE_*` macros are generic over any `basic_json` specialization, including `nlohmann::ordered_json`. Simply use `ordered_json` as the target type and members - are serialized in declaration order -- no separate macro or extra code is needed. + are serialized in declaration order — no separate macro or extra code is needed. ```cpp namespace ns { @@ -283,7 +308,7 @@ For _derived_ classes and structs, use the following macros There is currently no `NLOHMANN_DEFINE_TYPE_*`-style macro for types that are not [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). This is not an - intentional omission of documentation -- no such macro exists yet; see + intentional omission of documentation — no such macro exists yet; see [How can I use `get()` for non-default constructible/non-copyable types?](#how-can-i-use-get-for-non-default-constructiblenon-copyable-types) for the manual pattern to use instead. @@ -299,6 +324,8 @@ flowchart LR B -->|"user specialization replaces the default"| E["user's adl_serializer specialization for T"] ``` +

How a conversion between `json` and a type `T` is dispatched: the default `adl_serializer` calls the free `to_json`/`from_json` functions found by argument-dependent lookup, while a user specialization of `adl_serializer` replaces this path.

+ The library uses **JSON Serializers** to convert types to JSON. The default serializer for `nlohmann::json` is `nlohmann::adl_serializer` (ADL means [Argument-Dependent Lookup](https://en.cppreference.com/w/cpp/language/adl)). @@ -438,7 +465,7 @@ For smart pointers, class hierarchies, `std::variant`, and `std::chrono`, see ## Why can't I convert to/from `std::any`? `std::any` is intentionally excluded from `get()`/generic conversion support, so `get()` and -containers like `std::map` fail to compile by design -- there is no way to know, from a +containers like `std::map` fail to compile by design — there is no way to know, from a `json` value alone, which concrete type to store inside the `std::any`. To work with heterogeneous JSON values, dispatch on the value's type manually and construct the `std::any` (or extract from it) yourself: @@ -470,7 +497,7 @@ what the library does instead. ## Why does `std::wstring` convert or dump incorrectly? -The library assumes UTF-8 encoding internally, so `std::wstring` is not supported out of the box -- see the FAQ +The library assumes UTF-8 encoding internally, so `std::wstring` is not supported out of the box — see the FAQ entry on [wide string handling](../home/faq.md#wide-string-handling) for why, and for a UTF-8 conversion recipe. ## Can I write my own serializer? (Advanced use) @@ -536,3 +563,12 @@ struct bad_serializer - [Specializing enum conversion](enum_conversion.md) - map enums to JSON strings instead of integers - [Supported macros](macros.md) - reference for `NLOHMANN_DEFINE_TYPE_*` and related macros - [`adl_serializer`](../api/adl_serializer/index.md) - the default `JSONSerializer` used in the conversion dispatch +- [`NLOHMANN_DEFINE_TYPE_INTRUSIVE`](../api/macros/nlohmann_define_type_intrusive.md) - generate the conversion functions inside the type +- [`NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`](../api/macros/nlohmann_define_type_non_intrusive.md) - generate the conversion functions outside the type +- [`NLOHMANN_DEFINE_TYPE_WITH_NAMES`](../api/macros/nlohmann_define_type_with_names.md) - use JSON keys that differ from the member names +- [`NLOHMANN_DEFINE_DERIVED_TYPE`](../api/macros/nlohmann_define_derived_type.md) - generate the conversion functions for derived classes +- [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md) - map enum values to JSON values +- [`get`](../api/basic_json/get.md) - convert a JSON value to a C++ type +- [`get_to`](../api/basic_json/get_to.md) - convert a JSON value into an existing variable +- [`json_serializer`](../api/basic_json/json_serializer.md) - the `JSONSerializer` template parameter + diff --git a/docs/mkdocs/docs/features/assertions.md b/docs/mkdocs/docs/features/assertions.md index c551fef30..2f2ad3ff7 100644 --- a/docs/mkdocs/docs/features/assertions.md +++ b/docs/mkdocs/docs/features/assertions.md @@ -167,4 +167,8 @@ result in undefined behavior. Since version 3.12.0, this library checks for `nul ## See also -- [JSON_ASSERT](../api/macros/json_assert.md) - control behavior of runtime assertions +- [`JSON_ASSERT`](../api/macros/json_assert.md) - control behavior of runtime assertions +- [Supported Macros](macros.md) - all supported macros +- [Working Without Exceptions](no_exceptions.md) - using the library without exceptions +- [Exceptions](../home/exceptions.md) - all exceptions and their error codes +- [Macros](../api/macros/index.md) - reference of all macros diff --git a/docs/mkdocs/docs/features/binary_formats/bjdata.md b/docs/mkdocs/docs/features/binary_formats/bjdata.md index bc0599fcc..c458af6b3 100644 --- a/docs/mkdocs/docs/features/binary_formats/bjdata.md +++ b/docs/mkdocs/docs/features/binary_formats/bjdata.md @@ -338,3 +338,12 @@ The library maps BJData types to JSON value types as follows: ```json --8<-- "examples/from_bjdata.output" ``` + +## See also + +- [Binary formats](index.md) - overview and comparison of the supported binary formats +- [Binary values](../binary_values.md) - how binary values and subtypes map to BJData +- [`to_bjdata`](../../api/basic_json/to_bjdata.md) - serialize a JSON value to BJData +- [`from_bjdata`](../../api/basic_json/from_bjdata.md) - parse a JSON value from BJData +- [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) - reject ill-formed UTF-8 in strings +- [SAX interface](../parsing/sax_interface.md) - decode binary formats without building a value diff --git a/docs/mkdocs/docs/features/binary_formats/bon8.md b/docs/mkdocs/docs/features/binary_formats/bon8.md index 9efa00b73..c6db009db 100644 --- a/docs/mkdocs/docs/features/binary_formats/bon8.md +++ b/docs/mkdocs/docs/features/binary_formats/bon8.md @@ -201,3 +201,12 @@ of the message are reported as [parse_error.110](../../home/exceptions.md#jsonex ```json --8<-- "examples/from_bon8.output" ``` + +## See also + +- [Binary formats](index.md) - overview and comparison of the supported binary formats +- [Binary values](../binary_values.md) - how binary values and subtypes map to BON8 +- [`to_bon8`](../../api/basic_json/to_bon8.md) - serialize a JSON value to BON8 +- [`from_bon8`](../../api/basic_json/from_bon8.md) - parse a JSON value from BON8 +- [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) - reject ill-formed UTF-8 in strings +- [SAX interface](../parsing/sax_interface.md) - decode binary formats without building a value diff --git a/docs/mkdocs/docs/features/binary_formats/bson.md b/docs/mkdocs/docs/features/binary_formats/bson.md index 04aef6950..ae3ddeccc 100644 --- a/docs/mkdocs/docs/features/binary_formats/bson.md +++ b/docs/mkdocs/docs/features/binary_formats/bson.md @@ -204,3 +204,12 @@ The library maps BSON record types to JSON value types as follows: ```json --8<-- "examples/from_bson.output" ``` + +## See also + +- [Binary formats](index.md) - overview and comparison of the supported binary formats +- [Binary values](../binary_values.md) - how binary values and subtypes map to BSON +- [`to_bson`](../../api/basic_json/to_bson.md) - serialize a JSON value to BSON +- [`from_bson`](../../api/basic_json/from_bson.md) - parse a JSON value from BSON +- [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) - reject ill-formed UTF-8 in strings +- [SAX interface](../parsing/sax_interface.md) - decode binary formats without building a value diff --git a/docs/mkdocs/docs/features/binary_formats/cbor.md b/docs/mkdocs/docs/features/binary_formats/cbor.md index 35d72d6a1..9b126a331 100644 --- a/docs/mkdocs/docs/features/binary_formats/cbor.md +++ b/docs/mkdocs/docs/features/binary_formats/cbor.md @@ -287,3 +287,13 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https:// ```json --8<-- "examples/from_cbor.output" ``` + +## See also + +- [Binary formats](index.md) - overview and comparison of the supported binary formats +- [Binary values](../binary_values.md) - how binary values and subtypes map to CBOR +- [`to_cbor`](../../api/basic_json/to_cbor.md) - serialize a JSON value to CBOR +- [`from_cbor`](../../api/basic_json/from_cbor.md) - parse a JSON value from CBOR +- [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) - reject ill-formed UTF-8 in strings +- [SAX interface](../parsing/sax_interface.md) - decode binary formats without building a value +- [`cbor_tag_handler_t`](../../api/basic_json/cbor_tag_handler_t.md) - how CBOR tags are handled when parsing diff --git a/docs/mkdocs/docs/features/binary_formats/index.md b/docs/mkdocs/docs/features/binary_formats/index.md index 6e1ada110..c2e7d24f4 100644 --- a/docs/mkdocs/docs/features/binary_formats/index.md +++ b/docs/mkdocs/docs/features/binary_formats/index.md @@ -53,11 +53,32 @@ See [binary values](../binary_values.md) for more information. | UBJSON (size) | 58.6 % | 92.3 % | 86.8 % | 97.4 % | | UBJSON (size+type) | 55.9 % | 92.3 % | 85.0 % | 95.0 % | -Sizes compared to minified JSON value; the smallest size per file is printed in bold. +

Size of each test file in each format, in percent of the minified JSON value; the smallest size per file is printed in bold. The (size) and (size+type) rows are written with the `use_size` and `use_type` options of [`to_bjdata`](../../api/basic_json/to_bjdata.md) and [`to_ubjson`](../../api/basic_json/to_ubjson.md).

+ +
+
+--8<-- "images/binary_sizes_canada.svg" +
+
+--8<-- "images/binary_sizes_twitter.svg" +
+
+--8<-- "images/binary_sizes_citm_catalog.svg" +
+
+--8<-- "images/binary_sizes_jeopardy.svg" +
+
+

The table above as charts: size per base format relative to minified JSON (vertical line at +100 %); the smallest format per file is drawn in a stronger colour than the others. The size and size+type variants of BJData and UBJSON are +omitted here; see the table.

## See also +- [`cbor_tag_handler_t`](../../api/basic_json/cbor_tag_handler_t.md) - how CBOR tags are handled when parsing +- [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) - reject ill-formed UTF-8 in strings - [Binary values](../binary_values.md) - binary values and subtypes -- [**to_cbor**](../../api/basic_json/to_cbor.md) / [**from_cbor**](../../api/basic_json/from_cbor.md), [**to_msgpack**](../../api/basic_json/to_msgpack.md) / [**from_msgpack**](../../api/basic_json/from_msgpack.md), [**to_bson**](../../api/basic_json/to_bson.md) / [**from_bson**](../../api/basic_json/from_bson.md), [**to_ubjson**](../../api/basic_json/to_ubjson.md) / [**from_ubjson**](../../api/basic_json/from_ubjson.md), [**to_bjdata**](../../api/basic_json/to_bjdata.md) / [**from_bjdata**](../../api/basic_json/from_bjdata.md), [**to_bon8**](../../api/basic_json/to_bon8.md) / [**from_bon8**](../../api/basic_json/from_bon8.md) - the conversion functions -- [Parsing untrusted input](../parsing/untrusted_input.md) +- [`to_cbor`](../../api/basic_json/to_cbor.md) - / [`from_cbor`](../../api/basic_json/from_cbor.md), [`to_msgpack`](../../api/basic_json/to_msgpack.md) / [`from_msgpack`](../../api/basic_json/from_msgpack.md), [`to_bson`](../../api/basic_json/to_bson.md) / [`from_bson`](../../api/basic_json/from_bson.md), [`to_ubjson`](../../api/basic_json/to_ubjson.md) / [`from_ubjson`](../../api/basic_json/from_ubjson.md), [`to_bjdata`](../../api/basic_json/to_bjdata.md) / [`from_bjdata`](../../api/basic_json/from_bjdata.md), [`to_bon8`](../../api/basic_json/to_bon8.md) / [`from_bon8`](../../api/basic_json/from_bon8.md) - the conversion functions +- [Parsing untrusted input](../parsing/untrusted_input.md) - harden an application that parses untrusted binary data - [SAX interface](../parsing/sax_interface.md) - decode binary formats without building a value + diff --git a/docs/mkdocs/docs/features/binary_formats/messagepack.md b/docs/mkdocs/docs/features/binary_formats/messagepack.md index cca6e7660..17c05625d 100644 --- a/docs/mkdocs/docs/features/binary_formats/messagepack.md +++ b/docs/mkdocs/docs/features/binary_formats/messagepack.md @@ -242,3 +242,12 @@ written as the reserved negative ext types (255 as -1), so `json::binary({1,2,3, ```json --8<-- "examples/from_msgpack.output" ``` + +## See also + +- [Binary formats](index.md) - overview and comparison of the supported binary formats +- [Binary values](../binary_values.md) - how binary values and subtypes map to MessagePack +- [`to_msgpack`](../../api/basic_json/to_msgpack.md) - serialize a JSON value to MessagePack +- [`from_msgpack`](../../api/basic_json/from_msgpack.md) - parse a JSON value from MessagePack +- [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) - reject ill-formed UTF-8 in strings +- [SAX interface](../parsing/sax_interface.md) - decode binary formats without building a value diff --git a/docs/mkdocs/docs/features/binary_formats/ubjson.md b/docs/mkdocs/docs/features/binary_formats/ubjson.md index 0e7ea2ca1..578d13e3a 100644 --- a/docs/mkdocs/docs/features/binary_formats/ubjson.md +++ b/docs/mkdocs/docs/features/binary_formats/ubjson.md @@ -221,3 +221,12 @@ The library maps UBJSON types to JSON value types as follows: ```json --8<-- "examples/from_ubjson.output" ``` + +## See also + +- [Binary formats](index.md) - overview and comparison of the supported binary formats +- [Binary values](../binary_values.md) - how binary values and subtypes map to UBJSON +- [`to_ubjson`](../../api/basic_json/to_ubjson.md) - serialize a JSON value to UBJSON +- [`from_ubjson`](../../api/basic_json/from_ubjson.md) - parse a JSON value from UBJSON +- [`JSON_STRICT_BINARY_UTF8`](../../api/macros/json_strict_binary_utf8.md) - reject ill-formed UTF-8 in strings +- [SAX interface](../parsing/sax_interface.md) - decode binary formats without building a value diff --git a/docs/mkdocs/docs/features/binary_values.md b/docs/mkdocs/docs/features/binary_values.md index 5be9e5ba7..7d0d7336f 100644 --- a/docs/mkdocs/docs/features/binary_values.md +++ b/docs/mkdocs/docs/features/binary_values.md @@ -25,6 +25,8 @@ class vector ["std::vector"] vector <|-- binary_t ``` +

Class diagram of `json::binary_t`: it derives from `std::vector` and adds functions to set, clear, query, and test an optional subtype.

+ By default, binary values are stored as `std::vector`. This type can be changed by providing a template parameter to the `basic_json` type. To store binary subtypes, the storage type is extended and exposed as [`json::binary_t`](../api/basic_json/binary_t.md): @@ -430,3 +432,13 @@ as an array of uint8 values. The library implements this translation. "binary": [202, 254, 186, 190] } ``` + +## See also + +- [Binary formats](binary_formats/index.md) - the formats that can store binary values +- [`binary_t`](../api/basic_json/binary_t.md) - type for binary values +- [`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) - container for binary values with subtype +- [`binary`](../api/basic_json/binary.md) - create a binary value +- [`get_binary`](../api/basic_json/get_binary.md) - access the stored binary value +- [`is_binary`](../api/basic_json/is_binary.md) - check whether a value is binary +- [`cbor_tag_handler_t`](../api/basic_json/cbor_tag_handler_t.md) - store or ignore CBOR tags when parsing diff --git a/docs/mkdocs/docs/features/comments.md b/docs/mkdocs/docs/features/comments.md index 86321bc4a..d8a91a244 100644 --- a/docs/mkdocs/docs/features/comments.md +++ b/docs/mkdocs/docs/features/comments.md @@ -11,7 +11,7 @@ This library does not support comments *by default*. It does so for three reason 3. It is dangerous for interoperability if some libraries add comment support while others do not. Please check [The Harmful Consequences of the Robustness Principle](https://tools.ietf.org/html/draft-iab-protocol-maintenance-01) on this. -However, you can set parameter `ignore_comments` to `#!cpp true` in the [`parse`](../api/basic_json/parse.md) function to ignore `//` or `/* */` comments. Comments will then be treated as whitespace. Combined with [`ignore_trailing_commas`](trailing_commas.md) (also a `parse` parameter), this covers what is commonly referred to as **JSONC** (JSON with Comments, as used e.g. by Visual Studio Code's `.jsonc` files) -- comments and trailing commas, nothing more. This is a different, smaller extension than [JSON5](https://json5.org), which additionally allows unquoted keys, single-quoted strings, and other syntax changes that this library does not support. +However, you can set parameter `ignore_comments` to `#!cpp true` in the [`parse`](../api/basic_json/parse.md) function to ignore `//` or `/* */` comments. Comments will then be treated as whitespace. Combined with [`ignore_trailing_commas`](trailing_commas.md) (also a `parse` parameter), this covers what is commonly referred to as **JSONC** (JSON with Comments, as used e.g. by Visual Studio Code's `.jsonc` files) — comments and trailing commas, nothing more. This is a different, smaller extension than [JSON5](https://json5.org), which additionally allows unquoted keys, single-quoted strings, and other syntax changes that this library does not support. For more information, see [JSON With Commas and Comments (JWCC)](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html). @@ -38,3 +38,10 @@ For more information, see [JSON With Commas and Comments (JWCC)](https://nigelta ``` --8<-- "examples/comments.output" ``` + +## See also + +- [`parse`](../api/basic_json/parse.md) - deserialize from a compatible input +- [Trailing Commas](trailing_commas.md) - accept trailing commas in arrays and objects +- [Parsing](parsing/index.md) - overview of the parsing functions +- [Parsing Untrusted Input](parsing/untrusted_input.md) - limits and checks for input you do not control diff --git a/docs/mkdocs/docs/features/conversions.md b/docs/mkdocs/docs/features/conversions.md index c0d383a5a..69c2de834 100644 --- a/docs/mkdocs/docs/features/conversions.md +++ b/docs/mkdocs/docs/features/conversions.md @@ -177,7 +177,7 @@ which forces the explicit `get` form and can catch unintended conversions at com `std::optional` from delegating to `get()`, to avoid a constructor ambiguity with `std::optional`'s own converting constructor from `basic_json`. As a result, `static_cast>(json_value)` goes through `std::optional`'s own converting - constructor rather than through `get>()`, which can behave differently -- for example, + constructor rather than through `get>()`, which can behave differently — for example, with a custom `adl_serializer>` specialization. Prefer `get>()`/`get_to()` over `static_cast` for optional types. @@ -193,7 +193,7 @@ which forces the explicit `get` form and can catch unintended conversions at com - The JSON array has **too many** elements: the surplus is **silently discarded**, and no exception is thrown. - The JSON array has **too few** elements: `at` throws - [`out_of_range.401`](../home/exceptions.md#jsonexceptionout_of_range401) for the first missing index -- + [`out_of_range.401`](../home/exceptions.md#jsonexceptionout_of_range401) for the first missing index — an out-of-range error, not a [`type_error`](../home/exceptions.md#type-errors), even though the cause is a shape mismatch. @@ -211,7 +211,7 @@ which forces the explicit `get` form and can catch unintended conversions at com ## Omitting a field when serializing `std::optional` -By default, `to_json` for `std::optional` writes either the value or `#!json null` -- there is no built-in way +By default, `to_json` for `std::optional` writes either the value or `#!json null` — there is no built-in way to make a field disappear from the serialized object entirely when the `std::optional` is `std::nullopt`. Because a specialization of `adl_serializer>` only controls how the *value* is converted (it cannot prevent the containing object's `to_json` from inserting the key in the first place), omission has to be @@ -243,7 +243,7 @@ json j = numbers; // [1,2,3] !!! info "Constructing from a C++20 range view" A `json` array can also be constructed directly from a C++20 range view (`std::ranges::view`), such as the result - of `std::views::filter` or `std::views::transform` -- no intermediate container is needed: + of `std::views::filter` or `std::views::transform` — no intermediate container is needed: ```cpp std::vector nums{1, 2, 37, 42, 21}; @@ -266,7 +266,12 @@ The conversions above are built in for standard types. To make the same syntax w - [`get`](../api/basic_json/get.md) - get a copy converted to a given type - [`get_to`](../api/basic_json/get_to.md) - convert into an existing variable -- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying +- [`get_ref`](../api/basic_json/get_ref.md) - / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying - [Arbitrary types conversions](arbitrary_types.md) - support your own types - [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions - [Zero-copy JSON views](json_view.md) - get values out of a document or a view +- [`adl_serializer`](../api/adl_serializer/index.md) - the default serializer behind the conversions +- [`type`](../api/basic_json/type.md) - query the type of a value +- [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) - toggle conversion to tuples of references +- [`JSON_HAS_RANGE_VIEW_CONVERSION`](../api/macros/json_has_range_view_conversion.md) - toggle conversion from ranges + diff --git a/docs/mkdocs/docs/features/creating_values.md b/docs/mkdocs/docs/features/creating_values.md index fd4c32cd8..d1ae587fc 100644 --- a/docs/mkdocs/docs/features/creating_values.md +++ b/docs/mkdocs/docs/features/creating_values.md @@ -118,8 +118,13 @@ string `#!json "42"`. ## See also - [`basic_json` constructors](../api/basic_json/basic_json.md) - all ways to construct a value -- [`array`](../api/basic_json/array.md) / [`object`](../api/basic_json/object.md) - force array or object type +- [`array`](../api/basic_json/array.md) - / [`object`](../api/basic_json/object.md) - force array or object type - [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal - [Converting values](conversions.md) - which C++ types can be used - [Parsing](parsing/index.md) - create a value from JSON text - [Zero-copy JSON views](json_view.md) - create a document and a view from JSON text instead of a value +- [`basic_json`](../api/basic_json/index.md) - the class template and its member functions +- [Types](types/index.md) - the JSON value types +- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) - change how a single braced value is interpreted +- [`operator=`](../api/basic_json/operator=.md) - assign a new value + diff --git a/docs/mkdocs/docs/features/element_access/checked_access.md b/docs/mkdocs/docs/features/element_access/checked_access.md index d8dab0104..3a1c5f762 100644 --- a/docs/mkdocs/docs/features/element_access/checked_access.md +++ b/docs/mkdocs/docs/features/element_access/checked_access.md @@ -89,3 +89,13 @@ non-existing, an exception is thrown. | access to valid array index | reference to existing value is returned | const reference to existing value is returned | | access to non-existing object key | `basic_json::out_of_range` exception is thrown | `basic_json::out_of_range` exception is thrown | | access to invalid array index | `basic_json::out_of_range` exception is thrown | `basic_json::out_of_range` exception is thrown | + +

Result of [`at`](../../api/basic_json/at.md) in each scenario, for a non-const and a const value.

+ +## See also + +- [Element Access](index.md) - overview of all ways to access elements +- [Unchecked access: operator[]](unchecked_access.md) - access without range checking +- [Access with default value: value](default_value.md) - fall back to a default instead of throwing +- [`at`](../../api/basic_json/at.md) - checked access by reference +- [JSON Pointer](../json_pointer.md) - access nested values with `at` and a pointer diff --git a/docs/mkdocs/docs/features/element_access/default_value.md b/docs/mkdocs/docs/features/element_access/default_value.md index be100b34b..7848e5368 100644 --- a/docs/mkdocs/docs/features/element_access/default_value.md +++ b/docs/mkdocs/docs/features/element_access/default_value.md @@ -127,8 +127,10 @@ containing `.` need a different separator. ## See also -- [`value`](../../api/basic_json/value.md) for access with default value -- documentation on [checked access](checked_access.md) -- documentation on [JSON Pointer](../json_pointer.md) -- [`contains`](../../api/basic_json/contains.md) to check whether a key or JSON Pointer exists -- [`json_pointer::operator/=`](../../api/json_pointer/operator_slasheq.md) to build a pointer token by token +- [`value`](../../api/basic_json/value.md) - for access with default value +- [Checked access: at](checked_access.md) - access with bounds checking +- [JSON Pointer](../json_pointer.md) - addressing values by path +- [`contains`](../../api/basic_json/contains.md) - to check whether a key or JSON Pointer exists +- [`json_pointer::operator/=`](../../api/json_pointer/operator_slasheq.md) - to build a pointer token by token +- [Unchecked access: operator[]](unchecked_access.md) - access without range checking +- [Element Access](index.md) - overview of all ways to access elements diff --git a/docs/mkdocs/docs/features/element_access/index.md b/docs/mkdocs/docs/features/element_access/index.md index 4ec625a20..d2ceb467e 100644 --- a/docs/mkdocs/docs/features/element_access/index.md +++ b/docs/mkdocs/docs/features/element_access/index.md @@ -12,16 +12,23 @@ Testing whether a key or index exists before accessing it is also possible, with [`contains`](../../api/basic_json/contains.md) or [`find`](../../api/basic_json/find.md) (which returns an iterator to the value, or `end()` if it is not found). +How do I get a value out? Start at the top and follow the first question that applies: + ```mermaid flowchart TD - A["accessing a value"] --> B{"must it exist?"} - B -->|"yes, missing is an error"| C["at() -- throws"] - B -->|"yes, but checking is my job"| D["operator[] -- unchecked"] - B -->|"no, a fallback is fine"| E["value() -- default value"] - A --> F{"just testing first?"} - F -->|"yes"| G["contains() / find()"] + A["getting a value out"] --> P{{"nested path such as /server/port?"}} + P -->|"yes"| JP["JSON Pointer: at(ptr), value(ptr, default), contains(ptr)"] + P -->|"no, one key or index"| T{{"only testing whether it exists?"}} + T -->|"yes"| CF["contains() / find()"] + T -->|"no"| M{{"may it be missing?"}} + M -->|"yes, a fallback is fine"| V["value(key, default)"] + M -->|"yes, and missing is an error"| AT["at(): throws out_of_range"] + M -->|"no, it exists and its type is known"| OP["operator[] and get#lt;T#gt;()"] + OP -.- W["non-const operator[] inserts null for a missing key"] ``` +

Decision tree for reading a value: the labels on the arrows are the answers to the questions, and the boxes name the functions to use. The dashed line marks a side effect of the non-const `operator[]`.

+ ## Does this key exist? Use [`contains`](../../api/basic_json/contains.md) to test for a key; it does not modify the value and does not throw: @@ -63,9 +70,16 @@ if (auto it = j.find("name"); it != j.end()) ## See also -- [**operator[]**](../../api/basic_json/operator%5B%5D.md) - unchecked access -- [**at**](../../api/basic_json/at.md) - checked access -- [**value**](../../api/basic_json/value.md) - access with default value -- [**contains**](../../api/basic_json/contains.md) and [**find**](../../api/basic_json/find.md) - test for existence -- [**front**](../../api/basic_json/front.md) and [**back**](../../api/basic_json/back.md) - first and last element -- [**items**](../../api/basic_json/items.md) - iterate over keys and values +- [`operator[]`](../../api/basic_json/operator%5B%5D.md) - unchecked access +- [`at`](../../api/basic_json/at.md) - checked access +- [`value`](../../api/basic_json/value.md) - access with default value +- [`contains`](../../api/basic_json/contains.md) - and [`find`](../../api/basic_json/find.md) - test for existence +- [`front`](../../api/basic_json/front.md) - and [`back`](../../api/basic_json/back.md) - first and last element +- [`items`](../../api/basic_json/items.md) - iterate over keys and values +- [Checked access: at](checked_access.md) - access with range checking +- [Unchecked access: operator[]](unchecked_access.md) - access without range checking +- [Access with default value: value](default_value.md) - fall back to a default instead of throwing +- [Iterators](../iterators.md) - visit all elements of arrays and objects +- [JSON Pointer](../json_pointer.md) - address nested values with a single string +- [Modifying values](../modifying_values.md) - add, merge and remove elements +- [`basic_json`](../../api/basic_json/index.md) - all member functions diff --git a/docs/mkdocs/docs/features/element_access/unchecked_access.md b/docs/mkdocs/docs/features/element_access/unchecked_access.md index 7e3f92ba1..891a1b1ae 100644 --- a/docs/mkdocs/docs/features/element_access/unchecked_access.md +++ b/docs/mkdocs/docs/features/element_access/unchecked_access.md @@ -125,3 +125,13 @@ for (int i = 0; i < 1000; ++i) { | access to valid array index | reference to existing value is returned | const reference to existing value is returned | | access to non-existing object key | reference to newly inserted `#!json null` value is returned | **undefined behavior**; [runtime assertion](../assertions.md) in debug mode | | access to invalid array index | reference to newly inserted `#!json null` value is returned; any index between previous maximal index and passed index are filled with `#!json null` | **undefined behavior**; [runtime assertion](../assertions.md) in debug mode | + +

Result of [`operator[]`](../../api/basic_json/operator%5B%5D.md) in each scenario, for a non-const and a const value.

+ +## See also + +- [Element Access](index.md) - overview of all ways to access elements +- [Checked access: at](checked_access.md) - access with range checking +- [Access with default value: value](default_value.md) - fall back to a default instead of throwing +- [`operator[]`](../../api/basic_json/operator%5B%5D.md) - unchecked access by reference +- [Runtime assertions](../assertions.md) - assertions that guard `operator[]` misuse diff --git a/docs/mkdocs/docs/features/enum_conversion.md b/docs/mkdocs/docs/features/enum_conversion.md index 1b6e2b874..aa9fdd260 100644 --- a/docs/mkdocs/docs/features/enum_conversion.md +++ b/docs/mkdocs/docs/features/enum_conversion.md @@ -108,3 +108,12 @@ Other Important points: ```json --8<-- "examples/nlohmann_json_serialize_enum_strict_err.output" ``` + +## See also + +- [Arbitrary Type Conversions](arbitrary_types.md) - convert your own types +- [Converting values](conversions.md) - the general overview of `get`/`get_to` +- [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md) - define the enum-to-JSON mapping +- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](../api/macros/nlohmann_json_serialize_enum_strict.md) - throw on unmapped values +- [`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md) - disable the default enum conversion +- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) - serialize maps with enum keys as objects diff --git a/docs/mkdocs/docs/features/index.md b/docs/mkdocs/docs/features/index.md index 88443c796..f9b0f20cc 100644 --- a/docs/mkdocs/docs/features/index.md +++ b/docs/mkdocs/docs/features/index.md @@ -10,7 +10,8 @@ C++ types, and finally serialize it again. understand the `#!cpp {}` vs. `#!cpp []` ambiguity. - [Parsing](parsing/index.md) — read a JSON value from a string, file, or stream, including [JSON Lines](parsing/json_lines.md), [callbacks](parsing/parser_callbacks.md), the - [SAX interface](parsing/sax_interface.md), [error handling](parsing/parse_exceptions.md), and + [SAX interface](parsing/sax_interface.md), [error handling](parsing/parse_exceptions.md), + [large and partial input](parsing/large_and_partial_input.md), and [parsing untrusted input](parsing/untrusted_input.md). - [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree; strings and numbers stay in the input and are only decoded when needed. @@ -33,6 +34,8 @@ C++ types, and finally serialize it again. - [Converting values](conversions.md) — get values out with [`get`](../api/basic_json/get.md)/[`get_to`](../api/basic_json/get_to.md), and understand implicit conversions. - [Arbitrary types conversions](arbitrary_types.md) — teach the library about your own structs and classes. +- [Recipes for common types](type_recipes.md) — convert smart pointers, `std::variant`, `std::chrono` types, and class + hierarchies, which the library deliberately leaves to you. - [Specializing enum conversion](enum_conversion.md) — map enums to strings instead of integers. ## Serializing values @@ -52,6 +55,7 @@ C++ types, and finally serialize it again. template parameters has to provide. - [Object order](object_order.md) — keep insertion order with [`ordered_json`](../api/ordered_json.md). - [Performance](performance.md) — practical advice on parsing, memory use, serialization, and compile times. +- [Working without exceptions](no_exceptions.md) — what to use when exceptions are unwanted or unavailable. - [Runtime assertions](assertions.md), [supported macros](macros.md), the [`nlohmann` namespace](namespace.md), and [C++ modules](modules.md) — build-time and runtime configuration. @@ -65,5 +69,15 @@ C++ types, and finally serialize it again. - [API documentation](../api/basic_json/index.md) - reference of all classes and functions - [FAQ](../home/faq.md) - answers to common questions - [Exceptions](../home/exceptions.md) - all exceptions and their error codes -- [Design goals](../home/design_goals.md) +- [Design goals](../home/design_goals.md) - the goals behind the library - [Integration](../integration/index.md) - how to add the library to a project +- [Macros](../api/macros/index.md) - reference of all macros +- [`json_pointer`](../api/json_pointer/index.md) - the JSON Pointer class +- [`adl_serializer`](../api/adl_serializer/index.md) - customization point for type conversions +- [`json_sax`](../api/json_sax/index.md) - interface of the SAX parser events +- [`json_document`](../api/json_document.md) - an immutable document with a flat index +- [`json_view`](../api/json_view.md) - a non-owning view of a value in a document +- [`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) - binary values with a subtype +- [`ordered_json`](../api/ordered_json.md) - a JSON type preserving insertion order +- [Supported Macros](macros.md) - all supported macros +- [`nlohmann` Namespace](namespace.md) - the library namespace diff --git a/docs/mkdocs/docs/features/iterators.md b/docs/mkdocs/docs/features/iterators.md index de493dd72..c6e4d6eac 100644 --- a/docs/mkdocs/docs/features/iterators.md +++ b/docs/mkdocs/docs/features/iterators.md @@ -9,7 +9,11 @@ As for other containers, [`begin()`](../api/basic_json/begin.md) returns an iter placeholder and cannot be dereferenced. In case of null values, empty arrays, or empty objects, `begin()` will return `end()`. -![Illustration from cppreference.com](../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
+ +

The range of `begin()` and `end()`: `begin()` refers to the first element, `end()` to the position after the last element, which must not be dereferenced.

### Iteration order for objects @@ -112,7 +116,11 @@ for (auto& [key, val] : j_object.items()) [`rbegin()`](../api/basic_json/rbegin.md) and [`rend()`](../api/basic_json/rend.md) return iterators in the reverse sequence. -![Illustration from cppreference.com](../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
+ +

The range of `rbegin()` and `rend()` over the same elements: `rbegin()` refers to the last element and `rend()` to the position before the first one. Each reverse iterator stores an iterator to the element after the one it refers to.

??? example "Example: reverse iteration with `rbegin()` and `rend()`" @@ -159,3 +167,13 @@ Note that "value" means a JSON value in this setting, not values stored in the u | Operations | invalidated iterators | |------------|-----------------------| | `clear` | all | + +## See also + +- [`begin`](../api/basic_json/begin.md) - / [`end`](../api/basic_json/end.md) - iterators to the first and one past the last element +- [`rbegin`](../api/basic_json/rbegin.md) - / [`rend`](../api/basic_json/rend.md) - reverse iterators +- [`cbegin`](../api/basic_json/cbegin.md) - / [`cend`](../api/basic_json/cend.md) - const iterators +- [`items`](../api/basic_json/items.md) - iterate over keys and values +- [Object Order](object_order.md) - iteration order of object keys +- [Element Access](element_access/index.md) - access single elements +- [Modifying values](modifying_values.md) - add and remove elements (including iterator invalidation rules) diff --git a/docs/mkdocs/docs/features/json_patch.md b/docs/mkdocs/docs/features/json_patch.md index d35ea55b0..3329c6cba 100644 --- a/docs/mkdocs/docs/features/json_patch.md +++ b/docs/mkdocs/docs/features/json_patch.md @@ -7,6 +7,24 @@ of operations to apply to a JSON document. Operations address locations in the d [JSON Pointer](json_pointer.md) paths. With the [`patch`](../api/basic_json/patch.md) function, a JSON Patch is applied to the current JSON value by executing all operations from the patch, yielding the patched document as a new value. +The following steps through one operation of each kind. The first three operations are those of the example below; the +other three continue on its result. + +
+ +| Operation | Before | After | +|-------------------------------------------------------------|------------------------------------------------|-------------------------------------------------| +| `{"op": "replace", "path": "/baz", "value": "boo"}` | `{"baz": "qux", "foo": "bar"}` | `{"baz": "boo", "foo": "bar"}` | +| `{"op": "add", "path": "/hello", "value": ["world"]}` | `{"baz": "boo", "foo": "bar"}` | `{"baz": "boo", "foo": "bar", "hello": ["world"]}` | +| `{"op": "remove", "path": "/foo"}` | `{"baz": "boo", "foo": "bar", "hello": ["world"]}` | `{"baz": "boo", "hello": ["world"]}` | +| `{"op": "move", "from": "/baz", "path": "/hello/1"}` | `{"baz": "boo", "hello": ["world"]}` | `{"hello": ["world", "boo"]}` | +| `{"op": "copy", "from": "/hello/0", "path": "/greeting"}` | `{"hello": ["world", "boo"]}` | `{"greeting": "world", "hello": ["world", "boo"]}` | +| `{"op": "test", "path": "/greeting", "value": "world"}` | `{"greeting": "world", "hello": ["world", "boo"]}` | unchanged (a failed test throws) | + +
+ +

Stepper through the six operations: *Next* applies the next operation, *Back* undoes it, and *Reset* returns to the original document. The current operation is highlighted in the patch list, and the lines it changes are marked in the document.

+ !!! tip "Applying a patch without copying" [`patch`](../api/basic_json/patch.md) leaves the original value unchanged and returns the patched result as a copy. @@ -45,6 +63,8 @@ flowchart LR P -.->|"applied to source, yields"| T ``` +

How `diff` and `patch` relate: `diff(source, target)` yields a patch, and applying that patch to the source yields the target.

+ !!! success "Invariant" For two JSON values *source* and *target*, the following code yields true: @@ -85,3 +105,6 @@ flowchart LR - [`patch`](../api/basic_json/patch.md) - apply a JSON Patch, returning the result as a copy - [`patch_inplace`](../api/basic_json/patch_inplace.md) - apply a JSON Patch without copying - [`diff`](../api/basic_json/diff.md) - compute a JSON Patch from two values +- [`json_pointer`](../api/json_pointer/index.md) - type to represent JSON Pointers +- [`merge_diff`](../api/basic_json/merge_diff.md) - compute a JSON Merge Patch from two values +- [Modifying values](modifying_values.md) - simple in-place modifications diff --git a/docs/mkdocs/docs/features/json_pointer.md b/docs/mkdocs/docs/features/json_pointer.md index a26454be0..1c3b5bf21 100644 --- a/docs/mkdocs/docs/features/json_pointer.md +++ b/docs/mkdocs/docs/features/json_pointer.md @@ -18,6 +18,40 @@ Consider the following JSON document } ``` +Select a pointer (or a node of the tree) to walk it through this document; each segment of the pointer goes one level +deeper. The table below lists the same pointers. + +
+

Highlighted: the path of the pointer /nested/three/1, which identifies false.

+
    +
  • root object +
      +
    • "array" array +
        +
      • 0: "A"
      • +
      • 1: "B"
      • +
      • 2: "C"
      • +
      +
    • +
    • "nested" object +
        +
      • "one": 1
      • +
      • "two": 2
      • +
      • "three" array +
          +
        • 0: true
        • +
        • 1: false
        • +
        +
      • +
      +
    • +
    +
  • +
+
+ +

The highlighted nodes are the path from the root to the selected level, and the line below the tree shows the value the pointer resolves to. Select a segment in the pointer bar to stop at a shallower level, or a node of the tree to select its pointer.

+ Then every value inside the JSON document can be identified as follows: | JSON Pointer | JSON value | @@ -138,12 +172,22 @@ The reverse function, [`unflatten`](../api/basic_json/unflatten.md) recreates th auto j_original = j_flat.unflatten(); ``` +Each leaf of the document becomes one key of the flat object, and `unflatten()` reverses the mapping: + +
+--8<-- "images/json_pointer_flatten.svg" +
+ +

Left: the example document as a tree; right: the flat object it becomes. `flatten()` converts from left to right, `unflatten()` from right to left.

+ ## See also -- Class [`json_pointer`](../api/json_pointer/index.md) -- Functions [`value`](../api/basic_json/value.md), [`contains`](../api/basic_json/contains.md), and - [`at`](../api/basic_json/at.md) accept JSON Pointers; see [Nested values](element_access/default_value.md#nested-values) -- Function [`flatten`](../api/basic_json/flatten.md) -- Function [`unflatten`](../api/basic_json/unflatten.md) +- [`json_pointer`](../api/json_pointer/index.md) - the JSON Pointer class +- [Nested values](element_access/default_value.md#nested-values) - [`value`](../api/basic_json/value.md), + [`contains`](../api/basic_json/contains.md), and [`at`](../api/basic_json/at.md) accept JSON Pointers +- [`flatten`](../api/basic_json/flatten.md) - convert a value to an object of JSON Pointers and primitive values +- [`unflatten`](../api/basic_json/unflatten.md) - restore a value from a flattened object - [JSON Patch](json_patch.md) - paths inside a patch are JSON Pointers - [JSON Merge Patch](merge_patch.md) - an alternative patch format that does not use JSON Pointer +- [`operator""_json_pointer`](../api/operator_literal_json_pointer.md) - user-defined literal to create a JSON pointer +- [Element Access](element_access/index.md) - other ways to access elements diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index bef6259de..f2660f539 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -21,18 +21,26 @@ referenced in place in the source text: a float is converted when it is read, an sequences are decoded, into one shared buffer owned by the document. [`basic_json_view`](../api/basic_json_view/index.md) is a small, trivially copyable handle (two pointers) into that -index. It gives you the read-only, type-inspection part of the `basic_json` interface -- +index. It gives you the read-only, type-inspection part of the `basic_json` interface — [`type()`](../api/basic_json_view/type.md) and the `is_*()` predicates, -[`size()`](../api/basic_json_view/size.md)/[`empty()`](../api/basic_json_view/empty.md) -- as well as element access +[`size()`](../api/basic_json_view/size.md)/[`empty()`](../api/basic_json_view/empty.md) — as well as element access ([`operator[]`](../api/basic_json_view/operator%5B%5D.md), [`at`](../api/basic_json_view/at.md), [`front`](../api/basic_json_view/front.md)/[`back`](../api/basic_json_view/back.md)), lookup ([`find`](../api/basic_json_view/find.md), [`contains`](../api/basic_json_view/contains.md), [`count`](../api/basic_json_view/count.md)), and iteration ([`begin`](../api/basic_json_view/begin.md)/[`end`](../api/basic_json_view/end.md), -[`items`](../api/basic_json_view/items.md)) -- without ever allocating a `basic_json` value. When you do need an +[`items`](../api/basic_json_view/items.md)) — without ever allocating a `basic_json` value. When you do need an actual `basic_json` value for a subtree, [`materialize()`](../api/basic_json_view/materialize.md) builds exactly the one [`parse()`](../api/basic_json/parse.md) would have produced for it. +The same small text, parsed both ways: + +
+--8<-- "images/json_view_layout.svg" +
+ +

Top: the tree of separate heap allocations that `basic_json::parse()` builds. Bottom: `json_document::parse()` keeps the source text and builds one flat index of 16-byte nodes that point back into it, plus a buffer of decoded strings.

+ ## How to use it Include `` in addition to (or instead of) ``. Parse into a @@ -62,7 +70,7 @@ A document either **borrows** the text it was parsed from, or **owns** its own c [`owns_source()`](../api/basic_json_document/owns_source.md) to find out which happened. [`parse()`](../api/basic_json_document/parse.md) decides this from the value category and type of its argument (an lvalue `#!cpp std::string` is borrowed; an rvalue `#!cpp std::string` is moved in, owned without a copy; a stream is -read into an owned buffer; and so on -- see [`parse`'s Notes](../api/basic_json_document/parse.md#notes) for the full +read into an owned buffer; and so on — see [`parse`'s Notes](../api/basic_json_document/parse.md#notes) for the full table). [`parse_copy()`](../api/basic_json_document/parse_copy.md) always owns a copy, regardless of the input. !!! warning "A borrowed document depends on your buffer" @@ -84,10 +92,16 @@ Moving the `basic_json_document<>` itself is fine and does **not** invalidate it allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md) whenever any of the other conditions above was not met. +
+--8<-- "images/json_view_lifetime.svg" +
+ +

Four timelines of a document and its views, each bar showing when an object is alive or valid and when it dangles. Destroying the borrowed text, re-parsing, or shrinking the document invalidates the views taken before; moving the document does not.

+ A view can dangle, exactly like a `#!cpp std::string_view`: nothing stops you from using it after its document is gone. This is deliberate. A view is two pointers and trivially copyable; making it safe (a `#!cpp std::shared_ptr` in the document and a `#!cpp std::weak_ptr` in each view) would make it larger, add reference counting to every copy, -and a `lock()` to every access -- exactly the costs the view exists to avoid. What is caught is the obvious case: +and a `lock()` to every access — exactly the costs the view exists to avoid. What is caught is the obvious case: because a view dies with its document, [`root()`](../api/basic_json_document/root.md) is not callable on a temporary document: `#!cpp auto v = json_document::parse(text).root();` does not compile. Give the document a name first. @@ -109,7 +123,7 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile. [`parse()`](../api/basic_json_document/parse.md) accept and reject exactly the same inputs as [`basic_json::accept()`](../api/basic_json/accept.md)/[`basic_json::parse()`](../api/basic_json/parse.md), with the same `ignore_comments` and `ignore_trailing_commas` options. -- **Errors.** A failing parse throws the same exception -- the same id, message, and position -- because on failure +- **Errors.** A failing parse throws the same exception — the same id, message, and position — because on failure the library's own parser is run on the same bytes to produce the diagnostic. Valid input never pays for this, and the replay builds no values: it costs about a lexing pass up to the error. `#!cpp allow_exceptions == false` skips the replay and gives a @@ -155,7 +169,7 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile. element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the view has no `basic_json` value to point at, so the exception is created without one, regardless of how `BasicJsonType` was built. -- **Ordering comparisons are not provided** by `basic_json_view` -- there is no `#!cpp operator<`. +- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`. [`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when [`materialize()`](../api/basic_json_view/materialize.md) or [`parse()`](../api/basic_json/parse.md) would produce @@ -227,15 +241,15 @@ and other types the library converts (`#!cpp std::vector`, `#!cpp std::map`, `#! Two conversions never copy at all: - [`get_string()`](../api/basic_json_view/get_string.md) (equivalently, `#!cpp get()`) returns a - string as a `string_view_t` pointing into the document's [`source()`](../api/basic_json_document/source.md) text -- - or, for a string that contains escape sequences, into the document's own buffer of decoded strings -- instead of + string as a `string_view_t` pointing into the document's [`source()`](../api/basic_json_document/source.md) text — + or, for a string that contains escape sequences, into the document's own buffer of decoded strings — instead of allocating a new `#!cpp std::string`. - [`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written in the source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get()` (and [`basic_json::parse()`](../api/basic_json/parse.md)) would. -Both results are only valid as long as the view -- and, for a string with no escapes, the borrowed source text -- is. +Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is. ## Writing generic code @@ -328,14 +342,14 @@ Two things differ when a `from_json()` reads the view: [`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a `basic_json` value. An object's members are written in document order, not sorted by key, and *every* occurrence of a -repeated key is written, not only the last one -- the same two ways [iteration](#what-is-different) already differs +repeated key is written, not only the last one — the same two ways [iteration](#what-is-different) already differs from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see [Duplicate keys](#duplicate-keys). `#!cpp materialize().dump()` gives a different result in both respects for a `json_view`. By default, numbers are written the way [`basic_json::dump()`](../api/basic_json/dump.md) would. [`number_format::source`](../api/basic_json_view/number_format.md) instead copies every number exactly as it was -written in the source text -- a price like `#!cpp 19.90`, a long order or account ID with more digits than any number -type holds, or a high-precision coordinate -- something `basic_json` cannot do at all, since parsing already reduces +written in the source text — a price like `#!cpp 19.90`, a long order or account ID with more digits than any number +type holds, or a high-precision coordinate — something `basic_json` cannot do at all, since parsing already reduces a number to its parsed `#!cpp double`/`#!cpp int64_t` value. [`operator<<`](../api/basic_json_view/operator_ltlt.md) writes a view to a stream the way `basic_json`'s does, using @@ -344,14 +358,14 @@ the stream's `width`/`fill` for indentation. ## Editing a document Everything above is read-only: a `json_document`/`json_view` lets you look at a parsed text without copying it, but -not change it. [`basic_json_document`](../api/basic_json_document/index.md) -- more conveniently +not change it. [`basic_json_document`](../api/basic_json_document/index.md) — more conveniently spelled [`json_editable_document`](../api/json_editable_document.md) or -[`ordered_json_editable_document`](../api/ordered_json_editable_document.md) -- also lets you +[`ordered_json_editable_document`](../api/ordered_json_editable_document.md) — also lets you [`set`](../api/basic_json_document/set.md) a value, [`push_back`](../api/basic_json_document/push_back.md) onto or [`insert`](../api/basic_json_document/insert.md) into an array, and [`erase`](../api/basic_json_document/erase.md) an object member or an array element, still without ever building a `basic_json` tree for parts you do not touch. -The [mode](../api/view_mode.md) defaults to `#!cpp view_mode::read_only`, so `json_document`/`ordered_json_document` are unaffected -- they carry +The [mode](../api/view_mode.md) defaults to `#!cpp view_mode::read_only`, so `json_document`/`ordered_json_document` are unaffected — they carry none of the bookkeeping edits need, and calling `set`/`push_back`/`insert`/`erase` on one is a compile error, not a runtime one. @@ -361,15 +375,35 @@ The `#!cpp 19.90` price from [above](#writing-a-view-back) is exactly the kind o or `ordered_json` value in place lossy. Say you parse a configuration file, patch one field, and write it back: - **`json`** re-sorts every key on the way in (`object_t` is a `#!cpp std::map`) and rewrites every number to its - shortest round-trip form on the way out -- a one-field patch turns into a diff that reorders the whole file and + shortest round-trip form on the way out — a one-field patch turns into a diff that reorders the whole file and rewrites `#!cpp 19.90` to `#!cpp 19.9`. - **`ordered_json`** keeps the key order, but still rewrites every number the same way: parsing has already reduced it to a `#!cpp double`/`#!cpp int64_t`, and there is no way back to how it was spelled in the source text. +The patch of the example below (set `"host"`, add `"retries"`) written back by each of them: + +
+

json

keys sorted, numbers rewritten

{"host":"db2",
+ "name":"cache",
+ "port":6379,
+ "price":19.9,
+ "retries":3}
+

ordered_json

key order kept, numbers rewritten

{"name":"cache",
+ "host":"db2",
+ "port":6379,
+ "price":19.9,
+ "retries":3}
+

json_editable_document

order and spelling kept

{"name":"cache",
+ "host":"db2",
+ "port":6379,
+ "price":19.90,
+ "retries":3}
+
+ An editable document keeps both. [`dump()`](../api/basic_json_view/dump.md) of an edited document writes members in -document order -- a member [`set`](../api/basic_json_document/set.md) added goes at the end, exactly where it was +document order — a member [`set`](../api/basic_json_document/set.md) added goes at the end, exactly where it was inserted, and an [`erase`](../api/basic_json_document/erase.md)d member simply leaves a gap: everything around it -keeps its place -- and [`number_format::source`](../api/basic_json_view/number_format.md) keeps the exact spelling +keeps its place — and [`number_format::source`](../api/basic_json_view/number_format.md) keeps the exact spelling of every number an edit did not itself touch; a number an edit *did* touch is written the way [`BasicJsonType::dump()`](../api/basic_json/dump.md) would write it, since there is no source spelling for a brand new value. @@ -388,14 +422,14 @@ new value. ### What stays valid, and what an edit costs -The source text itself is **never written**, and the parsed index never moves -- a value keeps the node it was +The source text itself is **never written**, and the parsed index never moves — a value keeps the node it was parsed into for as long as it is not itself replaced. So every [view](../api/basic_json_view/index.md) taken before an edit, including a previously obtained [`root()`](../api/basic_json_document/root.md), stays valid and, if it still refers to the edited value, sees the edit; a view of a value a later edit drops or replaces just keeps showing what it last held. New values go to storage the document allocates and owns on demand. The one thing an edit does -invalidate is the **iterators** taken over an edited array or object: the first time one of its elements is set, -appended to, inserted into, or erased, its elements move from the parsed, fixed layout to a growable block of links -so that [`push_back`](../api/basic_json_document/push_back.md) can later grow it in amortized constant time -- +invalidate is the **iterators** taken over an edited array or object: the first time an element is added to it, +inserted into it, erased from it, or replaced by a value that spans a different number of nodes, its elements move from the parsed, fixed layout to a growable block of links +so that [`push_back`](../api/basic_json_document/push_back.md) can later grow it in amortized constant time — existing elements are not touched, but an iterator that was walking the old layout no longer matches. A string obtained with [`get_string()`](../api/basic_json_view/get_string.md) is unaffected either way and stays valid across further @@ -404,16 +438,53 @@ edits. See [`basic_json_document`'s Edits](../api/basic_json_document/index.md#e throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in the index is described in the [architecture overview](../home/architecture.md#node-index-of-json-views). +Step through the edits of a small document (the node fields follow the [architecture overview](../home/architecture.md#node-index-of-json-views)): + +
+
+
+--8<-- "images/json_view_edit_0.svg" +
+
Step 1 of 5. After parsing: nine nodes in the parsed index, no owned storage yet.
+
+
+
+--8<-- "images/json_view_edit_1.svg" +
+
Step 2 of 5. doc.set(doc.root(), "port", 6380) overwrites the node of the integer in place; no member is added or removed, so the object keeps its parsed layout.
+
+
+
+--8<-- "images/json_view_edit_2.svg" +
+
Step 3 of 5. doc.erase(doc.root()["tags"], 0): the array is edited for the first time, so its elements move to a block of links in owned storage. The node of "eu" stays in the index, but nothing refers to it any more; iterators over the array taken before are invalid.
+
+
+
+--8<-- "images/json_view_edit_3.svg" +
+
Step 4 of 5. doc.push_back(doc.root()["tags"], "new"): the new string gets its own node, its text goes to the buffer of edited text, and a link to it is appended to the block of the array.
+
+
+
+--8<-- "images/json_view_edit_4.svg" +
+
Step 5 of 5. doc.set(doc.root(), "retries", 3): a new member, so the object moves to a block too: copies of its keys, links to the values it already had, then the new key and a link to the new integer.
+
+
+ +

The node index and the owned storage of the document after each of five edits. Use *Back*, *Next*, or the numbered buttons to switch between the steps; without JavaScript all steps are shown one below the other.

+ ## Images [`save()`](../api/basic_json_document/save.md) writes a document as an *image*: a byte buffer that -[`load()`](../api/basic_json_document/load.md) reads back into a document without parsing -- no lexing, no building +[`load()`](../api/basic_json_document/load.md) reads back into a document without parsing — no lexing, no building the node index, nothing but copying the nodes and pointing the text and the decoded strings at the image. Where [`parse_copy()`](../api/basic_json_document/parse_copy.md) still has to scan the whole input, [`load()`](../api/basic_json_document/load.md) turns that scan into a copy of the node index alone. -**Why.** A document that is parsed once and then read many times -- a configuration loaded at startup, a template -rendered on every request, a large reference dataset a worker process needs in memory -- pays for parsing once but +**Why.** A document that is parsed once and then read many times — a configuration loaded at startup, a template +rendered on every request, a large reference dataset a worker process needs in memory — pays for parsing once but can amortize [`save()`](../api/basic_json_document/save.md)'s cost across every later load. That makes images useful for a cache: save a document the first time it is parsed (to a file, a shared-memory segment, an in-process cache), and [`load()`](../api/basic_json_document/load.md) it on every later use instead of parsing the source text again. @@ -424,10 +495,10 @@ plus pointers into the received bytes, not a re-parse. **Choosing a check.** [`load()`](../api/basic_json_document/load.md) takes an [`image_check`](../api/basic_json_document/load.md#image_check) that trades validation against speed: `image_check::full` (the default) checks everything the parser itself guarantees, so a checked image is exactly as -safe to read and serialize as a freshly parsed document -- the right choice whenever the image did not come straight +safe to read and serialize as a freshly parsed document — the right choice whenever the image did not come straight from this process's own [`save()`](../api/basic_json_document/save.md), such as a file or a network peer. -`image_check::bounds` only checks structure and bounds -- cheaper, since it skips scanning the text and the decoded -strings -- and fits a cache the process trusts, one it wrote and reads back itself. `image_check::none` skips +`image_check::bounds` only checks structure and bounds — cheaper, since it skips scanning the text and the decoded +strings — and fits a cache the process trusts, one it wrote and reads back itself. `image_check::none` skips validation entirely, for an image trusted as much as the process's own memory. See [`load()`'s Notes](../api/basic_json_document/load.md#notes) for exactly what each level does and does not guarantee. @@ -447,7 +518,7 @@ validation entirely, for an image trusted as much as the process's own memory. S The image format is versioned but not yet stable, and may change in an incompatible way before it is declared stable. It is little-endian only (on big-endian targets, `save()` and `load()` do not exist), and tied to the - library build that wrote it -- use it to cache a document or to hand one to another process running the *same* + library build that wrote it — use it to cache a document or to hand one to another process running the *same* build, not as a long-term storage format; keep the original JSON text if a saved document needs to be readable by a future library version. @@ -463,8 +534,21 @@ either. | **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document | same as `json_document`; edits go to storage the document owns | | **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../api/basic_json_document/set.md)/[`push_back`](../api/basic_json_document/push_back.md)/[`insert`](../api/basic_json_document/insert.md)/[`erase`](../api/basic_json_document/erase.md) edit in place; the source text is never rewritten | | **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../api/basic_json_view/number_format.md), the spelling of every untouched number | -| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back -- a configuration file, for instance -- where the rest of it should come back exactly as it was | -| **Caching/reload** | not applicable -- re-parse, or roll your own serialization | not applicable | [`save()`](../api/basic_json_document/save.md)/[`load()`](../api/basic_json_document/load.md): cache the parsed index as an image and reload it without parsing | same, saving the document's current -- possibly edited -- state | +| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back — a configuration file, for instance — where the rest of it should come back exactly as it was | +| **Caching/reload** | not applicable — re-parse, or roll your own serialization | not applicable | [`save()`](../api/basic_json_document/save.md)/[`load()`](../api/basic_json_document/load.md): cache the parsed index as an image and reload it without parsing | same, saving the document's current — possibly edited — state | + +

Each column is one way to work with JSON in this library, and each row compares one property of them.

+ +## See also + +- [`basic_json_document`](../api/basic_json_document/index.md) - the document that owns the parsed index (parse, access, images, edits) +- [`basic_json_view`](../api/basic_json_view/index.md) - the non-owning handle to a value in a document +- [`json_document`](../api/json_document.md) - and [`ordered_json_document`](../api/ordered_json_document.md) - the read-only documents +- [`json_editable_document`](../api/json_editable_document.md) - and [`ordered_json_editable_document`](../api/ordered_json_editable_document.md) - the documents that can be edited in place +- [Architecture](../home/architecture.md#node-index-of-json-views) - the layout of the node index +- [SAX interface](parsing/sax_interface.md) - event-based parsing without building a tree +- [Performance](performance.md) - benchmarks and tuning of parsing and serialization +- [Large, Streamed, and Partial Input](parsing/large_and_partial_input.md) - reading only the part of a text you need ## Version history diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index 620cbe0da..33b2273b9 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -322,3 +322,17 @@ These macros are defined by the library and contain the version numbers accordin [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). See [full documentation of `NLOHMANN_JSON_VERSION_MAJOR`, `NLOHMANN_JSON_VERSION_MINOR`, and `NLOHMANN_JSON_VERSION_PATCH`](../api/macros/nlohmann_json_version_major.md). + +## See also + +- [Macros](../api/macros/index.md) - reference of all macros +- [Runtime Assertions](assertions.md) - runtime assertions in detail +- [`nlohmann` Namespace](namespace.md) - the library namespace and its macros +- [Working Without Exceptions](no_exceptions.md) - using the library without exceptions +- [Modules](modules.md) - macros and C++20 modules +- [Exceptions](../home/exceptions.md) - all exceptions and their error codes +- [CMake](../integration/cmake.md) - set macros via CMake options +- [Compile times](../integration/compile_times.md) - reduce compile times with macros +- [Architecture](../home/architecture.md) - how the library is put together +- [Specializing enum conversion](enum_conversion.md) - enum macros +- [Arbitrary Type Conversions](arbitrary_types.md) - type conversion macros diff --git a/docs/mkdocs/docs/features/merge_patch.md b/docs/mkdocs/docs/features/merge_patch.md index 9d87f4c50..ceafe8724 100644 --- a/docs/mkdocs/docs/features/merge_patch.md +++ b/docs/mkdocs/docs/features/merge_patch.md @@ -12,6 +12,78 @@ easier to read and write for object-shaped documents. The function [`merge_diff`](../api/basic_json/merge_diff.md) creates a merge patch that changes one JSON value into another. +The example below applies one patch to one document. Here is the document before: + +```json +{ + "title": "Goodbye!", + "author": { + "givenName": "John", + "familyName": "Doe" + }, + "tags": ["example", "sample"], + "content": "This will be unchanged" +} +``` + +This is the patch: + +```json +{ + "title": "Hello!", + "phoneNumber": "+01-123-456-7890", + "author": { + "familyName": null + }, + "tags": ["example"] +} +``` + +And this is the document after applying it: + +```json +{ + "title": "Hello!", + "author": { + "givenName": "John" + }, + "tags": ["example"], + "content": "This will be unchanged", + "phoneNumber": "+01-123-456-7890" +} +``` + +The table goes through the members one by one. "Member" is the name of an object member in the patch (and in the +document); it is not a [JSON Pointer](json_pointer.md) and nothing of it is passed to the function: the whole patch is +passed to [`merge_patch`](../api/basic_json/merge_patch.md). + +
+ +| Rule | Member | Before | Patch | After | +|------|--------|--------|-------|-------| +| change value | `title` | `#!json "Goodbye!"` | `#!json "Hello!"` | `#!json "Hello!"` | +| add key | `phoneNumber` | (no member) | `#!json "+01-123-456-7890"` | `#!json "+01-123-456-7890"` | +| object merges recursively | `author` | `#!json {"givenName": "John", "familyName": "Doe"}` | `#!json {"familyName": null}` | `#!json {"givenName": "John"}` | +| null removes key | `familyName` inside `author` | `#!json "Doe"` | `#!json null` | (removed) | +| array replaced whole | `tags` | `#!json ["example", "sample"]` | `#!json ["example"]` | `#!json ["example"]` | +| unchanged key | `content` | `#!json "This will be unchanged"` | (no member) | `#!json "This will be unchanged"` | + +
+ +An array in the patch replaces the array in the target, so a merge patch cannot change a single element. Each member of +the patch is applied like this; a patch that is not an object replaces the whole target. + +```mermaid +flowchart LR + M["member of the patch"] --> N{{"value null?"}} + N -->|yes| D["delete the key"] + N -->|no| O{{"value an object?"}} + O -->|yes| R["merge recursively into the target's value"] + O -->|no| S["set the key to the value"] +``` + +

How one member of a merge patch is applied: a `null` value deletes the key, an object is merged recursively, and any other value replaces the value of the key.

+ !!! tip "Not a general deep merge" A merge patch is not a general deep merge: a `#!json null` value in the patch deletes the key from the target. @@ -49,6 +121,7 @@ another. - [JSON Patch and Diff](json_patch.md) - a more expressive alternative that describes a sequence of operations - [JSON Pointer](json_pointer.md) - the addressing scheme used by JSON Patch -- Function [`merge_patch`](../api/basic_json/merge_patch.md) -- Function [`merge_diff`](../api/basic_json/merge_diff.md) +- [`merge_patch`](../api/basic_json/merge_patch.md) - apply a JSON Merge Patch to a value +- [`merge_diff`](../api/basic_json/merge_diff.md) - create a JSON Merge Patch from two values - Function [`update`](../api/basic_json/update.md) - merge objects, optionally recursively +- [Modifying values](modifying_values.md) - simple in-place modifications diff --git a/docs/mkdocs/docs/features/modifying_values.md b/docs/mkdocs/docs/features/modifying_values.md index 1d6f2e27d..255a62d8b 100644 --- a/docs/mkdocs/docs/features/modifying_values.md +++ b/docs/mkdocs/docs/features/modifying_values.md @@ -99,10 +99,14 @@ editable document types, such as [`json_editable_document`](../api/json_editable ## See also -- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array +- [`push_back`](../api/basic_json/push_back.md) - / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array - [`emplace`](../api/basic_json/emplace.md) - insert into an object if the key is absent - [`update`](../api/basic_json/update.md) - merge objects (shallow, or recursive with `merge_objects`) - [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch -- [`erase`](../api/basic_json/erase.md) / [`clear`](../api/basic_json/clear.md) - remove elements -- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) - structured modifications +- [`erase`](../api/basic_json/erase.md) - / [`clear`](../api/basic_json/clear.md) - remove elements +- [JSON Patch and Diff](json_patch.md) - and [JSON Merge Patch](merge_patch.md) - structured modifications - [Editing a document](json_view.md#editing-a-document) - change a parsed document in place +- [`insert`](../api/basic_json/insert.md) - insert elements at an iterator position or merge a range +- [`operator+=`](../api/basic_json/operator+=.md) - shorthand for `push_back` +- [Element Access](element_access/index.md) - read elements +- [Iterators](iterators.md) - iterate over elements diff --git a/docs/mkdocs/docs/features/modules.md b/docs/mkdocs/docs/features/modules.md index b87cb8950..780809f08 100644 --- a/docs/mkdocs/docs/features/modules.md +++ b/docs/mkdocs/docs/features/modules.md @@ -4,6 +4,8 @@ This library has experimental support for C++ modules, introduced in C++20. The Please be aware that the module is experimental and a full test is outstanding, and the exported symbols are subject to change. +Importing the module instead of including the header reduces the compile time of every translation unit that uses the library, at the price of building the module once. The [Compile times](../integration/compile_times.md#use-c20-modules) page has measurements and the one-off cost. + ## Requirements The `nlohmann.json` module requires that the build system is configured to build and resolve modules when imported. Obviously, as modules were introduced in C++20, this feature can only be used in C++20 and subsequent versions. @@ -66,3 +68,12 @@ C++20 modules support is exercised in CI against current GCC and Clang on Ubuntu - **MSVC** could fail with `C2039: 'json_sax_dom_callback_parser' is not a member of ... detail`; fixed by exporting the required internal symbols from `json.cppm` (see [Exported symbols](#exported-symbols) above). ([issue #3970](https://github.com/nlohmann/json/issues/3970)) If you hit a different module-related build failure, search [existing issues](https://github.com/nlohmann/json/issues?q=is%3Aissue+modules) before filing a new one. + +## See also + +- [Supported Macros](macros.md) - macros are not exported by modules +- [Macros](../api/macros/index.md) - reference of all macros +- [`nlohmann` Namespace](namespace.md) - the library namespace +- [CMake](../integration/cmake.md) - enable the module build in CMake +- [Compile times](../integration/compile_times.md) - reduce compile times +- [`basic_json`](../api/basic_json/index.md) - the main class and all its member functions diff --git a/docs/mkdocs/docs/features/namespace.md b/docs/mkdocs/docs/features/namespace.md index e59b4b331..3eb52ab7a 100644 --- a/docs/mkdocs/docs/features/namespace.md +++ b/docs/mkdocs/docs/features/namespace.md @@ -38,6 +38,22 @@ For example, the namespace name for version 3.11.2 with `JSON_DIAGNOSTICS` defin nlohmann::json_abi_diag_v3_11_2 ``` +The name is assembled from these segments; pick a version and macros to see the resulting name: + +
+
+nlohmannroot namespace +::  +json_abialways +_diagJSON_DIAGNOSTICS=1 +_v3_11_2version 3.11.2 +
+
Version 3.11.2 with JSON_DIAGNOSTICS defined to 1. ABI tags are appended in the order of the +list above, the version suffix comes last.
+
+ +

The name of the inline namespace, assembled from its segments. Select a version and macros to see which ABI tags appear; a tag the chosen version does not have is disabled.

+ ## Purpose Several incompatibilities have been observed. Amongst the most common ones is linking code compiled with different @@ -55,6 +71,8 @@ graph app --> library ``` +

The application uses the library with `JSON_DIAGNOSTICS=1` and links `some library`, which uses the same library version with `JSON_DIAGNOSTICS=0`. Arrows point from a component to what it uses.

+ In releases prior to 3.11.0, mixing any version of the JSON library with different `JSON_DIAGNOSTICS` settings would result in a crashing application. If `some_library` never passes instances of JSON library types to the application, this scenario became safe in version 3.11.0 and above due to the inline namespace yielding distinct symbol names. @@ -102,6 +120,15 @@ follows: Overriding the namespace and mixing ABI-incompatible versions will result in crashes or incorrect behavior. You have been warned! +## See also + +- [`NLOHMANN_JSON_NAMESPACE`](../api/macros/nlohmann_json_namespace.md) - the library namespace as macro +- [`NLOHMANN_JSON_NAMESPACE_BEGIN`, `NLOHMANN_JSON_NAMESPACE_END`](../api/macros/nlohmann_json_namespace_begin.md) - open and close the library namespace +- [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](../api/macros/nlohmann_json_namespace_no_version.md) - disable the version component +- [Supported Macros](macros.md) - all supported macros +- [Migration Guide](../integration/migration_guide.md) - do not hard-code the library namespace +- [`basic_json`](../api/basic_json/index.md) - the main class and all its member functions + ## Version history - Introduced inline namespace (`json_v3_11_0[_abi-tag]*`) in version 3.11.0. diff --git a/docs/mkdocs/docs/features/no_exceptions.md b/docs/mkdocs/docs/features/no_exceptions.md index cfb489876..bf1677c58 100644 --- a/docs/mkdocs/docs/features/no_exceptions.md +++ b/docs/mkdocs/docs/features/no_exceptions.md @@ -109,5 +109,10 @@ With exceptions off, you can replace the `abort()` by your own code, for example - [Exceptions](../home/exceptions.md) - all exceptions and their ids - [Parsing and exceptions](parsing/parse_exceptions.md) - the parsing alternatives in detail -- [Parsing untrusted input](parsing/untrusted_input.md) -- [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md) +- [Parsing untrusted input](parsing/untrusted_input.md) - harden an application that parses data it does not control +- [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md) - disable exceptions in the library +- [`exception`](../api/basic_json/exception.md) - base class of all exceptions thrown by the library +- [`JSON_THROW_USER`](../api/macros/json_throw_user.md) - replace the throw statements of the library +- [`parse`](../api/basic_json/parse.md) - deserialize from a compatible input +- [`accept`](../api/basic_json/accept.md) - check whether the input is valid JSON +- [Supported Macros](macros.md) - all supported macros diff --git a/docs/mkdocs/docs/features/object_order.md b/docs/mkdocs/docs/features/object_order.md index b9997238e..79f06ef2c 100644 --- a/docs/mkdocs/docs/features/object_order.md +++ b/docs/mkdocs/docs/features/object_order.md @@ -117,3 +117,11 @@ Assume file `input.json` contains the JSON object above: "two": 2 } ``` + +## See also + +- [`ordered_json`](../api/ordered_json.md) - JSON type that preserves insertion order +- [`ordered_map`](../api/ordered_map.md) - the container behind `ordered_json` +- [`object_t`](../api/basic_json/object_t.md) - type used to store JSON objects +- [Iterators](iterators.md) - iteration order of object keys +- [Element Access](element_access/index.md) - access elements by key diff --git a/docs/mkdocs/docs/features/parsing/index.md b/docs/mkdocs/docs/features/parsing/index.md index 447b65ffc..901626952 100644 --- a/docs/mkdocs/docs/features/parsing/index.md +++ b/docs/mkdocs/docs/features/parsing/index.md @@ -13,6 +13,8 @@ flowchart LR A --> V["bool: is the input valid JSON?"] ``` +

The three entry points for parsing: `parse()` builds a `basic_json` value, optionally filtered by a parser callback, `sax_parse()` delivers events to a SAX handler, and `accept()` only checks whether the input is valid.

+ ## Input The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be @@ -34,7 +36,7 @@ json data = json::parse(f); ``` The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which -is read as UTF-16 or UTF-32 -- depending on the character type -- and converted to UTF-8; see the +is read as UTF-16 or UTF-32 — depending on the character type — and converted to UTF-8; see the [`parse`](../../api/basic_json/parse.md) page for an example. A single input may contain only one JSON value. Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format; input that arrives in chunks or contains several values is covered in @@ -154,4 +156,11 @@ options. - [Zero-copy JSON views](../json_view.md) - parse into a read-only document and read it through views - [parsing and exceptions](parse_exceptions.md) - control error handling - [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources -- [Zero-copy JSON views](../json_view.md) - parse into a document and read it through a view +- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream +- [`json_sax`](../../api/json_sax/index.md) - documentation of the SAX interface +- [`parse_error`](../../api/basic_json/parse_error.md) - exception thrown on a parse error +- [Comments](../comments.md) - ignore `//` and `/* */` comments while parsing +- [Trailing Commas](../trailing_commas.md) - accept trailing commas in arrays and objects +- [Serialization](../serialization.md) - pretty-printing, UTF-8 handling, and number output +- [Working Without Exceptions](../no_exceptions.md) - error handling when exceptions are disabled +- [Exceptions](../../home/exceptions.md) - list of exceptions and error codes diff --git a/docs/mkdocs/docs/features/parsing/json_lines.md b/docs/mkdocs/docs/features/parsing/json_lines.md index 5ecf9d4af..f7f525e57 100644 --- a/docs/mkdocs/docs/features/parsing/json_lines.md +++ b/docs/mkdocs/docs/features/parsing/json_lines.md @@ -61,5 +61,12 @@ JSON Lines input with more than one value is treated as invalid JSON by the [`pa } ``` - A value that is a number must be followed by whitespace -- see the [notes](../../api/operator_gtgt.md#notes) of + A value that is a number must be followed by whitespace — see the [notes](../../api/operator_gtgt.md#notes) of `operator>>` for details. + +## See also + +- [Parsing](index.md) - overview of the parsing functions +- [Large, Streamed, and Partial Input](large_and_partial_input.md) - chunked, concatenated, and very large input +- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input +- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream diff --git a/docs/mkdocs/docs/features/parsing/large_and_partial_input.md b/docs/mkdocs/docs/features/parsing/large_and_partial_input.md index d24b5fab1..89ce90bf8 100644 --- a/docs/mkdocs/docs/features/parsing/large_and_partial_input.md +++ b/docs/mkdocs/docs/features/parsing/large_and_partial_input.md @@ -17,6 +17,8 @@ flowchart TD H -->|"yes"| I["stop early with SAX, or read through a view"] ``` +

Questions about the input and the approach this page describes for each; an input can match more than one question.

+ ## Several values in one stream For newline-delimited input, read line by line and parse each line, as described in [JSON Lines](json_lines.md). For @@ -80,6 +82,14 @@ exception is one past the buffer size in the first case: The example feeds a document in five chunks. The first four are reported as incomplete, and a syntax error is reported as such, not as missing data. +The chunks of the example, and the bytes each attempt parses again: + +
+--8<-- "images/chunk_buffering.svg" +
+ +

Each row is one parse attempt on the growing buffer: an error at the byte after the end of the buffer means the value is incomplete, an error before the end is a real syntax error. The last line adds up the bytes parsed over all attempts.

+ !!! warning "Limits of buffering" - The buffer must contain exactly one value. Data that belongs to the next value is a syntax error. Use framing if @@ -93,6 +103,14 @@ such, not as missing data. The input does not have to become one huge `json` value. The two tools below process it piece by piece. +How the memory use develops while a large array of records is parsed (illustrative, not a measurement): + +
+--8<-- "images/memory_over_time.svg" +
+ +

Memory use over the number of records parsed: `parse()` keeps the whole tree, `json_document` the text plus its index, a discarding callback one record at a time, and SAX only what the handler keeps.

+ ### Handle and discard elements with a callback A [parser callback](parser_callbacks.md) is called for every finished value. If it returns `#!cpp false`, the value is @@ -152,3 +170,8 @@ and gives read-only [views](../../api/basic_json_view/index.md) of the parts you - [SAX interface](sax_interface.md) - event-based parsing - [Zero-copy JSON views](../json_view.md) - read parts of a large document without building a tree - [Untrusted input](untrusted_input.md) - limits for input you do not control +- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream +- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input +- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events +- [`JSON_PRECISE_STREAM_POSITION`](../../api/macros/json_precise_stream_position.md) - leave the stream positioned right after a parsed number +- [`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time diff --git a/docs/mkdocs/docs/features/parsing/parse_exceptions.md b/docs/mkdocs/docs/features/parsing/parse_exceptions.md index eb0ae5f58..e10b2cd04 100644 --- a/docs/mkdocs/docs/features/parsing/parse_exceptions.md +++ b/docs/mkdocs/docs/features/parsing/parse_exceptions.md @@ -143,3 +143,14 @@ acceptable, for example for configuration files. Note that `start_pos()` is a 0- For binary formats such as CBOR, there are no lines; `byte` is the position in the byte sequence. To find out how many bytes a successfully parsed value used, see [Several values in one stream](large_and_partial_input.md#several-values-in-one-stream). + +## See also + +- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input +- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON +- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events +- [`parse_error`](../../api/basic_json/parse_error.md) - exception thrown on a parse error +- [`JSON_DIAGNOSTIC_POSITIONS`](../../api/macros/json_diagnostic_positions.md) - record start and end positions of parsed values +- [Working Without Exceptions](../no_exceptions.md) - error handling when exceptions are disabled +- [Exceptions](../../home/exceptions.md) - list of exceptions and error codes +- [Parsing Untrusted Input](untrusted_input.md) - limits and checks for input you do not control diff --git a/docs/mkdocs/docs/features/parsing/parser_callbacks.md b/docs/mkdocs/docs/features/parsing/parser_callbacks.md index 6144011ec..187f10bab 100644 --- a/docs/mkdocs/docs/features/parsing/parser_callbacks.md +++ b/docs/mkdocs/docs/features/parsing/parser_callbacks.md @@ -31,6 +31,13 @@ table describes the values of the parameters `depth`, `event`, and `parsed`. | `parse_event_t::array_end` | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array | | `parse_event_t::value` | the parser finished reading a JSON value | depth of the value | the parsed JSON value | +
+--8<-- "images/callback_events.svg" +
+ +

Each coloured dot marks where the parser reports a `parse_event_t` event while reading +the JSON text on the left; the legend on the right names the event per colour.

+ ??? example "Example: sequence of callback events" When parsing the following JSON text, @@ -95,7 +102,7 @@ was called: The JSON specification leaves the handling of objects with repeated keys up to the implementation. As described in [`object_t`](../../api/basic_json/object_t.md#behavior), it is unspecified which value for a repeated key ends up in -the resulting `#!c json` value -- once parsing has produced that value, the duplicate is already gone, because object +the resulting `#!c json` value — once parsing has produced that value, the duplicate is already gone, because object storage maps each key to a single value. If duplicate keys should instead be treated as an error, a parser callback can detect them while the object is still being read, before that ambiguity ever applies. @@ -122,7 +129,7 @@ This approach has three limitations: maintaining a stack of the enclosing keys and array indices in the callback as well. A [SAX interface](sax_interface.md) does not lift the position limitation: its `key` function receives no position -either -- only `parse_error` is passed the byte position. +either — only `parse_error` is passed the byte position. ## Recipe: streaming a large homogeneous array @@ -171,3 +178,11 @@ tracking the maximum `depth` seen and throwing once it is exceeded: json::parse(input, callback); ``` + +## See also + +- [Parsing](index.md) - overview of the parsing functions +- [SAX Interface](sax_interface.md) - guide to event-based parsing +- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input +- [`parser_callback_t`](../../api/basic_json/parser_callback_t.md) - callback function type for the parser +- [`parse_event_t`](../../api/basic_json/parse_event_t.md) - enumeration of parser events diff --git a/docs/mkdocs/docs/features/parsing/sax_interface.md b/docs/mkdocs/docs/features/parsing/sax_interface.md index cfea680a4..872105a20 100644 --- a/docs/mkdocs/docs/features/parsing/sax_interface.md +++ b/docs/mkdocs/docs/features/parsing/sax_interface.md @@ -29,6 +29,8 @@ class sax_t ["json::sax_t"] { } ``` +

The interface `json::sax_t`: one function per event, each returning `bool`; the functions marked with `*` are pure virtual.

+ ```cpp // called when null is parsed bool null(); @@ -62,6 +64,34 @@ bool parse_error(std::size_t position, const std::string& last_token, const json The return value of each function determines whether parsing should proceed. +## Stepping through the events + +The events for a small input, step by step, and what two different handlers do with them. Press *Play* or step with *Next*: + +
+

input

{"a":[1,true],"b":"x"}

+

events, in the order the parser calls them

    +
  1. start_object(-1)
  2. +
  3. key("a")
  4. +
  5. start_array(-1)
  6. +
  7. number_unsigned(1)
  8. +
  9. boolean(true)
  10. +
  11. end_array()
  12. +
  13. key("b")
  14. +
  15. string("x")
  16. +
  17. end_object()
  18. +
+

handler: build a value tree, as parse does

{
+  "a": [
+    1,
+    true
+  ],
+  "b": "x"
+}

After end_object() the tree is complete and no container is open. The number of elements is passed as -1: the parser does not know it in advance.

+
+ +

The three lanes show the input consumed so far, the events called so far, and the state of the selected handler (*build a tree* or *keep only /b*). Check *return false at this event* to stop the parse at the current event and see which events are no longer called.

+ To implement your own SAX handler, proceed as follows: 1. Implement the SAX interface in a class. You can use class `nlohmann::json_sax` as base class, but you can also use any class where the functions described above are implemented and public. @@ -72,5 +102,9 @@ Note the `sax_parse` function only returns a `#!cpp bool` indicating the result ## See also -- [json_sax](../../api/json_sax/index.md) - documentation of the SAX interface -- [sax_parse](../../api/basic_json/sax_parse.md) - SAX parser +- [`json_sax`](../../api/json_sax/index.md) - documentation of the SAX interface +- [`sax_parse`](../../api/basic_json/sax_parse.md) - SAX parser +- [Parsing](index.md) - overview of the parsing functions +- [Parsing and Exceptions](parse_exceptions.md) - handling parse errors and locating them in the input +- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input +- [Binary Formats](../binary_formats/index.md) - CBOR, MessagePack, BSON, and other formats diff --git a/docs/mkdocs/docs/features/parsing/untrusted_input.md b/docs/mkdocs/docs/features/parsing/untrusted_input.md index 65f1bda25..e89a7a7a3 100644 --- a/docs/mkdocs/docs/features/parsing/untrusted_input.md +++ b/docs/mkdocs/docs/features/parsing/untrusted_input.md @@ -1,7 +1,7 @@ # Parsing Untrusted Input -This page is for applications that parse JSON -- or one of the supported [binary formats](../binary_formats/index.md) -(BJData, BON8, BSON, CBOR, MessagePack, UBJSON) -- from a source they do not fully control, such as a network +This page is for applications that parse JSON — or one of the supported [binary formats](../binary_formats/index.md) +(BJData, BON8, BSON, CBOR, MessagePack, UBJSON) — from a source they do not fully control, such as a network connection, an uploaded file, or another process. It summarizes what the library already does for such input and what remains the caller's responsibility, linking to the pages that cover each aspect in detail rather than repeating them. @@ -50,12 +50,12 @@ stack, however deeply it is nested. [SAX handler](sax_interface.md) (for the JSON parser or a binary format alike) and throwing once your limit is exceeded. -Once a value has been parsed, operations that walk it recursively -- serializing it with +Once a value has been parsed, operations that walk it recursively — serializing it with [`dump`](../../api/basic_json/dump.md), hashing it, copying it, comparing two values with `#!cpp ==`, `#!cpp <`, or (in C++20) `#!cpp <=>`, merging with [`update`](../../api/basic_json/update.md), applying a [`merge_patch`](../../api/basic_json/merge_patch.md), creating a patch with [`diff`](../../api/basic_json/diff.md) or [`merge_diff`](../../api/basic_json/merge_diff.md), [`flatten`](../../api/basic_json/flatten.md), and the binary -writers (`to_cbor`, `to_msgpack`, ...) -- descend at most 128 levels on the call stack and continue +writers (`to_cbor`, `to_msgpack`, ...) — descend at most 128 levels on the call stack and continue below that with an explicit stack instead, so none of them can exhaust the stack either, however deeply the value is nested. Destroying a value (its destructor) never recurses at all, regardless of nesting depth, for the same reason. @@ -63,12 +63,12 @@ nested. Destroying a value (its destructor) never recurses at all, regardless of The library does not limit the overall size of a JSON text; a value nested or wide enough will use memory proportional to the input. If you parse untrusted input of unbounded size, check the size of the file or stream -yourself before -- or while -- handing it to `parse()`. +yourself before — or while — handing it to `parse()`. For the binary formats, an announced size is never trusted outright: - Reading a string or binary value copies the input in bounded 4096-byte chunks and grows the result as bytes are - actually consumed, rather than allocating the announced length up front -- a truncated input runs out of bytes + actually consumed, rather than allocating the announced length up front — a truncated input runs out of bytes (reported as a parse error) instead of triggering an oversized allocation. - When an array announces its number of elements and the array container supports `reserve()` (as `#!cpp std::vector`, the default, does), the library reserves storage for at most 16384 of them upfront, regardless of how large the @@ -91,7 +91,7 @@ Invalid UTF-8 is rejected while parsing, not just while serializing: A `#!cpp '\0'` (NUL) byte *inside* a quoted JSON string is always rejected (it must be escaped as `\u0000`). A NUL byte *outside* of a string is different: by default it is silently treated as the end of the input, so trailing bytes after -it -- including further, otherwise well-formed JSON -- are silently ignored rather than rejected. Since untrusted input +it — including further, otherwise well-formed JSON — are silently ignored rather than rejected. Since untrusted input that happens to embed a NUL is a way to make part of it disappear without a parse error, see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) and consider defining [`JSON_STRICT_NUL_HANDLING`](../../api/macros/json_strict_nul_handling.md) to `1` to reject a NUL byte like any other @@ -116,7 +116,7 @@ silently resolving them one way or another, see the ## Numbers -A number whose value cannot be represented -- for instance `1E1000`, which overflows `double` -- is rejected while +A number whose value cannot be represented — for instance `1E1000`, which overflows `double` — is rejected while parsing as [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406) rather than silently becoming infinity. An integer that is syntactically valid but does not fit the 64-bit integer types is not rejected; it is instead stored as a `double`, which may lose precision for very large values. See @@ -139,7 +139,7 @@ conforming JSON consumers is exactly what the default rejects. [SAX handler](sax_interface.md); the library bounds the call stack but not memory use. - Bound the size of the input itself before parsing, independent of the library's own bounded allocations for binary format lengths. -- Decide up front how you want a string with invalid UTF-8 -- from any source, not only parsing -- to be serialized +- Decide up front how you want a string with invalid UTF-8 — from any source, not only parsing — to be serialized (`strict`, `replace`, or `ignore`), rather than discovering it from an uncaught `type_error.316`. - If a stray NUL byte silently truncating trailing input is a problem for your input format, define `JSON_STRICT_NUL_HANDLING`. @@ -160,3 +160,9 @@ report a security issue in the library itself, follow the [security policy](../. - [Number handling](../types/number_handling.md) - number ranges and overflow behavior - [Assurance case](../../community/assurance_case.md) - the library's threat model and countermeasures - [Security policy](../../community/security_policy.md) - how to report a vulnerability +- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input +- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON +- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events +- [`parser_callback_t`](../../api/basic_json/parser_callback_t.md) - callback function type for the parser +- [`JSON_STRICT_NUL_HANDLING`](../../api/macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input +- [`JSON_DIAGNOSTIC_POSITIONS`](../../api/macros/json_diagnostic_positions.md) - record start and end positions of parsed values diff --git a/docs/mkdocs/docs/features/performance.md b/docs/mkdocs/docs/features/performance.md index 7ad7b187e..ffea9c215 100644 --- a/docs/mkdocs/docs/features/performance.md +++ b/docs/mkdocs/docs/features/performance.md @@ -6,7 +6,7 @@ first. If a hard real-time budget or the last percent of throughput matters more [faster, more specialized library](https://github.com/miloyip/nativejson-benchmark#parsing-time) may be a better fit. That said, how you use this library still makes a measurable difference. This page collects practical, code-verified -techniques for reducing time, memory, and compile-time cost -- without repeating the detailed pages it links to. +techniques for reducing time, memory, and compile-time cost — without repeating the detailed pages it links to. ## Parsing input @@ -14,8 +14,8 @@ techniques for reducing time, memory, and compile-time cost -- without repeating `#!cpp FILE*` (see [Parsing](parsing/index.md#input)). Internally, every input is wrapped in an [input adapter](../home/architecture.md#input-adapters), and not all adapters are equally fast. -For inputs backed by contiguous, single-byte memory -- a `#!cpp std::string`, a `#!cpp std::vector`, a string -literal, or a pointer range -- the library uses `iterator_input_adapter`, wrapped in a raw pointer so the fast paths +For inputs backed by contiguous, single-byte memory — a `#!cpp std::string`, a `#!cpp std::vector`, a string +literal, or a pointer range — the library uses `iterator_input_adapter`, wrapped in a raw pointer so the fast paths below apply on every supported standard. This adapter exposes two optimizations the lexer detects at compile time: - it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is @@ -69,7 +69,7 @@ a DOM, three alternatives avoid building it: `basic_json` values. - Pass a [parser callback](parsing/parser_callbacks.md) to `parse`. This still builds a DOM, but the callback can discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the - unparsed remainder of the input) instead of the whole document -- see the [recipe for streaming a large homogeneous + unparsed remainder of the input) instead of the whole document — see the [recipe for streaming a large homogeneous array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array). - Parse into a [`json_document`](json_view.md) (`#!cpp `) instead of a `basic_json`. It keeps the input text and builds a flat index of 16 bytes per value; strings and floating-point numbers are not copied, but @@ -85,35 +85,53 @@ at a time, and a malformed line does not invalidate lines already processed. ## Binary formats JSON text is not a compact format. If the data is only exchanged between programs (not read by humans), the -[binary formats](binary_formats/index.md) -- BJData, BON8, BSON, CBOR, MessagePack, and UBJSON -- encode the same +[binary formats](binary_formats/index.md) — BJData, BON8, BSON, CBOR, MessagePack, and UBJSON — encode the same values more compactly, which reduces both the bytes transferred and, for most of them, the work needed to parse them back. The [size comparison](binary_formats/index.md#sizes) on that page, measured against minified JSON for four reference documents, shows the effect varies a lot by document shape: CBOR and MessagePack come out at 50.5% of the minified JSON size for the numeric-array-heavy `canada.json`, but only around 87-88% for the string-heavy `jeopardy.json`, where there is less numeric data to encode more compactly. BON8 is the most compact option in that comparison for text-heavy documents (63.5%-87.5%), at the cost of an -[incomplete serializer](binary_formats/index.md#completeness) (no unsigned integers above int64). Which format -- and -whether it is worth the loss of human readability at all -- depends on the actual data; see the +[incomplete serializer](binary_formats/index.md#completeness) (no unsigned integers above int64). Which format — and +whether it is worth the loss of human readability at all — depends on the actual data; see the [comparison tables](binary_formats/index.md#comparison) before choosing one. +
+
+--8<-- "images/binary_sizes_canada.svg" +
+
+--8<-- "images/binary_sizes_jeopardy.svg" +
+
+

Two of the four panels from the [size comparison](binary_formats/index.md#sizes); +the smallest format per file is drawn in a stronger colour than the others.

+ ## Object type: `json` vs. `ordered_json` The default [`json`](../api/json.md) type stores object keys in a `#!cpp std::map`, giving logarithmic-time lookup, insertion, and erasure, at the cost of sorting keys alphabetically rather than preserving insertion order (see [Object Order](object_order.md)). [`ordered_json`](../api/ordered_json.md) uses [`nlohmann::ordered_map`](../api/ordered_map.md) instead, a `#!cpp std::vector`-backed container with no lookup index: -every key-based operation is a **linear scan**, so building an object of `n` distinct keys costs **O(n²)** in total -- +every key-based operation is a **linear scan**, so building an object of `n` distinct keys costs **O(n²)** in total — this applies equally to inserting keys one by one and to parsing an object, since the parser inserts each key as it is read. The [measurements on the `ordered_map` page](../api/ordered_map.md#complexity) show this is negligible at typical object sizes (2000 keys: 0.7 ms for `json` vs. 3.6 ms for `ordered_json`, a 5x factor) but grows steeply for large, machine-generated objects (16 000 keys: 3.3 ms vs. 181.6 ms, a 54x factor). +
+--8<-- "images/ordered_json_scaling.svg" +
+

Parsing a flat object of `n` keys, measured with `-O2 -DNDEBUG`; all +four sizes from the [table on the `ordered_map` page](../api/ordered_map.md#complexity), both +axes logarithmic.

+ If insertion order matters *and* an object routinely has many thousands of keys, `ordered_json`'s quadratic build cost may not be acceptable. The library's [`ObjectType` template parameter](types/template_parameters.md#objecttype) can be set to a different container instead: `#!cpp nlohmann::fifo_map` keeps insertion order with a real lookup index (avoiding the quadratic cost), while `#!cpp std::unordered_map`, `#!cpp boost::unordered_flat_map`, `#!cpp absl::flat_hash_map`, and similar hash maps trade insertion order for average-case constant-time lookup (through -an adapter, since their template argument order does not match what `basic_json` expects) -- see +an adapter, since their template argument order does not match what `basic_json` expects) — see [Object Order](object_order.md#alternative-behavior-preserve-insertion-order) for the full list. ## Avoiding copies @@ -128,13 +146,13 @@ an adapter, since their template argument order does not match what `basic_json` - **Access without copying.** [`get()`](../api/basic_json/get.md) returns a copy of the stored value converted to `T`. When a reference or pointer to the value already stored inside the `basic_json` is enough, [`get_ref()`](../api/basic_json/get_ref.md) and [`get_ptr()`](../api/basic_json/get_ptr.md) access it directly: - both pages state, word for word, "No copies are made." -- at the cost of that reference or pointer becoming invalid + both pages state, word for word, "No copies are made." — at the cost of that reference or pointer becoming invalid once the underlying value changes. - **Iterate by reference.** `#!cpp basic_json::iterator::operator*()` returns a `reference` (an alias for `#!cpp basic_json&`), but a range-based for loop with a by-value loop variable (`#!cpp for (auto el : j)`) still copies each element, because plain `#!cpp auto` drops the reference. Write `#!cpp for (const auto& el : j)` (or `#!cpp auto&` for a mutable loop), and use [`items()`](../api/basic_json/items.md) the same way when the key is - needed too -- its own examples use `#!cpp for (auto& el : j.items())`. + needed too — its own examples use `#!cpp for (auto& el : j.items())`. - **Construct in place.** [`emplace_back()`](../api/basic_json/emplace_back.md) (arrays, amortized constant time) and [`emplace()`](../api/basic_json/emplace.md) (objects, logarithmic in the size of the container for `json`) forward their arguments directly to a `basic_json` constructor, rather than requiring a temporary value to be @@ -143,13 +161,13 @@ an adapter, since their template argument order does not match what `basic_json` in instead of copying it. - **Skip the bounds check when it is redundant.** [`at()`](../api/basic_json/at.md) and [`operator[]`](../api/basic_json/operator%5B%5D.md) have the same complexity (constant for a valid array index, - logarithmic for an object key in `json`) -- the difference is that `at()` additionally checks the key or index and + logarithmic for an object key in `json`) — the difference is that `at()` additionally checks the key or index and throws if it is invalid, while `operator[]` does not (see [unchecked access](element_access/unchecked_access.md) and [checked access](element_access/checked_access.md)). Prefer `operator[]` when the surrounding code has already established that the access is valid. - **Reserve array capacity.** `basic_json` has no public `reserve()`, but when building a large array incrementally with a known final size, [`get_ref()`](../api/basic_json/get_ref.md) exposes the underlying - `#!cpp array_t` so it can be reserved directly -- see + `#!cpp array_t` so it can be reserved directly — see ["reserving array capacity"](element_access/unchecked_access.md#performance-reserving-array-capacity) for the one-line recipe. @@ -182,7 +200,7 @@ an adapter, since their template argument order does not match what `basic_json` (word for word from the page); any non-negative `indent` pretty-prints instead, which is more readable but produces more bytes and more work. `dump()` builds and returns a complete `#!cpp string_t` containing the whole serialization. [`operator<<`](../api/operator_ltlt.md) writes directly to a `#!cpp std::ostream` instead, through the same -serializer, but without ever materializing that intermediate string -- so if the destination is a stream (a file, or +serializer, but without ever materializing that intermediate string — so if the destination is a stream (a file, or `#!cpp std::cout`), `#!cpp os << j;` avoids the allocation and copy that `#!cpp os << j.dump();` would incur for large values. @@ -193,7 +211,7 @@ while it is in use: - [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) adds a JSON Pointer to exception messages, pointing at the value that triggered the exception. Quoting the page directly: "enabling this macro increases the size of every - JSON value by one pointer and adds some runtime overhead" -- every value gains a parent pointer that has to be kept + JSON value by one pointer and adds some runtime overhead" — every value gains a parent pointer that has to be kept up to date as the document is built and modified. - [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) adds [`start_pos()`](../api/basic_json/start_pos.md) and [`end_pos()`](../api/basic_json/end_pos.md), the byte offsets a @@ -210,7 +228,7 @@ untrusted input, or in a debug build), and keep them off in a release build that [`basic_json`](../api/basic_json/index.md), [`json`](../api/json.md), [`ordered_json`](../api/ordered_json.md), [`json_pointer`](../api/json_pointer/index.md), and [`adl_serializer`](../api/adl_serializer/index.md), pulling in only a handful of lightweight standard headers instead of the full `json.hpp`. A header that only needs to *name* -`nlohmann::json` -- in a function signature or a class member declaration, for instance -- can include `json_fwd.hpp` +`nlohmann::json` — in a function signature or a class member declaration, for instance — can include `json_fwd.hpp` and leave `#!cpp #include ` to the source files that actually parse, build, or serialize values, the same way a project would forward-declare any other heavy class to keep it out of widely-included headers: @@ -231,8 +249,8 @@ nlohmann::json my_type::config() const { /* ... */ } ``` One caveat: ABI-affecting macros such as `JSON_DIAGNOSTICS` and `JSON_DIAGNOSTIC_POSITIONS` are encoded into the -library's [inline namespace name](namespace.md#limitations). Every translation unit -- whether it includes -`json_fwd.hpp` or the full header -- must define them the same way, or linking fails with undefined references +library's [inline namespace name](namespace.md#limitations). Every translation unit — whether it includes +`json_fwd.hpp` or the full header — must define them the same way, or linking fails with undefined references instead of a compile error. If I/O support is not needed at all, [`JSON_NO_IO`](../api/macros/json_no_io.md) excludes ``, ``, @@ -252,3 +270,9 @@ those headers are then never processed by the compiler at all. - [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices - [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types - [Supported macros](macros.md) - overview of all configuration macros, including the diagnostics ones above +- [`JSON_NO_SIMD`](../api/macros/json_no_simd.md) - disable SIMD acceleration +- [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) - use simdutf for UTF-8 validation +- [`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) - disable the stream bulk scan +- [`parse`](../api/basic_json/parse.md) - deserialize from a compatible input +- [`sax_parse`](../api/basic_json/sax_parse.md) - parse with a SAX interface +- [`json_view`](../api/json_view.md) - a non-owning view of a value in a document diff --git a/docs/mkdocs/docs/features/serialization.md b/docs/mkdocs/docs/features/serialization.md index 5e1aedac0..c1b23442c 100644 --- a/docs/mkdocs/docs/features/serialization.md +++ b/docs/mkdocs/docs/features/serialization.md @@ -128,3 +128,7 @@ Besides JSON text, a value can also be serialized to the more compact [binary fo - [`std::formatter`](../api/basic_json/std_formatter.md) - use JSON values with `std::format` and `std::print` - [`format_as`](../api/basic_json/format_as.md) - use JSON values with the {fmt} library - [Parsing](parsing/index.md) - the reverse operation +- [`error_handler_t`](../api/basic_json/error_handler_t.md) - how to treat invalid UTF-8 when dumping +- [`basic_json_view::dump`](../api/basic_json_view/dump.md) - serialize a view directly +- [`JSON_NO_IO`](../api/macros/json_no_io.md) - remove stream and file I/O +- [`JSON_NO_SIMD`](../api/macros/json_no_simd.md) - disable SIMD acceleration diff --git a/docs/mkdocs/docs/features/trailing_commas.md b/docs/mkdocs/docs/features/trailing_commas.md index 735ece569..2059f0174 100644 --- a/docs/mkdocs/docs/features/trailing_commas.md +++ b/docs/mkdocs/docs/features/trailing_commas.md @@ -37,3 +37,10 @@ For more information, see [JSON With Commas and Comments (JWCC)](https://nigelta ``` --8<-- "examples/trailing_commas.output" ``` + +## See also + +- [`parse`](../api/basic_json/parse.md) - deserialize from a compatible input +- [Comments](comments.md) - ignore `//` and `/* */` comments while parsing +- [Parsing](parsing/index.md) - overview of the parsing functions +- [Parsing Untrusted Input](parsing/untrusted_input.md) - limits and checks for input you do not control diff --git a/docs/mkdocs/docs/features/type_recipes.md b/docs/mkdocs/docs/features/type_recipes.md index 314425dc6..d46b858e6 100644 --- a/docs/mkdocs/docs/features/type_recipes.md +++ b/docs/mkdocs/docs/features/type_recipes.md @@ -120,5 +120,10 @@ same two functions. ## See also - [Arbitrary type conversions](arbitrary_types.md) - the conversion mechanism and macros -- [Why isn't my `to_json` found?](arbitrary_types.md#why-isnt-my-to_json-or-from_json-found) +- [Why isn't my `to_json` found?](arbitrary_types.md#why-isnt-my-to_json-or-from_json-found) - troubleshoot conversion + functions that are not picked up - [Converting values](conversions.md) - `get`, implicit conversions, and `std::optional` +- [`NLOHMANN_DEFINE_DERIVED_TYPE`](../api/macros/nlohmann_define_derived_type.md) - conversion functions for derived classes +- [`adl_serializer`](../api/adl_serializer/index.md) - customize conversions for third-party types +- [`get`](../api/basic_json/get.md) - convert a JSON value to a C++ type + diff --git a/docs/mkdocs/docs/features/types/index.md b/docs/mkdocs/docs/features/types/index.md index 03e8c0bae..e95e18965 100644 --- a/docs/mkdocs/docs/features/types/index.md +++ b/docs/mkdocs/docs/features/types/index.md @@ -64,6 +64,8 @@ basic_json .. json_value basic_json .. value_t ``` +

Storage of a `basic_json` value: the enumeration `value_t` holds the type, and the union `json_value` holds the value, as a pointer for objects, arrays, strings, and binary values and directly for booleans and numbers.

+ ## Template arguments The data types to store a JSON value are derived from the template arguments passed to class `basic_json`: @@ -290,8 +292,16 @@ Integer number values, unsigned integer number values, and floating-point number ## See also - [Number handling](number_handling.md) - how numbers are stored and converted -- [Template parameter requirements](template_parameters.md) -- [**basic_json**](../../api/basic_json/index.md) - the class template and its member types -- [**type**](../../api/basic_json/type.md) and [**value_t**](../../api/basic_json/value_t.md) - query the type of a value -- [**object_t**](../../api/basic_json/object_t.md), [**array_t**](../../api/basic_json/array_t.md), [**string_t**](../../api/basic_json/string_t.md), [**number_integer_t**](../../api/basic_json/number_integer_t.md), [**number_float_t**](../../api/basic_json/number_float_t.md) - the underlying types -- [Object order](../object_order.md) +- [Template parameter requirements](template_parameters.md) - what the template parameters of `basic_json` must provide +- [`basic_json`](../../api/basic_json/index.md) - the class template and its member types +- [`type`](../../api/basic_json/type.md) - and [`value_t`](../../api/basic_json/value_t.md) - query the type of a value +- [`object_t`](../../api/basic_json/object_t.md), [`array_t`](../../api/basic_json/array_t.md), [`string_t`](../../api/basic_json/string_t.md), [`number_integer_t`](../../api/basic_json/number_integer_t.md), [`number_float_t`](../../api/basic_json/number_float_t.md) - the underlying types +- [Object order](../object_order.md) - how object keys are ordered +- [`json`](../../api/json.md) - the default JSON type +- [`boolean_t`](../../api/basic_json/boolean_t.md) - the underlying boolean type +- [`number_unsigned_t`](../../api/basic_json/number_unsigned_t.md) - the underlying unsigned integer type +- [`is_primitive`](../../api/basic_json/is_primitive.md) - and the other `is_*` functions query the type +- [`is_structured`](../../api/basic_json/is_structured.md) - true for objects and arrays +- [Converting values](../conversions.md) - convert JSON values to and from C++ types +- [Creating JSON values](../creating_values.md) - build JSON values + diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index d11ad2747..82bbe1824 100644 --- a/docs/mkdocs/docs/features/types/number_handling.md +++ b/docs/mkdocs/docs/features/types/number_handling.md @@ -27,7 +27,11 @@ JSON defines the syntax of numbers as follows: The following railroad diagram from [json.org](https://json.org) visualizes the number syntax: -![Syntax for JSON numbers](../../images/json_syntax_number.png) +
+--8<-- "images/json_syntax_number.svg" +
+ +

Read the diagram from left to right: every path along the lines spells a valid number, made of an optional minus sign, an integer part, an optional fraction, and an optional exponent.

### Number interoperability @@ -69,15 +73,17 @@ otherwise, it uses unsigned integer storage. ```mermaid flowchart TD - A["number literal"] --> B{"has a fraction (.) or exponent (e/E)?"} + A["number literal"] --> B{{"has a fraction (.) or exponent (e/E)?"}} B -->|"yes"| F["number_float_t"] - B -->|"no"| C{"has a leading minus sign?"} + B -->|"no"| C{{"has a leading minus sign?"}} C -->|"yes"| D["try number_integer_t"] C -->|"no"| E["try number_unsigned_t"] D -->|"overflow"| F E -->|"overflow"| F ``` +

How the parser picks the storage type of a number literal: a fraction or exponent always gives `number_float_t`; otherwise the sign selects the signed or unsigned integer type, and a value that overflows it is stored as `number_float_t`.

+ !!! info "Notes" - Numbers with a decimal digit or scientific notation are always stored as `#!c double`. @@ -131,6 +137,32 @@ The JSON number grammar allows for different ways to express zero, and this libr That is, `-0` is stored as a signed integer, but the serialization does not reproduce the `-`. +The literals mentioned on this page, parsed with `json::parse` and serialized with `dump()` (checked against the +library; pick one or type it to see the row): + +
+ +| Literal | Stored as (`type()`) | `dump()` | +|---------|----------------------|----------| +| `0` | `number_unsigned` (`std::uint64_t`) | `0` | +| `-0` | `number_integer` (`std::int64_t`) | `0` | +| `-0.0` | `number_float` (`double`) | `-0.0` | +| `1E3` | `number_float` (`double`) | `1000.0` | +| `-12345678912345789123456789` | `number_float` (`double`) | `-1.2345678912345788e+25` | +| `18446744073709551615` | `number_unsigned` (`std::uint64_t`) | `18446744073709551615` | +| `18446744073709551616` | `number_float` (`double`) | `1.8446744073709552e+19` | +| `-9223372036854775808` | `number_integer` (`std::int64_t`) | `-9223372036854775808` | +| `1E400` | exception `out_of_range.406` | (nothing, parsing fails) | +| `1E-400` | `number_float` (`double`) | `0.0` | +| `3.141592653589793238462643383279` | `number_float` (`double`) | `3.141592653589793` | +| `2555.5599999999999` | `number_float` (`double`) | `2555.56` | +| `0.0000972439793401814` | `number_float` (`double`) | `9.72439793401814e-05` | +| `12345E-5` | `number_float` (`double`) | `0.12345` | + +
+ +

Each row shows how a literal is stored and how `dump()` writes it back. The explorer only knows the literals in this list; other input is reported as not in the verified list.

+ ### Number serialization - Integer numbers are serialized as is; that is, no scientific notation is used. @@ -329,6 +361,8 @@ the stored number: | [`type_name()`](../../api/basic_json/type_name.md) | `#!c "number"` | `#!c "number"` | `#!c "number"` | `#!c "string"` | | [`type()`](../../api/basic_json/type.md) | `number_unsigned` | `number_integer` | `number_float` | `string` | +

Return value of each function for a value that stores an unsigned integer, a signed integer, or a floating-point number, and for a string for comparison.

+ ### Template number types The number types can be changed with template parameters. @@ -362,3 +396,12 @@ The number types can be changed with template parameters. Note values should then be parsed with `json_ld::parse` rather than `json::parse` as the latter would parse floating-point values to `#!c double` before then converting them to `#!c long double`. + +## See also + +- [Types](index.md) - how all value types are stored +- [Converting values](../conversions.md) - convert numbers to C++ types +- [`number_integer_t`](../../api/basic_json/number_integer_t.md) - the signed integer type +- [`number_unsigned_t`](../../api/basic_json/number_unsigned_t.md) - the unsigned integer type +- [`number_float_t`](../../api/basic_json/number_float_t.md) - the floating-point type +- [`is_number`](../../api/basic_json/is_number.md) - check for a number of any kind diff --git a/docs/mkdocs/docs/features/types/template_parameters.md b/docs/mkdocs/docs/features/types/template_parameters.md index cc079cd72..f3868f594 100644 --- a/docs/mkdocs/docs/features/types/template_parameters.md +++ b/docs/mkdocs/docs/features/types/template_parameters.md @@ -1,7 +1,7 @@ # Template Parameter Requirements Class [`basic_json`](../../api/basic_json/index.md) is configurable through eleven template parameters. The library -never formally states what a type passed for one of these parameters has to provide -- the requirements are implied by +never formally states what a type passed for one of these parameters has to provide — the requirements are implied by the way the library uses the resulting [`object_t`](../../api/basic_json/object_t.md), [`array_t`](../../api/basic_json/array_t.md), [`string_t`](../../api/basic_json/string_t.md), etc. This page collects these requirements so they do not have to be discovered by trial and error. Each section lists the concrete types @@ -16,9 +16,9 @@ To change a single template parameter and keep the others, use the member alias Requirements are split into two groups: -- **Always required** -- needed to instantiate `basic_json` at all, or needed by functions that virtually every program +- **Always required** — needed to instantiate `basic_json` at all, or needed by functions that virtually every program uses (construction, element access, [`dump`](../../api/basic_json/dump.md)). -- **Required for ...** -- only needed when a particular part of the API is instantiated. Member function templates are +- **Required for ...** — only needed when a particular part of the API is instantiated. Member function templates are only instantiated when they are used, so a type may be perfectly usable even though it does not satisfy these requirements, as long as the corresponding functions are never called. @@ -56,8 +56,8 @@ Requirements are split into two groups: !!! warning "Third-party containers and incomplete types" - `object_t` is instantiated inside the definition of `basic_json` -- it is probed for a `key_compare` member to - form [`object_comparator_t`](../../api/basic_json/object_comparator_t.md) -- i.e. while `basic_json` is still an + `object_t` is instantiated inside the definition of `basic_json` — it is probed for a `key_compare` member to + form [`object_comparator_t`](../../api/basic_json/object_comparator_t.md) — i.e. while `basic_json` is still an incomplete type. `#!cpp std::map` is required by the standard to support incomplete mapped types; most third-party maps are not, and inspecting the mapped type at class scope (for instance with `#!cpp std::is_trivially_move_assignable`) makes them unusable as `ObjectType`, no matter how their template @@ -65,7 +65,7 @@ Requirements are split into two groups: `robin_hood::unordered_node_map`, `folly::F14FastMap`, and `eastl::hash_map`. `array_t` is only *named* in the class definition and is not instantiated until `basic_json` is complete, so an - `ArrayType` that inspects its value type at class scope is generally fine -- `boost::container::small_vector` and + `ArrayType` that inspects its value type at class scope is generally fine — `boost::container::small_vector` and `static_vector` both reject incomplete value types yet work here. `absl::InlinedVector` is the exception: the `#!cpp std::is_trivially_move_assignable` it evaluates while instantiating itself re-enters the library's own trait machinery mid-instantiation. @@ -93,7 +93,7 @@ i.e., the template arguments follow the order and meaning of `std::map`. - The template must be usable with **four** type arguments in the order shown above. The third argument is a **comparator**; containers that expect something else in this position (e.g., a hash function) need an alias template - or wrapper -- see [Notes](#notes). + or wrapper — see [Notes](#notes). - An optional member type `key_compare`. If it is present it becomes [`object_comparator_t`](../../api/basic_json/object_comparator_t.md); otherwise [`default_object_comparator_t`](../../api/basic_json/default_object_comparator_t.md) is used. @@ -116,7 +116,7 @@ i.e., the template arguments follow the order and meaning of `std::map`. semantics; multimaps cannot be used. - The type must be swappable (via `std::swap` or an ADL `swap`). - The comparison operators `==` and `<`; `!=`, `<=`, `>`, and `>=` are derived from them. Where the library uses - three-way comparison (C++20), `==` and `<=>` are required **instead** -- the six two-way operators do not satisfy + three-way comparison (C++20), `==` and `<=>` are required **instead** — the six two-way operators do not satisfy it. They implement [`basic_json`'s comparison operators](../../api/basic_json/operator_eq.md). ### Required for heterogeneous key lookup @@ -200,7 +200,7 @@ object keys are serialized by [`dump`](../../api/basic_json/dump.md) and visited With [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md) enabled, the library detects insertion-ordered maps by probing for a `capacity()` member function (`nlohmann::ordered_map` inherits it from `std::vector`) and refreshes all parent pointers after every insertion. An `ObjectType` that happens to have a `capacity()` member is therefore treated -conservatively -- this is correct, but slower. +conservatively — this is correct, but slower. #### Key order and duplicate keys @@ -211,7 +211,7 @@ The library does not sort or de-duplicate keys itself; the behavior described in `docs/mkdocs/docs/examples/custom_object_type.hpp` wraps a private `#!cpp std::map` and satisfies every requirement above. It does not define `key_compare`, so `object_comparator_t` falls back to - [`default_object_comparator_t`](../../api/basic_json/default_object_comparator_t.md) -- a good starting point for + [`default_object_comparator_t`](../../api/basic_json/default_object_comparator_t.md) — a good starting point for a custom `ObjectType`. ```cpp @@ -305,7 +305,7 @@ using array_t = ArrayType>; !!! tip "Reference implementation" `docs/mkdocs/docs/examples/custom_array_type.hpp` wraps a private `#!cpp std::vector` and satisfies every - requirement above -- a good starting point for a custom `ArrayType`. + requirement above — a good starting point for a custom `ArrayType`. ```cpp --8<-- "examples/custom_array_type.hpp" @@ -384,13 +384,13 @@ using array_t = ArrayType>; ### Required for JSON Pointer, `flatten`, and `diff` -- A static member `npos` and the member function `find_first_of(char, size_type)` -- together with `data()`, +- A static member `npos` and the member function `find_first_of(char, size_type)` — together with `data()`, `reserve(n)`, and `append(const char*, size_type)` they implement the escaping and unescaping of reference tokens described in RFC 6901. Neither `find(const StringType&, size_type)`, nor `substr(pos, count)`, nor `replace(pos, count, const StringType&)` is required. - `empty()`. -- `resize(n)` (one argument) -- used by `to_string` to size the result before the reference tokens are copied into it. -- `begin()` and `end()` -- used by +- `resize(n)` (one argument) — used by `to_string` to size the result before the reference tokens are copied into it. +- `begin()` and `end()` — used by [`operator[](const json_pointer&)`](../../api/basic_json/operator%5B%5D.md) to decide whether a reference token denotes an array index. @@ -416,7 +416,7 @@ using array_t = ArrayType>; | `#!cpp std::pmr::string` | see the warning below before relying on the memory resource | | `boost::container::string` | needs a user-supplied `#!cpp std::hash` specialization (Boost provides `boost::hash` instead) | | `folly::fbstring` | requires C++20, see the note above | -| `eastl::string` | needs a user-supplied `#!cpp std::hash` and an ADL `int_to_string` (it is not assignable from a `#!cpp std::string`); [`parse`](../../api/basic_json/parse.md) does not accept it directly -- pass a character range or a `#!cpp std::string` | +| `eastl::string` | needs a user-supplied `#!cpp std::hash` and an ADL `int_to_string` (it is not assignable from a `#!cpp std::string`); [`parse`](../../api/basic_json/parse.md) does not accept it directly — pass a character range or a `#!cpp std::string` | | a custom string class in a user-defined namespace | if the requirements above are met | ### Types that cannot be used @@ -434,13 +434,13 @@ using array_t = ArrayType>; allocation and has to be stateless (see [`AllocatorType`](#allocatortype)), and string values the library creates are constructed with their own default allocator. So: - - Every string the library itself produces -- from [`parse`](../../api/basic_json/parse.md), from - [`dump`](../../api/basic_json/dump.md), or by default construction -- allocates from + - Every string the library itself produces — from [`parse`](../../api/basic_json/parse.md), from + [`dump`](../../api/basic_json/dump.md), or by default construction — allocates from `#!cpp std::pmr::get_default_resource()`. - **Copying** an arena-backed string into a value silently drops its memory resource: the copy lands on the default resource, because `#!cpp std::pmr::polymorphic_allocator` does not propagate on copy construction. Nothing warns about this. - - **Moving** one in does keep it, and later growth still allocates from that arena -- but it does not survive a + - **Moving** one in does keep it, and later growth still allocates from that arena — but it does not survive a copy of the enclosing `basic_json`. - Passing `#!cpp std::pmr::polymorphic_allocator` as `AllocatorType` does not work around any of this; it does not compile. @@ -451,7 +451,7 @@ using array_t = ArrayType>; !!! tip "Reference implementation" `docs/mkdocs/docs/examples/custom_string_type.hpp` wraps a private `#!cpp std::string` and satisfies every - requirement above -- a good starting point for a custom `StringType`. The unit test + requirement above — a good starting point for a custom `StringType`. The unit test `tests/src/unit-alt-string.cpp` contains a more thorough variant, `alt_string`, exercised against a larger part of the API. @@ -479,8 +479,8 @@ using array_t = ArrayType>; - A literal type that is trivially default-constructible, trivially copyable, and trivially destructible; otherwise the union's special member functions are deleted. -- **Implicitly** convertible from `#!cpp bool` -- an `#!cpp explicit` constructor is not enough, because the - `to_json` overload for a custom `BooleanType` is constrained on `#!cpp std::is_convertible` -- and contextually +- **Implicitly** convertible from `#!cpp bool` — an `#!cpp explicit` constructor is not enough, because the + `to_json` overload for a custom `BooleanType` is constrained on `#!cpp std::is_convertible` — and contextually convertible to `#!cpp bool` (here an `#!cpp explicit operator bool` is fine). - Comparison operators `==`, `!=`, `<`, `<=`, `>`, `>=` (or `<=>`). - Convertible from and to `#!cpp bool` through the serializer, because @@ -491,7 +491,7 @@ There is little reason to use anything other than `#!cpp bool` here. ### Compatible types `#!cpp bool` is the only usable choice. Another trivially copyable type that is implicitly convertible to and from -`#!cpp bool` -- `#!cpp std::uint8_t`, say -- does compile, and JSON booleans still round-trip, but the type then +`#!cpp bool` — `#!cpp std::uint8_t`, say — does compile, and JSON booleans still round-trip, but the type then serves as both `boolean_t` and an ordinary integer: `basic_json` can no longer be constructed or assigned from a `#!cpp std::uint8_t` at all (the boolean and unsigned-integer `to_json` overloads become ambiguous), and [`get()`](../../api/basic_json/get.md) on a number throws @@ -505,7 +505,7 @@ Both types are stored **directly** inside `basic_json`'s union. - `#!cpp std::is_integral` must be satisfied: `NumberIntegerType` must be a **signed** integer type, `NumberUnsignedType` an **unsigned** integer type or the same type as `NumberIntegerType` (see - [below](#same-type-for-both)). Class types are not supported -- among others, the constructors taking integer values + [below](#same-type-for-both)). Class types are not supported — among others, the constructors taking integer values are constrained on `#!cpp std::is_integral`. - Trivially default-constructible, trivially copyable, and trivially destructible (union member). - `#!cpp std::numeric_limits` must be specialized for both types. @@ -635,7 +635,7 @@ process deeply nested values without recursion. ### Always required -- The template must accept **two** type arguments. It does not have to give the second one a default -- `basic_json` +- The template must accept **two** type arguments. It does not have to give the second one a default — `basic_json` declares the parameter as `#!cpp template class JSONSerializer`, so uses such as `#!cpp JSONSerializer` inside the library supply `#!cpp void` themselves. The second parameter exists so that partial specializations can be constrained by SFINAE. @@ -668,7 +668,7 @@ using binary_t = nlohmann::byte_container_with_subtype; ### Always required -- A non-`final` class type -- [`byte_container_with_subtype`](../../api/byte_container_with_subtype/index.md) derives +- A non-`final` class type — [`byte_container_with_subtype`](../../api/byte_container_with_subtype/index.md) derives from it publicly. - A member type `value_type` that is **exactly one byte** wide (e.g., `#!cpp std::uint8_t`, `#!cpp char`, or `#!cpp std::byte`). Readers and writers reinterpret the container's storage as raw bytes, so a wider `value_type` is @@ -695,7 +695,7 @@ such a container to a `basic_json` value. !!! tip "Reference implementation" `docs/mkdocs/docs/examples/custom_binary_type.hpp` wraps a private `#!cpp std::vector` and satisfies - every requirement above -- a good starting point for a custom `BinaryType`. + every requirement above — a good starting point for a custom `BinaryType`. ```cpp --8<-- "examples/custom_binary_type.hpp" @@ -731,7 +731,7 @@ such a container to a `basic_json` value. | `QByteArray` | no `empty()` (it spells that `isEmpty()`); its `insert` takes an index rather than an iterator; and it converts to `string_t`, which makes `to_json` ambiguous between a string and a binary value | | `#!cpp std::string` | `binary_t::container_type` and `string_t` would be the same type, so the two [`swap`](../../api/basic_json/swap.md) overloads collide and `basic_json` cannot be instantiated at all | | `#!cpp std::deque` | storage is not contiguous, so there is no `data()` | -| containers whose `value_type` is wider than one byte | see above -- accepted by the compiler, wrong at runtime | +| containers whose `value_type` is wider than one byte | see above — accepted by the compiler, wrong at runtime | ## `CustomBaseClass` @@ -778,7 +778,15 @@ See [issue #3425](https://github.com/nlohmann/json/issues/3425), [`string_t`](.. ## See also -- [Types](index.md) -- overview of how JSON values are stored -- [Number Handling](number_handling.md) -- how the number types affect parsing and serialization -- [Object Order](../object_order.md) -- using an insertion-ordered `ObjectType` -- [`basic_json`](../../api/basic_json/index.md) -- API documentation of the class template +- [Types](index.md) - overview of how JSON values are stored +- [Number Handling](number_handling.md) - how the number types affect parsing and serialization +- [Object Order](../object_order.md) - using an insertion-ordered `ObjectType` +- [`basic_json`](../../api/basic_json/index.md) - API documentation of the class template +- [`object_t`](../../api/basic_json/object_t.md) - the `ObjectType` in use +- [`array_t`](../../api/basic_json/array_t.md) - the `ArrayType` in use +- [`string_t`](../../api/basic_json/string_t.md) - the `StringType` in use +- [`json_serializer`](../../api/basic_json/json_serializer.md) - the `JSONSerializer` in use +- [`json_base_class_t`](../../api/basic_json/json_base_class_t.md) - the `CustomBaseClass` in use +- [`with_t`](../../api/basic_json/with_t.md) - derive a specialization with one parameter replaced +- [`get_allocator`](../../api/basic_json/get_allocator.md) - the allocator in use + diff --git a/docs/mkdocs/docs/home/architecture.md b/docs/mkdocs/docs/home/architecture.md index 295ea09ba..82a660e3a 100644 --- a/docs/mkdocs/docs/home/architecture.md +++ b/docs/mkdocs/docs/home/architecture.md @@ -32,6 +32,8 @@ flowchart LR oa --> output ``` +

Data flow through the library: input runs left to right through an input adapter and the lexer and parser (JSON text) or the `binary_reader` (binary formats) into SAX events, which build the value tree; the `serializer` or the `binary_writer` writes the tree to an output adapter.

+ - **JSON text** is read by an [input adapter](#input-adapters), tokenized by the lexer, and turned into SAX events by the parser. - **Binary formats** (BJData, BON8, BSON, CBOR, MessagePack, UBJSON) are read by an input adapter and turned into the same SAX @@ -192,6 +194,8 @@ packet-beta 96-127: "next" ``` +

Layout of one 16-byte node of a JSON view index. Each row is 32 bits wide, and the numbers are the bit offsets of the fields `kind`, `flags`, `extra`, `off`, `len`, and `next`.

+ | Bytes | Field | Type | Contents | |-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------| | 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below). There is no node for a discarded value: a [discarded](../api/basic_json_view/is_discarded.md) view has none | @@ -201,6 +205,8 @@ packet-beta | 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements | | 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself | +

Fields of a node, by their byte offset within the 16-byte node. Integers use bytes 8-15 differently, see below.

+ - **Integers** keep their converted value in bytes 8-15 instead of `len` and `next`; the length of their token follows from the number of digits in `extra` (and the sign). Non-negative integers are unsigned integers, as with [`parse`](../api/basic_json/parse.md). @@ -234,6 +240,8 @@ flowchart LR n2 -. next .-> e ``` +

The five nodes of `#!json {"a": [1, 2.5]}` in document order. Solid arrows lead from each node to the one after it; dotted arrows show where `next` leads from the object and the array, past their subtrees.

+ | Node | `kind` | `extra` | `off` | `len` | `next` | |------|----------------------|---------|-------|-------|--------| | 0 | 1 (object) | 0 | 0 | 1 | 5 | @@ -242,6 +250,8 @@ flowchart LR | 3 | 6 (unsigned integer) | 0x0001 | 7 | - | - | | 4 | 7 (float) | 0x0101 | 10 | 3 | 0 | +

Fields of the five nodes of `#!json {"a": [1, 2.5]}`. `off` is the byte offset in the text, and - marks the bytes 8-15 that hold the integer's value instead of `len` and `next`.

+ All `flags` are 0. The integer's bytes 8-15 hold its value, 1; its `extra` says it has one digit. The float's `extra` says it has one integer and one fraction digit, and its `len` is that of the token `2.5`. @@ -287,8 +297,9 @@ template std::size_t get_elements(T* dest, std::size_t count = 1); ``` -The lexer detects two optional extensions at compile time. Only `iterator_input_adapter` provides them, and only for -random-access input of single-byte characters: +The lexer detects two optional extensions at compile time. `iterator_input_adapter` provides both, and only for +random-access input of single-byte characters. `input_stream_adapter` provides the second one (as a window onto the +streambuf's buffer) unless `JSON_DISABLE_STREAM_BULK_SCAN` is defined: - `supports_seek`, `get_consumed_count()`, and `copy_consumed_range()` let the lexer reconstruct already consumed input for error messages instead of copying every character it reads. With `get_read_count()` and @@ -306,6 +317,40 @@ The function `input_adapter` picks the right adapter for the argument passed to - `input_stream_adapter` reads from a `std::istream`. - `file_input_adapter` reads from a `std::FILE*`. +The adapters have no common base class: the lexer and the parser are templates on the adapter type, and +`wide_string_input_adapter` wraps an `iterator_input_adapter`: + +```mermaid +classDiagram + class input_adapter ["input adapter"] { + <> + +char_type + +get_character() int_type + +get_elements(T* dest, size_t count) size_t + } + class iterator_input_adapter { + -IteratorType current + -SentinelType end + } + class wide_string_input_adapter { + -BaseInputAdapter base_adapter + } + class input_stream_adapter { + -std::istream* is + -std::streambuf* sb + } + class file_input_adapter { + -std::FILE* m_file + } + input_adapter <|.. iterator_input_adapter + input_adapter <|.. wide_string_input_adapter + input_adapter <|.. input_stream_adapter + input_adapter <|.. file_input_adapter + wide_string_input_adapter o-- iterator_input_adapter : converts to UTF-8 +``` + +

The input adapters as a class diagram. The dotted arrows mean that each adapter implements the input adapter concept; the diamond shows that `wide_string_input_adapter` holds an `iterator_input_adapter`.

+ ## SAX interface The parser does not build values itself. It reports what it reads as events to a [SAX](../features/parsing/sax_interface.md) @@ -321,6 +366,7 @@ The library comes with two consumers in `detail/input/json_sax.hpp`: The `binary_reader` emits the same events for binary formats, so [`sax_parse`](../api/basic_json/sax_parse.md) works with a user-defined consumer for JSON and for all binary formats alike. +The [SAX event stepper](../features/parsing/sax_interface.md#sax-event-stepper) shows the events for a small input. ## Output adapters @@ -339,6 +385,36 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`] - `output_stream_adapter` writes to a `std::ostream`. - `output_string_adapter` appends to a string. +They implement the abstract class `output_adapter_protocol`. The class `output_adapter` creates the one that matches +its argument and holds it in a `std::shared_ptr` (`output_adapter_t`): + +```mermaid +classDiagram + class output_adapter_protocol~CharType~ { + <> + +write_character(CharType c)* + +write_characters(const CharType* s, size_t length)* + } + class output_vector_adapter { + -std::vector& v + } + class output_stream_adapter { + -std::basic_ostream& stream + } + class output_string_adapter { + -StringType& str + } + class output_adapter { + -output_adapter_t oa + } + output_adapter_protocol <|-- output_vector_adapter + output_adapter_protocol <|-- output_stream_adapter + output_adapter_protocol <|-- output_string_adapter + output_adapter o-- output_adapter_protocol : shared_ptr +``` + +

The output adapters as a class diagram. The three adapters derive from `output_adapter_protocol` (solid arrows), and `output_adapter` holds one of them through a `shared_ptr` (diamond).

+ The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the @@ -377,3 +453,14 @@ release. Besides the components above, it contains: - type traits to detect the capabilities of user-defined types (`detail/meta/type_traits.hpp`), - backports of C++14/17 features to C++11 (`detail/meta/cpp_future.hpp`), and - helpers such as `string_concat` and `string_escape`. + +## See also + +- [`basic_json`](../api/basic_json/index.md) - the main class and all its member functions +- [`adl_serializer`](../api/adl_serializer/index.md) - customization point for type conversions +- [`json_sax`](../api/json_sax/index.md) - interface of the SAX parser events +- [SAX Interface](../features/parsing/sax_interface.md) - how to consume parser events without building a DOM +- [Zero-copy JSON views](../features/json_view.md) - the flat node index that backs views and documents +- [Template Parameter Requirements](../features/types/template_parameters.md) - the template parameters of basic_json +- [Serialization](../features/serialization.md) - the output side of the library +- [`nlohmann` Namespace](../features/namespace.md) - how the library namespace is structured diff --git a/docs/mkdocs/docs/home/debugging.md b/docs/mkdocs/docs/home/debugging.md index 8e8cafba5..d4009dcd0 100644 --- a/docs/mkdocs/docs/home/debugging.md +++ b/docs/mkdocs/docs/home/debugging.md @@ -50,5 +50,12 @@ Defining [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) before includin `type_error`/`out_of_range`-style exceptions with a JSON Pointer to the offending value, which can help pinpoint where in a large document a runtime error occurred. This only applies to exceptions thrown *after* a value exists (e.g. during element access); parse errors, which happen before any value exists to point at, are not -covered by this mechanism -- see [Parsing and exceptions](../features/parsing/parse_exceptions.md) for how parse +covered by this mechanism — see [Parsing and exceptions](../features/parsing/parse_exceptions.md) for how parse errors report their own location instead. + +## See also + +- [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) - extended exception messages with the path of the failing value +- [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) - record byte positions of values for error messages +- [Exceptions](exceptions.md) - all exceptions and their error codes +- [`exception`](../api/basic_json/exception.md) - base class of all exceptions thrown by the library diff --git a/docs/mkdocs/docs/home/design_goals.md b/docs/mkdocs/docs/home/design_goals.md index 987124378..ffd07c43c 100644 --- a/docs/mkdocs/docs/home/design_goals.md +++ b/docs/mkdocs/docs/home/design_goals.md @@ -15,3 +15,10 @@ Other aspects were not so important to us: - **Speed**. There are certainly [faster JSON libraries](https://github.com/miloyip/nativejson-benchmark#parsing-time) out there. However, if your goal is to speed up your development by adding JSON support with a single header, then this library is the way to go. If you know how to use a `std::vector` or `std::map`, you are already set. See the [contribution guidelines](https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#please-dont) for more information. + +## See also + +- [`basic_json`](../api/basic_json/index.md) - the main class and all its member functions +- [Macros](../api/macros/index.md) - reference of all macros +- [Performance](../features/performance.md) - how the library performs and what is not optimized +- [Architecture](architecture.md) - how the library is put together diff --git a/docs/mkdocs/docs/home/exceptions.md b/docs/mkdocs/docs/home/exceptions.md index aa2afe6ee..8d4473cb0 100644 --- a/docs/mkdocs/docs/home/exceptions.md +++ b/docs/mkdocs/docs/home/exceptions.md @@ -6,35 +6,35 @@ All exceptions inherit from class `json::exception` (which in turn inherits from `std::exception`). It is used as the base class for all exceptions thrown by the `basic_json` class. This class can hence be used as "wildcard" to catch exceptions. -``` mermaid -classDiagram - direction LR - class `std::exception` { - <> - } +The map shows the class tree and every exception id documented on this page; click an id to jump to its section. - class `json::exception` { - +const int id - +const char* what() const - } +
+--8<-- "images/exception_map.svg" +
- class `json::parse_error` { - +const std::size_t byte - } +

The exception classes from `std::exception` down to the five families, each with the ids it can throw. Every id chip links to the section that describes it.

- class `json::invalid_iterator` - class `json::type_error` - class `json::out_of_range` - class `json::other_error` +Which exception an element access throws depends on the function and on the type of the value: - `std::exception` <|-- `json::exception` - `json::exception` <|-- `json::parse_error` - `json::exception` <|-- `json::invalid_iterator` - `json::exception` <|-- `json::type_error` - `json::exception` <|-- `json::out_of_range` - `json::exception` <|-- `json::other_error` +```mermaid +flowchart LR + S{{"which call?"}} -->|"at(key)"| A{{"j is"}} + A -->|"object, key missing"| A1["out_of_range.403"] + A -->|"array or null"| A2["type_error.304"] + S -->|"at(index)"| B{{"j is"}} + B -->|"array, index >= size()"| B1["out_of_range.401"] + B -->|"object or null"| B2["type_error.304"] + S -->|"non-const operator[](key)"| C{{"j is"}} + C -->|"object or null"| C1["no exception: a missing key is inserted as null"] + C -->|"array"| C2["type_error.305"] + S -->|"value(key, default)"| D{{"j is"}} + D -->|"object, key missing"| D1["no exception: returns the default"] + D -->|"object, incompatible value type"| D2["type_error.302"] + D -->|"array or null"| D3["type_error.306"] ``` +

Exceptions thrown by element access. Start at the call on the left, follow the edge that matches the type of `j`, and read the exception id (or "no exception") in the box at the end.

+ ### Switch off exceptions Exceptions are used widely within the library. They can, however, be switched off with either using the compiler flag `-fno-exceptions` or by defining the symbol [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md). In this case, exceptions are replaced by `abort()` calls. You can further control this behavior by defining `JSON_THROW_USER` (overriding `#!cpp throw`), `JSON_TRY_USER` (overriding `#!cpp try`), and `JSON_CATCH_USER` (overriding `#!cpp catch`). @@ -230,7 +230,7 @@ JSON uses the `\uxxxx` format to describe Unicode characters. Code points above !!! failure "Example message" ``` - parse error at 14: missing or wrong low surrogate + [json.exception.parse_error.102] parse error at 14: missing or wrong low surrogate ``` !!! note @@ -244,7 +244,7 @@ Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are inva !!! failure "Example message" ``` - parse error: code points above 0x10FFFF are invalid + [json.exception.parse_error.103] parse error: code points above 0x10FFFF are invalid ``` !!! note @@ -947,7 +947,7 @@ The provided array index `i` is larger than `size-1`. !!! failure "Example message" ``` - array index 3 is out of range + [json.exception.out_of_range.401] array index 3 is out of range ``` ### json.exception.out_of_range.402 @@ -957,7 +957,7 @@ The special array index `-` in a JSON Pointer never describes a valid element of !!! failure "Example message" ``` - array index '-' (3) is out of range + [json.exception.out_of_range.402] array index '-' (3) is out of range ``` ### json.exception.out_of_range.403 @@ -967,7 +967,7 @@ The provided key was not found in the JSON object. !!! failure "Example message" ``` - key 'foo' not found + [json.exception.out_of_range.403] key 'foo' not found ``` ### json.exception.out_of_range.404 @@ -978,7 +978,7 @@ contains other characters (e.g., `1a`), or `-` where it cannot be used. !!! failure "Example message" ``` - unresolved reference token 'foo' + [json.exception.out_of_range.404] unresolved reference token 'foo' ``` ### json.exception.out_of_range.405 @@ -990,7 +990,7 @@ also thrown when the parent or a reference token of an empty JSON Pointer is req !!! failure "Example message" ``` - JSON pointer has no parent + [json.exception.out_of_range.405] JSON pointer has no parent ``` ### json.exception.out_of_range.406 @@ -1002,7 +1002,7 @@ double-precision number when `number_float_t` is `#!cpp float`. !!! failure "Example messages" ``` - number overflow parsing '10E1000' + [json.exception.out_of_range.406] number overflow parsing '10E1000' ``` ``` [json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow @@ -1038,10 +1038,10 @@ exception, because they serialize integers beyond 64 bits as high-precision numb !!! failure "Example messages" ``` - integer number 18446744073709551615 cannot be represented by BON8 as it does not fit int64 + [json.exception.out_of_range.407] integer number 18446744073709551615 cannot be represented by BON8 as it does not fit int64 ``` ``` - integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1] + [json.exception.out_of_range.407] integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1] ``` ### json.exception.out_of_range.408 @@ -1059,7 +1059,7 @@ count and one marker per element are kept), so any value it produces can still b !!! failure "Example messages" ``` - excessive array size: 8658170730974374167 + [json.exception.out_of_range.408] excessive array size: 8658170730974374167 ``` ``` [json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size @@ -1078,7 +1078,7 @@ Key identifiers to be serialized to BSON cannot contain code point U+0000, since !!! failure "Example message" ``` - BSON key cannot contain code point U+0000 (at byte 2) + [json.exception.out_of_range.409] BSON key cannot contain code point U+0000 (at byte 2) ``` ### json.exception.out_of_range.410 @@ -1090,8 +1090,8 @@ a JSON pointer exceeds the range of `size_type` (e.g., on 32-bit platforms). !!! failure "Example message" ``` - enum value out of range - array index 18446744073709551615 exceeds size_type + [json.exception.out_of_range.410] enum value out of range + [json.exception.out_of_range.410] array index 18446744073709551615 exceeds size_type ``` The second message is thrown, for instance, by `#!cpp json::parse("[1, 2]").at("/18446744073709551615"_json_pointer)` @@ -1106,7 +1106,7 @@ A JSON Patch `add` operation cannot be applied because the target location's par !!! failure "Example message" ``` - cannot add value: the JSON Patch 'add' target's parent is of type string, but must be an object or array + [json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type string, but must be an object or array ``` !!! note @@ -1124,10 +1124,10 @@ is thrown when a value is too large to be described by such a length field. !!! failure "Example messages" ``` - BSON length 2147483661 exceeds maximum of 2147483647 + [json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647 ``` ``` - MessagePack length 4294967296 exceeds maximum of 4294967295 + [json.exception.out_of_range.412] MessagePack length 4294967296 exceeds maximum of 4294967295 ``` !!! note @@ -1144,7 +1144,7 @@ A JSON Patch `remove` operation cannot be applied because the target location's !!! failure "Example message" ``` - cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array + [json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array ``` !!! note @@ -1158,7 +1158,7 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa !!! failure "Example message" ``` - cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0' + [json.exception.out_of_range.414] cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0' ``` !!! note @@ -1262,3 +1262,15 @@ do not throw. This exception was added in version 3.13.0. Before that, debug builds aborted on an assertion and release builds wrote a `$` marker without `#`, which [`from_ubjson`](../api/basic_json/from_ubjson.md) then rejected. + +## See also + +- [`exception`](../api/basic_json/exception.md) - base class of all exceptions thrown by the library +- [`parse_error`](../api/basic_json/parse_error.md) - exceptions indicating a parse error +- [`invalid_iterator`](../api/basic_json/invalid_iterator.md) - exceptions indicating errors with iterators +- [`type_error`](../api/basic_json/type_error.md) - exceptions indicating a wrong type +- [`out_of_range`](../api/basic_json/out_of_range.md) - exceptions indicating access out of range +- [`other_error`](../api/basic_json/other_error.md) - exceptions indicating other library errors +- [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md) - switch off exceptions +- [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) - extended exception messages with the path of the failing value +- [Working Without Exceptions](../features/no_exceptions.md) - use the library without exceptions diff --git a/docs/mkdocs/docs/home/faq.md b/docs/mkdocs/docs/home/faq.md index d52bdc006..c43acef37 100644 --- a/docs/mkdocs/docs/home/faq.md +++ b/docs/mkdocs/docs/home/faq.md @@ -20,7 +20,7 @@ yield different results (`#!json [true]` vs. `#!json true`)? -This is a known issue, and -- even worse -- the behavior differs between GCC and Clang. The "culprit" for this is the library's constructor overloads for initializer lists to allow syntax like +This is a known issue, and — even worse — the behavior differs between GCC and Clang. The "culprit" for this is the library's constructor overloads for initializer lists to allow syntax like ```cpp json array = {1, 2, 3, 4}; @@ -192,6 +192,8 @@ The value is a `json`, and printing it produces JSON text. Convert it first: `#! Use `contains` or `find`. Do not test with the non-const `operator[]`: like `std::map::operator[]`, it inserts a `null` value for a missing key. See [Does this key exist?](../features/element_access/index.md#does-this-key-exist). +For choosing between `value`, `contains`/`find`, `at`, `operator[]`, and JSON Pointer, see the decision flow in +[Element access](../features/element_access/index.md). ### Reading a value with a fallback @@ -392,8 +394,8 @@ As described [above](#parse-errors-reading-non-ascii-characters), the library st No. `basic_json` provides no built-in synchronization, the same as `std::map` or `std::vector`. Concurrent reads of the same value from multiple threads are safe, as are concurrent (non-overlapping) accesses to independent `json` -objects. However, any concurrent write to a `json` object -- or a concurrent read while another thread writes to the -same object -- is a data race and requires external synchronization (e.g., a `std::mutex`) by the caller. +objects. However, any concurrent write to a `json` object — or a concurrent read while another thread writes to the +same object — is a data race and requires external synchronization (e.g., a `std::mutex`) by the caller. ### Schema validation @@ -596,3 +598,10 @@ well. See [#3669](https://github.com/nlohmann/json/issues/3669) for details. This is not an issue with the code, but rather with the compiler itself. On Android, use a current NDK (see above). For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. + +## See also + +- [`basic_json`](../api/basic_json/index.md) - the main class and all its member functions +- [Macros](../api/macros/index.md) - reference of all macros +- [Exceptions](exceptions.md) - all exceptions and their error codes +- [Features](../features/index.md) - overview of all features diff --git a/docs/mkdocs/docs/home/releases/index.md b/docs/mkdocs/docs/home/releases/index.md index e0dbca2d5..83ab024e1 100644 --- a/docs/mkdocs/docs/home/releases/index.md +++ b/docs/mkdocs/docs/home/releases/index.md @@ -14,6 +14,14 @@ changed in it. The API overview is generated from the "Version history" section [API pages](../../api/basic_json/index.md); it is not a change log, but helps to see whether a feature is available in the version you use. +All releases at a glance (hover a marker for its version and date): + +
+--8<-- "images/release_timeline.svg" +
+ +

From the first commit (diamond) in 2013 to the latest release, on a time axis by year. Filled dots with a version label are minor or major releases, rings are patch releases.

+ ## Unreleased | Release | Date | Summary | @@ -24,124 +32,124 @@ the version you use. | Release | Date | Summary | |---------|------|---------| -| [v3.12.0](v3.12.0.md) | 2025-04-11 | Fixes bugs found in 3.11.3 and adds several features. No public API was removed or renamed. | +| [v3.12.0](v3.12.0.md) | 2025-04-11 | Fixes bugs found in 3.11.3 and adds several features. No public API was removed or renamed. **Highlights:** diagnostic byte positions (`start_pos`/`end_pos`); templated conversion macros and `NLOHMANN_DEFINE_DERIVED_TYPE`; `std::optional` support. | ## Version 3.11.x | Release | Date | Summary | |---------|------|---------| -| [v3.11.3](v3.11.3.md) | 2023-11-28 | Adds features and fixes bugs found in 3.11.2. All changes are backward-compatible. | -| [v3.11.2](v3.11.2.md) | 2022-08-12 | Fixes bugs found in 3.11.1 and restructures the namespace. All changes are backward-compatible. | -| [v3.11.1](v3.11.1.md) | 2022-08-01 | Fixes a regression from 3.11.0. All changes are backward-compatible. | -| [v3.11.0](v3.11.0.md) | 2022-08-01 | One of the largest releases ever. All changes are backward-compatible. | +| [v3.11.3](v3.11.3.md) | 2023-11-28 | Adds features and fixes bugs found in 3.11.2. All changes are backward-compatible. **Highlights:** custom base class for `basic_json`; serialization-only conversion macros; Bazel and Swift Package Manager support. | +| [v3.11.2](v3.11.2.md) | 2022-08-12 | Fixes bugs found in 3.11.1 and restructures the namespace. All changes are backward-compatible. **Highlights:** fixes `value` for strings and size types; restores `json_pointer` as associative-container key; restructured inline namespace. | +| [v3.11.1](v3.11.1.md) | 2022-08-01 | Fixes a regression from 3.11.0. All changes are backward-compatible. **Highlights:** restores the global `_json` and `_json_pointer` literals. | +| [v3.11.0](v3.11.0.md) | 2022-08-01 | One of the largest releases ever. All changes are backward-compatible. **Highlights:** `std::string_view` keys; BJData binary format; C++20 support including `operator<=>`. | ## Version 3.10.x | Release | Date | Summary | |---------|------|---------| -| [v3.10.5](v3.10.5.md) | 2022-01-03 | Bug-fix release. All changes are backward-compatible. | -| [v3.10.4](v3.10.4.md) | 2021-10-16 | Fixes regressions introduced in 3.10.0. All changes are backward-compatible. | -| [v3.10.3](v3.10.3.md) | 2021-10-08 | Fixes more regressions from 3.10.0. All changes are backward-compatible. | -| [v3.10.2](v3.10.2.md) | 2021-08-26 | Re-release of 3.10.1, whose Git tag pointed at the wrong commit due to a bug in the release script. All changes are backward-compatible. | -| [v3.10.1](v3.10.1.md) | 2021-08-24 | Fixes a regression from 3.10.0. All changes are backward-compatible. | -| [v3.10.0](v3.10.0.md) | 2021-08-17 | Feature release. All changes are backward-compatible. | +| [v3.10.5](v3.10.5.md) | 2022-01-03 | Bug-fix release. All changes are backward-compatible. **Highlights:** guards the `std::filesystem` conversions with `JSON_HAS_FILESYSTEM`. | +| [v3.10.4](v3.10.4.md) | 2021-10-16 | Fixes regressions introduced in 3.10.0. All changes are backward-compatible. **Highlights:** fixes the `std::filesystem::path` conversion; fixes types with explicit defaulted constructors; fixes `std::find`/`std::remove` return values. | +| [v3.10.3](v3.10.3.md) | 2021-10-08 | Fixes more regressions from 3.10.0. All changes are backward-compatible. **Highlights:** fixes extended-diagnostics assertions in `update` and array insertion; custom allocators for binary output; conversion from `begin()`/`end()`-only types. | +| [v3.10.2](v3.10.2.md) | 2021-08-26 | Re-release of 3.10.1, whose Git tag pointed at the wrong commit due to a bug in the release script. All changes are backward-compatible. **Highlights:** re-release of 3.10.1 with the correct Git tag. | +| [v3.10.1](v3.10.1.md) | 2021-08-24 | Fixes a regression from 3.10.0. All changes are backward-compatible. **Highlights:** fixes an extended-diagnostics assertion with `ordered_json`; hardens the GDB pretty-printer. | +| [v3.10.0](v3.10.0.md) | 2021-08-17 | Feature release. All changes are backward-compatible. **Highlights:** extended diagnostic messages (`JSON_DIAGNOSTICS`); GDB pretty-printer; CBOR tag storage. | ## Version 3.9.x | Release | Date | Summary | |---------|------|---------| -| [v3.9.1](v3.9.1.md) | 2020-08-06 | Fixes two regressions from 3.9.0. All changes are backward-compatible. | -| [v3.9.0](v3.9.0.md) | 2020-07-27 | Feature release adding four long-requested features. All changes are backward-compatible. | +| [v3.9.1](v3.9.1.md) | 2020-08-06 | Fixes two regressions from 3.9.0. All changes are backward-compatible. **Highlights:** accepts consecutive comments; completes the `ordered_json` interface. | +| [v3.9.0](v3.9.0.md) | 2020-07-27 | Feature release adding four long-requested features. All changes are backward-compatible. **Highlights:** comment parsing; `ordered_json`; switch to disable implicit conversions. | ## Version 3.8.x | Release | Date | Summary | |---------|------|---------| -| [v3.8.0](v3.8.0.md) | 2020-06-14 | Feature release. All changes are backward-compatible. | +| [v3.8.0](v3.8.0.md) | 2020-06-14 | Feature release. All changes are backward-compatible. **Highlights:** binary value type for CBOR, BSON, and MessagePack; input adapters for any `LegacyInputIterator` container; fixes `contains` for JSON pointers. | ## Version 3.7.x | Release | Date | Summary | |---------|------|---------| -| [v3.7.3](v3.7.3.md) | 2019-11-17 | Fixes a regression from 3.7.2 that could yield quadratic complexity in destructor calls. All changes are backward-compatible. | -| [v3.7.2](v3.7.2.md) | 2019-11-10 | Fixes a stack overflow for deeply nested input by making the destructor iterative; parsing of JSON text is now bounded only by available memory (the binary-format parsers still recurse). All changes are backward-compatible. | -| [v3.7.1](v3.7.1.md) | 2019-11-06 | Bug-fix release. All changes are backward-compatible. | -| [v3.7.0](v3.7.0.md) | 2019-07-28 | Convenience features and house-keeping. All changes are backward-compatible. | +| [v3.7.3](v3.7.3.md) | 2019-11-17 | Fixes a regression from 3.7.2 that could yield quadratic complexity in destructor calls. All changes are backward-compatible. **Highlights:** fixes quadratic complexity in destructor calls. | +| [v3.7.2](v3.7.2.md) | 2019-11-10 | Fixes a stack overflow for deeply nested input by making the destructor iterative; parsing of JSON text is now bounded only by available memory (the binary-format parsers still recurse). All changes are backward-compatible. **Highlights:** iterative destructor fixes a stack overflow on deeply nested input. | +| [v3.7.1](v3.7.1.md) | 2019-11-06 | Bug-fix release. All changes are backward-compatible. **Highlights:** fixes a crash when serializing the minimum `std::int64_t`; fixes `contains` for JSON pointers; `items` with custom string types. | +| [v3.7.0](v3.7.0.md) | 2019-07-28 | Convenience features and house-keeping. All changes are backward-compatible. **Highlights:** `contains` overload for JSON pointers; generic `to_string`; return value for `emplace_back`. | ## Version 3.6.x | Release | Date | Summary | |---------|------|---------| -| [v3.6.1](v3.6.1.md) | 2019-03-20 | Fixes a regression (GCC 7/8 compilation) and a `` build error introduced in 3.6.0. All changes are backward-compatible. | -| [v3.6.0](v3.6.0.md) | 2019-03-20 | Feature release. All changes are backward-compatible. | +| [v3.6.1](v3.6.1.md) | 2019-03-20 | Fixes a regression (GCC 7/8 compilation) and a `` build error introduced in 3.6.0. All changes are backward-compatible. **Highlights:** fixes a GCC 7/8 compilation regression; fixes a `` build error. | +| [v3.6.0](v3.6.0.md) | 2019-03-20 | Feature release. All changes are backward-compatible. **Highlights:** reworked JSON pointer interface; `contains` function; faster integer serialization. | ## Version 3.5.x | Release | Date | Summary | |---------|------|---------| -| [v3.5.0](v3.5.0.md) | 2018-12-22 | Feature release. All changes are backward-compatible. | +| [v3.5.0](v3.5.0.md) | 2018-12-22 | Feature release. All changes are backward-compatible. **Highlights:** structured bindings via `items`; reading from `FILE*`; fixes `eofbit` handling on streams. | ## Version 3.4.x | Release | Date | Summary | |---------|------|---------| -| [v3.4.0](v3.4.0.md) | 2018-10-30 | Feature release. All changes are backward-compatible. | +| [v3.4.0](v3.4.0.md) | 2018-10-30 | Feature release. All changes are backward-compatible. **Highlights:** BSON support; Unicode error handlers for `dump`; `NLOHMANN_JSON_SERIALIZE_ENUM`. | ## Version 3.3.x | Release | Date | Summary | |---------|------|---------| -| [v3.3.0](v3.3.0.md) | 2018-10-05 | Feature release. All changes are backward-compatible. | +| [v3.3.0](v3.3.0.md) | 2018-10-05 | Feature release. All changes are backward-compatible. **Highlights:** GCC 4.8 support; `get_to`; overhauled CMake integration. | ## Version 3.2.x | Release | Date | Summary | |---------|------|---------| -| [v3.2.0](v3.2.0.md) | 2018-08-20 | Feature release. All changes are backward-compatible. | +| [v3.2.0](v3.2.0.md) | 2018-08-20 | Feature release. All changes are backward-compatible. **Highlights:** SAX interface and non-recursive parser; parsing from wide strings; conversion to `std::string_view`. | ## Version 3.1.x | Release | Date | Summary | |---------|------|---------| -| [v3.1.2](v3.1.2.md) | 2018-03-14 | Bug-fix release. All changes are backward-compatible. | -| [v3.1.1](v3.1.1.md) | 2018-02-13 | Bug-fix release. All changes are backward-compatible. | -| [v3.1.0](v3.1.0.md) | 2018-02-01 | Feature release. All changes are backward-compatible. | +| [v3.1.2](v3.1.2.md) | 2018-03-14 | Bug-fix release. All changes are backward-compatible. **Highlights:** fixes a memory leak in the parser callback; user-defined string types in parser and serializer. | +| [v3.1.1](v3.1.1.md) | 2018-02-13 | Bug-fix release. All changes are backward-compatible. **Highlights:** fixes parsing of indefinite-length CBOR strings; fixes a user-defined conversion to vector types; fixes UBJSON overflow detection. | +| [v3.1.0](v3.1.0.md) | 2018-02-01 | Feature release. All changes are backward-compatible. **Highlights:** UBJSON support; JSON Merge Patch; Grisu2 floating-point output. | ## Version 3.0.x | Release | Date | Summary | |---------|------|---------| -| [v3.0.1](v3.0.1.md) | 2017-12-29 | Fixes small issues in the [JSON Pointer](../../features/json_pointer.md) and [JSON Patch](../../features/json_patch.md) implementations (invalid "copy" targets and non-integer array indices). All changes are backward-compatible. | -| [v3.0.0](v3.0.0.md) | 2017-12-17 | First 3.x release — a major release with breaking changes (see the [migration guide](../../integration/migration_guide.md)). | +| [v3.0.1](v3.0.1.md) | 2017-12-29 | Fixes small issues in the [JSON Pointer](../../features/json_pointer.md) and [JSON Patch](../../features/json_patch.md) implementations (invalid "copy" targets and non-integer array indices). All changes are backward-compatible. **Highlights:** fixes invalid JSON Patch "copy" targets; fixes non-integer array indices in JSON Pointer. | +| [v3.0.0](v3.0.0.md) | 2017-12-17 | First 3.x release — a major release with breaking changes (see the [migration guide](../../integration/migration_guide.md)). **Highlights:** user-defined exceptions with identifiers; non-throwing `accept` and `allow_exceptions`; `update` to merge objects. | ## Version 2.1.x | Release | Date | Summary | |---------|------|---------| -| [v2.1.1](v2.1.1.md) | 2017-02-25 | Bug-fix release. All changes are backward-compatible. | -| [v2.1.0](v2.1.0.md) | 2017-01-28 | Feature release. All changes are backward-compatible. | +| [v2.1.1](v2.1.1.md) | 2017-02-25 | Bug-fix release. All changes are backward-compatible. **Highlights:** locale-independent number parsing and serialization; correct floating-point round-tripping; GPG-signed commits and files. | +| [v2.1.0](v2.1.0.md) | 2017-01-28 | Feature release. All changes are backward-compatible. **Highlights:** conversions from and to arbitrary user-defined types; `meta` function; `JSON_NOEXCEPTION`. | ## Version 2.0.x | Release | Date | Summary | |---------|------|---------| -| [v2.0.10](v2.0.10.md) | 2017-01-02 | Fixes several security-relevant bugs in the CBOR and MessagePack parsers found by OSS-Fuzz. All changes are backward-compatible. | -| [v2.0.9](v2.0.9.md) | 2016-12-16 | Adds the [CBOR](../../features/binary_formats/cbor.md) and [MessagePack](../../features/binary_formats/messagepack.md) binary formats. All changes are backward-compatible. | -| [v2.0.8](v2.0.8.md) | 2016-12-02 | Adds the [`emplace`](../../api/basic_json/emplace.md) and [`emplace_back`](../../api/basic_json/emplace_back.md) functions and improves parsing and serialization performance. All changes are backward-compatible. | -| [v2.0.7](v2.0.7.md) | 2016-11-02 | Fixes several parser bugs found through the "Parsing JSON is a Minefield" study (short files, encoding detection, surrogate pairs). All changes are backward-compatible. | -| [v2.0.6](v2.0.6.md) | 2016-10-15 | Fixes [`operator[]`](../../api/basic_json/operator%5B%5D.md) for [JSON pointers](../../features/json_pointer.md) so that it creates missing values like the other overloads. All changes are backward-compatible. | -| [v2.0.5](v2.0.5.md) | 2016-09-14 | Fixes a remaining stream end-of-file detection bug in the parser. All changes are backward-compatible. | -| [v2.0.4](v2.0.4.md) | 2016-09-11 | Fixes stream end-of-file detection in the parser. All changes are backward-compatible. | -| [v2.0.3](v2.0.3.md) | 2016-08-31 | Generalizes the parser to accept any contiguous sequence of one-byte elements and deprecates the input-stream constructor in favor of the [`parse`](../../api/basic_json/parse.md) function. All changes are backward-compatible. | -| [v2.0.2](v2.0.2.md) | 2016-07-31 | Overhauls the parser (now rejecting unescaped control characters), tightens the class invariants, and cleans up the code. All changes are backward-compatible. | -| [v2.0.1](v2.0.1.md) | 2016-06-28 | Fixes a performance regression in the [`dump`](../../api/basic_json/dump.md) function by adjusting the stream locale once per serialization. All changes are backward-compatible. | -| [v2.0.0](v2.0.0.md) | 2016-06-24 | Feature release with a minor (potentially non-backward-compatible) API change from added `noexcept` and `constexpr` specifiers. | +| [v2.0.10](v2.0.10.md) | 2017-01-02 | Fixes several security-relevant bugs in the CBOR and MessagePack parsers found by OSS-Fuzz. All changes are backward-compatible. **Highlights:** fixes security-relevant bugs in the CBOR parser; fixes security-relevant bugs in the MessagePack parser. | +| [v2.0.9](v2.0.9.md) | 2016-12-16 | Adds the [CBOR](../../features/binary_formats/cbor.md) and [MessagePack](../../features/binary_formats/messagepack.md) binary formats. All changes are backward-compatible. **Highlights:** CBOR support; MessagePack support. | +| [v2.0.8](v2.0.8.md) | 2016-12-02 | Adds the [`emplace`](../../api/basic_json/emplace.md) and [`emplace_back`](../../api/basic_json/emplace_back.md) functions and improves parsing and serialization performance. All changes are backward-compatible. **Highlights:** `emplace` and `emplace_back`; faster parsing; faster serialization. | +| [v2.0.7](v2.0.7.md) | 2016-11-02 | Fixes several parser bugs found through the "Parsing JSON is a Minefield" study (short files, encoding detection, surrogate pairs). All changes are backward-compatible. **Highlights:** fixes short-file handling; fixes encoding detection; fixes surrogate pairs. | +| [v2.0.6](v2.0.6.md) | 2016-10-15 | Fixes [`operator[]`](../../api/basic_json/operator%5B%5D.md) for [JSON pointers](../../features/json_pointer.md) so that it creates missing values like the other overloads. All changes are backward-compatible. **Highlights:** `operator[]` creates missing values for JSON pointers. | +| [v2.0.5](v2.0.5.md) | 2016-09-14 | Fixes a remaining stream end-of-file detection bug in the parser. All changes are backward-compatible. **Highlights:** fixes a remaining stream end-of-file bug. | +| [v2.0.4](v2.0.4.md) | 2016-09-11 | Fixes stream end-of-file detection in the parser. All changes are backward-compatible. **Highlights:** fixes stream end-of-file detection. | +| [v2.0.3](v2.0.3.md) | 2016-08-31 | Generalizes the parser to accept any contiguous sequence of one-byte elements and deprecates the input-stream constructor in favor of the [`parse`](../../api/basic_json/parse.md) function. All changes are backward-compatible. **Highlights:** parser accepts any contiguous sequence of one-byte elements; deprecates the input-stream constructor in favor of `parse`. | +| [v2.0.2](v2.0.2.md) | 2016-07-31 | Overhauls the parser (now rejecting unescaped control characters), tightens the class invariants, and cleans up the code. All changes are backward-compatible. **Highlights:** overhauled parser rejecting unescaped control characters; tighter class invariants; code cleanup. | +| [v2.0.1](v2.0.1.md) | 2016-06-28 | Fixes a performance regression in the [`dump`](../../api/basic_json/dump.md) function by adjusting the stream locale once per serialization. All changes are backward-compatible. **Highlights:** fixes a performance regression in `dump`. | +| [v2.0.0](v2.0.0.md) | 2016-06-24 | Feature release with a minor (potentially non-backward-compatible) API change from added `noexcept` and `constexpr` specifiers. **Highlights:** JSON Pointer support in `at` and `operator[]`, plus `flatten` and `unflatten`; JSON Patch via `diff` and `patch`; unsigned 64-bit integers. | ## Version 1.1.x | Release | Date | Summary | |---------|------|---------| -| [v1.1.0](v1.1.0.md) | 2016-01-24 | Bug-fix and feature release. All changes are backward-compatible. | +| [v1.1.0](v1.1.0.md) | 2016-01-24 | Bug-fix and feature release. All changes are backward-compatible. **Highlights:** improved floating-point round-tripping; `get_ref`; runtime assertions. | ## Version 1.0.x @@ -153,3 +161,5 @@ the version you use. - [Migration Guide](../../integration/migration_guide.md) — how to future-proof your code for the next major version and replace deprecated functions. +- [Roadmap: Version 4.0](../../community/roadmap.md#version-40) — what the next major version will change and how to + try it today. diff --git a/docs/mkdocs/docs/home/sponsors.md b/docs/mkdocs/docs/home/sponsors.md index ddbfbf3f0..c42b6ce5b 100644 --- a/docs/mkdocs/docs/home/sponsors.md +++ b/docs/mkdocs/docs/home/sponsors.md @@ -1,18 +1,31 @@ # Sponsors -You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nlohmann). +You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nlohmann). Thanks to everyone who +supports the project! -## Priority Sponsor +## Priority Sponsors -- [Martti Laine](https://github.com/codeclown) -- [Paul Harrington](https://github.com/phrrngtn) +- [Mercedes-Benz](https://github.com/mercedes-benz) +- [FastNetMon](https://github.com/FastNetMon) ## Named Sponsors +- [Paul Harrington](https://github.com/phrrngtn) +- [Steve Wagner](https://github.com/ciroque) +- [mccaffers](https://github.com/mccaffers) +- [jambolo](https://github.com/jambolo) + +## Further Sponsors + +- [Grisha Kirilin](https://github.com/GrishaKirilin) +- [zimmerman-dev](https://github.com/zimmerman-dev) +- [WAAutoMaton](https://github.com/WAAutoMaton) +- [F3D](https://github.com/f3d-app) +- [David Alvarez Rosa](https://github.com/david-alvarez-rosa) +- [Alex Kong](https://github.com/alex-lt-kong) +- [Mark Oates](https://github.com/MarkOates) +- [Martti Laine](https://github.com/codeclown) - Michael Hartmann - [Stefan Hagen](https://github.com/sthagen) - [Steve Sperandeo](https://github.com/homer6) - [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe) -- [Steve Wagner](https://github.com/ciroque) - -Thanks everyone! diff --git a/docs/mkdocs/docs/images/binary_sizes_canada.svg b/docs/mkdocs/docs/images/binary_sizes_canada.svg new file mode 100644 index 000000000..659c505ec --- /dev/null +++ b/docs/mkdocs/docs/images/binary_sizes_canada.svg @@ -0,0 +1,30 @@ + +Size of canada.json in each binary format relative to minified J​SON +B​JData 53.2 %; B​ON8 50.5 %; B​SON 85.8 %; C​BOR 50.5 %; MessagePack 50.5 %; U​BJSON 53.2 %. Smallest: B​ON8 and C​BOR and MessagePack. +canada.json + +0 % + +50 % +B​JData +canada.json, B​JData: 53.2 % of minified J​SON +B​ON8 +canada.json, B​ON8: 50.5 % of minified J​SON (smallest) +B​SON +canada.json, B​SON: 85.8 % of minified J​SON +C​BOR +canada.json, C​BOR: 50.5 % of minified J​SON (smallest) +MessagePack +canada.json, MessagePack: 50.5 % of minified J​SON (smallest) +U​BJSON +canada.json, U​BJSON: 53.2 % of minified J​SON + +minified J​SON = 100 % +100 % +53.2 % +50.5 % +85.8 % +50.5 % +50.5 % +53.2 % + diff --git a/docs/mkdocs/docs/images/binary_sizes_citm_catalog.svg b/docs/mkdocs/docs/images/binary_sizes_citm_catalog.svg new file mode 100644 index 000000000..814de63d2 --- /dev/null +++ b/docs/mkdocs/docs/images/binary_sizes_citm_catalog.svg @@ -0,0 +1,30 @@ + +Size of citm_catalog.json in each binary format relative to minified J​SON +B​JData 78.1 %; B​ON8 63.5 %; B​SON 95.8 %; C​BOR 68.4 %; MessagePack 68.5 %; U​BJSON 78.2 %. Smallest: B​ON8. +citm_catalog.json + +0 % + +50 % +B​JData +citm_catalog.json, B​JData: 78.1 % of minified J​SON +B​ON8 +citm_catalog.json, B​ON8: 63.5 % of minified J​SON (smallest) +B​SON +citm_catalog.json, B​SON: 95.8 % of minified J​SON +C​BOR +citm_catalog.json, C​BOR: 68.4 % of minified J​SON +MessagePack +citm_catalog.json, MessagePack: 68.5 % of minified J​SON +U​BJSON +citm_catalog.json, U​BJSON: 78.2 % of minified J​SON + +minified J​SON = 100 % +100 % +78.1 % +63.5 % +95.8 % +68.4 % +68.5 % +78.2 % + diff --git a/docs/mkdocs/docs/images/binary_sizes_jeopardy.svg b/docs/mkdocs/docs/images/binary_sizes_jeopardy.svg new file mode 100644 index 000000000..19ee8bb66 --- /dev/null +++ b/docs/mkdocs/docs/images/binary_sizes_jeopardy.svg @@ -0,0 +1,30 @@ + +Size of jeopardy.json in each binary format relative to minified J​SON +B​JData 96.6 %; B​ON8 87.5 %; B​SON 106.7 %; C​BOR 88 %; MessagePack 87.9 %; U​BJSON 96.6 %. Smallest: B​ON8. +jeopardy.json + +0 % + +50 % +B​JData +jeopardy.json, B​JData: 96.6 % of minified J​SON +B​ON8 +jeopardy.json, B​ON8: 87.5 % of minified J​SON (smallest) +B​SON +jeopardy.json, B​SON: 106.7 % of minified J​SON +C​BOR +jeopardy.json, C​BOR: 88 % of minified J​SON +MessagePack +jeopardy.json, MessagePack: 87.9 % of minified J​SON +U​BJSON +jeopardy.json, U​BJSON: 96.6 % of minified J​SON + +minified J​SON = 100 % +100 % +96.6 % +87.5 % +106.7 % +88.0 % +87.9 % +96.6 % + diff --git a/docs/mkdocs/docs/images/binary_sizes_twitter.svg b/docs/mkdocs/docs/images/binary_sizes_twitter.svg new file mode 100644 index 000000000..490f2cfe8 --- /dev/null +++ b/docs/mkdocs/docs/images/binary_sizes_twitter.svg @@ -0,0 +1,30 @@ + +Size of twitter.json in each binary format relative to minified J​SON +B​JData 91.1 %; B​ON8 83.8 %; B​SON 95.2 %; C​BOR 86.3 %; MessagePack 86 %; U​BJSON 91.3 %. Smallest: B​ON8. +twitter.json + +0 % + +50 % +B​JData +twitter.json, B​JData: 91.1 % of minified J​SON +B​ON8 +twitter.json, B​ON8: 83.8 % of minified J​SON (smallest) +B​SON +twitter.json, B​SON: 95.2 % of minified J​SON +C​BOR +twitter.json, C​BOR: 86.3 % of minified J​SON +MessagePack +twitter.json, MessagePack: 86 % of minified J​SON +U​BJSON +twitter.json, U​BJSON: 91.3 % of minified J​SON + +minified J​SON = 100 % +100 % +91.1 % +83.8 % +95.2 % +86.3 % +86.0 % +91.3 % + diff --git a/docs/mkdocs/docs/images/callback_events.png b/docs/mkdocs/docs/images/callback_events.png deleted file mode 100644 index 09aa2b38355a7227b444b5b6779f2c7ca9fa05d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46039 zcmb4qWmp`|(&*w20fKvQclY2 znVIhD>XMnZs;&-GRhB_RCPW4R0BCZul4<||6!>qt1QGu4b9@>+`u7EEC9Wh608~e# zyqLoM&6AtSswn{g-)H~;P!Is{^cMs=1OVLG0Dxl?06-uG0Kjwp)$&E?Zvm3Ctgb5n zfP($c4FyR1h5wg>u8oF{n~svAfSHp6i>bNOR|^&|2j{=o0DzE}z~7{Ug_|k4mxH~d ztALmAr+;Ax{7wG@X8lC|FBCUB;ZHhBs^k(*Knrqi7A_XHPa??VK5$=c1$S%8(*)6XvNCT&(F`w#=*+L!TcA4+11<8&D4w8(UtPwME*lZ z(!$jYXyfc=1y8f41e-&i?r-YTAg^l&U zY5%P%^bb@(1!!aOm-9dLMc9S@h55h1{)>+g>p#r@7h(Rb>A#?VRTV)NV*T&WCW5Tk zL#qn_hymmz#WlR3P7M)2n*DPk_ZWR(h9|B^@ZX)#a4 zjMw(-&+0xbZpnF_+%I8rI&jtCMO=Ed{N5U-730qL`|h2^ZRWk^-t^Dh#Vsu;Gr}yTa02amX%2uSORJxXRO&pO2hL27K9BltM7MB6%E))1`|fW&__1kq%pO zl;ppA7>Zgis3MF?pMi8y^2sQm>;cCO-Ckus30H5kOqXtri;7MtAGx9Vttk(MmMZ2u z1tMr-vsVJoY66D!59qat)sDQlxJ8%4>_@GjDxFVnT$6vk)Q-ZjVst1WUFLi!gf`aZ ziJditY~4DqE)6hvy>O0Ri!|w`-r8>wFAfLt5f+>BGvV=}7nU=ccrMC~DhE%^$9?e# z9`;zN*7sjQUf6TYv2W$r&vhTXK7AL&8q&lA&aoQ?kOf)KH}`R(c{FhMP;}8be;{EQ zN(@1tx3N)wo?Rsy(jDw@vxf&>Eu5*!jP6$o5|Sv#`_>$-Zh1%Tn5NXOLh+xb@5BfC zuCxPj{8yUK4A|E+++gY&)_BJu6*l6GBWY@Z;!QmsaKX76ku?bCL7f|L3ExwxE` zABFf_4^dyxs*t=`FS*ENxb0;ZVBVqH;D%BW1(A6%S|*`L;eVm}Vn3jegWXU?u^#Zq zA7gmI7J~R*@G)B$0J&#j6aGv7O%>2?j$q?P_e9AHE0}QAd)hy<*;QU_AsG9MkfBUe zXvXb+s2Ib2GI$VLH=q%A7Z0X}(+l~5D+bx1QSiKZ4Rv?()X@Yp#-@60N{Hyeb91N5 z5PYhNUx@Q0S7m(h(5>3#46$0XN`D}Vu`m%_MeyQ}xf&KlY{T;U5L1hiHVJJE$OKT4 z&BWrZ5U)&Ji|t!EkzY%!=Yd)QPB3>+HHNjqozO4Lsu%$J=ri<>AOr^m5JuuAxS z)NpvIAXu2VQ+_Z)aNi(>2xM?T71Kd56gn2phA%sGi>gDfgSnWK0JsLj@^O3XtqLZ%6O zC`9Ig_wt|%PRv7N0S2jg#4OaSQ!pO3?kjQd=XPXN7!+_3;2EmqWl33Qavea5NM#*t z->UasBZ3P<20ss_0KNb$;UaoMu|oU9zEH=wCwF;#qqs!cv?Whuy`Bc-Z#%-qAtW-b zFdpfugw@Sn2%D&wAP<4Z{l!A6Cr;Y#LtZ^h!U8XP^T|dZF~}{%@lkz0JP^k`nhNG; zh!MENXai2+?4f~x;X5G&FM&&Ya#Pou8v3@4vrkZ3sGs>`5~X|lt5gYCU`b1bO^pEg zqGWX9gGGKYml$;b7C1GE+An2rH5{1pfZ@Qdrl)S#Kk6vN04isyZa05<1QV{9^H?_k zzKSQb##EU>%ast|S)Tkc6hsLk6anq2MD5VB4PV0#j&lU;E!tT7+nd-k*>eRUIwPqT zRVsOTSH=HrPp%9PDm5cR0u8OB7iNoQZakc{`e)r{D-lE&`9V7`XNu*Jl!7O$@nh(GGut;#vOul8@h0_&yH1xf~ z`5j?M`Nt(;6^644`RvXQGi`Ejj&^0S&zTNGoKeFMui*Cs;OvOk1@sc4L>F9_co&5j z^z~Wy1S6El#@|kpGHJl5I1dMXf`8I?E;hj-r?83@Lk7N0?E1oW^8Dro=zvvAC57GP zz{C-HyhBjye#4R7Jj@b%9k>hy*~^H+V->&XUhv0IoWl4*$$r~6C3WVC>GJSMs9D~rUm9+6$jaj#XNGgV>zKY zpcmIj!W4AIxDdj@T%sjT=UVYKG$F)do24I1mwX%)LKwdD>{i{xs8lg;Y@44pziz^a zX&D(-o8E2)$U=W9Dl3bIZNOm|ngn&ishOS#z}uyFJq@=s{*e;j8x}{~(jiBn(YV8d zS&~krS%E*va{MVkM$A>6L4wqVDZ5^_W3S%gchb}uLN-gC3B8I)r2)Xmfvduc;Ut3= z%wq%#Ob}F^@Ku#g(8{5JASMF1B!C7LhoWa&a_?HyT{-8_6_J&rWP2fc4(^#yDoR{ut3lmUKnauN@TZtH?`vW!)=?o8hOtpY6MPJ4}i zItva2TZnzv+SfRjgua$EAugg%r~zFP#mc8f!pH3~A+9MG2T-fQNF8j7xz(A_Bt} zR-;tmk$@b3OaBVu1Z@RC@&UM0YLPxnCu+Bff-F$0vUaL*X^J++ zh-QlfkH}Fm-ejn{S|+AX{FS}aN#X-3=Y1HAp-;$ckjiGyJvJK#F{SrL#DhcpOdg4c zCURIWDadm~W6q+lbTRn(mWgDiY^ujOeg*P0gwc!f9)6qPs1lB~B#0pibv$tkfHSA< zxyxH|Wj-a|-3y1gs7#zTR4)AecZNX&TXYiek$92EWM5mg{v9D{pw?EDC6Rqq%cc+n z@6t`M&fzQI&rwiejG^PS82f99?<-B?HVC2wq1Ag?O-(I;(Yudb!=YD58ZTu{L8M|H z=w)4HsJpFDI_Oj{1Vfm}jI7`*w*qe%4@f&;D5M6pIPopGs&<>>Vh~F1o-c+CT{hTA z5dlOpS3W`mp;H_Ii4m}q%CH0l05&ull3oOrCl}?YyISO%(1u#NpwLeGCqbCxuR5WV zZl}pTR8^$aq;?N@OaZRMl%^c(K4J~=#{7;9Qu7Y;D+nadcx)wYC?ME*afTzT&1mr7 zSrls62~qXh@|Ctq_=v**O7I9Bp+?xHP=ug(3rYw7E&mQwaw|oDY6!CDU4TX^0`NjD z-65X-Y0~JM_&c!lH;jA1fLNjAW)X%sjL@i>+=I_{ULEm}tow%%I8ahxA>i6UR#+?@ zFfQ~X$QFc3hC+21o=*hB_^CI`rN@mg@zG#Vh4D#!i{?xP1bbF_ElG-M)lW-|vP)Nh zI}{5mNc3GaS0%VDm!ISWw?GZ!2o5rprb7UCb1i-7Y*Pd51`@#ESd!fW!3T`HkSiZL* z?jQ~NA&yCjc_5c*@7}Yi_EJ|(MI$H4XWjbJ#fV9Deq2PQLPIcw0hOgc1CTrc1dY5T z07LC13Pl(W(i1_!92PM&+MXV^wSBqCrb_;|h--=%k`LfBHzZY0=&_xzCK%-4K*fep z?k-C3JJbLuvda@BCH6h3`V8XV_#1Y$r?AG{;*Y;xHwR2FXamM0w--uDhL#MHBc>&S zCLn}}!Gq3Q^~vSWnR4Kk0Q##Zj7MP+jAD8QmJAcD(n&)Astms{V63u2Lx4nUdkw6& zWEW3~;Sj0`HEULIW+A9wFbL#e5|v5W*&`6FhFx`ca!QCV1|!F~D=v6XWFvWt8Im1Y zu1%YkB`+%*>2>#}felTFcAI2JdGfEjpGNOm8DqNG3Z>LxR^pTc{RLRg6kUz=h@C@Y zF&?p3-y&+WAoT(G12?N@Kl~#uq6UGe@V1LExD$n3O*ZveR*qIkyI3t1Ld$Me-DiVM zj=V>Q$vEsJ8$=3Vy5p`sF2vu(H|1tabZ-UOlw-CNf@LabWdi6>W^sbfx> z4H05IJAP}_RIE-=`s~n2L4MfH8wSCj^7&n6x{TYJ+GezJnq^^ z7qK?oqI(uFK#s~TNL(w(W_&iaJ10%33u2`Ct`aPwA+3)#Z|A;KtlESTqq?DvABo$Y z^(t&!uiOv19X1TUq0ZXav#^q1o6shKTBE8`+*)6Cc)1Q+WE$_R{3x5&>P}juj0oL) z2c~Tfy_U-#kGxt$>;%r{g=~)5GHBM8Rc0*)XSW>shYLMh36dRqk{%W{R<0IKVD(Uv zF(gG3<|!Hi^i9(|T(9Kf8U0y0$FEk@{z_6WeNOaY5x-`3XnzacbA>=OJ;|TYO=8w@ z@?|4ItU@4eG#dof>DBHYHuV2xRR4bgsvNMU@WS`P z$0PyA5{f zcBz1PVQz0rpK7S63x;mggcVdB-x8FVucbbvj%IPZW%mCEyW<&aiUe>0$b#D?&+=l4 z$xi49p6)#Bv#KmZ0*8w5*ADj9s+{=`zK}sC?BOsOXG(jg-ZGE=BhLm2l6gXNJT-&2 zk>FS*WEDk->;W;R7TP~deh~_PB5pE&i8d?U*0!TZw&1BpaPL}<>th^5!ZX^~N9`b$ z`xNn$bYCk;^!Mxkaz|{4Bbi6fyBB4P2HvH_Tg^c^zZ!c2I*a-C`?E#wiYH|+Li)3+ zOyy0N}VL1@b3GDmf@(pm4alC10m-Yw#T4ZSp<)4#`W6j#V#Naj>8FDXd?H zRF-tz9+Z3YAKiriA!P7;aQOZXIsqw(!6nU-5o=DB4_WEzJb_ypnc98xhq>x4;FWas z;x}a`q!<`#W?c@2djIpY-qNwAMsyjktzuKZZ6a=Zk>5i%>HbQ^e0ben=p#`1&;)*l zU!;uD`7`tj7Z~c}@gMijgQKjNC$s>sN)M2X4PEH(BG`ta>~^uVv((lPMreC^yvn`{ zwp+1(W~MQnKh!i)liJ}HZ|RG0<3FuiunBfmW@ttC<0H>1Nx)tqR|MO|}=9{QjkSqs6-E3xRg3>pG*e*SC;=l-g*3&3O8x&x4gURN??GNsrL_>ud)IV=GDwD^n@b{ zlocvoVeBcXstp9|X08g&CXxI1gKdQ0k$z8PxRL3zz7(@#jOegE8PH(Wj!CP+*-?q% zMY#nM$lZqS(XTO^e|M;^Mkc7Jk0qB&xU>O4OR#_Mdsu@=Zc2y zFE-f*nw+BeZ6^IKIIc-ruRr>KvH3`f&DJsSY#-TKm9A&FpB%g2{&ajXS?dkoA7HXb zO4MQ$ag<>Z$moCs5`!h4PzKtifPLiaIPQ8x+|jad;!b$}pF8KWchK(bS6F!cDh?RwM#bp?6(Pi6kbJ=#sl7EBsW zXV6c%->9Xitz@M?r!W&n2W!9mb+=wRD3D`SCyp%M4|=pVeRbR8c8#uP@p-1R+t$zf zXvDKacXHf3sJGB5daP+hFKvi2t0h8N!F?W9i;k4Ve}=!#UMSZ1=)xJj@+zOtY4e0B zYSu-4L1Ijt>Idvlf-9H_nRw_eoQ2odLu!~R{=)4(-js~NnC4h-pi}bLgk#mmfTHYW zAxXoWUvdYPa@Tx8>@%8pL1OV*|K{U!*k)f|GHc#8O!pk5tvoTgn|ize4l`CF_T4)* zD<5_ubD?iv^$yY+pK^)*{+My7CooFq)#rMpB~+1e)RB%x!fx{Y)_Jq1(O(t&@$$KH zrky!vI0id4Vo4AK!5`gR=JMmE&1L{dfLM!2r)$pD=u0|Qo(!sTG^RbYpm0jzmEu3+ zREHmuY7Sr)uhXYzwts7vV4ORw*aE+M>{A7@v$(LfaVWUSNUx*^~a&GK074K8-k%-bNwXH&<^Pg5^( zRy}XKojO{G*~hE(=cA&E#PP4j(!f>4`*Jw&zyfu0H;nLRg=~e3lgH4+<)D=Ioad^3 zi`Mw8cx0JB%bf#qpJWczSXC4es7x*wD9S3+xlVTz@(;;9ZpHF)_@IFNHH>IerG1HH)ZX2)%OHB?<7e^SQejuG&7rM~LWc;Z zNH<5F_qPl6utgQ6Q9Aj887==IuL3*q>%{nbZ%>hH4#&5q+zF!2Z@h=K{u`$k22Nam zMD{Rb=~T*+{e6dCW-n4azq!1(L|dh%NN<(gaVsjkh`aLdrgUjZ-nWUp%%r3LdbDC` zmXg*i#?IVLM|siBJOvaVGJ<6QgE0I=UL>+Z=UiUQusa`I@?|p+3+Dc{%kcISYjZQg z!SzLu_S|A1&x%V*op>n3nW?w{ZRO|3H+*!zqJE(rV|Ro#k(Q>mv>;oUx#7+oWn(oz zIhRu4TiJJ$mQI|Q);u|5_fHOnDcU9?KBK=V$LwUryatlx;msoH^LJa<$bR-ihrG9> zJSZb3!B%v*Phcuk60q+~TL*H%`9c;FY(5(kx#Z7tcJ>ipIQ_V@jN95zB-Wq_;68X~ zVPUZC=rmQg)gzyLH&9>YEzI`0EVSEr^*>CKSnr6+n1f?vIgB@G*$vF~{jDuP9nI5U z-d`x{?4@qzi|^GI7e3U#P{%5@X5}sBjgjYg*FrO)*?XR+cK|OY;DN-CsBT1XVBJ1K}hML?C4mp~VZ*PI2 zbqe1gTII1k^MZxvURp=0`7(;leOWR#>WgPcV$t?bT;=1XwS`WFPhWUkHo^}G_)M1y zR0@-|Sx^fiPd0sN_O1tC!3jFUecR_5U-K&0-`g#USom(EYYJ^`!1xE?{prEz$7kD= zcrdbZx{F@&`ZU^3Ki8kkt`k~c0;1>sMaQGkx%r7J7m`regJexq}U z7@SPij~1`aCKV(muX3`Q%FC*G8U{EAcNnN&ZI3SL#oa8R@?Z}vNJ2AqyTR(4-4ykD0NOap+o6&MYC5ThpC+7U_EiEJO z&7dm^)XpA@6I#qeZ}RP*J-aUSPf(QRrMxel6|bCdstzkRsnY6WY9~k63{|m9$RmlP z8^1*O^l-{O1OPjkx_-UUs4uT+2^Md&Ru6wdPL@+*QTNlo(#&Fd06J6j)c7p!>4_8r z4l}H02#c2>f2OD_Nh*1wQR0cdea^|_wM{MfIG_I7<829Oiwv8MZn6E^Un~et)~~iE zn`P_$y_-F;yLVmCv0$)P=eA8+3d2U3*c&MS$ge;iJ5ob_RAnC*{gY*HM>7}Vjl`qj zF4KKE-hJu4Cx73+vrp?z6kjdP_;5H^8}*F`vW4N#GcKTh_S0D__pzWeMZc#m=Kd>W zucZ@x*868!=9J&|m>J9aT)K>~+fk>?29wg&1%XZ2V_cy?VOG>ZfeVDA(`T{8bEX@o z@~-{h!}E{>o0l8|eW&{!K?E6uh($-Gmq|7lBN`P$tpW3NgPi&JiutpC27nUyM5|=iMbRNE@^jpDLZ2GPGAbcZXYn&WHpHiA{yHlojnxF88RyY0aD4;#H4qR*ExFbt)5Ec zIKSp=_KXSW6-0!-Dsy~~grXJH=X%l4UzGm-Wx^dhLd0@VM>=GB_f17Q0fbLdNPEHJ8LmPjL;QOQUz@VLna zr_!gFz(=d5))&2LOT=YU7q6x)Of!0ambY?#DJFF-(e3zP31)(pshBht8+x)lLL0lD zjM0LPnSfoNfOMbXvK`AkJp9lBLg(g6(~{Wg-*s0ePy@fJ|2&)d_*H2%y=Bw~sWw{? z9IH3?#aCOqbZyv`*QHwjvj_B5cc)WJ=$X+3{DJpjJ5p6SD}X`Joc>$WAA zIZQEQd^{Rx@v0hPg)8ZEUx+MgYDJ}dD)C98o%+C>S<2u2z?(@yd_mTUiAQixt4_PY z_nAOW0hUxXje74ckz2Ye^d+}du3vLdy&(}nCxvCU$tf$j>56|J-F~%VsjiTT`7JbQ zu0IR>orN9M2ZkpDaXZ{1!oyc9!3DQN&wN<6t*Dy|Y76B4g%tOq_id3hHug^X5{}c~ zS*8f&EhWd0YD1>@fuxS`l%4dtYbUptU0t07pURL`{2ZDqH{QKvN;Yl8)~;*r-chM; zd{&ti=UoP7w{QdsI9ivf#`e3@;PnyEY;|RF)R#iHs%7bVedIT*N-H{#0JE1c@&*~4 zlPGvDg4zABn5Dz6Dj%zh?#NNIEseRR@j!VnCWnuMGaMV;6_nmKwPI!6X|wL;n=yzN zBG(v=Ftmn(P+Hl61bIAxd)=EqrpPVKL>C^?EYPG8-1^c`bIR5n$j+19_bn&u^wvMX z`&fM_Qior4!ecj4j&XacqwP1UxIdXpJO05gr^HUv3YU&Q#Lp>)IPxL7oR~@C04-_F zs7oVhHTb_Cd22~FK%<b7y5NU*{Bvk`9S{&X`6p zEVjEkc>50Wt)}x8m~H>^sbhHdw1Bu}1*Vae<-~2_dwi>FlFMGz;7P!H`vO_xSXZdE zO)zhZo1Vi?oR?5)T=vm3X0$gXSco)Uilm|2%5+$C&AIo-gu;=_@ECRgp{)LazMP$z zCTGQgwkqxMS{T=Qrn5Nz`Zuj2KUJPf&VBr7D&L$gJhbrvmqr#k5^1%L#N!g_s0+Sa%)M3HCA%PB(zU^aYrz9K(*0yg_~%`F zraCm|?cX(9niB46fT+LgfeE5^)78{QhZMHEBs8lmfBoLzqR0@@4Qa)A<)UO zpNE&oLyPrbgg7|B?V6ceT9YA`u%_}JcdjG5B-*l%Us`=an2v(A0z@F(QS{cSGyXG( z&&_v=qhEZU_lUdo;dD81_WshbK8N9z@Z zU_JF#O!`^h9s{j~Ots*zv_v<~F?N{W#WIg-x5F@6_X^N6BQQ(!Du&YFR6l;xx_X0Z z&n&A5SD8G~b?(k5$g`yxCw#S7n3(T9N-K;GLFcNZEKlVrmyD+#cD{_2;|!>Js!7dT zz`^0!u&}qz1g;G=J`U@p`okodvdI@^m=wm@?@VCh@{{_&1+aRziBNPXM82qI&y)S~ zd2G!SZG8!xb|oa@>DQWL@~iTVI3CMq7up^;<}Sf_Swm(?no zZ((fA^+%a@)z4C*yuNFrc~fAj{C5VwSv-!>9cH{cc|6%!j#7JOr@7&oCU=;*W^Y+z zXWGl#VKj^1bFrH#pNr0y9CroSQPu>qldE`y#7ZTd4*HI`m+5$`wmCLTptYl|!S!QY zpwFTZOl95U&KXI5&bU|1Cr+xQA0g&CyPHvq(2#Abt7Q6&GAif` z{$oaS>Ef<;7q-8oF3HBVsvwzXpdf<^Q!3CMv{hB~UNKvQIdlj=MIPbip~5c{>4+~S z-LS~b%D^r-&W|p9_|PS5yp;kK>un%T{8i5E4=uMHJNt?K2+a&&szgQ+^?LRz9q_|TT=5TLfma+z4**H< zgNN|9_xk&iy{}rPMKslfRc}tEdvW{DuHkO6l7uOzf$z%DPt+MYQ-`BvYt>F8EF$Rf(N0d4zj(oypMT=A1-;GeJwR%PIUPg&iD2gz5-^zAoKKi?O+e>GCy?ns&9;-A>6>cG# z^h-n>j9f0<;pHm8Q4&a4&}sDOEys^}3w7BoR~st)tb2&Z$=PCJD*?>jtt!0y@e@Q zkn!7V@K%1ocpACAelGPSUTw<o4?HJURy{JfFS+HRr@M9wexQ$~%A6 zs5XPOU~$?_TCX{!!Bg?{ADU1cyv z97=$5qm`iPe9fLgw(sc9FMl*j#=YONITs5@uEUt+6$+m1|4q;%a~G2`SOz#l)dp9co+ zp87*pSmB2(<`I36s)0?arb9lPUpQsU9$QC60?3#UDgsGHBSbA0Z)Vg+H2(IOhrB`G z&X+%s4DDFx0Wi|augptBp#_MNmszYN>4cS#HI1f`#fDr!G203Qpfu%0^4 z3$DEKD0t@XNJp9NTjJ7>anR>@(CgrOXNr$3q=Y(J&TGX|pXfK}0F6nAG}GD-pR9St zTIyyq#Azlcg?Vi`HuAp~u&9^^c-UC)B{P%K;hncAz72U*nj+=#(*%4SH$y}3sS%)Q z^j$ZeShs5yf+HQQP#lFR_Dfvztx-{u_vdfS^3|~(ie*|10oUX_b9fVIr7o$weNY|= z#^>iXOChpW9-dQmzDIqLr(vF^G*CC{smg8eF^GdpYSWERLg`~2B?xd%rY_qpDZn!T zdL;K1nw6T_28IpSaj`_C@bJj&WPIMb;8l4T*4>gZ@|7Q9vtZPg8RHuv0Q`dPoBH@m z$`OX4gN+x3PJqfBX4lPQkyZx{v)iW4B?iU2wYzWnLa`(2`SUnw)%UNw8p0?NR*;2> zkY8$ch;Iez>S~S;Wt}g;i#?!dDmTI|TZH}F%_BC@`V+P2dG7=dcJAQSwfD)QI1^AYy4yR&lg_y7^A|eR2a#hoO=Z@4M@J>2ArsTVF+rG6KrM zPrN5};h3qtCJxI!Z-)FK^4=cGpf%UC9DU;X=h#c?x5pMv&yb(n}AA zy(y|!?#+%{q#>uI4PMr4;f@Zgt53x+&}B7X&;7L!lT z^kC6_e5_6$S&P2do24&(b?U1HA5BD-C-3fT4x{-7-t%aRADVXsl#p!?&_=wrEf~F^ zL_PMjvBGHIgJXAiO>3xsmi?Qg-kzMqIewjD#n+`N`WJT)eNm7if6btpw~s&qq&R60 zG0q&c2M)B5ujIR5wUSp;D&nP5_q`;LN~ahI(O)oOtD;{MDmcNO%o@O!OXM3OMoB?#eefL9~v)Tl-h_sxp;v6J+RH)%#G zmmYB85(mh?9Z6JUSPwNP_ z3Q~7nMCF;;uf$#ZN@ZIS3+thnzvl+0BM2IA5Cdbh3e>6v2<@|(lPt-E2`yG&Wn)S2 z)w38GGxU?LJ8MJ7jCi`4gd6&WQ{NYr8LWD^OH7Jq1x1i72qs-Q#0k&CKN z{%jgIQ7{wTpU{UAxt=-^bOutRx81N=s^>YUk&@Maw(R}our<)0DqL`Q>6wo;^4uRZ z6t)XPdMvceB9eOJ)X5|OKUs+%+WX{3@pJux)~kTXZVy0L$Z5-NbeP5J*6JBx6Z$v=bqgfVSt9|2rQ%iHC$8Kj7=?q&W-%W$6TTin6iQ^wh_1Xf-jJYh zOhmhMGLpDoB_L3;A0O0#)8EuZEDO?H(-T?_U0ESq)<=P2w=TqG zf;8B?CCWo4h?Zcwb>Nf0e4*N{EP?R;6 z3_wA4GO@Lc-FBeEt@9Nuq$h;Q1c7ahV9OMfz?Qt0pDyc^L3k`^_2I||sEi3+RhR(_ zrDdCsZv8^yNn=VNE+4-8(;w+*EN_HbBo%=bT84x{$vQ(^m1>}dMi-uxiL=$~DC*oK zGMHIY4DOJ!;j@-YG0kS=95hi1>8ql#NjrFF9yLGdK_CVzxPOT|B=P6#2!EM?Jol+& zQhlQ0rXwE6uW)Ay*r1M+)6<_R_SbE2z43g`Ks(2jOq$}50ekki8J;qxBcOUmT&Kt* zzQmJ9aC*;vhZlp10_?Us#_F0O!MjG_p!6jzrMw*d?dEy`n|_`?f0wY9*6Tr<(xIHr zQe$-+o@`PVONaZ_936MHx9PSLJ{@iaKgRZ>wMBj zpiK$J$+%pn=*0$D10uY0N5_swuNv()H#?nX+=jLi_qzMdlyV>;^E z6qvQJ0Yx7tnTw1YJz4^^pLNA_{H@!1GW%QNlZjA}|3wJb@nRp#2GOzU@C9xL6U zKj0LFw0x>A*iQOTrzaFOF|JAntKh$CGR)YU{QH8Gk+lrlHK>v_EtfV`cw8eBaZ0Fp zK&GFPFYp<2VjhFcfR2@DkxDbdKopaXBRlE<*`|7ZXyt){!CbC*lvCPpt+MsW*N{k^ zo{k^pd1DO9Uy0Ia7bVCxZo1wOf#>(QW@>mr_WU<%rlxb`Hlm@0HO(bc4>~b)7mi)o z44OMN_?;+SBq@ib0j83{$EOL2s_u!)zdI;#-|uWJPCs<3jE57)1J!E+1(D>6DRWy* zF+z7Q&9a7sihap{IjJ|~QjkbIp1SyQT@&A22M2waK~D*9QY@P#eP>%})3<^B8KTfe zYLlv?{CR|jst*;OT9@cPIp0w`)z-9J<}H5Ho%C{{>j1n=Hcx`rgwDbn)nhV1FF`-{ zH$e5=O=yLSPSoxr{AYUvFY;3KI=Qt>(4EM_-6h1jK^OXcTp3W*R-pN0{rd9J5P%@( zSEHP0gELR4Y_w|3qV$~KL0Y6;MZb!1^1N8|@fs&W4`yNtYkc+6EEYidrQiXR$cbh7 zCB0f4b=}H;ojmd9^RSZeE97w`AJvA#LRMVXam6pkeo?Jp|1dbcZQ!+0leyIzB3F<0 zyC@MhPzud!G(BymOKR_>XTQ+gO?W-+B^5KZ;Y&NMN!HgXd2=?%EuZSa75v|mSOq^f zYa>5|i5U$up^u=J#NA4!#4!oooNL3#f zQtGiJVdNdgCgrioG4 zcTCF64%_)?Kko7Oq&AjU>L^#4U!HO9RpfMG%f&%zMg5Kg`gq1!aBLY_P@V`zQ09Nb zHVV&I*v01zS3d-;S|!c6S!MU|xUPjc5>+auOuYgq{r&D$-CA5WjE#3U5dyPGv}gqP z<4+$98r6D8COaIWRB`L^lg$g?@`E;{=}7fodr6Q-mb}(p`4v(8Ls$4@Ul!1&kQNpA z2*>$fk>~5})Z>nc;_3*J>HZ!;%VA3!LfIn9v8f)UhXhvcih6}^O45~tPEoAZQc>K> zw$h~}wXnE6GTSV}9P1%^z+$Z5o_`O+zDJx}P#(~K5LMDGIfv>s=%gokm#GQ$w%eS* z)3R!(DoGuX4L#`09Wo!J`%tgy2U2i>$0qXfHy4R4D52i zHy+M%-tSi6$HsiMqs?%AAEx%p*5RJL6o z22JP6;NA4$jZnUXkc6y6vZ`zAJwN1S8HT`oF48M8Cu4UhYMr|&to*%7+Ig1p$S(l! z;L&*S1Q^3wj-WiFqa0RW#hH1Rz8&i;sGaO*W!&hlUHww`)&eRu1svK?LUs$wo|aq6 zViA=MrOKQ+bXWVG4B{N4@W7i=VXM~8T;v7}%=FcZB5sn9^xALrPO!qZ&WkN=zeelj zGf&+P&kBpuUD!t1ywOnBNKbdMQs%UKe!DRA8;%{pxP-Lzb;w}U!@0vB^j*bG;We>Z zor}5K8^oR@PZaJPZuvBE(n9+M1I+wAw@klkc3W-_?c`EmeTAj{#Xa@l=5z%89w&vM zz^+2V?rSHm{dJ(0Mj6$KiSfi-iD}f$`rH}MNt3imu81ek(3HeB1D#+Zh>6ZDH@!LO z_uY;qtNH3JTs?G!=rYcYh{BSvvZDoT3a6AZzdwKcib5T2&qXsy@)l za(^25XarP@M(9!OUUiq#7e-2V>V) zf(s|nyX;#MiDMfh#`pu>XaC06zGb%WP{(*9l1x6OyPCsE!}D{fe*hDuxU6)XJ@>C-_4#~a&Lgu=du*(-k@n8hL01Lcb3T&@N-9Vml-jC z-t#BocSZBnfSd1;fkuh!x;<~QH*Zxx};2pk^72fd@_>jQfGy-}{r%@qIDAV(6NmTS;nYj1+@XZZ5 zjeNZ=OJMGP_Gnh6uZ?vxz-?sX&}ve{+2zptfCL{R75e;<;O6^emrpN-5rjdmQ%^y}4anUhi9z__fP({a}TPOp|x1 zjlER=er}*pJi>A#O4D^dwiv(u$DEyE^lY?c#dZ7+YJ$D(ZmQL*Wrdw63DPsUEI0mc z5tJxk9_N&5l=~I%>>+J9mC`Dd7m5tF0_Hr01oXP)6s{rrFupk& zEw5*`IYvoeyByfK-XNM~>h0N~+~|{>PQ*7f_DhDgD)BPEuALircp5aWDV^MTuGHGB zIxfqlpEce(Mlo~kM~rvF#q|`>TVga|TD8ayDeN z4W?Vw86N9&s_(-QsDHKy-6l_rQa$|KR^7rx;|}yXmM6Rp4~WKdDX1P6%5cmxP`hUR z)uy6t`~=8iea&|H;vJY4>s1s=mwATnAaZ(^S(6N+1z#k=E-qD3ZYqK(4VUM}u=mkX zGJZa7??9Mj(>GMQh|ZG|t?=PyOzza-%17SX@(q1CKd`ACDmWiVFC~AM zg;+19@!t7-zPF328mzdfRSJn#EJmzDltelT@kfvEEYA<@O z6Lxbzjn=nw9xD$zY^)zS!UQlVJATBf$6ABud7q>6%0F&mNuIFY*vjE=z|5N6PQ&H0 zcT_zP+VScq;pKv~Dcy6;Wl68-4A`#9JnE|h!!8HbkO z_XZ_cYCxydWTdcG*`b2O*>)T)tF5UzSp|NO$+&tu;B83q51DV9g8vse`^a0EC%Bog zS)y$vW4CXIiT8(E7nD=m-5!4rj-J4(UT`P~I=23nWH1Pwb_;%8C>shIR@xo60B}je z1~Sr4vNe!3&0sdRmw!y4yT7eEyfDc@ORkO}jW1xaGB1@0tKV0N<2YxoOeBIhRBc&> zfne&HHSy%z`wst+002M$Nkl?^@Fsb_9y z38)9RJ<`BE?>%Ed@jbpk)}FDebjO+SV4wYE4cGse-t^&8*e@zwf_{DmLd-DEsdxKQ zy+OM@vePb}RG`pWssT85)sWngwLkCF?`lfaz>8)Gr?UGRtvlYf*q7qSEUwBZ!4=GP zHC?2sOH_8jl@5)4=c~JiTCwB_j`GR~PAfsaEQJF+v9ujWRl3|fdO04*+n?#ji~Al0 zZgkuk*+B98kUdog5T<~qL*xb}{G7^1(|Kn5CBTCVYF+*Q`&X`-FyVK_61xG{oKx;R zY@r7^>Q<&AG{X8+^CJeXG0t^5NT-@k;CoqkU*BiOR?fWQx$&iUj$M^kn|@={#GfD7 z`FBswCS4%h`Te|9EgEv;mjw_1^%)&ko9a|uBqGLL*Dd|(tg){aq=P@}y?Xh8jaEDw z?i#nZ?47ssbsW#!z7purP5_mQ>c@Hl>wwF?lurv2l00&t3}1sQf-w)=vu3soL~*#` zCea2t1url?=8s7g-V`m_V94$8zIr;A5=Ld6WFcS1A&|dq`}*&e=r$M5F!ElG50y0G zMKWWt{0DxVDP?9foFRZF^%_2UvLq%IYjKQl{czc*N2RRQ@!h#QQ9T}`L zrb9&d}{=p-Uq?6$;59i0LPwHkE->bD! z<@vvRb(KDj%Je+=b|kP}mV?fzmlQ^?+=(hGEs!P*JC>oh$!iL=98(+|l9vYuWn>S( zoXpojF+3?`kxTjG0xCo*M~zf|5nDNDsHo(!=@3W-l>tYAXZRo>^F=JiC;v#Ppgzlt zXj0u9!;^%{Kf}baV66jJ???hu=%(6W>rpL95XPxD-*L#K2e%%5DJ~3yAD5LfbWXfX zzN?bB1`RGmEL<8Ad>TN>36rLl>znn+?aRrNdu$v(?m=tK@@3KapDu|nS@~7nKYX9p zocHr_Iv-4rakGGa2vU$M+zHyO!Q~&m34A4xdjdUx3xNdQ9IB3{qb%v#mHVC8ykPkI zO&h9~H&(A(5^aip6`eoq`RIy0c4G&?j=+xQ{eDvk@VRqo5GRFFJ^%FhIU9W8GozOI zPRg=ygNRj|ko234zQm8aKmPp1OpEn{akA6B(ec2U|76li*QQNbPp@5ncgJT9c?Ca8 zS|)t0sn(^(?lSW3$cBPlhK#CD`QG-WjmzqyjW5(iqfZo=#;?N3bWg>qca2)AysFn> z|Ex*-KIRo-fl%rR6^1v4mFfNLuoB29k+=KGDtuB>DFS>0;BWZYxrOBoE*B^z=`P{B zeI@yEf4Pr-)1ma8unaG{r}e}Y(pVmG!ZJJ}t`3230IN4uU2g)jV(U0G8SZ{l*&Tn; z@e;zP78AJF=1=Z8x$M?n($m^6Q=Uv3NH0@~rz5|gAm2K^J1xukTq48xw(aT>EpJax zM`uL;7y8O^Ukwmz!vMMXQly11s#%})vp))hbbRapNcYkC=kK}dtW&G`258OLpDWx5 zv;OSUEbF|nR_%GG+)ujAKqil%$&_U?^g2kFwKqKKftfX5O+ZsE_rP3TxzDZEis6>E za1X1wa+npJH%yt<^5ItV7sKij)x!=^^j=3mF?1NHJtEh0B zS~vEz#HO=<_Nn&IZm-(kGq;}vw8HJE6z~jf$Ty@S=-dyEuXlZfEu{ei?X_YOKre_1 zOqWJ5(*IM7;$$Fzib(PL2w$?oJtx0LtsD3$M#l5|$upsDG6|oE-b6(R!W6DOC`{GM zwI9DL=i&mi?w`G@I-E-fEps{+DAle@ahps^&Iadhye)1)KY688m00$~rybwLH*hb+HxW2U zR~-}blYR@|%k7kq-{Ae6v4J4I&)ZKHgYgZ+zLIB2$MDbVWa<|KC4#-NGQ6QQe%(4r z;Od8@+Smk0-BXo0S#?lO-&ct^EB$~Gvuh1L zu#HTp21=4xDwU2UQ&zWd@Z#q4fBJFw;TQg-XATHv15N{v26E}%I;_LuJQQWnJ&+n~XalmoClCi6sH(?xVpTNnC_?v1|@%((VsCixLFV@Dv`x#xA z)UxBiG>Lif;2TOne?Ud=4|Ods$5l}Z)YZ^NMMOQTEhXddVP~&^`wqMc;?PK>86HFh z&XSJR^^QAqIyy*ZPk1;mu>bNN1yv1$>Z=w$-92ddV_~_71g^T7q4zA?Ujn+(c6dr7 zR>&FsOO#TPkzdJegLlO@0G8Qb`hteBZcsWC=YegdlS#i6HYf>|FMLk&s(!fX653)t z7MUku8afsC-;b-SmT4UIgHI{~1s|=G*LoOo6)r8Cfn}(xa5usf^bos~DbtEoNijYs za2)2kFQK+y56|CY0sZ^9UG7B-@oRjt9Bb`U5$~@|9-pCH+9dKU@I|bEPJv3}+P2_( z#|a#gdkOx327PB-NOuJN3CuInO23Tzck<#=(M9BJr1uBhHGLIYhtWyonNR!M%S`y7 zZ!z+@7cNbE15r-^%{54SE;i3^TsK%oN5F^6SH~}v1$*J+@5NuKi`ZoE5;>&L3b_Sw zM1~JXa=008+}NLVHmw8e;t5{SjQ&I^aE1|igQ&!kX^G)e>s*#$$AP@+aFEE!K+Xit z0uBZe82(KFNocUwn9bVeEBVPa1VHqvlhApf8ER_85^=t7V4}&d&XUWX-#8CC1D@$f zfCmKl>@~vMz8ZQfB*eHN&(sfe0(h#ePog<*)2ro9JqwxY%v!6{ANGwd{%V2QP(8!6 z;ys&uiPOUOKR-it9Ih)^wjxU&={J->N6!Q@*+DI_@UajzLWZ567H`+6u6($dWAJ_9 zH%H)R=5=y2zWgsqLlk~a+dj(nwr zXY`kDDA)I)Hq}Q_=Gf__OJXvvpDBZJ*Y()_R>;%j@y2j@6bB}j7nv%JN9#>#-UZia zmjIHttq z++1M!ny?Y*H>{y@;c)|yn`PqM3h7Wk1QU}Y_Q&!ikbK(ptU$L}P@RtR)@Om3o$c$K zTNFrEWJPJlEG`K>g>P0uw|LOe62Qa#Msm#+KbIf#t^9ShbQJvHaCmk1ly$0UrsLfX zD1OJd=nuGT7OyJc+t3|dR*zr%B(U8&0oDQ5HeEqrXLZ8SoD~ZG=*{cign8&rz zPq8Sxp>o&;sbA%jDczECSui07!TVKA84kw=szCAnfpok%bjX*2!Mwd;iv&4BPjSt? zYQ3C=uVn3wrI{Lh%J4wWU!j--j0Za3KlhP5qXw$Vve)OAVokrgo1{j}QT$ZiUjzRW z&>4UyN6!YX8G}`1grhj2_`X+@lBPWirNbh|$-ar983 z0e-o)3pxNWHp=s*1~VEQ!Lx5ZWn6$7O(6l5HtDq84o0R8p6^h>+AGUsD8en|H{Oaj z8y)lh1@IhKq|GsNk!^*Q_t;eKwfSr^kIYR{GHcaak%C9MgGgYv`|ro(I&(X#vjn8R zbFhPyVCTywbm(@2%+X4wkgdEjsXCWOLGe-rNLAEc3QELe5Issv`HKFjp7z(M5_qqX z+6&rIjU2GHQH(+~;wX|*Ug!Xv!$$T?U*M5`d4TM{V!f=y2W$U;57CeAoRTB4!@L*Q zC2*;hN*6z=RG-YKTk#sWOp@*n0OZE;=wM*lYL%82Q2B|cWEl29mLj)m7Swm8$`5-a zl@5M+Fp?c)8f8}UM}Xx^s8dh|JQN?zmSMT9+NZGeFn=Jc(rTkg}Y-`51IUY)DD~%`-Lf-AFaYrxS%%Na&JC)1JfmrP+^YiV48`W_;dLU{7NGR_V2M)dEJ(OHlNXLwcto^SM+Pst*>@=As?3(<8U$f@?~^%L zcpZ+-XJl1@QtJ4;WnUX4~! zcFdGT$3^0af`A{>q4YH4@=qJp=9`wiO~m}^m%J$Q_NA{A*plgVbg}%aJAV951GOIAiZlz$NbD0AU_>sN+}fG^TtYP?rJaj#L;>b$;77Ea|$V=mc%&Vz3*0j&ge7r^Ii3ECmdj(gq_@rO?j_l3gM?PSY2N1V_Z z#Y3=>$2!^3^-rg7VN}PqwY~W3bhgbb>(VJaHrxlf>$qpp&YWZvU_0r*F&UnKN^|xS zc^pR~2$@7-F7JgNS8U+JUP-Tg8SPp#9+n9Rd_29*s7*@S(9y)hg= z6IV>EB(KBqnZy+-`3wAyUW;%X$NaKuQZ}WV)Sd7uzfi2NV~Ivq*U6U%UxFd}+aXJO zc1D$hB363>v+b9h__@Rf@$JJw%Nk?@XqVRDg|Lx>xa2P~V}Oin#E)Sn(dYf77U%jG_Z0ct#ZDIZ%mIIVaB4Ka+sVyht||xweBDB!K-W+(;5SXP zHqrcgu$%8NBl42{2|=pg)^H%4d?qIX`#3>cOxvN`qM??AoQ`(=uKj-0&}cqtKC^(AG4sESVQ9C{9z03fJ|`ZeET}WEhg>}*b+b;fki37o|AhP6#jPC9z~I% z-6b+G4;77_F-PL6U2cWQSVuI9jvxAR_y^ThwUp@eKN%EfFwAx;8trx za#7&1yoa6iv*~p@+5Xwt%|&O&IlXIf@Pzyy75=2K2O_ZzY;AenY2`{MJDlz<(Qz|T z>7QUt{MzIyS=#0NE_zSQ&bOQEcCvF6@NI)qLTLCeV=;R@hKbGia6FH$VXZ*;ari_i zDYeYh_m;`apAMFN>JsvEC=;9xl~=I=Fr~*b!CQt(z>3#FE9CUK1LQB@RmymA_+l^N z1=?XXjpgz*M&pf$e+1ki9P4?l$|qk|rR0o0hO8vrtM&+5(5YPi@c@~Nw}B7CP};%= z(BsjxtRM}(U;JrJTz-eA)`+VmFz5$+e9XQHFFc}Cm`55khV)2`uJQ8QnFv1sdGh1q z=?nKRmWK_TdLl`;xU6kR%XOXn@($gER6y2mYL@H4=Y6{0LSI31OJE>93k}$a-23W^ zd#3Id{NjzD);Bc_3;E4heqrFv;Db{XKOWJ;(LMJp1KCsX2mG=@a4mi*kd?1pX@f2s z$cMmsMII^*j_UDTaTWdRTxOdPt3L4F=BkDht(X*tI+@GD!;BXUz|Px5MupZ3>gD<&?7JKSw|k+eE;>h^cr z8Mm$Yq>JPzPcHgge(rQ~*|{{7%b#iLH#>8LXFrE*caA8ny!7+g1-|UKC$tL9dd?i> zvopw|QBL^h%zrLg&##>lh}g0&x@cIpg0pt1EIhBbRWPqF>SYUaz7d+4A4q za)iTArH*A#Ii}@pcxSy`LdS)64;+(k1&;~;Owk?RwHSw3ea;A2PC!a9k8bs0<|TJ^e$HRybxGuxtLI0*4_ui~Lb5_3j~FYEMAD_%O! zNRvlSF;ErrB*fAFZu8YScyY~@ujv>Kmx7=INE5((BO3|~eBj-Wv|E*hAAMgkN77iN zZy7N-5!z#bxlvJC>MI&LR6W-by?dve7k4%nowR-+WwN#DvQjf%Kj32ELLfJG3xT%* zR{}K+tTxykL8&Qo>J6#KptQ6i#bqNM?wK7X0VVr_@qL~)OzR9Qn&!oRL7=K1Fe&q- z5i)~@k-n?`(Y2Rgsj`+uQg8*D$xp6 z7p=gt`R8JOfSHQb4Y{pBIqr%7D|$2e=)8u19`2W{HhzQkgj0~GFph83nniME?B%9c z5dR^JwZ|YWmbDv-z5&~5c=^*P7Bf9J{$}(9#cNAH&dA<>eBn_>B6TA+NV;(Ru6tR@rq9%ZoSlAln@>aC@T-w)jop2F)nBu!8mdQK_~**5 z{hYC`9$4G_8A5T3mD~-zRgG1o3yq+Go59j&#@~oO6F9bDUmWxJ*m>$P%QSAr^!NlL zPY$t?=?RHx(MRQn`Mm>;)@7>Whw_J}>j`B#K5bZ&ldm`5EqE6PM>t}EV+$`f{OS88 znx1AEzUQFF$C!C?9QfZH|7Y|%<}X~H>Y*GayX5j6+H=2lNx65AW070wtxm7&z@>T9Eupz!;H zSy}w`=I2%T8Tn5JR$H6_QYk-Uq!Mup@1@}V-^FIo8XbM3d6}GA^vA%uGz2|eBQf3JFacV>2%pa`g4(A(WruhN_9_o`mK z_v!|jpZ=VemE$VcFTt_H)}8=a=Kn0CEvt#qisP;5oUpaY%-B=z3I5q2;@3bv8$dga zSWRd;@^$2@I4AmUNbUoo#DB=u1RnGcgU?VE+64ctU^=7RUHHwxr>$8##qU!HSWxf< zM#S8RejEl4tN=!}o-5`1!4m7s2?-NE!lnC3cfK@VdK*n5f!`jIg$Zz$i%(u`Gzuag6~7ny$J*b0tnC2 z7z*4Bgg*#xVsN_1UaG6$C;1nMm^iYrB+`m$kM+X)EaI*Rbr90bMb3UDh)bB=pt|S6 zjGAjFq}>c>)oq|aR}S$>7gXZQcqb#fj$y`Zva!(f-WZ)L8Gr&2k%o=n7+9IyaK*bD z?@>5~^b;PUyRc$lYzLgqc;NERXoPEQShEKj*CwOAjc9lzL_3w*3BH@Lyv;(>8en4D zk3qlN2q^;r_b!OPUqqRyTMWQj1q%_BJtKox>{);TOu)+Lwa{y!ZEP6!RaU^*)x^F@ zOJ7j4<{bElpCA*kci(9b_h1C7Z%=jYPkZ)O7WL;q!R$9~4iY~1yInu&3}$@{{tEXP zNE%J{+ErSvLM}uYX3^y9D>2C57Ut{$3*0$ICQGk0Pl*i=Z$K)QYu;+FIAv?dtZ z_aiaO`*FsRI(l3X@L;wKN&W=Pw8vmn~{4-^c_n~b&-j_88> zUl!Pxvh8*__d||>LW{sPJi7S{Y`QLl7>5W_v{}124laWEJyI9f+_Q5d(kqkyS!lHka(BtRAg;0)mxZ5J zzD-$L@}KnVdi1Oo*!ep8zfk9Ro9NyH?_KrX;TNq8vfrFdm-Qx>pM?_IVD$llkCt0n zO2v3aFcm%mab{*67~Me*2ff1Yr+-95Q?KNjii+sR;R4LSJDCVv(Rlf8F@|K=J-?;a zW53(E1h*ItH{t%qfSaBca4eYv4PYZbU$`btB%NxSNzv6JJU%R$o;;}RD&?J+(E)(5 z+gpe^AK{3unbR=4LfJBn>T$vk00Wk;IthC+z1M<(gV=L7+D$}=2q9et;&9%dS+o*PrsYHPKk!xfHfGZ9$}he5#8XYz1picM{v3}Zj$WoXp$R#+h0nnBIE>IvNxL4k z8AsYbdkImYZh{Pc7q*T6<~l8HIEi$Sh5Fk^SPI|hkE5hNTsnU z*qLb^yuZrsDWBt_E#X$bChuG#r2ZQ5)K8WDMlv(`x`#h;o&Rkil>RaLFv4}3_cs#W zwqLlQgCB~Z_P0rzJ{F^CDwN^`D@D8C2GLiPW%xbLa!+&*CJ^B85irt|(fD1aX|||+ z*i_VkfBFG`&_pb`IqYQ7%T>ef#Y`gyKav|AieY2EZJNqv&Hs$gh!a&YVhsB^IPDGi zDaK;fvt3i8j~V*YcNZ5=11FXQ)cz6oh~*>4h?yv-KY;K-VW=~FMm!jHYwIU3TzJGT zJM)+`MTf`^c9Y7)$?2|!>poeK{*i18&8k3PhaD$+sbTgfl=W+9J$PLzed#>G<&niU1<2^>?^2$@E~^{up7aq0w`#{$A)`QeKa}0`;V{bKw{AML=Py%)UkoqNwvo zl=!4RyWtX9)1osVmcFIIq=GPwj#u0=(&9^xC!6NN~xk6+pHFYQH&E8xE1zHEMl(UigF7? zjgz?I4rt1GFwB@nGa#C+OXAmZFPoSIj&AuScB`VQro4iQ2M$mpN^C|V5gajtFyh#{ zToy^cK2pkV(j-sErjdwrrL=t57!((BeaMN>#HeA*uz%JDKm0!p#>2>cOKrq;%z3A8 z9`NG3aA*z2R8YXwLabo&#);8b$J`IEJ1iEn4!A*aG{9(OqY=!|V?;iWum=bRZnCSX zK7=HgBgTmln4>(Tf_>*Hf-!iiOt^Y^gC0EjFItrvzbiS1EC47`B1x5*%27|D20{ieL_#_NCS)#eZO+0c6!FqN=QE$I|zQ{#OKJ0 zBC|tpQ$Yyyt;uhNJx>!U7E!!u4Q4$|7~-CR<%TWbjE{mZsT0$eLnU+yHU!$D1qMdm zN5U=Mpn_zZpRTS(#baD2rTqr=jezdI!F9NLcj2^Z3b4h&U(Q~y+wDC!5EFn0C^4+_=&+yW%2htM#QDp^-0F^#s=}TB0J&M5`2o75P<|lFwqq!2Z z^r4~?CJR+_p{h5yZhr4Y{qOt>xko^I1x`5pF<{BB$&CV>s>g{{6@F8Sr`5u~1sccX z_)Wz+l3X2H-$7AI8AL4KPGCB8`X1F2Uv0R8{tNht;toJPgDl^aE&Qr% zM%V|gE-RjBH_h> zk|Pzalq^{?ekZ8bU|J0%hM;a7r|IC7%@ABNM$}PLJ<^`WmxlN zYZ4X?TWv;lK~+oRn)aKX7{8w{f_rJ%01P=r!D#~s>(=o|-mzk^7GZy3BKTs_qHaY) zMv4bf-cAWCi96Q^94!WDI{O=s1z-7eQP+?2^2NRAF~ms-ahO5JiGHpyyNekOug+i8 z<$pu+#XpeRr0N&n2Vi=`2~3f+zol^jRGS7<(^SzAq*d1!HuIpWobZFdfbC9s52DY# z_CtUO#`XwhF_k99BEJUYh-)lib9FIS;8(bxmDp!mdNyMKi2w)wS&q~2i!~ci^wE1OzGAl2l6S0Ad<6f0vh>EhKjR%O(7Ls_XNmd{EzBOpCx@iQ;yRE)KQ1oZx81VT*lI zkvn)WE&GJBd_JFVy4dOP9=8$H_$I(jgR1tzSPb4(%$OAApq|#u=pIEXa?MBcn7l^i5SZEh!ts3war;rri7$<@34O^1ggLy=HPY{1mYuRNW z@4X42yu66J-35eK9c)AnbQr2gdCIJ-R_}L=NU(i7PnIzRyu>gbow1RN^Ifo*wZ)$?U&GWU=5K@(*ZL*4l@w0 z84ZF_U9o2(QFaboxv;R_@m9dxN-z+a4b+V|j2fG@^fMP$OlXti8X?#-|<`ZzXEFXl$Wt&5kW-DwRfu3HR%2t5VN zu1^+eAnJbyV%lV9G~Dfv6j1Fp}X!vAXDnu7?Q*?OyNoum?;Xy5V3%1cQ47OsOlLqXYvx zhE>#V3QAV?oo3a}bHEK?@B@Jrn52~=iFuTHv%M^~b8+nMQ$J?+Af<3Jeaxs%;=dp6 zFXE!5{?)h$_tfsg{R2Z2e;R=naf}tfP_hWO0wTV+7adAB-aojpA#n&S;{W&GcKlwb zg#6LjOcOuYfe-YN{y>Ap0P;qO;V#$-xNuS!;$;a{WoU&S5W83hzbPu zftap`v5Wi=TvCC+nVKn-G);@nS+dCUI%y4&9Dm4Iak-DPe-qg?^qu!FTiWqDn*9^; zCt|H_WLKiBAP8v$b&UaK{hea!D+dm&9R1vL9fJhp-NLTNVDUgMGaBg=6(-(>Ex z^7(4vV*WgvDqJIq;eSM7ZJ~po4a>->uqja8X3f-vOS2!t_yS>VVifidgq6&4oCdXX z@lt-W9hZ8~8!0}=5o}e9m+%u))9iPYv(yjYKZyRbnB$;37SDPksyZnOI$DGqCjT(kX<0+ z*^ZUOQySMFTevtI`?j*pP%9H^0koN@%sQx{6&G_IFV*M_tZFC-4WcYj2$%xACBQ)4 zbzfklagQtqJ&CaZ9s>cz2y}mm-SAb3+7P-CF(<+DECYK0n;EZrA=WW;U+mUN4brkW z(72Iq6OXq5zYNz%TtxhxaF}2K#0x9HLPLmSb0cf3E{a7(JJ|afE*C3v^>aVR`Ipre z%-(05L4ie|v{P=^Q3s zN^$ReC7SOdu~0vvV)=X z(gK&Nv27cYs%(6LpCP$vjP^FFDl{g>ANCbS*LU(2D=fy9R0cVWlYW&Ddqqh)YgnVP zo@dvy02l>@0IeF5Rw;x-Ib*^7Q(VX5%EPr17ZHla!H;Jr9Apd-K_UXQb#>mQZA@{O zKaG=n6)WIZYp65IJ#EYjwN7i^w_(3c6Nzx49(x?LSi}K@p>ah_rm;S|Z2N9EAP`^* zuDy=IJ;5cGxog*H9dos9W%XM^zsnfD_CIz#d%N3Z*!R}pkuHBPJAFsVAkj7>_Li&r z+9YkNy_I9<-q&i+?; zT5%Zx)NLJlyiYTFs~sC&=X_J+Y9?5o-wCs@vy@om6W(*}H)vN9VYNzc`fImah)MqN z<_+f=wXwe7Xoe?^Z&Z7kuW-J)u5R}cdFe6Z$oe|9N1D*@&`t9tcb4lFBWC8{>yN78 z$bWgib<%Z&s2$zk++8;@R4Y1Ql^=0;P~Ye6O0deeLjBG$9lU1!scOfPXRFIbbobUo zE+#hR?Gt;Jd)I8JtelXQ8Qd{RSC}VKW!&%8#3rq+dMDGN;sHNnYFfC6XRljtx7#lF z+ujp~!?!KsaL7l*Ix!>>s(3;w%%0$F*nKHyUtn6Q%dUgcS}G$kkNs@t*%SPMf}uL? zvgoqzDanCmccy*aYI8&Bx>%6|DRsTNOP*QJm>Z9_*8Y?)NLM zaKu>R8@zf9CDDuqKWx$jN?=CKF{2Ky8uqR~(>N_$X_yMU0=aWk7OmBb-Ti_mbH1(e zUNPBs$%a)ceX(eHy{?)$S$vELgF9q$7Hf#!oo3Fi5y((mA(X31yJ=q8o zx!Kig9T_vSjcfAm>zT=~4OLi+P+yM5!qu?`IA1x*-*?r*ZEJ`9sY^G0XJl*WDsR6v z4~w}yy?p$p@Xpo4?rGmeTpz1ynCR-Z{sR)D@bN9n)a@0(dJygoW%VDXrSTr&8XT_! z-3(I`hOd`-6nC##vT^;8={;2u2nDh8NMa~jg>Xm^1asqgr?0EXKw?j*Dc3Nw9J5Our6n~?a?#cT(MUe{MtUuW=Cr z_>qjMq<;SJUJ9+>VR%DyV5Xb(2uEobs~H2Jercf)k~q&3^}UD6jzSJE?+Jg10nn?d z@n_-eaJX;CD%CUS8(v$l-sW!S&JV6mJB`vuvxo+-xMi*^*C@Z+$c3U>zvWv0oZ50$ zriZvxMScQXaaerpaA&*nJzA_2i-rb#u5`|>DQDaC29@@rWbk;|2a&LGhA(fG>L0R- z?~EvCdb2e@H0ORt0KpDzX4bCP8$cc}qjzEe4~U5#w_!|BKU24KavPAI1zZ}l~8 zOYr)%bk>8#BKf|&Z(N?3D_yin>&CW6S5O&DY4;Bn9PH6E+KlyOsW=|kyv0BG8&9l0 zqn%H5=*u!zNn0M#AgZBL_ghy^hYa9IsqfXceO;Yc6RqR72+^1J9~sm zS_Tx-Pu6P-giHu=5*r^2JIv~YQ} z-qX(*%e$>#H=@@Y)!2S}*EFrYw-PV`RNxPnM;g5SqhooW4QodB`CpAL00Yqq>Y?$( zJ9jUc?d|!^yKGZ|TiiR&x3pJ}IaLj86%UDFRQG_)qKXJtUiM^aN9ddS97S?H`UM64ORa;6X`r|~0J1k-Yh%XqI%Qq0w>S5cVf6-G_XyNil~ zr+_-TXO3C2o97kP)4mI}J9qHBGGCvSPqEyhVAb5=*%^FZdDK)tg7;z{4kg2dHmQo~ z%f!sTCi0lO&zh?!?$eB3Z`N$pp|vR0!5QzjxOQZZh6;$K!iplG^fFU#b5d~)ABpQU zOYB7X4n%O6z=6P`Qw9-!T9$&`IN@-@Kqw0RGkoPBy7sCqwRyC`$|DA>05foM7KMVM zX0Wz+schBo$-z~5%QHk}ZNthzwVAAH8QG|#63IA1%tJ>EyEWBZ#reAOI1z>tg>r33 zV*{rvGuG$RetoPcfd_&_%4yVD8-5D-xohXwQ?9g7}#2o_$B%F1A$5 zbWb%gB=8MFZ{pdCcdiV>4)CBUkS-bG2Cn{X4yk2&{?g6wLDqe9;d zs`0dslmka~8EWiGr0=e;H{Fy+ekt&O@8RYr^h#y0MPM+INPDlL{e$8t+`x2MHn2hX zVI-2Hdg%E;23h!lEbx$)1av{78Zd_f7_1i2Yiy9OGLY3t{64(he9_h3U8uHI zN^4g~{{+?bN$Kf^CBG-COQqPSuMN&Mr{obc+5q)#M9cOtk$3;p5tNldY(&Q81gejDxrG`5+*sHl_Mc;Utso9 zO;R#x0;LAn5J>gV)OhFpu*Cp$?B)2{y?%H=lnoILD+Wdzz8a(lzZz)9FFm+ope{=D zM0j04DT}xC_mjJ1m$PH{$VXePh;2iJzPbN&8(=OxvsbUm+o`YY zE9LZdTeq({;%c#Run4Uh{GPF9kce#TPlk*8EDEyAPrR3kf_61f&x~&A{}RQ|4an6B zD_wa)53wl^bq)A)BD{RYL4(yBhJF@a(NBtu?DUFNgI*7Q4LsN6wV}8T)niT;+w+82 z-Rn|%rwv}~ZcPLU5A==%zI*ME717oGm&^BrKg8e?Ucb|0^N;;I6?Z5yi^iG%=%d6q zV6R8_F6>Rra6=Z+wPvv6juS(nr(b~Wu{KcKTGyo`d-shucy%JozPH{|Bc(E-J>MON zBdx-bkL#_n=m+E`>4)>YwspltVNSn0WwC$#wxUE zLp$s>cyxYsbn~E%!qhq%-;_-EwNd$JGq(ErRlO_I z$rx)dL9Q0C>0Q6>$cwaa@CZ+?y3A0OP-s;}L3%rABLz)-*Nhb$Zxys5H&W@{&8pK) ze!ISIV2Hbw-QhacF|Fa0 zC8i44X1|x!(tZE?17`vjK^<|7IKqsuf53(GDKrFYYRW*E+13JxW3Wn#<&76dgzDL1 z5JC)j;2x;nvdsvN8&`1_4s)5aKZMP)LEteQ7&9W&z~;k?r$1&?{0qfi+c)k)8pk-E zVwADB@x8#Kg>?E87&$up>KUhbf7y8M02syaqr_QQR6ZQ7WL?0Vu=!)E(P)q~G*;#1 zmAJvkqDLGJ)w2kDI$RA)T*1PetKJ44z6>yrlMx)jjKH}AW{5onn9&vt^M-(;k1R4`@`$Z0ivk)mDl3UJkvcGCR}8@NRs)%?KC=L6(HM@6xTcYlmMJi$(qo2klQoECLT%%7dXQ zk*6tKe#cba+O>T>z0>FHtoUZ=3)tGeT8*-oI7hSMZZaBY)lHQrKNZOtHJtaxj`6h9 zE(z~8LzpjVP;t^Cwfz2V%(z2l07J2e7>`P|001%2NklyCtH zx^;0ktgmTGSIvCw9%XE2Fp{POf;F*$n#t}`ao{)X7~ZR_<4e)tiD~U|ItT)sfi;A8 z8_cT;xw)KAmdPn$#!7sp2zqAvt-+l&UA$bmBw86e-uWMA-YfXw0|1eyJb)b@L;vVgzyPAqQINA;J)=|mT|M4^zw5RD_6UgRf=K4NO*X1i zE?Wr8-MV$#+1a3fuWBOp!IGt}i8Lz*v1|52H1BN_+^|vRB8~B}1j9wyy~WEqUr*2Q z3fkElVH?5nceQQH#w-2V*6p9MN8!)tLT*%Ew|H^e`w0#{il%rOd&P_y>pY5jM|u`p zgh3Xms$yk0EetalZVnnF`ne6Z2GfNQKF&Kb(%AcOKT(+zE3Ygp%EfAdiy=peKG17e z3vM38yyEtBKYNd0{Qionx8BOF1Jgf8uC{Kj45F0D}J8->@4FG4z^hB`bQ8e%Hy%YA;TBou88P%kwxT_>DJs0DT=aooCuw(hKG*hn- zoky0oYS~UO9dauK@7y{JwSf${>ekfxGjhtqynRVMxo?NmAdiSNWTcCJ|M^=WCIOa(k@pg)MdNk*2~cl%k2qBfOhRK`MP z8_gHx&>l)mt6Q@nC&TMAc#ru~b(C=4W@KFEk3Lk6_#&-M->waWzr=PVK1*f@rbuy> z>+`d6=)vud@@|VDF_dd?qoCss} zzj@zrp7BvQziffVYHwS-IO~qS6W4myz27gQ{$bUbU=MGyXf+O|!R#Lvr%mNyrsqnB zLPO2k*wEESvMaBwCz&Nk!#*(2rc#`zLx%y<)JZg&!Vkxb<`$^5F}p9VA|gj{HZ!2- z{Ux@PXI}&AOA>da)>T_t03S#-?V~l3d)jo-CPvF5cY6A-zSZt0-RrwWr5wJcI@2lA z5BXT9;!5eEK_;Hg7{D^Vlcr1r|2{ZguoB@E?H9ACV8DAjW z#f4lSSRi`3g6wV~La#3PEb9dvaz7K4uyFvP`uvz4saM!Ha?|U`V?@5ORs2^|agwU3 z4?ghf7)0AmWoF>bu{)Y$6h;`<7+GguLk)|B)F zfqp7FyH|*4G`rolhbevCn7%rD9}f-XH!%`hS_~j%Y+xCx2V=sW*-zcr_Y5HaWj3Xd zW{~oCpmLmWcw>MV0LEur@{9+1+D8UJJ+q0S%?#pkBqnKGB~YB5Zo^YtYS-ep}F#|q8zmHO%j(lYdO$*>@@E&QN&(3)EbuCTmFQh{B4)xjC{)r-*}xH~~# z6UqXZz;xOTb~ zH8j0Zc{aQqn^=w=_g(HOANqGG>QLns+&WC%GDJyk#dIKjI(}Hps!tGo+(Gsr$aNfA%Ncm*PcWCd{i)irxXbAd-=9su?$3*--Yemguz8+$7~Q;i${(03rCC;=fxTr_MceR(kf9uYHZzwOFzqa?dwO_AjleV99i1=fcd2! z#2GYzDG?$#YE;z)OPBK37{f*`JbspwKP7kzh4^fOn_OUv=pD0X=T%~>Ttm8?3BuAd zOO8|PV`vE<)(0639fd^OZFEa;PTXON0Xd^=B&*-9k*P#zZ|^PDlA5O_7>Y}UqjwNc zNf_a`=mEpUUUPT%FR=Xy#fhsV-tD`p>tjLB8IbQAEQQj+0VIN{Xd2lZbbx4e1J%_y znWNM_h17N>!nTx_8khQ>n7^gx{e^F-jMn0a^ZmdB7nTgY)oxhaSA)!wvvuJ03qg>- zFqym8rr@N90#LOIfJuOsCVU#u{=w|oE|E5aF&qU5=tbDnDR3)0e_lARUpbq?4djYp z6W`thqiKsC6I$mHqhfF2{D8-p5n1s0Ql%GkAzoLZjG@DGD6A`eXbZzhFmGaZKM|Tr zeZfj@WQvwo73Pmnz0n5N8)p$@Xo~pKqp?fc<*@$3%}$4$#%IJ_&!gQMrZS!3S0qzY zV=HikY1EtX=C8i z!WIWLzj3lc$*BQ}5=2^urY1V?4h9Ye28bXMv3Ga;{4=d>$*aTw@GcFh5{V_}9|m3N zU(DedUlws+^5sIDD`UmS+=s(n_aK))82vfab)@jeL*1NjQ@=EZB7-fWPjVt_dWcdX zfVyLrD+&D9A99kZUnAr32WRg-t*PD%KmB;ulZ>_h6hxKd0+A!)k30c_v>1atPzI`& zd^dX#vxjY}x0614oHzpoXT)>?Wr>{rV~-XSyQQ5{R{8g>Tc{F$oDUDbLGfclbQObt!yi`#-;3-_1R}# zUa;E2-@^epH>%8Y@8VCZdtm!Y?h`1iym0a2&d*yg+WW(f5;v%(=U+nf?t-d!=H)Ss zTv#Li1Zt+0o4awgdti$_n5A)H z6EaXPIos9rYEo+f6R>Ca_U5Hfd-ep;MgReQom54ck(Ng9Rw-u1~d8#^c3X(8Z+`R7y>5rjBTr{9z|)S>M3C$mB&~X@0CNfG?Q(EX*!FUS$UM!bsUrJ z=88PF5zCF3Tja3qux^AitBfIuFrd>|-Xd67V1rBIvQ3@chEvQ~rpMX~fNa$w_R@7U z;JsNd;CwSYNM~coi~uVhxrT|=#!OH6Ju05%txSfIk$TC0vr2YF~0`uT( zmPMkT3norn3%yFLI>v}U`#h{Y=ON72p>^+O^Xa6sx`=!Fur8{M7N` z0R%JpM9~(+pI|~dE0!yTLTm;PJ6arz&FJG@O8tL6{B+<(5PI2PY7d(w&kAPdzlPoq zG3i{KC5$6*D1lgpWF@#m@zk5n6qo)-_V=G{k}9G&j*_69aQI_@2>&c;fMgLQn=s8K zv0{9tdieL^huOmQ%ks0fe%Zgv;fm)UEEBNg^l()BB|ERGo>jl~&U8b=`NCv_x4R*8 z&F*Rt(oOuYG;At@=Cz(Q7CCaH_y!xO<=AB13VHl69}iym-p8FcgV;6V0ebsALGIFE zPXHBw@F2=xP{0&gZ4|x7caRk>UGKlo%RhaaZ-le&bl7kyZ@tCq7?{n<&-h}v+I>KE zr4{Vh)wg{3D6s=}47VH6`l8{(|9CIe_0@d7A*Qf9Rn33I`i*0EA2~`a!>PxM;Ztf$ z8-L_#s*?irai&47!B-vetOm?dqn?#x)47M{s=I8?k};4ffuZ==G)#xw<@KIb1QV@+ zDyqVIyVUxr^X45_E;oh=Fv|Xo@>!a@=0182pdoaZfz&($zyYk)jK0H9XF`99saUtb zJtI~JE}Son{eoB}Gj~nXADW2#5DOL3UV{^U2pAC9_adS{1+(JivxlXvThOQdvbX!U z+X(;r;{&v(1;q%!>_=akRudSeE#zyA!yuEefN6}2!#BR~0Y-un@wBhhx+rgI9KE*| z-x6$MoZXhz2)12rBU~%>Q<=*iwJfJMeqMRo=4JKkS!El!m)2U@iS0O;&-?SkAIx^` zwXUKfO9lvpE`uoM?>N8r4v0o(;5mTj+oMtQ9z}ShC8W!xEI@$|VPE!rkhbR`?D%8b z?1nD$m!v-{2^yyJ_7RgMr~T#m7}5WULQDHF$_vzP{{agtwg!9A}M z+~iz~u&!9je-F9Q&v8Ec!>DhTi0RWNo$=2TXU?1%7tk>F^%8z-B*xCf=lLIG{S2#; zn+CLFLqGg9d!tppP*%Xz1P;&`g6a0(@QE%~x%qdmtA5CmMY%88FkjB&3vkTD5VY*C zHjGD$4qd#s9abGQ+n-|O!C(w60~<3gm@~iY8pQJe`b=$Z3<25)LBIaML&ig+f2+3b za)i*MH}g>CodCVm7}%qa@z5zV!9Fl%Z1GxSZs*}J#eif00cPGitE+FpGu;Dqn=00q zeHGf+u)TiMj^ePcU{lWJTP& zcOhIwCyPgj`IMJ8eHbm3MKBvSFvV_HOE8ITC%4p1eX?6X_on`}21D(%TxeT10g1U= ze5O?aD^gf#24|h?1e;!vdcJUr*__D32Lk~{>d(yl-_zZn>!g|ISJs(feDE-;ZycBO zJ;bup6?fD$@5#Tg^hTP+lGrG<7nBEi#ljbQT*5>8tx#I%slt#&VPZ{AI{!<*bGJVy z^MLp+e17rk9W~>#O)+h`CmeecYSrzzDL%_~M=$JiVZ|B-ubB&8SpRAV)m*kk*FN_| zBuocrI4o)7)i3D%n~K#`*4t0@IITT1ZqXy=IOy|}saTcQ#~=Qpo9oVtaf;YSPj_6yhqK=eySVg?x!Ct0u~%xJ?036wi(l?_ zfnFQA+r@c%9JEv8a*GE>UR-(`;D}C&k00+nqoX@Ktd4shkA%aI`&Hgi#6*eTWBxYp z!W~OU{DaxQ{@&Alj?QM0n?=Mt75QSOAvCV>x?!jBewkgj?@! z@J8Yx5RgWQK;x71@B_erEe3k|@jgSeXz2Xv5Qq*$5-GD#BJ5gS1E#l%IA46z zZ_V;6f2av*WCJr{^0tU85@mgM$HZ9#V+ixVK||e`=~Hj02??@aIe}2jMeGDj(=yTS zzd+O*(K$b(W4e0lnC(&6O|rX!9@l5xT>Oqu(42<%&qDtzk%ZL~GB3lvSjR4%!}7~4 zNF2uiObk-A1E%-MOT8{>?=~JU6>KiNtKM7}Gk?}TQ~UGsCp!C&xTI7biwTXqj;2pJ z+ZBzRt(fACdZB)k<`MrFjELuf*8xDL{>GDij%v#y^OcBM10r&#iDSXgkzYd?)+Hld zZTg-P8*zxAe*{e65M$7(-oe0Oh=Ibv)@;9HmYU#EHdsWQ)pvuRo~)cf6>;Xs({ zdR<&59VUJ}v-@bjs+>sG7k2gNzvC?E$5cH-F|Y4=aTydQ82k9K9wXCKb36zt_zN?7 zrDy*MGkZyZDIMX?j~?r?tFvmJhUdG^{`H3cef)U0Wxg0cw{~vl8#Bk3R=oXo@9y4R zkyGGr;-$8yRBokx1++J*FR~>3iOv?fiRSra6RW+F+IvvNfv5~#B!TjW{ zr3CZgquoINjq6!feT(fNm_r%9PbNT1sKjvCEPoOk8AK_WAv5q`hh%ic{WGd&bC)(pLT$Gxz{QNef&xN>+v*Vt^p$Qd(d`M-8LEM#OeP_N}~1q?vK z{BHB>1S2l9n)%5K-KQ&&PylMt(Tp@@xdC1U(NkOWVv}7~ecPN@w{0!I51z3vLflM` zE`CuHG0I0@RyLcRhrUe5Dj;A(w|sZEQMEASK=7Mept|{yoVI`AMG;4GhE!F2q0~hr za}+oz0eTaXu3?1%|A~Z8_z#oCt`ogO!&P6~axLsO<(@`PkT8!RU<68QtHU&WpG*M! zv2nwe>Y*RJaT%G255jm+pb?hA2#e>J!O|~~Ad^nakoZg+n58pSwx+*e{nv0-HPwVN zBn?UH2S|ilj@zl6Pe16$d#)aXW9qOc$_7)( zlLFRR2`uz3DH_>*1cGw5LmR|PKW`BYpK8h?0tksC80*yAB* zv@hCWw+98mv>Y(oAeBYhz??Zvmq#Mf-EuK)89%k0(TR01@T1275kdO4XsQ~`c3)c? zV^PTINi&G#5=4k~dL{-?A2I6F{QNpX%Jk{_WVmQg)2)Wj0lSKH0&#Ym29U%mqBIo(`UNf zp2^IiAj$HemX&$dL@(-fJx=rSic;eRXdy+@G7NGr&&mhyc_N6uqM3}nn4Z0Lci7w# zZs4~seWBxMKC&2l&`eOB+X}~dj{k(95Ue9w8=07l5+=4Es4w-d!YfPa6_-*RjIf_q zyx4glrO$n)+kZNxa}u5HQiHn97%Azn2}uO`0JTaY7G=GLOAZ#?Z^P9dS5lxsNAq>}P_u69Wacte?0%_2 zb1gR>JnALM9B7qbX}KiT>%2G^_$gpOep@uNKWD?zdGGW&s$oau8MrUW^SG>8dN=~A z7BiNnrHP9MoL9C=PRJnHNyUvl>*Ze8{GDaG-p0dUT=qnl>Cvj_0L-83d|s6WO(i2C zUoJ#F@+ifGxP(U>r^t8VKo}CcBnKkQSdFlU`nTOQeM8lvURm0uOCIZTvi|vwcJOcE zH9+j4Z_|--0Zz(o__-SveEekBIUO?iiQ$krzxOY8tw1uT{u5kl!Df_G#utUpexk>< zez)-&kBp(r6^#m@YY+jtR^(_d2=@YtJ`=HY3H)@zfryU zYUWDA>DL%fB%Ol-vv&F18*wc4FTw%7L0Z8&i~uNf!cP$cw4xTo2+mkv42n5)*~{G~ zRaYA$q2*$9&0!0BOey<_@Uo}VvOsL*;Vzu7SvB{iZbwzuv0p-N?)NkCP@8`4SJ$u6 zFUv|d=21B%oq9)(XS-&&6rt05Vj!X`-{HEMKiT~bx7vKSlweSw5&&U-cwycd+e@l{ zXDYnC%QPFhb>$DQ*{O|gW14b836Ylqu{^DIqQ>KVwa3j)RGBGVq=UYwv>#A3qe;x1 z*p|lUmFPR+=aS8@FMhZ8fU2_SuQ*p|#bYYEn4@O%w{2L^rk5Hb0y*T0Nir*)#a^ze zr!#-s!fE3sHtE96uYdAlPH}WYpw9wRJmbPaRg_`)m#| z7+Tu3!&QzefNKUW4Kohu$K>r-2lew;)UL{QGX==Sps|}H$BL^>1|&<0c}?DVr4QPx z2K#JV^><4iVs77+EV!Gv2Gv8EtwH9_a51xXqtR{4DE6ldcat#BW*mTVf`fsd90nS{ zpB8!!=8$xZV+$ZM3UGTga+1Uw48zWkkCEt|^-bE5DfiZ3_F5MxLx5Rzz587EliJ42 z58b-4{K%#;JcvI%)4t)7*Lq!M)<%DZRl>w@#E2F;k`(y$w_OCx&9{``?;aLUg>h872 zh9TfXNG9S|P8DlV!|zu7v7H)ith~^JZKC$Zlvw3)%moo-L88-rIuh!7Qopqw0mAKiuxv>)k#byYAY$;=EUWJbG5-iq(tv@B3vG;e%pB!I-6C4aU z82Djh0H3j>8e`7ed_8(Xmkg=<8so>8tV6<4@#MYU;!J0+a6dKx$pH^Vr>b|tK+v?7 zjjh%XB2Pvr86FW=uI*ug#vnbyFD40l%b6h&MAsCxn(=B&OTl3PEn~`wbujP)$3Ot$ zCqc6KmbHQC%ir7e1INI3YyLrJ0=BcYpN;skI;!xsZp+>-X^BN$igRU!)Y$wT)DI~x z+G(hSd@CIzvCrPFRMd6`?li>*N6d-qm zE7w??EY%YY#s1s*l(JR$8TnM6AA^jI8xIb8TfK70$IO#;0ycZnn~(GM(UH z;3tCtnt5}w?M>KdEy7y>7p0|~hk@AdDOd^`%5_Vw;E~8NXj3J3^O{(@9S^Zv&VgQz zqt5+3A->gOzfUG$b9K5m3$%O-Kx$ub-sXEhoz=70492d5{@F2D0eIb-Sgjc1siQ6{ zeTXtjhGDC*k>QtR30!K-5=U$EmfZkp;CVtfdcvfyfhmy>6<6?H{k~H_kHa|(fRN~f zpDqUIq__YMJ^E{1uCjgAMT#0~2R{#nY49?$ga0`e7*V}XMwP@Y;65|%nxzS!^gYr6 zM5Mkg0|+?`xmZrIC2poei2?=q4imNW-0iSum&;5@ON?AY zBpQ3lHlUIuriJ^Nco z_H)u*4!n2_&iZ9jKgwcPlt{-?GS(g}Ntv7%2LnGb48*?^^~%MUs+TXSRImD6Sl1W0 z7Yp_BqFw6sMH3}f62GZEqKRhn@zCrj(u6b{#=g@m-wO7fsei2aIoc5R-1@`-nbcw% z8uQsD;U14`R(~9%#c~p|JCBVRNFvCMu?OaXe2LJH-639Fi?Tb?vn58batag2TvAad z{mnt}KNmax1pydpw6@_RX9pMv1(*Q@vOA1?)IUbmvIi)xl_#t3`X3_y} zsMmZo1-{}QV>&*sNJLL+y6Vnl_0jjVn?5==7U1(+6%7fYs5g9dCe!P=@c@@DpJ6S9 z*?+)vUA=s)sj&e@$GX?pO}vghusc&-Uw#S??HURN4wvxyN{svtuR`<_ZWQ;>rf?Bf z4lx}QH=zjw-i9d9w;K=YbtPwUwSHIZ9=^yT`dp|ttUR5?s+e&=EW*;~3{c}WAw@?TEiSQbKDl(bZADW4PPVBklN0h)D_N8Ot@=ZI+C zT`UUMHt>~Jl4uINQkSg5}?I!FG$}I3C4KiZw zBVJeBmvQX`FrdHc|NL1oL|Iw--MOD*74kAS-GkV|g+7_5X)j`Lwf-d2Q<(t$__S0c zPMtgu_76SuBU9P*W7&6y0qh?R9cTvw-yH*ys3EQAlQeS@i^U3Bx?hPI+{eZk9Lc9F4FIAVQ zRj19XQR~^~&0V=Y=4XZhwcBoV51;H3D zfTp18`J}vyEPcjzqGhc#@W&I~gy5!GI>`-5)r$!EzAysv-(y|L@HBfM(T{y^&i#o) zOD>JO6C4aU7&vekAOlH+Sh&kZ?6%cf#1wM<=D}nN=6#%RjQys_+P^EbX8uW_c6r(9 z&%wL`97!;8iu3GOa3$0l`tpw!$0k_0K`BODE5(cF>*o@XKvK-iyTAl&K%Wx|{ zRcOAOh336UDB9Ib^G%DS)$NjS0ZGfOEY9C%7_en?gj5#}|-@gA6d=mEO5Jhcngm zH1J5pU8|vohCurLffg764NQBp=APB<7)c4scEbSdD9cG)V!ABI`Qu=~!N9k}08vN~ zz*$07Kl$o$ro=8{^_QGDX9!`uF<11rM;x zm$z5U;56`r5Lf(TvDbSJSIv9Ph+3;;L>9;HoA3cut6s5mHrxlC%z5NSQ+K<-v;N9( z{Gf<#|I(Z}X@mu{=oOycaf+zh_%8EyevBy|&g-|27T(%b#=lMjV~FD*u^-NJ5(ey* zfW1sCMk);g=oPb9a9xh;&$tMV#6-jjqy&*3{ZqSMszgfvC^m?$cs1axA)J0m$3ivN zeM?PXOI345Fj+DH?b@XUB4XUZ*6cVErNywoQ`wdC<|^*s9kBFWV$DXdv~+@l0S5!$ z0RvzFByNSf2z*QztG{0|2Osd|h#Q7G#6Ynn%oTaqxNpLnvJwlnX|k7E^DIp2KphN! z+jq6f&1-}Rb>V5X)`oE2E~sWBt#9|)>WZ(PWTxx4EVyGL=9U#)Q*RgH`afW|(V`({ zbqB7vZkO~qRzs_iI$`i@xi?)AM(|-&E17_i+2dxldR-rM;%+WNkQhbp#0q2(&h_nP z6D-3Wo~G9Cu46IO7ut0S%8EcNlm^Cek8FMbEe<41U{m|#ywgwGYJhqcdj{7PxQGeR zJ^{hYLE^ut)9Z?hpDm(zO;Gja9xN&hbB(vVYkWmuYiT1Rh|loVKdifqg)3&tqU6CZ z@gg#W!w7(BCpZ{55E!tCJC$S+c(Shd;IQ`x$66b~jNCp@+CGU* ztPl3;wH{_{gLeM-dgP6N#0j(R`@YBqx6oL9DO^&(mWQnQ+m}S8xQC3)O7r377a_Ad zXJy+}mewBhr3?|JI&Ci7hG=Xqya9Z*E{8=L8rY3T1Q|{jWc6)4>>heXIWSg}<=nkI zjd9(_yf|#UVpoU-Cc&YY6ZQoI^y4Ll_>U>IdgvD+%fM;4EG9tm{z7J?UDoXnSepG4 zC;QO4lF|~(iEy{#g<9JG>Yv<6;w1PH!5|~Ul|Pn@R%>e(Tg%Y|ub}`!fYFK@-amsu zZ|ADM!N8Oq@mY*Da4i_;uY&;x1K$<{wv}#EepuYkg;!BBxZJN`(+SFy_>5P{2q?bk zyG<7HroaY?0$>mtF#NcMkz-&Lx3~X5EzRwsS0^V96PHh`%kZ1;*w%ACz zdZI_vEWg3pLXZrAW)X?OE27x7SNVj@-*+5U`>!2KSu zwSb)*g28&gvJfP9u+UI|>ypgG{ex!N?JZ|TvD#IXVzkmMgA?Ikz`=ln0S5!$1p}!} zfXX69K;d-j#_q*EfSrA*SJt2_b%2QLn8hJdZ?N(c*q_nTIcXbVuLq1}3OBgR^|=%W zacCyA6a?gd)FQneibe$f5wXrq7KbyayY;)y`ONVJ4y-uA!GMDS2LsH0%Z2aPgMrpCP4 zF6K_tU_BV+yyk}3gD3q_!UtN~{E;cn9|r>t1{@4H7&t%}u!+50oXr$yc3**u+&KLX z7cm2inB##M0WK-3$+)Wf(ME0b1e^)%h8yp%2VBW` zaWLRuz`?*l!hkIXf@3K*9~WuooEER!PK&=fHwOa_1{@4H82B+_z+M$3_RV1ciP&(S z9Sk@aa4_&A!$2zGx8H5z-?SP0BkQ=+9R~vr1{@4H7;rG)V8FqEg8>HvKWPm7KZ~?1 UkR#h1#Q*>R07*qoM6N<$f`xGfOaK4? diff --git a/docs/mkdocs/docs/images/callback_events.svg b/docs/mkdocs/docs/images/callback_events.svg new file mode 100644 index 000000000..544bf4f6a --- /dev/null +++ b/docs/mkdocs/docs/images/callback_events.svg @@ -0,0 +1,39 @@ + +Example of the parse events a callback sees for the J​SON text {"name": "Berlin", "location": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete. +J​SON to parse +callback events +{ + +"name" + +: "Berlin" + +, +"location" + +: [ + +52.519444 + +, +13.406667 + +] + + +} + + + +object_start + +key + +object_end + +array_start + +array_end + +value + diff --git a/docs/mkdocs/docs/images/chunk_buffering.svg b/docs/mkdocs/docs/images/chunk_buffering.svg new file mode 100644 index 000000000..5b3c514d7 --- /dev/null +++ b/docs/mkdocs/docs/images/chunk_buffering.svg @@ -0,0 +1,50 @@ + +Buffering chunks until the value is complete: five chunks of {"name": "Mary", "list": [1, 2, 3]} appended to a buffer. +After chunks 1 to 4 the buffer has 4, 12, 19, and 30 bytes, and parsing it fails at byte 5, 13, 20, and 31: one past the end, so the input is incomplete and the program waits for more. After chunk 5 the buffer of 35 bytes parses as one complete value. For the input [1, ] of 5 bytes the error is at byte 5, before the end, so it is a real syntax error. Every attempt parses the buffer from byte 1, so 4 + 12 + 19 + 30 + 35 = 100 bytes are parsed for a 35-byte value, and the work grows quadratically. +chunk +buffer, parsed from byte 1 each time +result +chunk 1 + +{"na + +error at byte 5 = size + 1 +incomplete: wait for more +chunk 2 + + +{"name": "Ma + +error at byte 13 = size + 1 +incomplete: wait for more +chunk 3 + + +{"name": "Mary", "l + +error at byte 20 = size + 1 +incomplete: wait for more +chunk 4 + + +{"name": "Mary", "list": [1, 2 + +error at byte 31 = size + 1 +incomplete: wait for more +chunk 5 + + +{"name": "Mary", "list": [1, 2, 3]} +parsed, size 35 +complete: one value + +other input + +[1, ] + +error at byte 5, size 5 +before the end: a real error +Bytes parsed: 4 + 12 + 19 + 30 + 35 = 100 for a value of 35 bytes. +Every attempt starts again at byte 1, so the total work grows +quadratically with the size of the value. + diff --git a/docs/mkdocs/docs/images/compile_times.svg b/docs/mkdocs/docs/images/compile_times.svg new file mode 100644 index 000000000..e8e904a60 --- /dev/null +++ b/docs/mkdocs/docs/images/compile_times.svg @@ -0,0 +1,60 @@ + +Change in compile time against the default configuration at -O0, Apple clang and G​CC 16 +Grouped horizontal bars, negative is faster. json_fwd.hpp instead of json.hpp on a header-only translation unit: Apple clang -62 %, G​CC 16 -82 %; extern template on a parsing translation unit: Apple clang -7 %, G​CC 16 -21 %; JSON_NO_AUTOMATIC_UDLS on a model translation unit: Apple clang -11 %, G​CC 16 -11 %; JSON_NO_AUTOMATIC_UDLS on a parsing translation unit: Apple clang -1 %, G​CC 16 -2 %; JSON_HAS_FILESYSTEM=0 + JSON_NO_IO on a model translation unit: Apple clang -8 %, G​CC 16 -11 %; JSON_HAS_FILESYSTEM=0 on a model translation unit: Apple clang -5 %, G​CC 16 -9 %; JSON_NO_IO on a model translation unit: Apple clang -2 %, G​CC 16 -2 %; JSON_USE_GLOBAL_UDLS=0 on a model translation unit: Apple clang 0 %, G​CC 16 -1 %. + +Apple clang + +G​CC 16 + +−80 % + +−60 % + +−40 % + +−20 % +0 % +← faster +slower → +json_fwd.hpp instead of json.hpp · header-only TU +json_fwd.hpp instead of json.hpp, header-only TU (Apple clang): −62 % +−62 % +json_fwd.hpp instead of json.hpp, header-only TU (G​CC 16): −82 % +−82 % +extern template · parsing TU +extern template, parsing TU (Apple clang): −7 % +−7 % +extern template, parsing TU (G​CC 16): −21 % +−21 % +JSON_NO_AUTOMATIC_UDLS · model TU +JSON_NO_AUTOMATIC_UDLS, model TU (Apple clang): −11 % +−11 % +JSON_NO_AUTOMATIC_UDLS, model TU (G​CC 16): −11 % +−11 % +JSON_NO_AUTOMATIC_UDLS · parsing TU +JSON_NO_AUTOMATIC_UDLS, parsing TU (Apple clang): −1 % +−1 % +JSON_NO_AUTOMATIC_UDLS, parsing TU (G​CC 16): −2 % +−2 % +JSON_HAS_FILESYSTEM=0 + JSON_NO_IO · model TU +JSON_HAS_FILESYSTEM=0 + JSON_NO_IO, model TU (Apple clang): −8 % +−8 % +JSON_HAS_FILESYSTEM=0 + JSON_NO_IO, model TU (G​CC 16): −11 % +−11 % +JSON_HAS_FILESYSTEM=0 · model TU +JSON_HAS_FILESYSTEM=0, model TU (Apple clang): −5 % +−5 % +JSON_HAS_FILESYSTEM=0, model TU (G​CC 16): −9 % +−9 % +JSON_NO_IO · model TU* +JSON_NO_IO, model TU (Apple clang): −2 % +−2 % +JSON_NO_IO, model TU (G​CC 16): −2 % +−2 % +JSON_USE_GLOBAL_UDLS=0 · model TU* +JSON_USE_GLOBAL_UDLS=0, model TU (Apple clang): 0 % +0 % +JSON_USE_GLOBAL_UDLS=0, model TU (G​CC 16): −1 % +−1 % + + diff --git a/docs/mkdocs/docs/images/compile_times_modules.svg b/docs/mkdocs/docs/images/compile_times_modules.svg new file mode 100644 index 000000000..cb346e2b7 --- /dev/null +++ b/docs/mkdocs/docs/images/compile_times_modules.svg @@ -0,0 +1,30 @@ + +Compile time per translation unit with import nlohmann.json at -O0, and the one-off cost of the module +Horizontal bars, negative is faster, medians of nine runs, -std=c++20 for both. model TU, Apple clang: -48 % (1311 ms to 680 ms); model TU, G​CC 16: -47 % (2156 ms to 1153 ms); parsing TU, Apple clang: -42 % (1387 ms to 800 ms); parsing TU, G​CC 16: -36 % (2838 ms to 1810 ms); one-off build, Apple clang: 1630 ms; one-off build, G​CC 16: 2538 ms. + +Apple clang + +G​CC 16 + +−50 % + +−25 % +0 % +← faster per TU +model TU · import nlohmann.json +model TU, Apple clang: 1311 ms by default, 680 ms import nlohmann.json (−48 %) +−48 % +model TU, G​CC 16: 2156 ms by default, 1153 ms import nlohmann.json (−47 %) +−47 % +parsing TU · import nlohmann.json +parsing TU, Apple clang: 1387 ms by default, 800 ms import nlohmann.json (−42 %) +−42 % +parsing TU, G​CC 16: 2838 ms by default, 1810 ms import nlohmann.json (−36 %) +−36 % + +One-off cost · built once, reused by every TU + +Apple clang: 1630 ms once, saves 587 to 631 ms per TU + +G​CC 16: 2538 ms once, saves 1003 to 1028 ms per TU + diff --git a/docs/mkdocs/docs/images/compile_times_pch.svg b/docs/mkdocs/docs/images/compile_times_pch.svg new file mode 100644 index 000000000..95ddd970b --- /dev/null +++ b/docs/mkdocs/docs/images/compile_times_pch.svg @@ -0,0 +1,30 @@ + +Compile time per translation unit with a precompiled json.hpp at -O0, and the one-off cost of the PCH +Horizontal bars, negative is faster, medians of nine runs, -std=c++17. model TU, Apple clang: -43 % (1045 ms to 594 ms); model TU, G​CC 16: -41 % (1789 ms to 1060 ms); parsing TU, Apple clang: -38 % (1148 ms to 714 ms); parsing TU, G​CC 16: -25 % (2498 ms to 1884 ms); one-off build, Apple clang: 771 ms; one-off build, G​CC 16: 2254 ms. + +Apple clang + +G​CC 16 + +−50 % + +−25 % +0 % +← faster per TU +model TU · with PCH +model TU, Apple clang: 1045 ms by default, 594 ms with PCH (−43 %) +−43 % +model TU, G​CC 16: 1789 ms by default, 1060 ms with PCH (−41 %) +−41 % +parsing TU · with PCH +parsing TU, Apple clang: 1148 ms by default, 714 ms with PCH (−38 %) +−38 % +parsing TU, G​CC 16: 2498 ms by default, 1884 ms with PCH (−25 %) +−25 % + +One-off cost · built once, reused by every TU + +Apple clang: 771 ms once, saves 434 to 451 ms per TU + +G​CC 16: 2254 ms once, saves 614 to 729 ms per TU + diff --git a/docs/mkdocs/docs/images/exception_map.svg b/docs/mkdocs/docs/images/exception_map.svg new file mode 100644 index 000000000..66931e2ff --- /dev/null +++ b/docs/mkdocs/docs/images/exception_map.svg @@ -0,0 +1,174 @@ + +Exception id map +Class tree: std::exception is the base of json::exception, which is the base of the five exception families parse_error (ids 1xx), invalid_iterator (2xx), type_error (3xx), out_of_range (4xx) and other_error (5xx). Each family lists the ids documented on this page, 68 in total; every id links to its section. + + +std::exceptionC++ standard library base + +json::exceptionid, what() · catch-all + +json::parse_error1xx · 15 ids · adds member byte +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +112 +113 +114 +115 +116 + +json::invalid_iterator2xx · 14 ids +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 + +json::type_error3xx · 21 ids +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 + +json::out_of_range4xx · 16 ids +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 + +json::other_error5xx · 2 ids +501 +502 + + +Exception id map +Class tree: std::exception is the base of json::exception, which is the base of the five exception families parse_error (ids 1xx), invalid_iterator (2xx), type_error (3xx), out_of_range (4xx) and other_error (5xx). Each family lists the ids documented on this page, 68 in total; every id links to its section. + + +std::exceptionC++ standard library base + +json::exceptionid, what() · catch-all + +json::parse_error1xx · 15 ids · adds member byte +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +112 +113 +114 +115 +116 + +json::invalid_iterator2xx · 14 ids +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 + +json::type_error3xx · 21 ids +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 + +json::out_of_range4xx · 16 ids +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 + +json::other_error5xx · 2 ids +501 +502 + diff --git a/docs/mkdocs/docs/images/json-dark.svg b/docs/mkdocs/docs/images/json-dark.svg new file mode 100644 index 000000000..5757e976e --- /dev/null +++ b/docs/mkdocs/docs/images/json-dark.svg @@ -0,0 +1 @@ +JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each) \ No newline at end of file diff --git a/docs/mkdocs/docs/images/json-light.svg b/docs/mkdocs/docs/images/json-light.svg new file mode 100644 index 000000000..97bd55bed --- /dev/null +++ b/docs/mkdocs/docs/images/json-light.svg @@ -0,0 +1 @@ +JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each) \ No newline at end of file diff --git a/docs/mkdocs/docs/images/json_pointer_flatten.svg b/docs/mkdocs/docs/images/json_pointer_flatten.svg new file mode 100644 index 000000000..93314187e --- /dev/null +++ b/docs/mkdocs/docs/images/json_pointer_flatten.svg @@ -0,0 +1,52 @@ + +flatten() turns the nested example document into a flat object, and unflatten() turns it back. Left, the document as a tree: array with the elements A, B, C, and nested with one = 1, two = 2, and three with the elements true and false. Right, the flat object with one key per leaf: /array/0 = A, /array/1 = B, /array/2 = C, /nested/one = 1, /nested/two = 2, /nested/three/0 = true, /nested/three/1 = false. Each leaf is joined to its key by a line. + +nested value +flat object: key = J​SON Pointer + +flatten() + +unflatten() + + + + + + + + + + + + +"/array/0": "A" + + +"/array/1": "B" + + +"/array/2": "C" + + +"/nested/one": 1 + + +"/nested/two": 2 + + +"/nested/three/0": true + + +"/nested/three/1": false +root +"array" +0: "A" +1: "B" +2: "C" +"nested" +"one": 1 +"two": 2 +"three" +0: true +1: false + diff --git a/docs/mkdocs/docs/images/json_syntax_number.png b/docs/mkdocs/docs/images/json_syntax_number.png deleted file mode 100644 index be23ffa69d02833fb3b109b6a8c3e50e9701e2ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37014 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�)(qmJGY<%4C?Rkp9U{5) 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 JRK_gue*uKEw#)zk diff --git a/docs/mkdocs/docs/images/json_syntax_number.svg b/docs/mkdocs/docs/images/json_syntax_number.svg new file mode 100644 index 000000000..3a837a08c --- /dev/null +++ b/docs/mkdocs/docs/images/json_syntax_number.svg @@ -0,0 +1,66 @@ + +Railroad diagram of the J​SON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +− +0 +digit 1–9 +digit +. +digit +e +E +− ++ +digit +number +fraction +exponent + diff --git a/docs/mkdocs/docs/images/json_view_edit_0.svg b/docs/mkdocs/docs/images/json_view_edit_0.svg new file mode 100644 index 000000000..0768f9d23 --- /dev/null +++ b/docs/mkdocs/docs/images/json_view_edit_0.svg @@ -0,0 +1,57 @@ + +Step 1 of 5: parsed +After parse: nine nodes, nothing owned yet. The dump with number_format::source is {"port":6379,"price":19.90,"tags":["eu","a\"b"]}. + + + + + + +parsed index: fixed, never moves +the source text is never written + +0 +object +{ } 3 + +1 +key +port + +2 +uint +6379 + +3 +key +price + +4 +float +19.90 + +5 +key +tags + +6 +array +[ ] 2 + +7 +string +"eu" + +8 +string +a"b +owned storage: allocated on demand, never moves + +empty: a read-only json_document has none of this + +view v = root()["tags"] (taken after parse): +valid, sees every edit +iterator over tags (taken after parse): +valid +dump(): {"port":6379,"price":19.90,"tags":["eu","a\"b"]} + diff --git a/docs/mkdocs/docs/images/json_view_edit_1.svg b/docs/mkdocs/docs/images/json_view_edit_1.svg new file mode 100644 index 000000000..44883e7e1 --- /dev/null +++ b/docs/mkdocs/docs/images/json_view_edit_1.svg @@ -0,0 +1,57 @@ + +Step 2 of 5: doc.set(doc.root(), "port", 6380) +set: the node of the integer is overwritten in place. The dump with number_format::source is {"port":6380,"price":19.90,"tags":["eu","a\"b"]}. + + + + + + +parsed index: fixed, never moves +the source text is never written + +0 +object +{ } 3 + +1 +key +port + +2 +uint +6380 + +3 +key +price + +4 +float +19.90 + +5 +key +tags + +6 +array +[ ] 2 + +7 +string +"eu" + +8 +string +a"b +owned storage: allocated on demand, never moves + +empty: a read-only json_document has none of this + +view v = root()["tags"] (taken after parse): +valid, sees every edit +iterator over tags (taken after parse): +valid +dump(): {"port":6380,"price":19.90,"tags":["eu","a\"b"]} + diff --git a/docs/mkdocs/docs/images/json_view_edit_2.svg b/docs/mkdocs/docs/images/json_view_edit_2.svg new file mode 100644 index 000000000..159af3a4a --- /dev/null +++ b/docs/mkdocs/docs/images/json_view_edit_2.svg @@ -0,0 +1,70 @@ + +Step 3 of 5: doc.erase(doc.root()["tags"], 0) +erase: the array moves to a block of links in owned storage. The dump with number_format::source is {"port":6380,"price":19.90,"tags":["a\"b"]}. + + + + + + +parsed index: fixed, never moves +the source text is never written + +0 +object +{ } 3 + +1 +key +port + +2 +uint +6380 + +3 +key +price + +4 +float +19.90 + +5 +key +tags + +6 +array +moved +[ ] 2 + +7 +string +"eu" +unreferenced + +8 +string +a"b +owned storage: allocated on demand, never moves + + +0' +header +[ ] 1 + +1' +link +→ 8 + + +free + + +view v = root()["tags"] (taken after parse): +valid, sees every edit +iterator over tags (taken after parse): +invalid: tags no longer uses the parsed layout +dump(): {"port":6380,"price":19.90,"tags":["a\"b"]} + diff --git a/docs/mkdocs/docs/images/json_view_edit_3.svg b/docs/mkdocs/docs/images/json_view_edit_3.svg new file mode 100644 index 000000000..562977c49 --- /dev/null +++ b/docs/mkdocs/docs/images/json_view_edit_3.svg @@ -0,0 +1,80 @@ + +Step 4 of 5: doc.push_back(doc.root()["tags"], "new") +push_back: a new node, and a link appended to the block. The dump with number_format::source is {"port":6380,"price":19.90,"tags":["a\"b","new"]}. + + + + + + +parsed index: fixed, never moves +the source text is never written + +0 +object +{ } 3 + +1 +key +port + +2 +uint +6380 + +3 +key +price + +4 +float +19.90 + +5 +key +tags + +6 +array +moved +[ ] 2 + +7 +string +"eu" +unreferenced + +8 +string +a"b +owned storage: allocated on demand, never moves + + +0' +header +[ ] 2 + +1' +link +→ 8 + + +2' +link +→ 3' + + +3' +string +"new" + +edited text + +new + +view v = root()["tags"] (taken after parse): +valid, sees every edit +iterator over tags (taken after parse): +invalid: tags no longer uses the parsed layout +dump(): {"port":6380,"price":19.90,"tags":["a\"b","new"]} + diff --git a/docs/mkdocs/docs/images/json_view_edit_4.svg b/docs/mkdocs/docs/images/json_view_edit_4.svg new file mode 100644 index 000000000..29e6bc504 --- /dev/null +++ b/docs/mkdocs/docs/images/json_view_edit_4.svg @@ -0,0 +1,126 @@ + +Step 5 of 5: doc.set(doc.root(), "retries", 3) +set of a new member: the object moves to a block as well. The dump with number_format::source is {"port":6380,"price":19.90,"tags":["a\"b","new"],"retries":3}. + + + + + + +parsed index: fixed, never moves +the source text is never written + +0 +object +moved +{ } 3 + +1 +key +port + +2 +uint +6380 + +3 +key +price + +4 +float +19.90 + +5 +key +tags + +6 +array +moved +[ ] 2 + +7 +string +"eu" +unreferenced + +8 +string +a"b +owned storage: allocated on demand, never moves + + +0' +header +[ ] 2 + +1' +link +→ 8 + + +2' +link +→ 3' + + +3' +string +"new" + +4' +uint +3 + +5' +header +{ } 4 + +6' +key +port + +7' +link +→ 2 + + +8' +key +price + +9' +link +→ 4 + + +10' +key +tags + +11' +link +→ 6 + + +12' +key +retries + +13' +link +→ 4' + + + +edited text + +newretries + +view v = root()["tags"] (taken after parse): +valid, sees every edit +iterator over tags (taken after parse): +invalid: tags no longer uses the parsed layout +dump(): {"port":6380,"price":19.90,"tags":["a\"b","new"],"retries":3} + diff --git a/docs/mkdocs/docs/images/json_view_layout.svg b/docs/mkdocs/docs/images/json_view_layout.svg new file mode 100644 index 000000000..72277c1f1 --- /dev/null +++ b/docs/mkdocs/docs/images/json_view_layout.svg @@ -0,0 +1,182 @@ + +The same J​SON text parsed by basic_json::parse() and by json_document::parse() +The text {"port": 6379, "price": 19.90, "tags": ["eu", "a\"b"]}. Top: basic_json::parse() builds a tree: the root json value points to a std::map whose three nodes are separate heap allocations; the price is stored as the double 19.9; the tags array is a std::vector of two json values, each pointing to its own std::string. Bottom: json_document::parse() keeps the 54 bytes of source text and builds a flat index of nine 16-byte nodes in document order: object, key port, unsigned integer 6379 (the converted value is in the node), key price, float, key tags, array, string eu, and an escaped string. Keys, the float, and the plain string point back into the source text; the escaped string points into the document's buffer of decoded strings. A json_view is two pointers: one to the document and one to a node, here the array. + + + + + + + + + + +basic_json::parse(): a tree, every array, object, and string in its own heap allocation + +json + + +std::map + + +"port" +6379 + + +"price" +19.9 +double: 19.90 is gone + + +"tags" +• + + +std::vector + + + +json +json + + + +std::string eu + +std::string a"b + +a separate heap allocation + +json_document::parse(): the source text stays where it is; one flat index of 16-byte nodes +source text, 54 bytes (borrowed, or owned by the document) + + + + + + + + +{ +" +p +o +r +t +" +: +6 +3 +7 +9 +, +" +p +r +i +c +e +" +: +1 +9 +. +9 +0 +, +" +t +a +g +s +" +: +[ +" +e +u +" +, +" +a +\ +" +b +" +] +} +0 +10 +20 +30 +40 +50 + +json_document: owns the node index and the buffer of decoded strings +node index: 9 nodes × 16 bytes = 144 bytes, in document order + +0 +object +len 3 +next 9 + +1 +key +off 2 +len 4 + + +2 +uint +6379 +(converted) + +3 +key +off 16 +len 5 + + +4 +float +off 24 +len 5 + + +5 +key +off 32 +len 4 + + +6 +array +len 2 +next 3 + +7 +string +off 41 +len 2 + + +8 +string +escaped +len 3 +decoded strings + +a +" +b + +json_view v = doc.root()["tags"] + + +doc* +node* + + +integers are converted while parsing and kept in their node; +floats and strings point back into the text + diff --git a/docs/mkdocs/docs/images/json_view_lifetime.svg b/docs/mkdocs/docs/images/json_view_lifetime.svg new file mode 100644 index 000000000..8936b07c7 --- /dev/null +++ b/docs/mkdocs/docs/images/json_view_lifetime.svg @@ -0,0 +1,61 @@ + +When a json_view becomes invalid +Four timelines. First: a document borrows a std::string; when the string is destroyed, the document and every view taken from it dangle. Second: doc.read() re-parses the document; views taken before are invalid, a fresh view from root() is valid. Third: doc.shrink_to_fit() has the same effect. Fourth: moving the document does not invalidate its views; destroying the moved-to document does. + + + + +alive / valid + +dangling: using it is undefined behavior + +time → +The borrowed text is destroyed +std::string text + +json_document doc + + +json_view v + + + +text destroyed +every view, and the document itself +The document is re-parsed +json_document doc + +json_view v + + +v = doc.root() + + +doc.read(other) +views taken before +a fresh view is valid again +The document is shrunk +json_document doc + +json_view v + + +v = doc.root() + + +doc.shrink_to_fit() +views taken before +a fresh view is valid again +The document is moved, then destroyed +json_document doc + +moved = std::move(doc) + +json_view v + + + +std::move: views stay valid + +moved is destroyed + diff --git a/docs/mkdocs/docs/images/memory_over_time.svg b/docs/mkdocs/docs/images/memory_over_time.svg new file mode 100644 index 000000000..46b539ec8 --- /dev/null +++ b/docs/mkdocs/docs/images/memory_over_time.svg @@ -0,0 +1,20 @@ + +Illustrative memory use over time while a large array of records is parsed, not a measurement. +Four curves. D​OM, parse(): rises steadily until the whole tree is in memory. json_document: starts at about the size of the text, which it keeps, and grows a little while its index is built. Parser callback that discards every record: stays flat at the size of one record. S​AX: stays flat and lowest, because no value is built and only what the handler keeps is in memory. + +memory +time: records parsed +illustrative, not a measurement + + + + +D​OM: parse() +the whole tree +json_document +the text plus its index +callback, discard +one record at a time +S​AX +only what the handler keeps + diff --git a/docs/mkdocs/docs/images/ordered_json_scaling.svg b/docs/mkdocs/docs/images/ordered_json_scaling.svg new file mode 100644 index 000000000..fc6b55572 --- /dev/null +++ b/docs/mkdocs/docs/images/ordered_json_scaling.svg @@ -0,0 +1,31 @@ + +Time to parse a flat object of n keys, json against ordered_json, logarithmic axes +2000 keys: json 0.7 ms, ordered_json 3.6 ms (5x). 4000 keys: 0.8 ms and 14.0 ms (19x). 8000 keys: 1.6 ms and 67.8 ms (43x). 16000 keys: 3.3 ms and 181.6 ms (54x). + +1 ms + +10 ms + +100 ms + +2 000 +4 000 +8 000 +16 000 + +json, 2000 keys: 0.7 ms +json, 4000 keys: 0.8 ms +json, 8000 keys: 1.6 ms +json, 16000 keys: 3.3 ms +json +3.3 ms +0.7 ms + +ordered_json, 2000 keys: 3.6 ms +ordered_json, 4000 keys: 14 ms +ordered_json, 8000 keys: 67.8 ms +ordered_json, 16000 keys: 181.6 ms +ordered_json +181.6 ms +3.6 ms + diff --git a/docs/mkdocs/docs/images/qa_pipeline.svg b/docs/mkdocs/docs/images/qa_pipeline.svg new file mode 100644 index 000000000..4bf54e1e2 --- /dev/null +++ b/docs/mkdocs/docs/images/qa_pipeline.svg @@ -0,0 +1,240 @@ + +Quality assurance pipeline +Every push or pull request starts these checks in parallel: Build matrix (every push / PR): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (every push / PR): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Dynamic analysis (every push / PR): runtime assertions Valgrind (Memcheck) address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open. + + + +push or pull request + +checks, run in parallel on GitHub Actions and AppVeyor + + +Build matrix + +every push / PR +50+ compilers: G​CC, Clang, M​SVC, +AppleClang, Intel, NVIDIA, ... +C++11, 14, 17, 20, 23, and 26 +libc++ and libstdc++ +warnings: Clang -Weverything, +G​CC 300+ warnings + + + +Unit tests (doctest) + +every push / PR +every public A​PI function +every exception id and message +all Unicode code points +single header and split headers +custom number, string, array, +and object types + + + +Coverage + +every push / PR +unit tests cover all lines +of the code base + + + +Static analysis + +every push / PR + +scheduled +Clang-Tidy, Cppcheck, Infer +Clang Static Analyzer (52 +checkers), I​WYU, Codacy +CodeQL, Semgrep, Flawfinder +(also scheduled) + + + +Dynamic analysis + +every push / PR +runtime assertions +Valgrind (Memcheck) +address and undefined behavior +sanitizers, integer overflow, +nullability violations + + + +Fuzzing + +every PR + +continuous +CIFuzz on every pull request +OSS-Fuzz: billions of inputs +every crash fixed with a +reproducing unit test + + + +Style + +every push / PR +Artistic Style (astyle), +enforced in the C​I +cpplint with 61 rules + + + +Docs and examples + +every push / PR + +scheduled +every documentation example is +tested; changed output fails +site built with warnings +as errors; links checked +regularly (scheduled) + + + +Integration, supply chain + +every push / PR + +scheduled +CMake 3.5, 3.31.6, and 4.0.0 +Meson, Swift Package Manager +dependency review on every PR +Dependabot daily, OpenSSF +Scorecard, R​EUSE + + + +merge into develop + + +release +no open vulnerability alert + + +Quality assurance pipeline +Every push or pull request starts these checks in parallel: Build matrix (every push / PR): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (every push / PR): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Dynamic analysis (every push / PR): runtime assertions Valgrind (Memcheck) address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open. + + + +push or pull request + +checks, run in parallel + + +Build matrix + +every push / PR +50+ compilers: G​CC, Clang, M​SVC, +AppleClang, Intel, NVIDIA, ... +C++11, 14, 17, 20, 23, and 26 +libc++ and libstdc++ +warnings: Clang -Weverything, +G​CC 300+ warnings + + + +Unit tests (doctest) + +every push / PR +every public A​PI function +every exception id and message +all Unicode code points +single header and split headers +custom number, string, array, +and object types + + + +Coverage + +every push / PR +unit tests cover all lines +of the code base + + + +Static analysis + +every push / PR + +scheduled +Clang-Tidy, Cppcheck, Infer +Clang Static Analyzer (52 +checkers), I​WYU, Codacy +CodeQL, Semgrep, Flawfinder +(also scheduled) + + + +Dynamic analysis + +every push / PR +runtime assertions +Valgrind (Memcheck) +address and undefined behavior +sanitizers, integer overflow, +nullability violations + + + +Fuzzing + +every PR + +continuous +CIFuzz on every pull request +OSS-Fuzz: billions of inputs +every crash fixed with a +reproducing unit test + + + +Style + +every push / PR +Artistic Style (astyle), +enforced in the C​I +cpplint with 61 rules + + + +Docs and examples + +every push / PR + +scheduled +every documentation example is +tested; changed output fails +site built with warnings +as errors; links checked +regularly (scheduled) + + + +Integration, supply chain + +every push / PR + +scheduled +CMake 3.5, 3.31.6, and 4.0.0 +Meson, Swift Package Manager +dependency review on every PR +Dependabot daily, OpenSSF +Scorecard, R​EUSE + + + +merge into develop + + +release +no open vulnerability alert + diff --git a/docs/mkdocs/docs/images/range-begin-end.svg b/docs/mkdocs/docs/images/range-begin-end.svg index 99c9f8662..61a4cc721 100644 --- a/docs/mkdocs/docs/images/range-begin-end.svg +++ b/docs/mkdocs/docs/images/range-begin-end.svg @@ -1,447 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - Past-the-last element - - - - begin - - - - end - - - - + +A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position. + + + + + + + + + + + + + + + + + + +begin + + +end + +Past-the-last element + diff --git a/docs/mkdocs/docs/images/range-rbegin-rend.svg b/docs/mkdocs/docs/images/range-rbegin-rend.svg index 9667efd89..1e91f7158 100644 --- a/docs/mkdocs/docs/images/range-rbegin-rend.svg +++ b/docs/mkdocs/docs/images/range-rbegin-rend.svg @@ -1,1244 +1,54 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - Past-the-last element - - - - begin - - - - end - - - - - - - - - - - - - - - - - - - - - - - - - Reversed sequence - - Reverse past-the-last element - - - - - - - - rend - - - - rbegin - - - Reverse iterator stores an iterator to the nextelement than the one it actually refers to - - - - - - - - + +Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end(). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +rend + + +rbegin + +Reverse past-the-last element + + +A reverse iterator stores an iterator to the next element + + +begin + + +end + +Past-the-last element diff --git a/docs/mkdocs/docs/images/release_timeline.svg b/docs/mkdocs/docs/images/release_timeline.svg new file mode 100644 index 000000000..b70e92218 --- /dev/null +++ b/docs/mkdocs/docs/images/release_timeline.svg @@ -0,0 +1,116 @@ + +Timeline from the first commit (2013-07-04) and the 44 releases from v1.0.0 (2015-12-28) to v3.12.0 (2025-04-11): 17 minor or major releases, drawn as filled dots with their version above the axis, and 27 patch releases, drawn as rings. The first commit is drawn as a diamond. + +2013 + +2014 + +2015 + +2016 + +2017 + +2018 + +2019 + +2020 + +2021 + +2022 + +2023 + +2024 + +2025 + + + +1.x + +2.x + +3.x + +first commit + +1.0 + +1.1 + +2.0 + +2.1 + +3.0 + +3.1 + +3.2 + +3.3 + +3.4 + +3.5 + +3.6 + +3.7 + +3.8 + +3.9 + +3.10 + +3.11 + +3.12 +First commit 2013-07-04 +v2.0.1 released 2016-06-28 +v2.0.2 released 2016-07-31 +v2.0.3 released 2016-08-31 +v2.0.4 released 2016-09-11 +v2.0.5 released 2016-09-14 +v2.0.6 released 2016-10-15 +v2.0.7 released 2016-11-02 +v2.0.8 released 2016-12-02 +v2.0.9 released 2016-12-16 +v2.0.10 released 2017-01-02 +v2.1.1 released 2017-02-25 +v3.0.1 released 2017-12-29 +v3.1.1 released 2018-02-13 +v3.1.2 released 2018-03-14 +v3.7.1 released 2019-11-06 +v3.7.2 released 2019-11-10 +v3.7.3 released 2019-11-17 +v3.9.1 released 2020-08-06 +v3.10.1 released 2021-08-24 +v3.10.2 released 2021-08-26 +v3.10.3 released 2021-10-08 +v3.10.4 released 2021-10-16 +v3.10.5 released 2022-01-03 +v3.11.2 released 2022-08-12 +v3.11.3 released 2023-11-28 +v1.0.0 released 2015-12-28 +v1.1.0 released 2016-01-24 +v2.0.0 released 2016-06-24 +v2.1.0 released 2017-01-28 +v3.0.0 released 2017-12-17 +v3.1.0 released 2018-02-01 +v3.2.0 released 2018-08-20 +v3.3.0 released 2018-10-05 +v3.4.0 released 2018-10-30 +v3.5.0 released 2018-12-22 +v3.6.0, v3.6.1 released 2019-03-20 +v3.7.0 released 2019-07-28 +v3.8.0 released 2020-06-14 +v3.9.0 released 2020-07-27 +v3.10.0 released 2021-08-17 +v3.11.0, v3.11.1 released 2022-08-01 +v3.12.0 released 2025-04-11 + diff --git a/docs/mkdocs/docs/images/roadmap_lanes.svg b/docs/mkdocs/docs/images/roadmap_lanes.svg new file mode 100644 index 000000000..75b45c1b2 --- /dev/null +++ b/docs/mkdocs/docs/images/roadmap_lanes.svg @@ -0,0 +1,98 @@ + +Two lanes: the 3.x release train, whose minor and patch releases do not break code that uses the public A​PI, followed by version 4.0, which has no release date yet. Each breaking change is a macro that is available in 3.x today with its 3.x default and becomes the default in 4.0: JSON_USE_IMPLICIT_CONVERSIONS, added in 3.9.0, 3.x default 1, 4.0 value 0; JSON_USE_GLOBAL_UDLS, added in 3.11.0, 3.x default 1, 4.0 value 0; JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON, added in 3.11.0, 3.x default 0, 4.0 value 0, macro removed; JSON_BRACE_INIT_COPY_SEMANTICS, added in 3.13.0, 3.x default 0, 4.0 value 1; JSON_PRECISE_STREAM_POSITION, added in 3.13.0, 3.x default 0, 4.0 value 1; JSON_STRICT_NUL_HANDLING, added in 3.13.0, 3.x default 0, 4.0 value 1; JSON_STRICT_BINARY_UTF8, added in 3.13.0, 3.x default 0, 4.0 value 1; JSON_DISABLE_TUPLE_REFERENCE_CONVERSION, added in 3.13.0, 3.x default 0, 4.0 value 1; JSON_DELETE_DEPRECATED_FUNCTIONS, added in 3.13.0, 3.x default 0, 4.0 value 1, macro removed. + + +3.x release train +available today + +3.9.0 + +3.11.0 + +3.13.0 + + + +4.0 +no release date yet +A minor or patch release of 3.x does not break code that uses the public A​PI, unless that code opts in with a macro. +4.0 is not developed on a separate branch: it switches the defaults of these macros and removes the macros. +Macro +added +3.x default +define to try today +4.0 default + +JSON_USE_IMPLICIT_CONVERSIONS +3.9.0 + +1 + + +0 + +JSON_USE_GLOBAL_UDLS +3.11.0 + +1 + + +0 + +JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON +3.11.0 + +0 + + +removed + +JSON_BRACE_INIT_COPY_SEMANTICS +3.13.0 + +0 + + +1 + +JSON_PRECISE_STREAM_POSITION +3.13.0 + +0 + + +1 + +JSON_STRICT_NUL_HANDLING +3.13.0 + +0 + + +1 + +JSON_STRICT_BINARY_UTF8 +3.13.0 + +0 + + +1 + +JSON_DISABLE_TUPLE_REFERENCE_CONVERSION +3.13.0 + +0 + + +1 + +JSON_DELETE_DEPRECATED_FUNCTIONS +3.13.0 + +0 + + +removed +Versions on the 3.x lane are the ones that added a macro, not to scale. “removed”: see the 4.0 behavior column of the table. + diff --git a/docs/mkdocs/docs/images/wordmark-header.svg b/docs/mkdocs/docs/images/wordmark-header.svg new file mode 100644 index 000000000..c69773e72 --- /dev/null +++ b/docs/mkdocs/docs/images/wordmark-header.svg @@ -0,0 +1 @@ +nlohmann/json \ No newline at end of file diff --git a/docs/mkdocs/docs/index.md b/docs/mkdocs/docs/index.md index f53b8dcab..b18e1de1c 100644 --- a/docs/mkdocs/docs/index.md +++ b/docs/mkdocs/docs/index.md @@ -1,71 +1,19 @@ +--- +template: home.html +hide: + - toc +--- + # JSON for Modern C++ -![JSON for Modern C++](images/json.gif) - -JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-class C++ data type, using the operator -magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like -Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no -complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times -down, `` holds the user-defined string literals on their own, and -`` adds zero-copy views and editable documents. See -[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code -coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google -OSS-Fuzz. - -## Quick start - -Add the single header to your project and use the library like this: - -```cpp -#include -#include - -using nlohmann::json; - -int main() -{ - // parse a JSON string - json j = json::parse(R"({"happy": true, "pi": 3.141})"); - - // access and modify values - j["name"] = "Niels"; - j["list"] = {1, 0, 2}; - - // read a value into a C++ variable and use it - double pi = j["pi"].get(); - std::cout << "2 * pi = " << 2 * pi << '\n'; - - // serialize with an indent of 4 spaces - std::cout << j.dump(4) << '\n'; -} -``` - -Get the library by copying the single header [`json.hpp`](https://github.com/nlohmann/json/releases) from the -releases page into a directory `nlohmann` on your include path, or by installing it with a package manager: - -```sh -brew install nlohmann-json # Homebrew -vcpkg install nlohmann-json # vcpkg -``` - -```cmake -find_package(nlohmann_json 3.12.0 REQUIRED) -target_link_libraries(myproject PRIVATE nlohmann_json::nlohmann_json) -``` - -See [Integration](integration/index.md) for CMake in detail, all supported package managers (Conan, Meson, Bazel, -Conda, and more), and pkg-config. - -## Explore the documentation -
- :octicons-rocket-24:{ .lg .middle } __Features__ --- - Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, zero-copy views, - and more. + Creating, parsing, accessing, and serializing JSON values, converting your own C++ types, JSON Pointer/Patch/Merge + Patch, binary formats, zero-copy views, and more. [:octicons-arrow-right-24: Features](features/index.md) @@ -112,11 +60,5 @@ Conda, and more), and pkg-config.
-!!! info "Unreleased changes" - - This documentation is built from the `develop` branch and may describe changes that are not part of a release - yet. Their version numbers are followed by an unreleased badge; see - [Releases](home/releases/index.md) for what shipped in each version. - -The library is licensed under the [MIT License](home/license.md). The source code, issue tracker, and discussions -are on [GitHub](https://github.com/nlohmann/json). +![JSON for Modern C++](images/json-light.svg#only-light) +![JSON for Modern C++](images/json-dark.svg#only-dark) diff --git a/docs/mkdocs/docs/integration/cmake.md b/docs/mkdocs/docs/integration/cmake.md index 65eed72a8..c693e971f 100644 --- a/docs/mkdocs/docs/integration/cmake.md +++ b/docs/mkdocs/docs/integration/cmake.md @@ -8,6 +8,19 @@ to point to the appropriate include directories and [`INTERFACE_COMPILE_FEATURES for the necessary C++11 flags. Most [package managers](package_managers.md) that provide a CMake package configuration for this library expose this same target. +All ways of adding the library described below end at this one target: + +```mermaid +flowchart LR + FP["find_package()
installed package"] --> T + AS["add_subdirectory()
embedded source tree"] --> T + FC["FetchContent
downloaded at configure time"] --> T + PM["Package manager
its CMake package"] --> T + T["nlohmann_json::nlohmann_json
one INTERFACE target"] --> L["target_link_libraries()
link it PRIVATE"] +``` + +

The four ways to add the library in CMake all lead to the same `nlohmann_json::nlohmann_json` interface target, which a project links with `target_link_libraries`. The CMake options below apply to `add_subdirectory()` and `FetchContent`; installed packages have their options fixed at install time.

+ ### External To use this library from a CMake project, you can locate it directly with [`find_package()`](https://cmake.org/cmake/help/latest/command/find_package.html) @@ -255,3 +268,10 @@ add_executable(myproject main.cpp) target_link_libraries(myproject PRIVATE nlohmann_json_modules) target_compile_definitions(myproject PRIVATE NLOHMANN_JSON_BUILD_MODULES) ``` + +## See also + +- [Macros](../api/macros/index.md) - reference of all macros +- [Supported Macros](../features/macros.md) - the macros that the CMake options set +- [Modules](../features/modules.md) - C++20 module support +- [`nlohmann` Namespace](../features/namespace.md) - namespace options set by CMake diff --git a/docs/mkdocs/docs/integration/compile_times.md b/docs/mkdocs/docs/integration/compile_times.md index 279a00ec7..8798931cb 100644 --- a/docs/mkdocs/docs/integration/compile_times.md +++ b/docs/mkdocs/docs/integration/compile_times.md @@ -6,9 +6,68 @@ that cost, ordered by how much they typically save. !!! info "Measurements" - The numbers below are medians of nine runs compiling a single translation unit with `-std=c++17 -c` against the - single-header version, with Apple clang and GCC 16 on macOS (Apple silicon). They show the order of magnitude to - expect; measure your own code before and after a change. + The numbers below are medians of the wall-clock time of nine runs compiling a single translation unit with + `-std=c++17 -c` against the single-header version, with Apple clang 21.0.0 (clang-2100.3.34.2) and Homebrew GCC + 16.2.0 on macOS (Apple M1 Max). All configurations were run interleaved, in alternating order. They show the order + of magnitude to expect; measure your own code before and after a change. + + The translation units are listed below. They replace the ones used for earlier versions of this page, whose + sources were not published, so the numbers differ from earlier versions. + +??? example "Measured translation units" + + The *model* and *parsing* translation units start with `#include `, `#include `, and + `#include `; the *header-only* translation unit includes only the library header. + + ```cpp title="model translation unit" + struct person + { + std::string name; + int age = 0; + std::vector tags; + }; + + void to_json(nlohmann::json& j, const person& p) + { + j = nlohmann::json{{"name", p.name}, {"age", p.age}, {"tags", p.tags}}; + } + + void from_json(const nlohmann::json& j, person& p) + { + j.at("name").get_to(p.name); + j.at("age").get_to(p.age); + j.at("tags").get_to(p.tags); + } + + nlohmann::json roundtrip(const person& p) + { + nlohmann::json j = p; + return nlohmann::json(j.get()); + } + ``` + + ```cpp title="parsing translation unit" + std::size_t parse_and_dump(const std::string& s) + { + const nlohmann::json j = nlohmann::json::parse(s); + return j.dump(2).size(); + } + ``` + + ```cpp title="header-only translation unit" + #include // or + + struct person; + void to_json(nlohmann::json& j, const person& p); + void from_json(const nlohmann::json& j, person& p); + ``` + +
+--8<-- "images/compile_times.svg" +
+

Change in compile time at `-O0` against the default configuration of the same +translation unit, taken from the tables below (medians of nine runs, see above). * computed from the milliseconds in +[Options without effect on compile times](#options-without-effect-on-compile-times).

## Include `json_fwd.hpp` in headers @@ -37,8 +96,10 @@ void from_json(const nlohmann::json& j, person& p) { /* ... */ } | Compiler | `json.hpp` (`-O0`) | `json_fwd.hpp` (`-O0`) | Change | |-------------|-------------------:|-----------------------:|-------:| -| Apple clang | 704 ms | 329 ms | −53% | -| GCC 16 | 779 ms | 242 ms | −69% | +| Apple clang | 868 ms | 332 ms | −62% | +| GCC 16 | 1164 ms | 210 ms | −82% | + +

Compile time of the header-only translation unit at `-O0` with each header, as the median wall-clock time of nine runs (see "Measurements" above). A negative change means a faster build.

This is the most effective option, because it avoids the full header in every translation unit that includes *your* headers. @@ -67,13 +128,15 @@ conversions or only pass `json` values around: | Compiler | Translation unit | Default (`-O0` / `-O2`) | `JSON_NO_AUTOMATIC_UDLS` (`-O0` / `-O2`) | Change | |-------------|------------------|------------------------:|-----------------------------------------:|------------:| -| Apple clang | model | 776 ms / 846 ms | 629 ms / 692 ms | −19% / −18% | -| GCC 16 | model | 1022 ms / 1120 ms | 882 ms / 965 ms | −14% / −14% | -| Apple clang | parsing | 992 ms / 1815 ms | 1006 ms / 1823 ms | +1% / 0% | -| GCC 16 | parsing | 2018 ms / 3420 ms | 1990 ms / 3454 ms | −1% / +1% | +| Apple clang | model | 1051 ms / 1300 ms | 935 ms / 1170 ms | −11% / −10% | +| GCC 16 | model | 1801 ms / 2106 ms | 1598 ms / 1983 ms | −11% / −6% | +| Apple clang | parsing | 1157 ms / 2016 ms | 1146 ms / 2006 ms | −1% / 0% | +| GCC 16 | parsing | 2529 ms / 4087 ms | 2484 ms / 4059 ms | −2% / −1% | -Translation units that include only the header save up to a third. Translation units that parse anyway instantiate -the parser regardless and see no difference. +

Compile time of the model and parsing translation units without and with `JSON_NO_AUTOMATIC_UDLS`, at `-O0` and `-O2`.

+ +The model translation unit saves about a tenth. Translation units that parse anyway instantiate the parser regardless +and see no difference. ## Instantiate `basic_json` once @@ -97,10 +160,12 @@ Include `json_instance.hpp` instead of `` and compile and lin | Compiler | Translation unit | Default (`-O0` / `-O2`) | `extern template` (`-O0` / `-O2`) | Change | |-------------|---------------------|------------------------:|----------------------------------:|------------:| -| Apple clang | parsing | 992 ms / 1815 ms | 953 ms / 1625 ms | −4% / −10% | -| GCC 16 | parsing | 2018 ms / 3420 ms | 1522 ms / 2728 ms | −25% / −20% | -| Apple clang | `json_instance.cpp` | — | 2166 ms / 4660 ms | — | -| GCC 16 | `json_instance.cpp` | — | 5085 ms / 10616 ms | — | +| Apple clang | parsing | 1157 ms / 2016 ms | 1071 ms / 1844 ms | −7% / −9% | +| GCC 16 | parsing | 2529 ms / 4087 ms | 1994 ms / 3190 ms | −21% / −22% | +| Apple clang | `json_instance.cpp` | — | 2573 ms / 6221 ms | — | +| GCC 16 | `json_instance.cpp` | — | 6489 ms / 13759 ms | — | + +

Compile time of the parsing translation unit without and with the `extern template` declaration, at `-O0` and `-O2`. The `json_instance.cpp` rows are the one-off cost of the translation unit that holds the instantiation.

Notes: @@ -111,12 +176,97 @@ Notes: - The declaration covers exactly `nlohmann::json`. Add the same lines for `nlohmann::ordered_json` (`nlohmann::basic_json`) or your own `basic_json` specializations if you use them. +## Switch off filesystem support + +With C++17, the library includes `` to provide conversions from and to `std::filesystem::path` +(see [`JSON_HAS_FILESYSTEM`](../api/macros/json_has_filesystem.md)). Parsing `` is a fixed cost in every +translation unit, and it also pulls in ``, ``, and `` (and, with GCC's libstdc++, +``). If you do not convert paths, switch the detection off for the whole project: + +```cmake +target_compile_definitions(my_target PRIVATE JSON_HAS_FILESYSTEM=0 JSON_HAS_EXPERIMENTAL_FILESYSTEM=0) +``` + +| Compiler | Translation unit | Default (`-O0` / `-O2`) | Filesystem off (`-O0` / `-O2`) | Change | +|-------------|------------------|------------------------:|-------------------------------:|-----------:| +| Apple clang | model | 1051 ms / 1300 ms | 998 ms / 1257 ms | −5% / −3% | +| GCC 16 | model | 1801 ms / 2106 ms | 1636 ms / 2008 ms | −9% / −5% | +| Apple clang | parsing | 1157 ms / 2016 ms | 1107 ms / 1971 ms | −4% / −2% | +| GCC 16 | parsing | 2529 ms / 4087 ms | 2405 ms / 4029 ms | −5% / −1% | + +

Compile time with `JSON_HAS_FILESYSTEM=0` and `JSON_HAS_EXPERIMENTAL_FILESYSTEM=0` against the default configuration, at `-O0` and `-O2`.

+ +Only with filesystem support switched off does [`JSON_NO_IO`](../api/macros/json_no_io.md) remove the stream headers +from the translation unit, so the two macros save most together: + +| Compiler | Translation unit | Default (`-O0` / `-O2`) | Filesystem off and `JSON_NO_IO` (`-O0` / `-O2`) | Change | +|-------------|------------------|------------------------:|------------------------------------------------:|-----------:| +| Apple clang | model | 1051 ms / 1300 ms | 969 ms / 1219 ms | −8% / −6% | +| GCC 16 | model | 1801 ms / 2106 ms | 1605 ms / 1982 ms | −11% / −6% | +| Apple clang | parsing | 1157 ms / 2016 ms | 1083 ms / 1947 ms | −6% / −3% | +| GCC 16 | parsing | 2529 ms / 4087 ms | 2384 ms / 3972 ms | −6% / −3% | + +

Compile time with filesystem support switched off and `JSON_NO_IO` defined, against the default configuration, at `-O0` and `-O2`.

+ +The trade-off: the library's own `to_json`/`from_json` overloads for `std::filesystem::path` (which store the path as +UTF-8) are removed. In a test with both compilers on macOS, paths still converted through their implicit conversion +from and to `std::string`; whether that fallback exists depends on the platform's native path string type +(`std::wstring` on Windows), so check code that converts paths. `JSON_NO_IO` additionally removes all stream +functions (see [Options without effect](#options-without-effect-on-compile-times)). + ## Use C++20 modules With a toolchain that supports named modules, `import nlohmann.json;` compiles the library once into a module and avoids parsing the header in every translation unit. See [Modules](../features/modules.md) for requirements and known issues. Module support is experimental and currently depends heavily on the compiler version. +
+--8<-- "images/compile_times_modules.svg" +
+

Change in compile time per translation unit at `-O0` when `#include ` is +replaced by `import nlohmann.json;`, both with `-std=c++20`, and the one-off cost of building the module, taken from +the table below.

+ +| Compiler | Translation unit | `#include` (`-O0` / `-O2`) | `import nlohmann.json;` (`-O0` / `-O2`) | Change | +|-------------|---------------------------|---------------------------:|----------------------------------------:|------------:| +| Apple clang | model | 1311 ms / 1597 ms | 680 ms / 914 ms | −48% / −43% | +| GCC 16 | model | 2156 ms / 2588 ms | 1153 ms / 1580 ms | −47% / −39% | +| Apple clang | parsing | 1387 ms / 2319 ms | 800 ms / 1676 ms | −42% / −28% | +| GCC 16 | parsing | 2838 ms / 4520 ms | 1810 ms / 3476 ms | −36% / −23% | +| Apple clang | `json.cppm` (module) | — | 1630 ms / 1642 ms | — | +| GCC 16 | `json.cppm` (module) | — | 2538 ms / 2547 ms | — | + +

Compile time with `#include ` and with `import nlohmann.json;`, both with `-std=c++20`, at `-O0` and `-O2`. The `json.cppm` rows are the one-off cost of building the module.

+ +Both columns use `-std=c++20`; with `-std=c++17`, the `#include` baseline is 17% to 20% faster (see the next +section). The module is built once per set of compiler flags and pays for itself from about the third translation +unit that imports it. Notes from the measurement: + +- Apple clang only accepts the module declarations with `-fcxx-modules`; without it, `module;` is a syntax error. +- GCC 16 warns with `-Wexpose-global-module-tu-local` about internal constants of the library when building the + module. With GCC 16.2, including `` and `` before `import nlohmann.json;` works, but including + them after it fails with redefinition errors ([#5103](https://github.com/nlohmann/json/issues/5103)). + +??? example "How these numbers were measured" + + The model and parsing translation units are the ones listed at the top of this page, with `import nlohmann.json;` + in place of `#include ` (after `#include ` and `#include `). Same method as + for the other tables, with `-std=c++20`. + + ```sh + # Apple clang: build the module once, then import it + clang++ -std=c++20 -fcxx-modules -O0 -I single_include -c -x c++-module src/modules/json.cppm \ + -fmodule-output=nlohmann.json.pcm -o json_mod.o + clang++ -std=c++20 -fcxx-modules -O0 -fmodule-file=nlohmann.json=nlohmann.json.pcm -c model.cpp + + # GCC 16 (Homebrew on macOS): writes gcm.cache/nlohmann.json.gcm, found by the importing translation unit + g++-16 --sysroot=$(xcrun --show-sdk-path) -std=c++20 -fmodules -O0 -I single_include -c src/modules/json.cppm \ + -o json_mod.o + g++-16 --sysroot=$(xcrun --show-sdk-path) -std=c++20 -fmodules -O0 -c model.cpp + ``` + + Link `json_mod.o` into the program. + ## Use precompiled headers Build systems can precompile `` together with other stable headers, for example with CMake's @@ -129,22 +279,75 @@ target_precompile_headers(my_target PRIVATE ) This removes the cost of parsing the header, but not of instantiating templates in each translation unit, so it combines well with the options above. +
+--8<-- "images/compile_times_pch.svg" +
+

Change in compile time per translation unit at `-O0` with a precompiled +`` (`-std=c++17`), and the one-off cost of building the precompiled header, taken from the table +below.

+ +| Compiler | Translation unit | Default (`-O0` / `-O2`) | With PCH (`-O0` / `-O2`) | Change | +|-------------|----------------------|------------------------:|-------------------------:|------------:| +| Apple clang | model | 1045 ms / 1295 ms | 594 ms / 842 ms | −43% / −35% | +| GCC 16 | model | 1789 ms / 2101 ms | 1060 ms / 1376 ms | −41% / −35% | +| Apple clang | parsing | 1148 ms / 2008 ms | 714 ms / 1584 ms | −38% / −21% | +| GCC 16 | parsing | 2498 ms / 4075 ms | 1884 ms / 3452 ms | −25% / −15% | +| Apple clang | building the PCH | — | 771 ms / 760 ms | — | +| GCC 16 | building the PCH | — | 2254 ms / 2218 ms | — | + +

Compile time without and with a precompiled ``, at `-O0` and `-O2`. The last two rows are the one-off cost of building the precompiled header.

+ +The saving is largest in translation units that instantiate little, and the PCH pays for itself from the second +(Apple clang) or fourth (GCC) translation unit. A precompiled header only works with the flags it was built with: +GCC silently ignores a mismatching `.gch` file (for example one built with another `-O` level) unless `-Winvalid-pch` +is given. + +??? example "How these numbers were measured" + + `pch.hpp` contains only `#include `; the translation units are the ones + listed at the top of this page. + + ```sh + # Apple clang + clang++ -std=c++17 -O0 -I single_include -x c++-header pch.hpp -o pch.hpp.pch + clang++ -std=c++17 -O0 -I single_include -include-pch pch.hpp.pch -include pch.hpp -c model.cpp + + # GCC 16 (Homebrew on macOS): pch.hpp.gch next to pch.hpp is picked up by -include pch.hpp + g++-16 --sysroot=$(xcrun --show-sdk-path) -std=c++17 -O0 -I single_include -x c++-header pch.hpp -o pch.hpp.gch + g++-16 --sysroot=$(xcrun --show-sdk-path) -std=c++17 -O0 -I single_include -include pch.hpp -Winvalid-pch -c model.cpp + ``` + ## Options without effect on compile times Some configuration macros change what the library declares, but do not measurably change compile times: | Macro | Apple clang, model (`-O0` / `-O2`) | GCC 16, model (`-O0` / `-O2`) | |------------------------------------------------------------------------|-----------------------------------:|------------------------------:| -| default | 776 ms / 846 ms | 1022 ms / 1120 ms | -| [`JSON_NO_IO`](../api/macros/json_no_io.md) | 764 ms / 836 ms | 1022 ms / 1117 ms | -| [`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md)`=0` | 763 ms / 852 ms | 1019 ms / 1106 ms | +| default | 1051 ms / 1300 ms | 1801 ms / 2106 ms | +| [`JSON_NO_IO`](../api/macros/json_no_io.md) | 1034 ms / 1288 ms | 1756 ms / 2087 ms | +| [`JSON_USE_GLOBAL_UDLS`](../api/macros/json_use_global_udls.md)`=0` | 1055 ms / 1298 ms | 1783 ms / 2109 ms | + +

Compile time of the model translation unit with each macro defined; the first row is the default configuration for comparison.

+ +`JSON_NO_IO` removes the library's stream functions and its `#include`s of `` and ``, but with the +default C++17 settings `` includes these headers anyway (and with libc++, `` already includes +`` via ``), so only a few hundred lines of the library's own stream code go away. It also does not +change what the translation unit instantiates. On the parsing translation unit, the change is 0% / 0% (Apple clang) +and −2% / 0% (GCC). To get a saving, combine it with +[switching off filesystem support](#switch-off-filesystem-support). `JSON_USE_GLOBAL_UDLS` only controls *where* the literals are declared; to avoid their cost, use `JSON_NO_AUTOMATIC_UDLS` instead. ## See also +- [`JSON_HAS_FILESYSTEM`](../api/macros/json_has_filesystem.md) - control the conversions from and to + `std::filesystem::path` - [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md) - do not include the user-defined string literals automatically - [Modules](../features/modules.md) - C++20 module support - [Header only](index.md) - including the library +- [Macros](../api/macros/index.md) - reference of all macros +- [Supported Macros](../features/macros.md) - overview of all configuration macros +- [Performance](../features/performance.md) - run-time performance +- [`JSON_NO_IO`](../api/macros/json_no_io.md) - remove all stream and file I/O diff --git a/docs/mkdocs/docs/integration/index.md b/docs/mkdocs/docs/integration/index.md index 6b461ff99..dd73ae500 100644 --- a/docs/mkdocs/docs/integration/index.md +++ b/docs/mkdocs/docs/integration/index.md @@ -5,24 +5,31 @@ pick one: ```mermaid flowchart TD - A[Add the library to a C++ project] --> B{Already using CMake?} - B -- no --> C{Using pkg-config or plain Makefiles?} - C -- yes --> D[pkg-config] - C -- no --> E[Copy the single header] - B -- yes --> F{Library installed system-wide?} - F -- yes --> G["find_package()"] - F -- no --> H{Use a package manager?} - H -- yes --> I[Package manager] - H -- no --> J["add_subdirectory() or FetchContent"] + A[Add the library to a C++ project] --> B{{Using CMake?}} + B -- no --> C{{Package manager available?}} + C -- yes --> D["Use a package manager
see Package Managers"] + C -- no --> E["Copy the single header
see Header only"] + B -- yes --> F{{Already installed?}} + F -- yes --> G["find_package
see CMake, External"] + F -- no --> H{{CMake 3.14 or later?}} + H -- yes --> I["FetchContent
see CMake, FetchContent"] + H -- no --> J["add_subdirectory
see CMake, Embedded"] + G --> T["nlohmann_json::nlohmann_json"] + I --> T + J --> T ``` -- **Copy the single header**, as described [below](#header-only) — no build-system integration required. -- **CMake**: use [`find_package()`](cmake.md#external) if the library is already installed, - [`add_subdirectory()`](cmake.md#embedded) to embed the source tree, or [`FetchContent`](cmake.md#fetchcontent) to - download it at configure time; see [CMake](cmake.md). -- **Package managers**: install the library with a package manager such as Homebrew, Conan, or vcpkg; see - [Package Managers](package_managers.md). -- **pkg-config**: if you use bare Makefiles instead of CMake, [pkg-config](pkg-config.md) can supply the include flags +

Choosing an integration method: answer the questions in the hexagons from top to bottom to reach a method. All CMake methods end at the same target, `nlohmann_json::nlohmann_json`; the list below links to each method.

+ +- **Single header**: copy [`json.hpp`](#header-only) into your project; no build-system integration required. +- **CMake**, see [CMake](cmake.md): + - [`find_package()`](cmake.md#external) if the library is already installed; + - [`FetchContent`](cmake.md#fetchcontent) to download it at configure time (CMake 3.14 or later); + - [`add_subdirectory()`](cmake.md#embedded) to embed a vendored copy of the source tree; + - or [support both](cmake.md#supporting-both) with an option that selects installed or vendored. +- **Package managers**: install the library with Homebrew, Conan, vcpkg, or one of the many others; see + [Package Managers](package_managers.md) for the full list. +- **pkg-config**: if you use bare Makefiles instead of CMake, [pkg-config](pkg-config.md) supplies the include flags for an already-installed library. Once the library is integrated, see the [Migration Guide](migration_guide.md) for how to keep your code future-proof @@ -91,8 +98,13 @@ Visual Studio Code, but not with debuggers that wrap LLDB, such as `codelldb`; s ## See also - [CMake](cmake.md) - integrate the library into a CMake project -- [Package managers](package_managers.md) -- [pkg-config](pkg-config.md) +- [Package managers](package_managers.md) - install the library with a package manager +- [pkg-config](pkg-config.md) - generate include flags for plain Makefiles - [Compile times](compile_times.md) - reduce the compile time overhead - [Migration guide](migration_guide.md) - upgrade from an earlier version -- [Releases](../home/releases/index.md) +- [Releases](../home/releases/index.md) - release notes of all versions +- [`basic_json`](../api/basic_json/index.md) - the main class and all its member functions +- [Macros](../api/macros/index.md) - reference of all macros +- [Supported Macros](../features/macros.md) - configure the library with macros +- [Modules](../features/modules.md) - C++20 module support +- [`NLOHMANN_JSON_VERSION_MAJOR`](../api/macros/nlohmann_json_version_major.md) - library version macros diff --git a/docs/mkdocs/docs/integration/migration_guide.md b/docs/mkdocs/docs/integration/migration_guide.md index bfbf850e0..4e539a32f 100644 --- a/docs/mkdocs/docs/integration/migration_guide.md +++ b/docs/mkdocs/docs/integration/migration_guide.md @@ -283,3 +283,12 @@ exact version and configuration is relevant, use macro The `nlohmann::detail` namespace is not part of the public API of the library and can change in any version without an announcement. Do not rely on any function or type in the `detail` namespace. + +## See also + +- [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) - remove deprecated functions to check the migration +- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - control implicit conversions +- [`NLOHMANN_JSON_NAMESPACE`](../api/macros/nlohmann_json_namespace.md) - refer to the library namespace without hard-coding it +- [`nlohmann` Namespace](../features/namespace.md) - how the library namespace is structured +- [Converting values](../features/conversions.md) - explicit conversions with get +- [`basic_json`](../api/basic_json/index.md) - the main class and all its member functions diff --git a/docs/mkdocs/docs/integration/package_managers.md b/docs/mkdocs/docs/integration/package_managers.md index bfa994340..84fceae7a 100644 --- a/docs/mkdocs/docs/integration/package_managers.md +++ b/docs/mkdocs/docs/integration/package_managers.md @@ -949,3 +949,10 @@ There is no official package published to the [ESP-IDF Component Registry](https new release and can be used as an unofficial component/package for ESP-IDF and PlatformIO projects. As the library is header-only, it can otherwise be used directly by adding its `include/` directory to your component's/project's include paths, like any other integration method described on this page. + +## See also + +- [CMake](cmake.md) - use the library from CMake +- [Pkg-config](pkg-config.md) - use the library with pkg-config +- [`meta`](../api/basic_json/meta.md) - query the library version at run time +- [`NLOHMANN_JSON_VERSION_MAJOR`](../api/macros/nlohmann_json_version_major.md) - library version macros diff --git a/docs/mkdocs/docs/integration/pkg-config.md b/docs/mkdocs/docs/integration/pkg-config.md index 8b2e9047a..26530c70e 100644 --- a/docs/mkdocs/docs/integration/pkg-config.md +++ b/docs/mkdocs/docs/integration/pkg-config.md @@ -14,3 +14,9 @@ Users of the [Meson build system](package_managers.md#meson) will also be able t ```meson json = dependency('nlohmann_json', required: true) ``` + +## See also + +- [Integration](index.md) - all ways to integrate the library +- [CMake](cmake.md) - use the library from CMake +- [Package Managers](package_managers.md) - install the library via package managers diff --git a/docs/mkdocs/docs/js/external-links.js b/docs/mkdocs/docs/js/external-links.js new file mode 100644 index 000000000..bdafe4c2a --- /dev/null +++ b/docs/mkdocs/docs/js/external-links.js @@ -0,0 +1,32 @@ +/* + * Marks links that leave the site with the class "nj-external", which brand.css turns into a small icon in front of + * the link. A link is external when its host is neither the current one nor the production site. Links in the page + * actions (edit, raw), buttons, header anchors, and links that only wrap an image (badges) are left alone. + * Runs through Material's document$ (navigation.instant), so it also applies after an instant page switch. + */ +(function () { + "use strict"; + + function mark() { + var links = document.querySelectorAll(".md-typeset a[href]"); + for (var i = 0; i < links.length; i++) { + var a = links[i]; + if (a.hostname === "" || a.hostname === location.hostname || a.hostname === "json.nlohmann.me") { + continue; + } + if (a.protocol !== "http:" && a.protocol !== "https:") { + continue; + } + if (a.matches(".md-button, .md-content__button, .headerlink") || a.querySelector("img, svg")) { + continue; + } + a.classList.add("nj-external"); + } + } + + if (window.document$ && window.document$.subscribe) { + window.document$.subscribe(mark); + } else { + document.addEventListener("DOMContentLoaded", mark); + } +})(); diff --git a/docs/mkdocs/docs/js/fig-charts.js b/docs/mkdocs/docs/js/fig-charts.js new file mode 100644 index 000000000..310987811 --- /dev/null +++ b/docs/mkdocs/docs/js/fig-charts.js @@ -0,0 +1,106 @@ +/* figures: charts. Number literal explorer on features/types/number_handling.md. + The table in .nj-charts-num is the source and the no-JS fallback; its rows were verified by compiling a program + against include/ (json::parse + type() + dump()). The explorer only looks literals up in that table: it does not + parse numbers itself, so anything else is reported as "not in the verified list". */ +(function () { + "use strict"; + + var WHY = { + "0": "No fraction, exponent, or minus sign: stored as unsigned integer.", + "-0": "Leading minus sign: stored as signed integer; the serialization does not reproduce the -.", + "-0.0": "Has a fraction: stored as double, which keeps the sign of zero.", + "1E3": "Has an exponent: always stored as double.", + "-12345678912345789123456789": "Smaller than INT64_MIN: does not fit std::int64_t, so it is stored as double.", + "18446744073709551615": "2^64-1 is the largest value std::uint64_t can store without loss.", + "18446744073709551616": "2^64 does not fit std::uint64_t, so it is stored as double.", + "-9223372036854775808": "-2^63 is the smallest value std::int64_t can store.", + "1E400": "Its rounded value is not finite as double: parsing throws out_of_range.406.", + "1E-400": "Too small for double: becomes zero, with the sign of the number.", + "3.141592653589793238462643383279": "Rounded to the nearest double, then printed with the fewest digits that read back.", + "2555.5599999999999": "The fewest digits that read back as the same double are 2555.56.", + "0.0000972439793401814": "The %g layout switches to scientific notation.", + "12345E-5": "The %g layout prints this double without exponent." + }; + + function init() { + var roots = document.querySelectorAll(".nj-charts-num"); + Array.prototype.forEach.call(roots, setup); + } + + function setup(root, idx) { + if (root.dataset.njChartsReady) return; + var table = root.querySelector("table"); + if (!table) return; + root.dataset.njChartsReady = "1"; + + var entries = []; + Array.prototype.forEach.call(table.querySelectorAll("tbody tr"), function (tr) { + var c = tr.querySelectorAll("td"); + if (c.length < 3) return; + entries.push({ lit: c[0].textContent.trim(), type: c[1].textContent.trim(), dump: c[2].textContent.trim(), row: tr }); + }); + if (!entries.length) return; + + var id = "nj-charts-num-" + idx; + var ui = document.createElement("div"); + ui.className = "nj-charts-num-ui"; + ui.innerHTML = + '
    ' + + '
    '; + root.insertBefore(ui, root.firstChild); + + var list = ui.querySelector("ul"); + var out = ui.querySelector("#" + id + "-out"); + var buttons = []; + + entries.forEach(function (e) { + var li = document.createElement("li"); + var b = document.createElement("button"); + b.type = "button"; + b.className = "nj-charts-num-chip"; + b.textContent = e.lit; + b.setAttribute("aria-pressed", "false"); + b.setAttribute("aria-label", "Show literal " + e.lit); + b.addEventListener("click", function () { show(e.lit); }); + li.appendChild(b); + list.appendChild(li); + buttons.push(b); + }); + + function show(text) { + var lit = text.trim(); + var hit = null; + entries.forEach(function (e, i) { + var on = e.lit === lit; + if (on) hit = e; + buttons[i].setAttribute("aria-pressed", on ? "true" : "false"); + e.row.classList.toggle("nj-charts-num-hit", on); + }); + out.textContent = ""; + if (hit) { + var dl = document.createElement("dl"); + dl.className = "nj-charts-num-out"; + [["literal", hit.lit, true], ["stored as", hit.type, false], ["dump()", hit.dump, true], + ["why", WHY[hit.lit] || "", false]].forEach(function (p) { + if (!p[1]) return; + var dt = document.createElement("dt"); dt.textContent = p[0]; + var dd = document.createElement("dd"); + if (p[2] && p[1].indexOf("(") !== 0) { var c = document.createElement("code"); c.textContent = p[1]; dd.appendChild(c); } + else dd.textContent = p[1]; + dl.appendChild(dt); dl.appendChild(dd); + }); + out.appendChild(dl); + } + } + + show(entries[1] ? entries[1].lit : entries[0].lit); + } + + if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") { + document$.subscribe(init); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/docs/mkdocs/docs/js/fig-exceptions.js b/docs/mkdocs/docs/js/fig-exceptions.js new file mode 100644 index 000000000..f09e5a5fe --- /dev/null +++ b/docs/mkdocs/docs/js/fig-exceptions.js @@ -0,0 +1 @@ +/* figures: exceptions */ diff --git a/docs/mkdocs/docs/js/fig-integration.js b/docs/mkdocs/docs/js/fig-integration.js new file mode 100644 index 000000000..3e4167a54 --- /dev/null +++ b/docs/mkdocs/docs/js/fig-integration.js @@ -0,0 +1,128 @@ +/* figures: integration + * + * Namespace name builder on features/namespace.md. The page ships a static strip for version 3.11.2 with + * JSON_DIAGNOSTICS=1; this script adds a version selector and one checkbox per ABI-affecting macro and rebuilds the + * strip. Tags, their order, and the versions that know them follow include/nlohmann/detail/abi_macros.hpp and the + * "Version history" section of the page; a tag that a version does not have cannot be ticked. + */ +(function () { + "use strict"; + + var VERSIONS = ["3.11.2", "3.11.3", "3.12.0", "3.13.0"]; + // macro, tag, first version with the tag, true if any definition counts (#ifdef) instead of a non-zero value + var TAGS = [ + ["JSON_DIAGNOSTICS", "_diag", "3.11.2"], + ["JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON", "_ldvcmp", "3.11.2"], + ["JSON_DIAGNOSTIC_POSITIONS", "_dp", "3.12.0"], + ["JSON_BRACE_INIT_COPY_SEMANTICS", "_bics", "3.13.0"], + ["JSON_PRECISE_STREAM_POSITION", "_psp", "3.13.0"], + ["JSON_STRICT_NUL_HANDLING", "_snul", "3.13.0"], + ["JSON_STRICT_BINARY_UTF8", "_sbu8", "3.13.0"], + ["JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS", "_ekmo", "3.13.0"], + ["JSON_DISABLE_STREAM_BULK_SCAN", "_dsbs", "3.13.0"], + ["JSON_DISABLE_UNLOCKED_FILE_IO", "_dufio", "3.13.0"], + ["JSON_NO_SIMD", "_ns", "3.13.0", true] + ]; + var NO_VERSION = "NLOHMANN_JSON_NAMESPACE_NO_VERSION"; + + function el(tag, cls, text) { + var node = document.createElement(tag); + if (cls) { node.className = cls; } + if (text !== undefined) { node.textContent = text; } + return node; + } + + function seg(kind, code, note) { + var span = el("span", "nj-integration-ns__seg nj-integration-ns__seg--" + kind); + span.appendChild(el("code", "", code)); + var small = el("small"); + // allow line breaks after underscores so that long macro names stay narrow + (note || "\u00a0").split("_").forEach(function (part, i, all) { + small.appendChild(document.createTextNode(part + (i < all.length - 1 ? "_" : ""))); + if (i < all.length - 1) { small.appendChild(el("wbr")); } + }); + span.appendChild(small); + return span; + } + + function setup(fig) { + if (fig.getAttribute("data-nj-ready")) { return; } + fig.setAttribute("data-nj-ready", "1"); + var strip = fig.querySelector(".nj-integration-ns__strip"); + var caption = fig.querySelector("figcaption"); + var uid = "nj-integration-ns-" + Math.random().toString(36).slice(2, 8); + var form = el("form", "nj-integration-ns__controls"); + form.setAttribute("aria-label", "Namespace name builder"); + form.addEventListener("submit", function (e) { e.preventDefault(); }); + + var vlabel = el("label", "nj-integration-ns__version", "Library version "); + var select = el("select"); + VERSIONS.forEach(function (v) { + var option = el("option", "", v); + option.value = v; + option.selected = v === "3.11.2"; + select.appendChild(option); + }); + vlabel.appendChild(select); + form.appendChild(vlabel); + + var list = el("fieldset", "nj-integration-ns__list"); + list.appendChild(el("legend", "", "ABI-affecting macros, in tag order")); + var boxes = TAGS.concat([[NO_VERSION, "", "3.11.2"]]).map(function (t, i) { + var label = el("label", "nj-integration-ns__opt"); + var box = el("input"); + box.type = "checkbox"; + box.id = uid + "-" + i; + box.checked = t[0] === "JSON_DIAGNOSTICS"; + label.htmlFor = box.id; + label.appendChild(box); + label.appendChild(el("code", "", t[0] + (t[3] ? "" : "=1"))); + var meta = el("span", "nj-integration-ns__meta"); + meta.appendChild(el("span", "nj-integration-ns__hint", + t[0] === NO_VERSION ? "drops the version" : (t[3] ? "defined: " : "") + t[1])); + meta.appendChild(el("span", "nj-integration-ns__since", "since " + t[2])); + label.appendChild(meta); + list.appendChild(label); + return { def: t, box: box, label: label }; + }); + form.appendChild(list); + fig.insertBefore(form, caption); + + function update() { + var version = select.value, index = VERSIONS.indexOf(version); + var parts = [seg("root", "nlohmann", "root namespace"), seg("sep", "::"), seg("abi", "json_abi", "always")]; + var noVersion = false, defs = []; + boxes.forEach(function (b) { + var available = VERSIONS.indexOf(b.def[2]) <= index; + b.box.disabled = !available; + if (!available) { b.box.checked = false; } + b.label.classList.toggle("is-unavailable", !available); + if (!b.box.checked) { return; } + defs.push(b.def[0] + (b.def[3] ? "" : "=1")); + if (b.def[0] === NO_VERSION) { noVersion = true; } else { parts.push(seg("tag", b.def[1], b.def[0] + (b.def[3] ? "" : "=1"))); } + }); + if (!noVersion) { parts.push(seg("ver", "_v" + version.replace(/\./g, "_"), "version " + version)); } + strip.textContent = ""; + parts.forEach(function (p) { strip.appendChild(p); }); + strip.setAttribute("aria-label", "Resulting namespace: nlohmann::" + parts.slice(2).map(function (p) { + return p.firstChild.textContent; + }).join("")); + caption.textContent = "Version " + version + (defs.length === 0 ? " with no ABI-affecting macro defined" : + defs.length > 3 ? " with " + defs.length + " of the macros defined" : " with " + defs.join(", ")) + "."; + } + form.addEventListener("change", update); + update(); + } + + function init() { + Array.prototype.forEach.call(document.querySelectorAll("[data-nj-integration-ns]"), setup); + } + + if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") { + document$.subscribe(init); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/docs/mkdocs/docs/js/fig-jsonview.js b/docs/mkdocs/docs/js/fig-jsonview.js new file mode 100644 index 000000000..f5f76f5f2 --- /dev/null +++ b/docs/mkdocs/docs/js/fig-jsonview.js @@ -0,0 +1,78 @@ +/* figures: jsonview -- stepper for the edit frames on features/json_view.md. + Without JS the frames (.nj-jsonview-step) show one below the other; with JS one frame at a time, with + previous/next and one button per step. Never advances on its own. */ +(function () { + "use strict"; + + function njJsonviewInit(root) { + if (root.classList.contains("is-enhanced")) return; + var steps = Array.prototype.slice.call(root.querySelectorAll(".nj-jsonview-step")); + if (steps.length < 2) return; + var current = 0; + + var bar = document.createElement("div"); + bar.className = "nj-jsonview-controls"; + bar.setAttribute("role", "group"); + bar.setAttribute("aria-label", "Edit steps"); + + function button(label, aria, onClick) { + var b = document.createElement("button"); + b.type = "button"; + b.textContent = label; + b.setAttribute("aria-label", aria); + b.addEventListener("click", onClick); + bar.appendChild(b); + return b; + } + + var prev = button("‹ Back", "Previous step", function () { show(current - 1, true); }); + var dots = steps.map(function (s, i) { + return button(String(i + 1), "Step " + (i + 1) + ": " + (s.getAttribute("data-label") || ""), function () { show(i, true); }); + }); + var next = button("Next ›", "Next step", function () { show(current + 1, true); }); + var status = document.createElement("span"); + status.className = "nj-jsonview-status"; + status.setAttribute("aria-live", "polite"); + bar.appendChild(status); + + function show(i, focusKeep) { + current = Math.max(0, Math.min(steps.length - 1, i)); + steps.forEach(function (s, k) { + s.classList.toggle("is-current", k === current); + s.setAttribute("aria-hidden", k === current ? "false" : "true"); + }); + dots.forEach(function (d, k) { + if (k === current) d.setAttribute("aria-current", "step"); else d.removeAttribute("aria-current"); + }); + prev.disabled = current === 0; + next.disabled = current === steps.length - 1; + status.textContent = "Step " + (current + 1) + " of " + steps.length; + if (focusKeep && document.activeElement && document.activeElement.disabled) { + (current === 0 ? next : prev).focus(); + } + } + + root.addEventListener("keydown", function (e) { + if (e.target.tagName !== "BUTTON") return; + if (e.key === "ArrowRight") { show(current + 1, true); e.preventDefault(); } + else if (e.key === "ArrowLeft") { show(current - 1, true); e.preventDefault(); } + }); + + root.insertBefore(bar, root.firstChild); + root.classList.add("is-enhanced"); + show(0, false); + } + + function njJsonviewAll() { + var roots = document.querySelectorAll("[data-nj-jsonview-steps]"); + for (var i = 0; i < roots.length; i++) njJsonviewInit(roots[i]); + } + + if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") { + document$.subscribe(njJsonviewAll); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", njJsonviewAll); + } else { + njJsonviewAll(); + } +})(); diff --git a/docs/mkdocs/docs/js/fig-macros.js b/docs/mkdocs/docs/js/fig-macros.js new file mode 100644 index 000000000..7093e1736 --- /dev/null +++ b/docs/mkdocs/docs/js/fig-macros.js @@ -0,0 +1,585 @@ +/* figures: macro chooser for the NLOHMANN_DEFINE_TYPE_* / NLOHMANN_DEFINE_DERIVED_TYPE_* macros. + Usage:
    +{% endblock %} diff --git a/docs/mkdocs/overrides/home.html b/docs/mkdocs/overrides/home.html new file mode 100644 index 000000000..6ff5e09a8 --- /dev/null +++ b/docs/mkdocs/overrides/home.html @@ -0,0 +1,34 @@ +{#- Home page: the brand's blue hero band below the tabs (selected with "template: home.html" in index.md) -#} +{% extends "main.html" %} + +{% block tabs %} + {{ super() }} +
    +
    +
    +

    JSON for Modern C++

    +

    What if JSON was part of modern C++?

    +

    + Get started + API documentation + Latest release +

    +
    + +
    +
    +{% endblock %} diff --git a/docs/mkdocs/scripts/figures/charts.py b/docs/mkdocs/scripts/figures/charts.py new file mode 100644 index 000000000..6f1c4dc3e --- /dev/null +++ b/docs/mkdocs/scripts/figures/charts.py @@ -0,0 +1,280 @@ +"""Draws the measurement charts as inline SVGs into docs/images/. + +Generated files: + +- compile_times.svg: percent change in compile time per option at -O0 (integration/compile_times.md) +- compile_times_pch.svg, compile_times_modules.svg: percent change per translation unit with a precompiled header or + `import nlohmann.json;` at -O0, plus the one-off build cost (integration/compile_times.md) +- binary_sizes_.svg: encoded size per binary format relative to minified JSON, one panel per test file + (features/binary_formats/index.md, reused on features/performance.md) +- ordered_json_scaling.svg: parse time of a flat object for json vs. ordered_json (features/performance.md) + +Every number below is copied from the tables on those pages (or, where noted, computed from the milliseconds they +report); the tables stay on the pages as the accessible fallback. Update both together. The SVGs are not images: +the pages include their text with `--8<-- "images/.svg"`, so they pick up the colours and fonts of the page +(see the `nj-charts` rules in docs/css/fig-charts.css). Run `python3 scripts/figures/charts.py` from docs/mkdocs after +changing data or drawing, and commit the generated files. In SVG text write the word JSON as `J​SON`, +otherwise the abbreviation extension wraps it in an and the text disappears. +""" +import html +import math +import re +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +MINUS = "−" +JSON = "J​SON" + + +# words the abbreviation extension (includes/glossary.md) would wrap in inside the SVG text +ABBR = re.compile(r"(?' % (cls, W, H, tid, tid), + '%s' % (tid, esc(title)), + '%s' % (tid, esc(desc))] + s += body + s.append('') + (OUT / name).write_text(protect("\n".join(s)) + "\n") + + +def hbar(x0, x1, y, h, r=4): + """Horizontal bar from the baseline x0 to x1, rounded only at the data end.""" + if abs(x1 - x0) < 0.5: + return "M%g %g h0.5 v%g h-0.5 Z" % (x0, y, h) + r = min(r, h / 2, abs(x1 - x0)) + s = 1 if x1 > x0 else -1 + return ("M%g %g H%g Q%g %g %g %g V%g Q%g %g %g %g H%g Z" + % (x0, y, x1 - s * r, x1, y, x1, y + r, y + h - r, x1, y + h, x1 - s * r, y + h, x0)) + + +def pct(v): + if v == 0: + return "0 %" + return ("+" if v > 0 else MINUS) + "%g %%" % abs(v) + + +# ---------- compile times (integration/compile_times.md, -O0 columns) ---------- +# (label, translation unit, Apple clang %, GCC 16 %, computed?), ordered by saving +COMPILE = [ + ("json_fwd.hpp instead of json.hpp", "header-only TU", -62, -82, False), + ("extern template", "parsing TU", -7, -21, False), + ("JSON_NO_AUTOMATIC_UDLS", "model TU", -11, -11, False), + ("JSON_NO_AUTOMATIC_UDLS", "parsing TU", -1, -2, False), + ("JSON_HAS_FILESYSTEM=0 + JSON_NO_IO", "model TU", -8, -11, False), + ("JSON_HAS_FILESYSTEM=0", "model TU", -5, -9, False), + # computed from the ms in "Options without effect on compile times": 1034/1051, 1756/1801, 1055/1051, 1783/1801 + ("JSON_NO_IO", "model TU", round((1034 / 1051 - 1) * 100), round((1756 / 1801 - 1) * 100), True), + ("JSON_USE_GLOBAL_UDLS=0", "model TU", round((1055 / 1051 - 1) * 100), round((1783 / 1801 - 1) * 100), True), +] + + +def compile_times(): + W, L, R = 470, 8, 402 + lo, hi = -90, 10 + X = lambda v: L + (v - lo) / (hi - lo) * (R - L) + top, pitch, bh = 64, 52, 12 + H = top + pitch * len(COMPILE) + 30 + b = [] + # legend + b.append('' % L) + b.append('Apple clang' % (L + 18)) + b.append('' % (L + 118)) + b.append('GCC 16' % (L + 136)) + # grid and ticks + axis_y = top + pitch * len(COMPILE) - 8 + for t in (-80, -60, -40, -20): + b.append('' % (X(t), top - 6, X(t), axis_y)) + b.append('%s' % (X(t), axis_y + 16, pct(t))) + b.append('0 %%' % (X(0), axis_y + 16)) + b.append('← faster' % (L, axis_y + 34)) + b.append('slower →' % (W - 8, axis_y + 34)) + for i, (label, tu, clang, gcc, computed) in enumerate(COMPILE): + y = top + i * pitch + b.append('%s' + ' · %s%s' + % (L, y - 2, esc(label), tu, "*" if computed else "")) + for j, (v, cls, name) in enumerate(((clang, "clang", "Apple clang"), (gcc, "gcc", "GCC 16"))): + by = y + 6 + j * (bh + 2) + b.append('%s, %s (%s): %s' + % (cls, hbar(X(0), X(v), by, bh), esc(label), tu, name, esc(pct(v)))) + b.append('%s' % (max(X(0), X(v)) + 5, by + bh - 1.5, pct(v))) + b.append('' % (X(0), top - 6, X(0), axis_y)) + H = axis_y + 44 + desc = "; ".join("%s on a %s: Apple clang %s, GCC 16 %s" % (label, tu.replace("TU", "translation unit"), + pct(c).replace(MINUS, "-"), pct(g).replace(MINUS, "-")) for label, tu, c, g, _ in COMPILE) + write("compile_times.svg", W, H, "nj-charts--compile", "nj-charts-ct", + "Change in compile time against the default configuration at -O0, Apple clang and GCC 16", + "Grouped horizontal bars, negative is faster. " + desc + ".", b) + + +# ---------- precompiled headers and C++20 modules (integration/compile_times.md, -O0 columns) ---------- +# Per-TU medians of the wall-clock time in ms from the tables "Use precompiled headers" (-std=c++17) and +# "Use C++20 modules" (-std=c++20): {translation unit: {compiler: (default ms, with PCH/module ms)}}, plus the one-off +# build cost in ms per compiler. None instead of a value means "not measured"; NOT_AVAILABLE then gives the reason. +PCH = { + "model TU": {"Apple clang": (1045, 594), "GCC 16": (1789, 1060)}, + "parsing TU": {"Apple clang": (1148, 714), "GCC 16": (2498, 1884)}, +} +PCH_BUILD = {"Apple clang": 771, "GCC 16": 2254} +MODULES = { + "model TU": {"Apple clang": (1311, 680), "GCC 16": (2156, 1153)}, + "parsing TU": {"Apple clang": (1387, 800), "GCC 16": (2838, 1810)}, +} +MODULES_BUILD = {"Apple clang": 1630, "GCC 16": 2538} +NOT_AVAILABLE = {} # compiler: reason + + +def tradeoff(name, tid, rows, build, what, title, desc_intro): + """Percent change per TU (bars) and the one-off build cost (text rows below) for two compilers.""" + W, L, R = 440, 8, 372 + lo, hi = -60, 0 + X = lambda v: L + (v - lo) / (hi - lo) * (R - L) + top, pitch, bh = 64, 52, 12 + comps = (("Apple clang", "clang"), ("GCC 16", "gcc")) + b, desc = [], [] + b.append('' % L) + b.append('Apple clang' % (L + 18)) + b.append('' % (L + 118)) + b.append('GCC 16' % (L + 136)) + axis_y = top + pitch * len(rows) - 8 + for t in (-50, -25): + b.append('' % (X(t), top - 6, X(t), axis_y)) + b.append('%s' % (X(t), axis_y + 16, pct(t))) + b.append('0 %%' % (X(0), axis_y + 16)) + b.append('← faster per TU' % (L, axis_y + 34)) + for i, (tu, vals) in enumerate(rows.items()): + y = top + i * pitch + b.append('%s · %s' + % (L, y - 2, tu, esc(what))) + for j, (comp, cls) in enumerate(comps): + by = y + 6 + j * (bh + 2) + pair = vals[comp] + if pair is None: + b.append('%s: not available: %s' + % (L, by + bh - 1.5, comp, esc(NOT_AVAILABLE.get(comp, "not measured")))) + desc.append("%s, %s: not available" % (tu, comp)) + continue + d, w = pair + v = round((w / d - 1) * 100) + b.append('%s, %s: %d ms by default, %d ms %s (%s)' + % (cls, hbar(X(0), X(v), by, bh), tu, comp, d, w, esc(what), esc(pct(v)))) + b.append('%s' % (X(0) + 5, by + bh - 1.5, pct(v))) + desc.append("%s, %s: %s (%d ms to %d ms)" % (tu, comp, pct(v).replace(MINUS, "-"), d, w)) + b.append('' % (X(0), top - 6, X(0), axis_y)) + # one-off cost + y = axis_y + 62 + b.append('One-off cost · built once, ' + 'reused by every TU' % (L, y)) + for j, (comp, cls) in enumerate(comps): + yy = y + 20 + j * 20 + b.append('' % (cls, L, yy - 9.5)) + ms = build[comp] + saved = sorted(vals[comp][0] - vals[comp][1] for vals in rows.values() if vals[comp] is not None) + txt = ("%s: %d ms once, saves %d to %d ms per TU" % (comp, ms, saved[0], saved[-1])) if ms is not None else ("%s: not available: %s" % (comp, NOT_AVAILABLE.get(comp, "not measured"))) + b.append('%s' % (L + 16, yy, esc(txt))) + desc.append("one-off build, %s: %s" % (comp, ("%d ms" % ms) if ms is not None else "not available")) + H = y + 20 + 20 * len(comps) + write(name, W, H, "nj-charts--compile", tid, title, desc_intro + " " + "; ".join(desc) + ".", b) + + +# ---------- binary sizes (features/binary_formats/index.md, "Sizes") ---------- +FORMATS = ["BJData", "BON8", "BSON", "CBOR", "MessagePack", "UBJSON"] # colour slots 1-6, same in every panel +SIZES = { + "canada.json": [53.2, 50.5, 85.8, 50.5, 50.5, 53.2], + "twitter.json": [91.1, 83.8, 95.2, 86.3, 86.0, 91.3], + "citm_catalog.json": [78.1, 63.5, 95.8, 68.4, 68.5, 78.2], + "jeopardy.json": [96.6, 87.5, 106.7, 88.0, 87.9, 96.6], +} + + +def binary_sizes(): + W, L, R = 340, 104, 286 + X = lambda v: L + v / 110 * (R - L) + top, pitch, bh = 46, 26, 16 + for fname, vals in SIZES.items(): + b, vl = [], [] # value labels go on top of the 100 % line + lo = min(vals) + b.append('%s' % fname) + bottom = top + pitch * len(FORMATS) - 6 + for t in (0, 50): + b.append('' % (X(t), top - 6, X(t), bottom)) + b.append('%d %%' % (X(t), bottom + 15, t)) + for i, (fmt, v) in enumerate(zip(FORMATS, vals)): + y = top + i * pitch + best = v == lo + b.append('%s' + % (" nj-charts-best" if best else "", L - 8, y + bh - 3.5, fmt)) + b.append('%s, %s: %g %% of minified %s%s' + % (i + 1, "" if best else " nj-charts-dim", hbar(X(0), X(v), y, bh), fname, fmt, v, JSON, + " (smallest)" if best else "")) + vl.append('%.1f %%' + % (" nj-charts-best" if best else "", X(v) + 5, y + bh - 3.5, v)) + b.append('' % (X(100), top - 10, X(100), bottom)) + b.append('minified %s = 100 %%' + % (X(100) + 2, top - 14, JSON)) + b.append('100 %%' % (X(100), bottom + 15)) + b += vl + H = bottom + 24 + best = [f for f, v in zip(FORMATS, vals) if v == lo] + desc = "; ".join("%s %g %%" % (f, v) for f, v in zip(FORMATS, vals)) + stem = fname.split(".")[0] + write("binary_sizes_%s.svg" % stem, W, H, "nj-charts--sizes", "nj-charts-bs-" + stem, + "Size of %s in each binary format relative to minified %s" % (fname, JSON), + "%s. Smallest: %s." % (desc, " and ".join(best)), b) + + +# ---------- json vs. ordered_json (api/ordered_map.md, cited on features/performance.md) ---------- +SCALING = [(2000, 0.7, 3.6, 5), (4000, 0.8, 14.0, 19), (8000, 1.6, 67.8, 43), (16000, 3.3, 181.6, 54)] + + +def scaling(): + W, L, R, T, B = 440, 66, 330, 16, 210 + xs = [n for n, *_ in SCALING] + X = lambda n: L + math.log2(n / xs[0]) / math.log2(xs[-1] / xs[0]) * (R - L) + Y = lambda ms: B - (math.log10(ms) - math.log10(0.3)) / (math.log10(300) - math.log10(0.3)) * (B - T) + b = [] + for t in (1, 10, 100): + b.append('' % (L, Y(t), R, Y(t))) + b.append('%g ms' % (L - 6, Y(t) + 4, t)) + b.append('' % (L, B, R, B)) + for n, a, o, f in SCALING: + b.append('%s' % (X(n), B + 16, "{:,}".format(n).replace(",", " "))) + for key, idx, cls, name in (("json", 1, "json", "json"), ("ojson", 2, "ojson", "ordered_json")): + pts = [(X(r[0]), Y(r[idx])) for r in SCALING] + b.append('' % (cls, " ".join("%g,%g" % p for p in pts))) + for (x, y), r in zip(pts, SCALING): + b.append('%s, %d keys: %g ms' + % (cls, x, y, name, r[0], r[idx])) + last = SCALING[-1] + b.append('%s' % (cls, pts[-1][0] + 10, pts[-1][1] - 2, name)) + b.append('%g ms' % (pts[-1][0] + 10, pts[-1][1] + 13, last[idx])) + first = SCALING[0] + dy = 16 if key == "json" else 20 + b.append('%g ms' % (pts[0][0] + 6, pts[0][1] + dy, first[idx])) + write("ordered_json_scaling.svg", W, B + 28, "nj-charts--scaling", "nj-charts-sc", + "Time to parse a flat object of n keys, json against ordered_json, logarithmic axes", + "2000 keys: json 0.7 ms, ordered_json 3.6 ms (5x). 4000 keys: 0.8 ms and 14.0 ms (19x). " + "8000 keys: 1.6 ms and 67.8 ms (43x). 16000 keys: 3.3 ms and 181.6 ms (54x).", b) + + +if __name__ == "__main__": + compile_times() + tradeoff("compile_times_pch.svg", "nj-charts-pch", PCH, PCH_BUILD, "with PCH", + "Compile time per translation unit with a precompiled json.hpp at -O0, and the one-off cost of the PCH", + "Horizontal bars, negative is faster, medians of nine runs, -std=c++17.") + tradeoff("compile_times_modules.svg", "nj-charts-mod", MODULES, MODULES_BUILD, "import nlohmann.json", + "Compile time per translation unit with import nlohmann.json at -O0, and the one-off cost of the module", + "Horizontal bars, negative is faster, medians of nine runs, -std=c++20 for both.") + binary_sizes() + scaling() diff --git a/docs/mkdocs/scripts/figures/chunk_buffering.py b/docs/mkdocs/scripts/figures/chunk_buffering.py new file mode 100644 index 000000000..f21ef26a0 --- /dev/null +++ b/docs/mkdocs/scripts/figures/chunk_buffering.py @@ -0,0 +1,90 @@ +"""Draws the chunk-buffering strip of the large-input page as an inline SVG into docs/images/. + +The strip follows docs/examples/partial_input_buffering.cpp: five chunks are appended to a buffer, and after each one +the whole buffer is parsed again. A parse error whose byte is one past the buffer size means the input ended early +(wait for more); an error before the end is a real syntax error. The byte positions were checked by running the +example (4/5, 12/13, 19/20, 30/31, complete at 35; `[1, ]` fails at byte 5). + +The SVG is not an image: features/parsing/large_and_partial_input.md includes its text with +`--8<-- "images/chunk_buffering.svg"`, so it picks up the colours and fonts of the page (see the `nj-fig` rules in +docs/css/brand.css and the `nj-sax-` rules in docs/css/fig-sax.css). Run `python3 scripts/figures/chunk_buffering.py` +from docs/mkdocs after changing the drawing, and commit the generated file. In SVG text write the word JSON as +`J​SON`, otherwise the abbreviation extension wraps it in an and the text disappears. +""" +import html +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" + +CW = 8.4 # JetBrains Mono advance at 14px +TX = 96 # x of the buffer text +SX = 420 # x of the result column +ROW = 50 +CHUNKS = ['{"na', 'me": "Ma', 'ry", "l', 'ist": [1, 2', ', 3]}'] + + +def esc(s): + return html.escape(s).replace("J&#8203;SON", "J​SON") + + +def row(body, y, label, old, new, err_byte, status, status_cls, note): + body.append('%s' % (y + 5, esc(label))) + if old: + body.append('' % (TX - 3, y - 12, CW * len(old) + 3)) + body.append('' % (TX + CW * len(old), y - 12, CW * len(new) + 3)) + body.append('%s' % (TX, y + 5, esc(old + new))) + if err_byte is not None: + # the byte of a parse_error is 1-based: byte n is the character at index n - 1 + cx = TX + CW * (err_byte - 1) + CW / 2 + body.append('' % (status_cls, cx, y + 14)) + body.append('%s' % (status_cls, SX, y - 1, esc(status))) + body.append('%s' % (SX, y + 15, esc(note))) + + +def main(): + W = 700 + body = [] + body.append('chunk') + body.append('buffer, parsed from byte 1 each time' % TX) + body.append('result' % SX) + y, buf, parsed = 62, "", [] + for i, chunk in enumerate(CHUNKS): + old, buf = buf, buf + chunk + parsed.append(len(buf)) + if i < len(CHUNKS) - 1: + row(body, y, "chunk %d" % (i + 1), old, chunk, len(buf) + 1, + "error at byte %d = size + 1" % (len(buf) + 1), "nj-sax-s-wait", "incomplete: wait for more") + else: + row(body, y, "chunk %d" % (i + 1), old, chunk, None, + "parsed, size %d" % len(buf), "nj-sax-s-ok", "complete: one value") + y += ROW + # the syntax error of the example, in a buffer of its own + y += 8 + body.append('' % (y - 30, W - 16)) + row(body, y, "other input", "", "[1, ]", 5, "error at byte 5, size 5", "nj-sax-s-err", "before the end: a real error") + y += 46 + total = " + ".join(str(n) for n in parsed) + for k, line in enumerate(["Bytes parsed: %s = %d for a value of %d bytes." % (total, sum(parsed), parsed[-1]), + "Every attempt starts again at byte 1, so the total work grows", + "quadratically with the size of the value."]): + body.append('%s' % (y + 20 * k, esc(line))) + y += 40 + H = int(y + 36) + + tid, did = "fig-sax-buf-title", "fig-sax-buf-desc" + title = "Buffering chunks until the value is complete: five chunks of {\"name\": \"Mary\", \"list\": [1, 2, 3]} appended to a buffer." + desc = ("After chunks 1 to 4 the buffer has 4, 12, 19, and 30 bytes, and parsing it fails at byte 5, 13, 20, and 31: " + "one past the end, so the input is incomplete and the program waits for more. After chunk 5 the buffer of 35 " + "bytes parses as one complete value. For the input [1, ] of 5 bytes the error is at byte 5, before the end, " + "so it is a real syntax error. Every attempt parses the buffer from byte 1, so 4 + 12 + 19 + 30 + 35 = 100 " + "bytes are parsed for a 35-byte value, and the work grows quadratically.") + s = ['' % (W, H, tid, did), + '%s' % (tid, esc(title)), + '%s' % (did, esc(desc))] + s += body + s.append("") + (OUT / "chunk_buffering.svg").write_text("\n".join(s) + "\n") + + +main() diff --git a/docs/mkdocs/scripts/figures/exception_map.py b/docs/mkdocs/scripts/figures/exception_map.py new file mode 100644 index 000000000..c56fa2891 --- /dev/null +++ b/docs/mkdocs/scripts/figures/exception_map.py @@ -0,0 +1,168 @@ +"""Draws the exception id map of home/exceptions.md as an inline SVG into docs/images/exception_map.svg. + +The SVG is not an image: the page includes its text with `--8<-- "images/exception_map.svg"`, so it picks up the +colours and fonts of the page (see the `nj-fig` rules in docs/css/brand.css and the `nj-exceptions-*` rules in +docs/css/fig-exceptions.css). The file holds two drawings of the same map, a wide one and a narrow one for phones; +the CSS shows one of them. Every id chip is a link to the `###` section of that id on the page, every family box a +link to the family's `##` section. + +Run `python3 scripts/figures/exception_map.py` from docs/mkdocs after changing the drawing or after adding an +exception section to home/exceptions.md (the ids are read from the page), and commit the generated file. In SVG text +write the word JSON as `J​SON`, otherwise the abbreviation extension wraps it in an and the text +disappears. +""" +import html +import re +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +PAGE = Path(__file__).resolve().parents[2] / "docs" / "home" / "exceptions.md" +P = "nj-exceptions-" + +# family name, id range label, anchor of the family section, extra member +FAMILIES = [ + ("parse_error", "1xx", "parse-errors", "byte"), + ("invalid_iterator", "2xx", "iterator-errors", None), + ("type_error", "3xx", "type-errors", None), + ("out_of_range", "4xx", "out-of-range", None), + ("other_error", "5xx", "further-exceptions", None), +] + +# the chips show the id only; naming just some of them was confusing +LABELS = {} + + +GLOSSARY = Path(__file__).resolve().parents[2] / "includes" / "glossary.md" +ABBRS = sorted(re.findall(r"^\*\[([^\]]+)\]:", GLOSSARY.read_text(), re.M), key=len, reverse=True) + + +def noabbr(s): + """Breaks every glossary abbreviation with a zero-width space, so the abbr extension leaves SVG text alone.""" + for a in ABBRS: + s = re.sub(r"(?' % (P, " %schip--named" % P if label else "", anchor, esc(aria)), + '' % (x, y, w, CH), + '%d' % (P, x + 9, y + CH / 2 + 4.5, num)] + if label: + s.append('%s' % (P, x + 9 + 3 * MONO + 9, y + CH / 2 + 4.5, noabbr(esc(label)))) + s.append('') + return w, "".join(s) + + +def flow(fam, nums, x0, x1, y0): + """Places the chips of one family left to right, wrapping at x1. Returns (svg parts, bottom y).""" + parts, x, y = [], x0, y0 + for n in nums: + w, _ = chip(0, 0, fam, n) + if x > x0 and x + w > x1: + x, y = x0, y + CH + GAP + _, s = chip(x, y, fam, n) + parts.append(s) + x += w + GAP + return parts, y + CH + + +def box(x, y, w, h, title, sub, href=None, cls=""): + s = [] + if href: + s.append('' % (P, href, esc(title + ", " + sub))) + s.append('' % (P, cls, x, y, w, h)) + s.append('%s' % (P, x + 12, y + 22, noabbr(esc(title)))) + s.append('%s' % (P, x + 12, y + 41, noabbr(esc(sub)))) + if href: + s.append('') + return "".join(s) + + +def drawing(W, narrow, suffix): + global CH + CH = 34 if narrow else 28 # larger touch targets on phones + ids = ids_on_page() + mid = "nj-exc-tri-" + suffix + body = [] + trunk = 34 + # std::exception and json::exception + body.append(box(12, 12, 190 if narrow else 200, 52, "std::exception", "C++ standard library base")) + body.append('' % (P, trunk, 104, trunk, 66, mid)) + body.append(box(12, 104, 250 if narrow else 300, 52, "json::exception", "id, what() \u00b7 catch-all")) + y = 196 + last_branch = y + fam_w = W - 64 - 12 if narrow else 214 + for fam, rng, href, member in FAMILIES: + nums = ids[fam] + sub = "%s \u00b7 %d ids" % (rng, len(nums)) + (" \u00b7 adds member %s" % member if member else "") + body.append('' % (P, trunk, y + 26, 64, y + 26)) + last_branch = y + 26 + body.append(box(64, y, fam_w, 52, "json::" + fam, sub, href, " %sbox--fam" % P)) + if narrow: + parts, bottom = flow(fam, nums, 64, W - 12, y + 52 + 10) + else: + parts, bottom = flow(fam, nums, 64 + fam_w + 16, W - 12, y + 12) + body += parts + y = max(bottom, y + 52) + 26 + # trunk from json::exception down to the last family, with the inheritance triangle at the top + body.insert(0, '' % (P, trunk, last_branch, trunk, 158, mid)) + H = y - 14 + defs = ('' + '' % (mid, P)) + tid, did = "nj-exc-map-title-" + suffix, "nj-exc-map-desc-" + suffix + total = sum(len(v) for v in ids.values()) + desc = ("Class tree: std::exception is the base of json::exception, which is the base of the five exception " + "families parse_error (ids 1xx), invalid_iterator (2xx), type_error (3xx), out_of_range (4xx) and " + "other_error (5xx). Each family lists the ids documented on this page, %d in total; every id links to " + "its section." % total) + s = ['' + % (P, P, "narrow" if narrow else "wide", W, H, tid, did), + 'Exception id map' % tid, + '%s' % (did, noabbr(esc(desc))), + defs] + body + [''] + return "\n".join(s) + + +def main(): + out = drawing(760, False, "w") + "\n" + drawing(360, True, "n") + "\n" + (OUT / "exception_map.svg").write_text(out) + + +main() diff --git a/docs/mkdocs/scripts/figures/integration.py b/docs/mkdocs/scripts/figures/integration.py new file mode 100644 index 000000000..73bcb6ef4 --- /dev/null +++ b/docs/mkdocs/scripts/figures/integration.py @@ -0,0 +1,116 @@ +"""Draws the 3.x / 4.0 lane diagram of community/roadmap.md as an inline SVG into docs/images/. + +The rows come from the page itself: the macro table under "Trying out 4.0 today" gives each macro, its 3.x default, +whether 4.0 removes it, and the version that added it; the `#define` block below that table gives the 4.0 value. Run +`python3 scripts/figures/integration.py` from docs/mkdocs after changing that section, and commit the generated +docs/images/roadmap_lanes.svg. The page includes the SVG text with `--8<-- "images/roadmap_lanes.svg"`, so it follows +the light/dark toggle (see the `nj-fig` rules in docs/css/brand.css and the `nj-int-*` rules in +docs/css/fig-integration.css). In SVG text write J​SON and A​PI, otherwise the abbreviation extension +wraps the word in an and the text disappears. +""" +import re +import sys +from pathlib import Path + +DOCS = Path(__file__).resolve().parents[2] / "docs" +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +PAGE = DOCS / "community" / "roadmap.md" + + +def esc(text): + return text.replace("&", "&").replace("<", "<").replace(">", ">") + + +def vkey(version): + return tuple(int(p) for p in version.split(".")) + + +def parse(markdown): + section = markdown.split("### Trying out 4.0 today", 1)[1].split("\n### ", 1)[0] + rows = [] + for line in section.splitlines(): + cells = [c.strip() for c in line.strip().strip("|").split("|")] + m = re.match(r"\[`(JSON_[A-Z0-9_]+)`\]", cells[0]) if len(cells) == 5 else None + if not m: + continue + default = re.fullmatch(r"`(\d)`", cells[1]) + added = re.fullmatch(r"(\d+\.\d+\.\d+)", cells[4]) + if not default or not added: + sys.exit("cannot parse row: " + line) + rows.append({"macro": m.group(1), "default": default.group(1), "added": added.group(1), + "removed": cells[2].startswith("removed")}) + values = dict(re.findall(r"^#define (JSON_[A-Z0-9_]+) (\d)$", section, re.MULTILINE)) + for row in rows: + if row["macro"] not in values: + sys.exit("no #define for " + row["macro"]) + row["v4"] = values[row["macro"]] + if not rows: + sys.exit("no macro rows found in " + str(PAGE)) + return rows + + +def draw(rows): + W, ROW, TOP = 880, 34, 168 + H = TOP + ROW * len(rows) + 30 + versions = sorted({r["added"] for r in rows}, key=vkey) + s = ['' % (W, H)] + s.append('Two lanes: the 3.x release train, whose minor and patch releases do not ' + 'break code that uses the public A​PI, followed by version 4.0, which has no release date yet. ' + 'Each breaking change is a macro that is available in 3.x today with its 3.x default and becomes the ' + 'default in 4.0: %s.' + % "; ".join("%s, added in %s, 3.x default %s, 4.0 value %s%s" + % (r["macro"], r["added"], r["default"], r["v4"], ", macro removed" if r["removed"] else "") + for r in rows)) + s.append('') + # lanes + s.append('') + s.append('3.x release train') + s.append('available today') + x0, x1 = 300, 590 + step = (x1 - x0) / max(1, len(versions) - 1) if len(versions) > 1 else 0 + for i, v in enumerate(versions): + x = x0 + i * step if len(versions) > 1 else (x0 + x1) / 2 + s.append('' % (x, x)) + s.append('%s' % (x, v)) + s.append('') + s.append('') + s.append('') + s.append('4.0') + s.append('no release date yet') + # guarantee + s.append('A minor or patch release of 3.x does not break code that uses ' + 'the public A​PI, unless that code opts in with a macro.') + s.append('4.0 is not developed on a separate branch: it switches the ' + 'defaults of these macros and removes the macros.') + # header + hy = TOP - 18 + s.append('Macro' % hy) + s.append('added' % hy) + s.append('3.x default' % hy) + s.append('define to try today' % hy) + s.append('4.0 default' % hy) + for i, r in enumerate(rows): + y = TOP + i * ROW + ROW / 2 + s.append('' % (y - ROW / 2 + 2, ROW - 4)) + s.append('%s' % (y + 4.5, r["macro"])) + s.append('%s' % (y + 4.5, r["added"])) + s.append('' % (y - 11)) + s.append('%s' % (y + 4.5, r["default"])) + s.append('' % (y, y)) + s.append('' % (y - 11)) + if r["removed"]: + s.append('removed' % (y + 4.5)) + else: + s.append('%s' % (y + 4.5, r["v4"])) + s.append('Versions on the 3.x lane are the ones that added a macro, ' + 'not to scale. “removed”: see the 4.0 behavior column of the table.' % (H - 8)) + s.append('') + return "\n".join(s) + "\n" + + +rows = parse(PAGE.read_text(encoding="utf-8")) +(OUT / "roadmap_lanes.svg").write_text(draw(rows), encoding="utf-8") +print("wrote", OUT / "roadmap_lanes.svg", "with", len(rows), "macros") diff --git a/docs/mkdocs/scripts/figures/iterator_callback_figures.py b/docs/mkdocs/scripts/figures/iterator_callback_figures.py new file mode 100644 index 000000000..49d92ab28 --- /dev/null +++ b/docs/mkdocs/scripts/figures/iterator_callback_figures.py @@ -0,0 +1,103 @@ +"""Draws the parser-callback and iterator illustrations as an inline SVG into docs/images/. + +The SVG is not an image: the pages include its text with `--8<-- "images/callback_events.svg, range-begin-end.svg and range-rbegin-rend.svg"`, so it picks up the colours and fonts of the +page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/iterator_callback_figures.py` from docs/mkdocs after +changing the drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the +abbreviation extension wraps it in an and the text disappears. +""" +import html +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +CW = 9.6 # JetBrains Mono advance at 16px + +def write(name, W, H, cls, tid, title, body, defs=""): + s = ['' % (cls, W, H, tid), + '%s' % (tid, html.escape(title).replace('J&#8203;SON', 'J​SON'))] + if defs: s.append(defs) + s += body + s.append('') + open(OUT / name, "w").write("\n".join(s) + "\n") + +# ---------- callback events ---------- +def callback(): + body, y0, lh = [], 74, 28 + body.append('J​SON to parse') + body.append('callback events') + lines = [ + (0, [("{", None), (None, "os")]), + (2, [('"name"', None), (None, "k"), (': "Berlin"', None), (None, "v"), (",", None)]), + (2, [('"location"', None), (None, "k"), (": [", None), (None, "as")]), + (4, [("52.519444", None), (None, "v"), (",", None)]), + (4, [("13.406667", None), (None, "v")]), + (2, [("]", None), (None, "ae"), (None, "v")]), + (0, [("}", None), (None, "oe"), (None, "v")]), + ] + for i, (indent, parts) in enumerate(lines): + y = y0 + i * lh + x = 16 + indent * CW + for text, ev in parts: + if text is not None: + body.append('%s' % (x, y, html.escape(text))) + x += CW * len(text) + else: + body.append('' % (ev, x + 10, y - 5)) + x += 20 if text is None else 0 + legend = [("os", "object_start"), ("k", "key"), ("oe", "object_end"), ("as", "array_start"), ("ae", "array_end"), ("v", "value")] + for i, (ev, label) in enumerate(legend): + y = 74 + i * 30 + body.append('' % (ev, y - 5)) + body.append('%s' % (y, label)) + write("callback_events.svg", 480, 262, "nj-fig--cb", "fig-cb-title", + "Example of the parse events a callback sees for the J​SON text {\"name\": \"Berlin\", \"location\": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete.", body) + +# ---------- ranges ---------- +BW, GAP, X0 = 30, 4, 30 +def cell_x(i): return X0 + i * (BW + GAP) +def cell(i, y, kind): + c = "fg-cell" + (" fg-cell--end" if kind == "end" else "") + return '' % (c, cell_x(i), y, BW) +def pill(cx, y, label, W, label_w): + x = max(4, min(W - 4 - label_w, cx - label_w / 2)) + return ['' % (x, y, label_w), + '%s' % (x + label_w / 2, y + 16.5, label)] +def arrow(x, ya, yb, mid, dashed=False): + return '' % (" fg-arrow--dash" if dashed else "", x, ya, x, yb, mid) + +def ranges1(): + W, H, mid = 600, 150, "fig-be-arrow" + defs = '' % mid + body = [] + for i in range(15): body.append(cell(i, 62, "elem")) + body.append(cell(15, 62, "end")) + body += pill(cell_x(0) + BW / 2, 8, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid)) + body += pill(cell_x(15) + BW / 2, 8, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid)) + body.append('Past-the-last element' % (cell_x(15) + BW / 2 - 18)) + body.append(arrow(cell_x(15) + BW / 2, 124, 96, mid)) + write("range-begin-end.svg", W, H, "nj-fig--range", "fig-be-title", + "A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position.", body, defs) + +def ranges2(): + W, H, mid = 600, 236, "fig-rbe-arrow" + defs = '' % mid + body = [] + ytop, ybot = 62, 144 + body.append(cell(0, ytop, "end")) + for i in range(1, 16): body.append(cell(i, ytop, "elem")) + for i in range(15): body.append(cell(i, ybot, "elem")) + body.append(cell(15, ybot, "end")) + body += pill(cell_x(0) + BW / 2, 8, "rend", W, 52); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid)) + body += pill(cell_x(15) + BW / 2, 8, "rbegin", W, 68); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid)) + body.append('Reverse past-the-last element' % (cell_x(0) + BW / 2 + 36)) + for col in (0, 15): + x = cell_x(col) + BW / 2 + body.append('' % (x, x, ybot - 2)) + body.append('A reverse iterator stores an iterator to the next element') + body.append('') + body += pill(cell_x(0) + BW / 2, 204, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 202, 178, mid)) + body += pill(cell_x(15) + BW / 2, 204, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 202, 178, mid)) + body.append('Past-the-last element' % (cell_x(15) + BW / 2 - 40)) + write("range-rbegin-rend.svg", W, H, "nj-fig--range", "fig-rbe-title", + "Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end().", body, defs) + +callback(); ranges1(); ranges2() diff --git a/docs/mkdocs/scripts/figures/json_pointer_flatten.py b/docs/mkdocs/scripts/figures/json_pointer_flatten.py new file mode 100644 index 000000000..e9e1a744d --- /dev/null +++ b/docs/mkdocs/scripts/figures/json_pointer_flatten.py @@ -0,0 +1,110 @@ +"""Draws the flatten()/unflatten() illustration of features/json_pointer.md as an inline SVG into docs/images/. + +The SVG is not an image: the page includes its text with `--8<-- "images/json_pointer_flatten.svg"`, so it picks up +the colours and fonts of the page (see the `nj-fig` rules in docs/css/brand.css and the `nj-pointer` rules in +docs/css/fig-pointer.css). Run `python3 scripts/figures/json_pointer_flatten.py` from docs/mkdocs after changing the +drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the abbreviation +extension wraps it in an and the text disappears. + +Left: the example document of the page as a tree. Right: the flat object that flatten() returns, one key per leaf. +Each leaf is joined to its flat key by a connector; leaf, connector and flat value share the colour of the value type +(string, number, boolean) used by the code highlighting. +""" +import html +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +CW = 9.6 # JetBrains Mono advance at 16px +ROW = 28 +Y0 = 92 # baseline of the first tree row +X0 = 20 # x of the root label +IND = 26 # indentation per level + +# (depth, label, value or None, value type, flat key) +TREE = [ + (0, "root", None, None, None), + (1, '"array"', None, None, None), + (2, "0", '"A"', "s", "/array/0"), + (2, "1", '"B"', "s", "/array/1"), + (2, "2", '"C"', "s", "/array/2"), + (1, '"nested"', None, None, None), + (2, '"one"', "1", "n", "/nested/one"), + (2, '"two"', "2", "n", "/nested/two"), + (2, '"three"', None, None, None), + (3, "0", "true", "b", "/nested/three/0"), + (3, "1", "false", "b", "/nested/three/1"), +] +W = 760 +FX = 470 # x of the flat keys + + +def esc(s): + return html.escape(s, quote=False) + + +def main(): + body = [] + H = Y0 + (len(TREE) - 1) * ROW + 70 + mid = "fig-flat-arrow" + defs = ('' % mid) + + body.append('nested value' % X0) + body.append('flat object: key = J​SON Pointer' % FX) + + # flatten() arrow on top, unflatten() arrow at the bottom + ax1, ax2 = 250, FX - 20 + body.append('' % (ax1, ax2, mid)) + body.append('flatten()' % ((ax1 + ax2) // 2)) + yb = H - 22 + body.append('' % (ax2, yb, ax1, yb, mid)) + body.append('unflatten()' % ((ax1 + ax2) // 2, yb - 8)) + + # tree: elbow lines from each node to its parent + rows = [] + parents = [] + for i, (d, label, val, typ, key) in enumerate(TREE): + y = Y0 + i * ROW + x = X0 + d * IND + while parents and parents[-1][0] >= d: + parents.pop() + if parents: + px = X0 + parents[-1][0] * IND + 5 + py = parents[-1][1] + 7 + body.append('' % (px, py, y - 5, x - 4)) + parents.append((d, y)) + rows.append((x, y, label, val, typ, key)) + + leaves = [r for r in rows if r[3] is not None] + # flat keys: seven consecutive rows, centred on the leaf rows (tree rows 2 to 10) + for j, (x, y, label, val, typ, key) in enumerate(leaves): + fy = Y0 + (3 + j) * ROW + lx = x + CW * (len(label) + 2 + len(val)) + 10 + body.append('' + % (typ, lx, y - 5, lx + 120, y - 5, FX - 130, fy - 5, FX - 10, fy - 5)) + body.append('' % (typ, lx, y - 5)) + body.append('"%s": ' + '%s' % (FX, fy, esc(key), typ, esc(val))) + + for x, y, label, val, typ, key in rows: + if val is None: + cls = "fg-code nj-pointer-fl-inner" if label != "root" else "fg-h" + body.append('%s' % (cls, x, y, esc(label))) + else: + body.append('%s: ' + '%s' % (x, y, esc(label), typ, esc(val))) + + title = ("flatten() turns the nested example document into a flat object, and unflatten() turns it back. " + "Left, the document as a tree: array with the elements A, B, C, and nested with one = 1, two = 2, and " + "three with the elements true and false. Right, the flat object with one key per leaf: /array/0 = A, " + "/array/1 = B, /array/2 = C, /nested/one = 1, /nested/two = 2, /nested/three/0 = true, " + "/nested/three/1 = false. Each leaf is joined to its key by a line.") + s = ['' % (W, H), + '%s' % esc(title), defs] + s += body + s.append('') + (OUT / "json_pointer_flatten.svg").write_text("\n".join(s) + "\n") + + +main() diff --git a/docs/mkdocs/scripts/figures/json_view_layout.py b/docs/mkdocs/scripts/figures/json_view_layout.py new file mode 100644 index 000000000..9d362ab9a --- /dev/null +++ b/docs/mkdocs/scripts/figures/json_view_layout.py @@ -0,0 +1,391 @@ +"""Draws the json_view illustrations of docs/features/json_view.md as inline SVGs into docs/images/. + +The SVGs are not images: the page includes their text with `--8<-- "images/json_view_layout.svg"` (and +json_view_lifetime.svg, json_view_edit_0.svg ... json_view_edit_4.svg), so they pick up the colours and fonts of +the page (see the `nj-fig` rules in docs/css/brand.css and the `nj-jsonview-*` rules in docs/css/fig-jsonview.css). +The edit frames are the steps of a stepper (docs/js/fig-jsonview.js); without JavaScript they show one below the +other. Run `python3 scripts/figures/json_view_layout.py` from docs/mkdocs after changing the drawing, and commit the +generated files. In SVG text write the word JSON as `J​SON`, otherwise the abbreviation extension wraps it in +an and the text disappears. + +The node fields drawn here follow `struct node` in include/nlohmann/detail/view/node.hpp and the edit storage in +detail/view/edit.hpp and edit_storage.hpp, for the 54-byte text in TEXT (9 nodes). +""" +import html +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +TEXT = '{"port": 6379, "price": 19.90, "tags": ["eu", "a\\"b"]}' +assert len(TEXT) == 54 + +# concept -> colour class (fig-jsonview.css maps them to brand colours) +STRUCT, KEY, STR, NUM, DEC, OWN, BAD, LINE = "struct", "key", "str", "num", "dec", "own", "bad", "line" + + +def esc(s): + return html.escape(s, quote=True) + + +def write(name, W, H, tid, title, desc, body, extra_cls=""): + s = ['' % (extra_cls, W, H, tid, tid), + '%s' % (tid, esc(title).replace("\u200b", "​")), + '%s' % (tid, esc(desc))] + s += body + s.append('') + text = "\n".join(s) + "\n" + assert "\n\n" not in text # a blank line would end the raw HTML block + (OUT / name).write_text(text) + + +def markers(prefix, kinds=(LINE, STR, KEY, NUM, DEC, OWN, STRUCT, BAD)): + """one arrow head per colour; ids are unique per SVG (hidden frames must not own the markers)""" + m = [''] + for k in kinds: + m.append('' % (prefix, k, k)) + m.append('') + return m + + +def rect(x, y, w, h, cls, c=None, rx=5): + cc = " nj-jsonview-c-%s" % c if c else "" + return '' % (cls, cc, x, y, w, h, rx) + + +def text(x, y, s, cls="nj-jsonview-t", c=None, anchor=None): + cc = " nj-jsonview-c-%s" % c if c else "" + a = ' text-anchor="%s"' % anchor if anchor else "" + return '%s' % (cls, cc, x, y, a, esc(s)) + + +def curve(x1, y1, x2, y2, c, prefix, dashed=False, bend=None, head=True): + """vertical-ish cubic from (x1, y1) to (x2, y2)""" + b = bend if bend is not None else (y2 - y1) / 2 + d = "M%g %g C%g %g %g %g %g %g" % (x1, y1, x1, y1 + b, x2, y2 - b, x2, y2) + return '' % ( + c, " nj-jsonview-dash" if dashed else "", d, ' marker-end="url(#%s-%s)"' % (prefix, c) if head else "") + + +def line(x1, y1, x2, y2, c, prefix, dashed=False, head=True): + return '' % ( + c, " nj-jsonview-dash" if dashed else "", x1, y1, x2, y2, ' marker-end="url(#%s-%s)"' % (prefix, c) if head else "") + + +# ---------------------------------------------------------------- the parsed index of TEXT +# (index, colour, kind, field lines, source span or None, label for compact frames) +NODES = [ + (0, STRUCT, "object", ["len 3", "next 9"], None, "{ } 3"), + (1, KEY, "key", ["off 2", "len 4"], (2, 6), '"port"'), + (2, NUM, "uint", ["6379", "(converted)"], (9, 13), "6379"), + (3, KEY, "key", ["off 16", "len 5"], (16, 21), '"price"'), + (4, NUM, "float", ["off 24", "len 5"], (24, 29), "19.90"), + (5, KEY, "key", ["off 32", "len 4"], (32, 36), '"tags"'), + (6, STRUCT, "array", ["len 2", "next 3"], None, "[ ] 2"), + (7, STR, "string", ["off 41", "len 2"], (41, 43), '"eu"'), + (8, DEC, "string", ["escaped", "len 3"], (47, 51), 'a"b'), +] + + +def ribbon(body, x0, y, cw, prefix, label=True): + """the source text as a strip of bytes, with the spans the nodes refer to tinted""" + w = cw * len(TEXT) + body.append(rect(x0, y, w, 26, "nj-jsonview-ribbon", rx=4)) + for _, c, kind, _, span, _ in NODES: + if span: + body.append(rect(x0 + span[0] * cw + 1, y + 2, (span[1] - span[0]) * cw - 2, 22, "nj-jsonview-span", c, rx=3)) + for i, ch in enumerate(TEXT): + if ch != " ": + body.append(text(x0 + i * cw + cw / 2, y + 17.5, ch, "nj-jsonview-mono nj-jsonview-byte", anchor="middle")) + if label: + for off in (0, 10, 20, 30, 40, 50): + body.append(text(x0 + off * cw + cw / 2, y + 40, str(off), "nj-jsonview-tiny", anchor="middle")) + return w + + +# ---------------------------------------------------------------- figure 1: parse() vs json_document +def layout(): + W, H, P = 840, 692, "jvl" + b = markers(P) + # --- panel A: basic_json::parse() + b.append(text(16, 26, "basic_json::parse(): a tree, every array, object, and string in its own heap allocation", "nj-jsonview-h")) + b.append(rect(16, 98, 60, 34, "nj-jsonview-box")) + b.append(text(46, 120, "json", "nj-jsonview-mono", anchor="middle")) + b.append(line(76, 115, 106, 115, LINE, P)) + b.append(rect(108, 98, 84, 34, "nj-jsonview-heap")) + b.append(text(150, 120, "std::map", "nj-jsonview-mono", anchor="middle")) + rows = [('"port"', "6379", None), ('"price"', "19.9", "double: 19.90 is gone"), ('"tags"', "\u2022", None)] + for i, (k, v, note) in enumerate(rows): + y = 50 + i * 48 + b.append(curve(192, 115, 226, y + 17, LINE, P, bend=0)) + b.append(rect(228, y, 180, 34, "nj-jsonview-heap")) + b.append(text(240, y + 22, k, "nj-jsonview-mono", KEY)) + b.append(text(396, y + 22, v, "nj-jsonview-mono", NUM if i < 2 else LINE, anchor="end")) + if note: + b.append(text(418, y + 22, note, "nj-jsonview-note")) + yt = 50 + 2 * 48 + 17 + b.append(line(408, yt, 442, yt, LINE, P)) + b.append(rect(444, yt - 17, 104, 34, "nj-jsonview-heap")) + b.append(text(496, yt + 5, "std::vector", "nj-jsonview-mono", anchor="middle")) + b.append(line(548, yt, 576, yt, LINE, P)) + b.append(rect(578, yt - 17, 92, 34, "nj-jsonview-heap")) + b.append('' % (yt - 17, yt + 17)) + b.append(text(601, yt + 5, "json", "nj-jsonview-mono", anchor="middle")) + b.append(text(647, yt + 5, "json", "nj-jsonview-mono", anchor="middle")) + b.append(curve(601, yt - 17, 706, 130, LINE, P, bend=-20)) + b.append(curve(647, yt + 17, 706, 196, LINE, P, bend=20)) + b.append(rect(708, 113, 126, 34, "nj-jsonview-heap")) + b.append(text(771, 135, "std::string eu", "nj-jsonview-mono nj-jsonview-sm", STR, anchor="middle")) + b.append(rect(708, 179, 126, 34, "nj-jsonview-heap")) + b.append(text(771, 201, 'std::string a"b', "nj-jsonview-mono nj-jsonview-sm", STR, anchor="middle")) + # legend + b.append(rect(16, 186, 18, 14, "nj-jsonview-heap", rx=3)) + b.append(text(42, 198, "a separate heap allocation", "nj-jsonview-note")) + # --- panel B: json_document::parse() + y0 = 252 + b.append('' % (y0 - 18)) + b.append(text(16, y0 + 10, "json_document::parse(): the source text stays where it is; one flat index of 16-byte nodes", "nj-jsonview-h")) + b.append(text(16, y0 + 38, "source text, 54 bytes (borrowed, or owned by the document)", "nj-jsonview-note")) + cw, rx0, ry = 14, 42, y0 + 48 + ribbon(b, rx0, ry, cw, P) + # node strip inside the document frame + fy = ry + 66 + b.append(rect(16, fy, 808, 254, "nj-jsonview-frame", rx=10)) + nw, gap, nx0, ny, nh = 84, 4, 36, fy + 40, 68 + b.append(text(30, fy + 236, "json_document: owns the node index and the buffer of decoded strings", "nj-jsonview-note nj-jsonview-strong")) + b.append(text(30, ny + nh + 26, "node index: 9 nodes \u00d7 16 bytes = 144 bytes, in document order", "nj-jsonview-note")) + for i, c, kind, fields, span, _ in NODES: + x = nx0 + i * (nw + gap) + b.append(rect(x, ny, nw, nh, "nj-jsonview-node", c)) + b.append(text(x + 6, ny + 14, str(i), "nj-jsonview-tiny")) + b.append(text(x + nw / 2, ny + 28, kind, "nj-jsonview-kind", c, anchor="middle")) + for j, f in enumerate(fields): + b.append(text(x + nw / 2, ny + 45 + j * 14, f, "nj-jsonview-mono nj-jsonview-xs", anchor="middle")) + if span and c in (KEY, STR, NUM) and not (c == NUM and kind == "uint"): + sx = rx0 + (span[0] + span[1]) / 2 * cw + b.append(curve(x + nw / 2, ny, sx, ry + 28, c, P)) + # the integer has no pointer: its value is in the node + # decoded strings + dx, dy = 740, ny + nh + 44 + n8x = nx0 + 8 * (nw + gap) + nw / 2 + b.append(text(dx + 33, dy + 50, "decoded strings", "nj-jsonview-note", anchor="middle")) + b.append(rect(dx, dy, 3 * 22, 30, "nj-jsonview-node", DEC, rx=4)) + for k, ch in enumerate('a"b'): + b.append(text(dx + 11 + k * 22, dy + 20, ch, "nj-jsonview-mono", anchor="middle")) + b.append(curve(n8x, ny + nh, dx + 33, dy, DEC, P)) + # the view: two pointers + vx, vy = 480, fy + 254 + 28 + b.append(text(vx - 10, vy + 22, 'json_view v = doc.root()["tags"]', "nj-jsonview-mono nj-jsonview-sm", anchor="end")) + b.append(rect(vx, vy, 140, 34, "nj-jsonview-node", STRUCT, rx=6)) + b.append('' % (vx + 70, vy, vy + 34)) + b.append(text(vx + 35, vy + 22, "doc*", "nj-jsonview-mono", anchor="middle")) + b.append(text(vx + 105, vy + 22, "node*", "nj-jsonview-mono", anchor="middle")) + b.append(line(vx + 35, vy, vx + 35, fy + 254 + 2, STRUCT, P)) + n6x = nx0 + 6 * (nw + gap) + nw / 2 + b.append(curve(vx + 105, vy, n6x, ny + nh, STRUCT, P)) + b.append(text(30, ny + nh + 50, "integers are converted while parsing and kept in their node;", "nj-jsonview-note")) + b.append(text(30, ny + nh + 68, "floats and strings point back into the text", "nj-jsonview-note")) + write("json_view_layout.svg", W, H, "fig-jvl-title", + "The same J\u200bSON text parsed by basic_json::parse() and by json_document::parse()", + "The text {\"port\": 6379, \"price\": 19.90, \"tags\": [\"eu\", \"a\\\"b\"]}. Top: basic_json::parse() builds a tree: " + "the root json value points to a std::map whose three nodes are separate heap allocations; the price is stored " + "as the double 19.9; the tags array is a std::vector of two json values, each pointing to its own std::string. " + "Bottom: json_document::parse() keeps the 54 bytes of source text and builds a flat index of nine 16-byte nodes in " + "document order: object, key port, unsigned integer 6379 (the converted value is in the node), key price, float, " + "key tags, array, string eu, and an escaped string. Keys, the float, and the plain string point back into the " + "source text; the escaped string points into the document's buffer of decoded strings. A json_view is two " + "pointers: one to the document and one to a node, here the array.", b) + + +# ---------------------------------------------------------------- figure 2: lifetimes +def lifetime(): + W, P = 840, "jvt" + L, R = 210, 820 + b = markers(P, (BAD,)) + b.append(rect(L, 12, 34, 12, "nj-jsonview-bar", STRUCT, rx=3)) + b.append(text(L + 42, 23, "alive / valid", "nj-jsonview-note")) + b.append(rect(L + 160, 12, 34, 12, "nj-jsonview-bad", rx=3)) + b.append(text(L + 202, 23, "dangling: using it is undefined behavior", "nj-jsonview-note")) + b.append(line(L, 44, R, 44, LINE, P, head=False)) + b.append(text(R, 38, "time \u2192", "nj-jsonview-tiny", anchor="end")) + scen = [ + ("The borrowed text is destroyed", 470, "text destroyed", [ + ("std::string text", [(L, 470, "ok"), ], "end"), + ("json_document doc", [(L + 60, 470, "ok"), (470, R, "bad")], None), + ("json_view v", [(L + 120, 470, "ok"), (470, R, "bad")], None)]), + ("The document is re-parsed", 470, "doc.read(other)", [ + ("json_document doc", [(L, R, "ok")], None), + ("json_view v", [(L + 60, 470, "ok"), (470, R, "bad")], None), + ("v = doc.root()", [(530, R, "ok")], None)]), + ("The document is shrunk", 470, "doc.shrink_to_fit()", [ + ("json_document doc", [(L, R, "ok")], None), + ("json_view v", [(L + 60, 470, "ok"), (470, R, "bad")], None), + ("v = doc.root()", [(530, R, "ok")], None)]), + ("The document is moved, then destroyed", 680, "moved is destroyed", [ + ("json_document doc", [(L, 420, "ok")], None), + ("moved = std::move(doc)", [(420, 680, "ok")], "end"), + ("json_view v", [(L + 60, 680, "ok"), (680, R, "bad")], None)]), + ] + y = 70 + for title, ev, evlabel, rows in scen: + b.append(text(16, y + 4, title, "nj-jsonview-h nj-jsonview-sm2")) + top = y + 14 + for k, (name, segs, end) in enumerate(rows): + ry = top + k * 28 + b.append(text(L - 12, ry + 15, name, "nj-jsonview-mono nj-jsonview-sm", anchor="end")) + for x1, x2, st in segs: + b.append(rect(x1, ry + 3, x2 - x1, 16, "nj-jsonview-bar" if st == "ok" else "nj-jsonview-bad", STRUCT if st == "ok" else None, rx=3)) + bottom = top + len(rows) * 28 + if title.startswith("The document is moved"): + b.append('' % (top - 2, bottom)) + b.append(text(424, top - 4, "std::move: views stay valid", "nj-jsonview-tiny")) + b.append('' % (ev, top - 2, bottom)) + b.append(text(ev + 4, top - 4, evlabel, "nj-jsonview-tiny nj-jsonview-c-bad")) + if title == "The borrowed text is destroyed": + b.append(text(476, top + 2 * 28 + 15, "every view, and the document itself", "nj-jsonview-note nj-jsonview-onbad")) + elif title.startswith("The document is moved"): + pass + else: + b.append(text(476, top + 28 + 15, "views taken before", "nj-jsonview-note nj-jsonview-onbad")) + b.append(text(536, top + 2 * 28 + 15, "a fresh view is valid again", "nj-jsonview-note nj-jsonview-onok")) + y = bottom + 30 + H = y - 10 + write("json_view_lifetime.svg", W, H, "fig-jvt-title", + "When a json_view becomes invalid", + "Four timelines. First: a document borrows a std::string; when the string is destroyed, the document and every view " + "taken from it dangle. Second: doc.read() re-parses the document; views taken before are invalid, a fresh view from " + "root() is valid. Third: doc.shrink_to_fit() has the same effect. Fourth: moving the document does not invalidate its " + "views; destroying the moved-to document does.", b) + + +# ---------------------------------------------------------------- figure 3: edit frames +NW, NG, NX0 = 84, 4, 36 # parsed nodes +OW, OG, OX0 = 54, 3, 16 # owned storage slots + + +def nx(i): + return NX0 + i * (NW + NG) + + +def ox(i): + return OX0 + i * (OW + OG) + + +# owned storage in allocation order (all in the first 64-node chunk), per step +# entries: (colour, line1, line2, link target) ; link target = ("p", i) parsed node or ("o", j) owned slot +OWN_STEPS = { + 2: [(STRUCT, "header", "[ ] 1", None), (OWN, "link", "\u2192 8", ("p", 8)), (None, "", "", None)], +} +OWN_STEPS[3] = [(STRUCT, "header", "[ ] 2", None), (OWN, "link", "\u2192 8", ("p", 8)), (OWN, "link", "\u2192 3'", ("o", 3)), + (STR, "string", '"new"', None)] +OWN_STEPS[4] = OWN_STEPS[3] + [(NUM, "uint", "3", None), (STRUCT, "header", "{ } 4", None), + (KEY, "key", "port", None), (OWN, "link", "\u2192 2", ("p", 2)), + (KEY, "key", "price", None), (OWN, "link", "\u2192 4", ("p", 4)), + (KEY, "key", "tags", None), (OWN, "link", "\u2192 6", ("p", 6)), + (KEY, "key", "retries", None), (OWN, "link", "\u2192 4'", ("o", 4))] + +STEPS = [ + ("parsed", "After parse: nine nodes, nothing owned yet"), + ('doc.set(doc.root(), "port", 6380)', "set: the node of the integer is overwritten in place"), + ('doc.erase(doc.root()["tags"], 0)', "erase: the array moves to a block of links in owned storage"), + ('doc.push_back(doc.root()["tags"], "new")', "push_back: a new node, and a link appended to the block"), + ('doc.set(doc.root(), "retries", 3)', "set of a new member: the object moves to a block as well"), +] + +DUMPS = [ + '{"port":6379,"price":19.90,"tags":["eu","a\\"b"]}', + '{"port":6380,"price":19.90,"tags":["eu","a\\"b"]}', + '{"port":6380,"price":19.90,"tags":["a\\"b"]}', + '{"port":6380,"price":19.90,"tags":["a\\"b","new"]}', + '{"port":6380,"price":19.90,"tags":["a\\"b","new"],"retries":3}', +] + + +def edit_frame(step): + W, H, P = 840, 372, "jve%d" % step + b = markers(P, (LINE, OWN, STRUCT, BAD)) + b.append(text(16, 22, "parsed index: fixed, never moves", "nj-jsonview-note nj-jsonview-strong")) + b.append(text(824, 22, "the source text is never written", "nj-jsonview-note", anchor="end")) + ny, nh = 34, 50 + moved = {6: 0} if step >= 2 else {} + if step >= 4: + moved[0] = 5 + for i, c, kind, fields, span, short in NODES: + x = nx(i) + cls = "nj-jsonview-node" + label = short + changed = False + if i == 2 and step >= 1: + label, changed = "6380", step == 1 + if c == KEY: + label = label.strip('"') + gap_node = (i == 7 and step >= 2) + if gap_node: + cls += " nj-jsonview-gap" + if changed: + cls += " nj-jsonview-hot" + b.append(rect(x, ny, NW, nh, cls, c)) + b.append(text(x + 6, ny + 13, str(i), "nj-jsonview-tiny")) + b.append(text(x + NW / 2, ny + 27, kind, "nj-jsonview-kind nj-jsonview-xs", c, anchor="middle")) + if i in moved: + b.append(text(x + NW - 5, ny + 13, "moved", "nj-jsonview-tiny nj-jsonview-c-struct nj-jsonview-strong", anchor="end")) + b.append(text(x + NW / 2, ny + 43, label, "nj-jsonview-mono nj-jsonview-sm", anchor="middle")) + if gap_node: + b.append(text(x + NW / 2, ny + nh + 14, "unreferenced", "nj-jsonview-tiny nj-jsonview-c-bad", anchor="middle")) + # owned storage + oy, oh = 196, 46 + b.append(text(16, oy + oh + 46, "owned storage: allocated on demand, never moves", "nj-jsonview-note nj-jsonview-strong", OWN)) + slots = OWN_STEPS.get(step, []) + b.append(rect(OX0 - 6, oy - 4, 820, oh + 8, "nj-jsonview-store", rx=8)) + if not slots: + b.append(text(OX0 + 10, oy + 28, "empty: a read-only json_document has none of this", "nj-jsonview-note")) + for j, (c, l1, l2, tgt) in enumerate(slots): + x = ox(j) + if c is None: + b.append(rect(x, oy, OW, oh, "nj-jsonview-node nj-jsonview-free", OWN)) + b.append(text(x + OW / 2, oy + 28, "free", "nj-jsonview-tiny", anchor="middle")) + continue + hot = (step == 2 and j < 3) or (step == 3 and j in (2, 3)) or (step == 4 and j >= 4) + b.append(rect(x, oy, OW, oh, "nj-jsonview-node" + (" nj-jsonview-hot" if hot else ""), c)) + b.append(text(x + OW - 4, oy + 11, "%d'" % j, "nj-jsonview-tiny", anchor="end")) + b.append(text(x + OW / 2, oy + 24, l1, "nj-jsonview-kind nj-jsonview-xs", c, anchor="middle")) + b.append(text(x + OW / 2, oy + 39, l2, "nj-jsonview-mono nj-jsonview-xs", anchor="middle")) + if tgt: + cx = x + OW / 2 + if tgt[0] == "p": + b.append(curve(cx, oy, nx(tgt[1]) + NW / 2 + (8 if tgt[1] in moved else 0), ny + nh, OWN, P)) + else: + tx = ox(tgt[1]) + OW / 2 + d = "M%g %g C%g %g %g %g %g %g" % (cx, oy + oh, cx, oy + oh + 26, tx, oy + oh + 26, tx, oy + oh + 2) + b.append('' % (d, P)) + # moved containers point to their block header + for i, hj in moved.items(): + b.append(curve(nx(i) + NW / 2 - 8, ny + nh, ox(hj) + OW / 2, oy, STRUCT, P, dashed=True)) + # edit text buffer + texts = ["new", "retries"][: (1 if step == 3 else 2 if step >= 4 else 0)] + if texts: + tx0 = 596 + b.append(text(tx0 - 8, oy + oh + 40, "edited text", "nj-jsonview-note", anchor="end")) + b.append(rect(tx0, oy + oh + 22, 10 * len("".join(texts)) + 12, 26, "nj-jsonview-node", STR, rx=4)) + b.append(text(tx0 + 6, oy + oh + 40, "".join(texts), "nj-jsonview-mono nj-jsonview-sm")) + # views and iterators + vy = 318 + b.append('' % (vy - 18)) + b.append(text(16, vy, 'view v = root()["tags"] (taken after parse):', "nj-jsonview-mono nj-jsonview-xs")) + b.append(text(320, vy, "valid, sees every edit", "nj-jsonview-note nj-jsonview-c-struct nj-jsonview-strong")) + b.append(text(16, vy + 20, "iterator over tags (taken after parse):", "nj-jsonview-mono nj-jsonview-xs")) + if step >= 2: + b.append(text(320, vy + 20, "invalid: tags no longer uses the parsed layout", "nj-jsonview-note nj-jsonview-c-bad nj-jsonview-strong")) + else: + b.append(text(320, vy + 20, "valid", "nj-jsonview-note nj-jsonview-c-struct nj-jsonview-strong")) + b.append(text(16, vy + 42, "dump(): " + DUMPS[step], "nj-jsonview-mono nj-jsonview-xs nj-jsonview-muted")) + code, cap = STEPS[step] + write("json_view_edit_%d.svg" % step, W, H, "fig-jve%d-title" % step, + "Step %d of %d: %s" % (step + 1, len(STEPS), code), + cap + ". The dump with number_format::source is " + DUMPS[step] + ".", b, "nj-jsonview-editfig") + + +layout() +lifetime() +for s in range(len(STEPS)): + edit_frame(s) diff --git a/docs/mkdocs/scripts/figures/memory_over_time.py b/docs/mkdocs/scripts/figures/memory_over_time.py new file mode 100644 index 000000000..9cdd94515 --- /dev/null +++ b/docs/mkdocs/scripts/figures/memory_over_time.py @@ -0,0 +1,81 @@ +"""Draws the illustrative memory-over-time chart of the large-input page as an inline SVG into docs/images/. + +The chart shows the qualitative shape of the memory use while a large array of records is parsed in four ways: parse() +builds the whole tree (DOM), a parser callback discards every record, a SAX handler keeps only what it needs, and a +json_document keeps the text plus its index. It is not a measurement: there are no numbers on the axes, and the +relative heights depend on the data. + +The SVG is not an image: features/parsing/large_and_partial_input.md includes its text with +`--8<-- "images/memory_over_time.svg"`, so it picks up the colours and fonts of the page (see the `nj-fig` rules in +docs/css/brand.css and the `nj-sax-` rules in docs/css/fig-sax.css). Run `python3 scripts/figures/memory_over_time.py` +from docs/mkdocs after changing the drawing, and commit the generated file. In SVG text write the word JSON as +`J​SON` (likewise DOM and SAX, which esc() does), otherwise the abbreviation extension wraps it in an and +the text disappears. +""" +import html +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" + +W, H = 660, 312 +X0, X1 = 56, 440 # plot area +Y0, Y1 = 48, 272 # top, baseline + + +def esc(s): + # break the words of the glossary (includes/glossary.md) so the abbreviation extension leaves them alone + s = html.escape(s) + for word in ("DOM", "SAX", "JSON"): + s = s.replace(word, word[0] + "​" + word[1:]) + return s + + +def main(): + body = [] + tid, did = "fig-sax-mem-title", "fig-sax-mem-desc" + # axes + body.append('' % (X0, Y0 - 12, Y1, X1 + 8)) + body.append('memory' % (X0 - 40)) + body.append('time: records parsed' % (X0, Y1 + 22)) + body.append('illustrative, not a measurement' % (W - 12)) + + # DOM: grows with every record until the whole tree is built + body.append('' % (X0, Y1 - 4, X1, Y0 + 4)) + # json_document: the text is in memory from the start; the index grows while parsing + yt, yi = 168, 146 + body.append('' % (X0, yt, X1, yi)) + # callback: one record at a time, released when it is discarded + hi, lo, step = Y1 - 34, Y1 - 22, 32 + d, x = "M%d %d" % (X0, lo), X0 + while x + step <= X1: + d += " L%d %d L%d %d" % (x + step, hi, x + step, lo) + x += step + body.append('' % d) + # SAX: no value is built, only what the handler keeps + body.append('' % (X0, Y1 - 8, X1)) + + labels = [ + (Y0 + 9, "nj-sax-c-dom", "DOM: parse()", "the whole tree"), + (yi + 5, "nj-sax-c-doc", "json_document", "the text plus its index"), + (hi - 2, "nj-sax-c-cb", "callback, discard", "one record at a time"), + (Y1 + 6, "nj-sax-c-sax", "SAX", "only what the handler keeps"), + ] + for y, cls, name, note in labels: + body.append('%s' % (cls, X1 + 16, y - 9, esc(name))) + body.append('%s' % (X1 + 16, y + 8, esc(note))) + + title = "Illustrative memory use over time while a large array of records is parsed, not a measurement." + desc = ("Four curves. DOM, parse(): rises steadily until the whole tree is in memory. json_document: starts at about " + "the size of the text, which it keeps, and grows a little while its index is built. Parser callback that " + "discards every record: stays flat at the size of one record. SAX: stays flat and lowest, because no value " + "is built and only what the handler keeps is in memory.") + s = ['' % (W, H, tid, did), + '%s' % (tid, esc(title)), + '%s' % (did, esc(desc))] + s += body + s.append("") + (OUT / "memory_over_time.svg").write_text("\n".join(s) + "\n") + + +main() diff --git a/docs/mkdocs/scripts/figures/qa_pipeline.py b/docs/mkdocs/scripts/figures/qa_pipeline.py new file mode 100644 index 000000000..b706e76a4 --- /dev/null +++ b/docs/mkdocs/scripts/figures/qa_pipeline.py @@ -0,0 +1,196 @@ +"""Draws the quality-assurance pipeline overview of community/quality_assurance.md as an inline SVG into +docs/images/qa_pipeline.svg. + +The SVG is not an image: the page includes its text with `--8<-- "images/qa_pipeline.svg"`, so it picks up the colours +and fonts of the page (see the `nj-fig` rules in docs/css/brand.css and the `nj-exceptions-*` rules in +docs/css/fig-exceptions.css). The file holds two drawings, a three-column one and a one-column one for phones; the CSS +shows one of them. + +The lanes, tools and counts are taken from the text of community/quality_assurance.md, the trigger badges from the +`on:` sections of .github/workflows/*.yml. Update LANES when the page changes, run +`python3 scripts/figures/qa_pipeline.py` from docs/mkdocs, and commit the generated file. In SVG text write the word +JSON as `J​SON`, otherwise the abbreviation extension wraps it in an and the text disappears. +""" +import html +import re +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +P = "nj-exceptions-" + +# trigger badges: key -> (label, css modifier) +TRIGGERS = { + "push": ("every push / PR", "push"), + "pr": ("every PR", "push"), + "sched": ("scheduled", "sched"), + "cont": ("continuous", "cont"), +} + +# title, triggers, lines (at most 31 characters, so they fit a card of the three-column drawing) +LANES = [ + ("Build matrix", ["push"], [ + "50+ compilers: GCC, Clang, MSVC,", + "AppleClang, Intel, NVIDIA, ...", + "C++11, 14, 17, 20, 23, and 26", + "libc++ and libstdc++", + "warnings: Clang -Weverything,", + "GCC 300+ warnings", + ]), + ("Unit tests (doctest)", ["push"], [ + "every public API function", + "every exception id and message", + "all Unicode code points", + "single header and split headers", + "custom number, string, array,", + "and object types", + ]), + ("Coverage", ["push"], [ + "unit tests cover all lines", + "of the code base", + ]), + ("Static analysis", ["push", "sched"], [ + "Clang-Tidy, Cppcheck, Infer", + "Clang Static Analyzer (52", + "checkers), IWYU, Codacy", + "CodeQL, Semgrep, Flawfinder", + "(also scheduled)", + ]), + ("Dynamic analysis", ["push"], [ + "runtime assertions", + "Valgrind (Memcheck)", + "address and undefined behavior", + "sanitizers, integer overflow,", + "nullability violations", + ]), + ("Fuzzing", ["pr", "cont"], [ + "CIFuzz on every pull request", + "OSS-Fuzz: billions of inputs", + "every crash fixed with a", + "reproducing unit test", + ]), + ("Style", ["push"], [ + "Artistic Style (astyle),", + "enforced in the CI", + "cpplint with 61 rules", + ]), + ("Docs and examples", ["push", "sched"], [ + "every documentation example is", + "tested; changed output fails", + "site built with warnings", + "as errors; links checked", + "regularly (scheduled)", + ]), + ("Integration, supply chain", ["push", "sched"], [ + "CMake 3.5, 3.31.6, and 4.0.0", + "Meson, Swift Package Manager", + "dependency review on every PR", + "Dependabot daily, OpenSSF", + "Scorecard, REUSE", + ]), +] + + +GLOSSARY = Path(__file__).resolve().parents[2] / "includes" / "glossary.md" +ABBRS = sorted(re.findall(r"^\*\[([^\]]+)\]:", GLOSSARY.read_text(), re.M), key=len, reverse=True) + + +def noabbr(s): + """Breaks every glossary abbreviation with a zero-width space, so the abbr extension leaves SVG text alone.""" + for a in ABBRS: + s = re.sub(r"(?' % P, + '' % (P, x, y, w, h), + '%s' % (P, x + 14, y + 25, txt(title))] + bx = x + 14 + for t in triggers: + label, mod = TRIGGERS[t] + bw = badge_w(label) + s.append('' % (P, P, mod, bx, y + 36, bw)) + s.append('%s' % (P, P, mod, bx + bw / 2, y + 50, txt(label))) + bx += bw + 6 + for i, line in enumerate(lines): + s.append('%s' % (P, x + 14, y + 78 + i * 18, txt(line))) + s.append('') + return "\n".join(s) + + +def pill(cx, y, w, label, sub=None, cls=""): + h = 46 if sub else 34 + s = ['' % (P, cls, cx - w / 2, y, w, h, h / 2), + '%s' % (P, cx, y + 22, txt(label))] + if sub: + s.append('%s' % (P, cx, y + 38, txt(sub))) + return "\n".join(s), h + + +def arrow(x, y1, y2, mid): + return '' % (P, x, y1, x, y2, mid) + + +def drawing(W, cols, suffix): + mid = "nj-qa-arrow-" + suffix + body = [] + cx = W / 2 + s, h = pill(cx, 8, 230, "push or pull request") + body.append(s) + y = 8 + h + body.append(arrow(cx, y, y + 26, mid)) + # container with the lanes + top = y + 28 + pad, gap = 14, 12 + cw = (W - 24 - 2 * pad - (cols - 1) * gap) / cols + body.append('checks, run in parallel on GitHub Actions and AppVeyor' % (P, 12 + pad, top + 24) if cols > 1 else + 'checks, run in parallel' % (P, 12 + pad, top + 24)) + gy = top + 40 + rows = [LANES[i:i + cols] for i in range(0, len(LANES), cols)] + for r in rows: + ch = 78 + 18 * (max(len(l[2]) for l in r) - 1) + 16 + for j, (title, trig, lines) in enumerate(r): + body.append(card(12 + pad + j * (cw + gap), gy, cw, ch, title, trig, lines)) + gy += ch + gap + bottom = gy - gap + pad + body.insert(0, '' % (P, top, W - 24, bottom - top)) + # after the checks + body.append(arrow(cx, bottom, bottom + 26, mid)) + s, h = pill(cx, bottom + 28, 230, "merge into develop", cls=" %sstage--merge" % P) + body.append(s) + y = bottom + 28 + h + body.append(arrow(cx, y, y + 26, mid)) + s, h = pill(cx, y + 28, 300, "release", "no open vulnerability alert", cls=" %sstage--merge" % P) + body.append(s) + H = y + 28 + h + 8 + defs = ('' + '' % mid) + tid, did = "nj-qa-title-" + suffix, "nj-qa-desc-" + suffix + desc = ("Every push or pull request starts these checks in parallel: " + + "; ".join("%s (%s): %s" % (t, ", ".join(TRIGGERS[k][0] for k in trig), " ".join(lines)) for t, trig, lines in LANES) + + ". When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.") + out = ['' + % (P, P, "narrow" if cols == 1 else "wide", W, H, tid, did), + 'Quality assurance pipeline' % tid, + '%s' % (did, txt(desc)), + defs] + body + [''] + return "\n".join(out) + + +def main(): + (OUT / "qa_pipeline.svg").write_text(drawing(760, 3, "w") + "\n" + drawing(360, 1, "n") + "\n") + + +main() diff --git a/docs/mkdocs/scripts/figures/railroad_number.py b/docs/mkdocs/scripts/figures/railroad_number.py new file mode 100644 index 000000000..033bab87b --- /dev/null +++ b/docs/mkdocs/scripts/figures/railroad_number.py @@ -0,0 +1,76 @@ +"""Draws the JSON number railroad diagram as an inline SVG into docs/images/. + +The SVG is not an image: the pages include its text with `--8<-- "images/json_syntax_number.svg"`, so it picks up the colours and fonts of the +page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/railroad_number.py` from docs/mkdocs after +changing the drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the +abbreviation extension wraps it in an and the text disappears. +""" +import math +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +Y0, Y1, Y2, Y3 = 44, 92, 136, 180 +R = 10 +paths, shapes, texts = [], [], [] + +def rounded(pts, r=R): + d = "M%g %g" % pts[0] + for i in range(1, len(pts) - 1): + (x0, y0), (x1, y1), (x2, y2) = pts[i-1], pts[i], pts[i+1] + l1 = math.hypot(x1-x0, y1-y0); l2 = math.hypot(x2-x1, y2-y1) + rr = min(r, l1/2, l2/2) + ax, ay = x1 - (x1-x0)/l1*rr, y1 - (y1-y0)/l1*rr + bx, by = x1 + (x2-x1)/l2*rr, y1 + (y2-y1)/l2*rr + d += " L%g %g Q%g %g %g %g" % (ax, ay, x1, y1, bx, by) + d += " L%g %g" % pts[-1] + return d + +def path(*pts): paths.append(rounded(list(pts))) +def circle(cx, cy, ch): + shapes.append('' % (cx, cy)) + shapes.append('' % (cx, cy)) + texts.append('%s' % (cx, cy+5, ch)) +def box(x0, x1, cy, label): + shapes.append('' % (x0, cy-14, x1-x0)) + shapes.append('' % (x0, cy-14, x1-x0)) + texts.append('%s' % ((x0+x1)/2, cy+5, label)) +def loop(xr, xl, cy): # return path below a box row + path((xr, cy), (xr, cy+34), (xl, cy+34), (xl, cy)) + +# start / main +path((8, Y0), (850, Y0)) +# A: optional minus +path((24, Y0), (38, Y0), (38, Y1), (59, Y1)); circle(72, Y1, "−"); path((85, Y1), (106, Y1), (106, Y0), (120, Y0)) +# B: 0 | 1-9 digit* +circle(257, Y0, "0") +path((120, Y0), (134, Y0), (134, Y1), (160, Y1)); box(160, 242, Y1, "digit 1–9") +path((242, Y1), (380, Y1), (380, Y0), (396, Y0)) +path((242, Y1), (258, Y1), (258, Y2), (278, Y2)); box(278, 332, Y2, "digit") +path((332, Y2), (348, Y2), (348, Y1)); loop(340, 270, Y2) +# C: fraction +path((396, Y0), (410, Y0), (410, Y1), (425, Y1)); circle(438, Y1, ".") +path((451, Y1), (462, Y1)); box(462, 516, Y1, "digit"); loop(524, 456, Y1) +path((516, Y1), (530, Y1), (530, Y0), (546, Y0)) +# D: exponent +path((546, Y0), (560, Y0), (560, Y1), (577, Y1)); circle(590, Y1, "e") +path((546, Y0), (560, Y0), (560, Y2), (577, Y2)); circle(590, Y2, "E") +path((603, Y1), (728, Y1)); path((603, Y2), (620, Y2), (620, Y1)) +path((640, Y1), (654, Y1), (654, Y2), (670, Y2)); circle(683, Y2, "−") +path((640, Y1), (654, Y1), (654, Y3), (670, Y3)); circle(683, Y3, "+") +path((696, Y2), (712, Y2), (712, Y1)); path((696, Y3), (712, Y3), (712, Y1)) +path((728, Y1), (746, Y1)); box(746, 800, Y1, "digit"); loop(808, 738, Y1) +path((800, Y1), (816, Y1), (816, Y0), (832, Y0)) + +svg = ['', + 'Railroad diagram of the J​SON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits).', + ''] +for d in paths: svg.append('' % d) +svg.append('') +svg.append('' % (Y0-6, Y0, Y0+6)) +svg.append('' % (Y0-6, Y0, Y0+6)) +svg += shapes + texts +svg.append('number' % (Y0-12)) +svg.append('fraction' % (Y0-12)) +svg.append('exponent' % (Y0-12)) +svg.append('') +open(OUT / "json_syntax_number.svg", "w").write("\n".join(svg) + "\n") diff --git a/docs/mkdocs/scripts/figures/release_timeline.py b/docs/mkdocs/scripts/figures/release_timeline.py new file mode 100644 index 000000000..121323b7c --- /dev/null +++ b/docs/mkdocs/scripts/figures/release_timeline.py @@ -0,0 +1,127 @@ +"""Draws the release timeline of home/releases/index.md as an inline SVG into docs/images/. + +The releases and their dates come from the tables of that page (rows like `| [v3.11.2](v3.11.2.md) | 2022-08-12 | ...`), +so after adding a release to the page, run `python3 scripts/figures/release_timeline.py` from docs/mkdocs and commit +the regenerated docs/images/release_timeline.svg. The page includes the SVG text with +`--8<-- "images/release_timeline.svg"`, so it follows the light/dark toggle (see the `nj-fig` rules in +docs/css/brand.css and the `nj-int-*` rules in docs/css/fig-integration.css). Every marker has a tooltip. +The timeline also marks the first commit of the repository (FIRST_COMMIT) as a diamond. Rows without a date (the "Unreleased" row) are skipped; any other release row that cannot be parsed stops the script. +""" +import datetime +import re +import sys +from pathlib import Path + +DOCS = Path(__file__).resolve().parents[2] / "docs" +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +PAGE = DOCS / "home" / "releases" / "index.md" + +ROW = re.compile(r"^\|\s*\[v(\d+)\.(\d+)\.(\d+)\]\([^)]*\)\s*\|\s*([^|]*?)\s*\|") +W, LEFT, RIGHT = 880, 24, 856 +LABEL_ROW, CHAR_W = 17, 7.0 +FIRST_COMMIT = datetime.date(2013, 7, 4) # "- initial commit" of the repository + + +def parse(markdown): + releases = [] + for line in markdown.splitlines(): + if not line.startswith("| [v"): + continue + m = ROW.match(line) + if not m: + sys.exit("cannot parse release row: " + line) + try: + date = datetime.date.fromisoformat(m.group(4)) + except ValueError: + sys.exit("cannot parse date in row: " + line) + releases.append(((int(m.group(1)), int(m.group(2)), int(m.group(3))), date)) + if len(releases) < 2: + sys.exit("found fewer than two releases in " + str(PAGE)) + if len({v for v, _ in releases}) != len(releases): + sys.exit("duplicate release in " + str(PAGE)) + return sorted(releases, key=lambda r: (r[1], r[0])) + + +def name(version): + return "v%d.%d.%d" % version + + +def draw(releases): + first, last = FIRST_COMMIT, releases[-1][1] + start, end = datetime.date(first.year, 1, 1), datetime.date(last.year + 1, 1, 1) + span = (end - start).days + + def xof(date): + return LEFT + (RIGHT - LEFT) * (date - start).days / span + + # minor labels, placed greedily into the lowest free row above the axis + rows_end, labels = [], [] + for version, date in [(None, FIRST_COMMIT)] + releases: + if version is not None and version[2] != 0: + continue + text = "first commit" if version is None else "%d.%d" % version[:2] + x, half = xof(date), len(text) * CHAR_W / 2 + 2 + row = next((i for i, e in enumerate(rows_end) if e < x - half), None) + if row is None: + rows_end.append(0) + row = len(rows_end) - 1 + rows_end[row] = x + half + labels.append((x, row, text)) + top = 14 + LABEL_ROW * len(rows_end) + axis = top + 12 + height = axis + 70 + + s = ['<svg class="nj-fig nj-fig--timeline" viewBox="0 0 %d %d" role="img" aria-labelledby="nj-int-tl-title" ' + 'xmlns="http://www.w3.org/2000/svg">' % (W, height)] + minors = sum(1 for v, _ in releases if v[2] == 0) + s.append('<title id="nj-int-tl-title">Timeline from the first commit (%s) and the %d releases from %s (%s) to %s ' + '(%s): %d minor or major releases, drawn as filled dots with their version above the axis, and %d patch ' + 'releases, drawn as rings. The first commit is drawn as a diamond.' + % (first.isoformat(), len(releases), name(releases[0][0]), releases[0][1].isoformat(), + name(releases[-1][0]), last.isoformat(), minors, len(releases) - minors)) + # year grid + for year in range(start.year, end.year + 1): + x = xof(datetime.date(year, 1, 1)) + s.append('' % (x, top - 4, x, axis + 8)) + if year < end.year: + mid = xof(datetime.date(year, 7, 1)) + s.append('%d' % (mid, axis + 24, year)) + s.append('' % (LEFT, axis, RIGHT, axis)) + # major version spans + majors = {} + for version, date in releases: + majors.setdefault(version[0], []).append(date) + for major, dates in sorted(majors.items()): + x0, x1 = xof(min(dates)), xof(max(dates)) + y = axis + 42 + s.append('' % (x0, y - 5, y, x1, y - 5)) + s.append('%d.x' + % ((x0 + x1) / 2 if x1 - x0 > 30 else x0, y + 17, "middle" if x1 - x0 > 30 else "start", major)) + # leaders and labels + for x, row, text in labels: + y = top - row * LABEL_ROW + s.append('' % (x, y + 3, x, axis - 7)) + s.append('%s' % (x, y, text)) + # markers: the first commit, then one per date, patch rings first so that minor dots stay on top + x = xof(FIRST_COMMIT) + s.append('First commit %s' + '' + % (FIRST_COMMIT.isoformat(), x, axis, x, axis)) + by_date = {} + for version, date in releases: + by_date.setdefault(date, []).append(version) + groups = sorted(by_date.items(), key=lambda g: any(v[2] == 0 for v in g[1])) + for date, versions in groups: + minor = any(v[2] == 0 for v in versions) + x = xof(date) + tip = ", ".join(name(v) for v in sorted(versions)) + " released " + date.isoformat() + s.append('%s' + '' + % (tip, x, axis, "nj-int-minor" if minor else "nj-int-patch", x, axis, 6 if minor else 3.5)) + s.append('') + return "\n".join(s) + "\n" + + +releases = parse(PAGE.read_text(encoding="utf-8")) +(OUT / "release_timeline.svg").write_text(draw(releases), encoding="utf-8") +print("wrote", OUT / "release_timeline.svg", "with", len(releases), "releases") diff --git a/docs/mkdocs/scripts/slideshow/.gitignore b/docs/mkdocs/scripts/slideshow/.gitignore new file mode 100644 index 000000000..0480ebe75 --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/.gitignore @@ -0,0 +1 @@ +fonts/ diff --git a/docs/mkdocs/scripts/slideshow/Makefile b/docs/mkdocs/scripts/slideshow/Makefile new file mode 100644 index 000000000..ff343ef4e --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/Makefile @@ -0,0 +1,73 @@ +# Rebuild the animated feature slideshow of the home page (docs/images/json-light.svg and json-dark.svg) when +# slides.md, build.py or the fonts change. Needs uv (build.py declares its dependencies inline, PEP 723), curl, +# unzip and shasum. Run it from docs/mkdocs/scripts/slideshow. +# +# make download missing fonts and build both SVGs +# make fonts download only the fonts +# make SECONDS=12 change the time per slide +# make clean remove the generated SVGs +# make distclean also remove the downloaded fonts + +SECONDS ?= 15 + +IMAGES := ../../docs/images +LIGHT := $(IMAGES)/json-light.svg +DARK := $(IMAGES)/json-dark.svg + +RUN := uv run --script build.py +FONTS := fonts/Inter.ttf fonts/JetBrainsMono.ttf +DEPS := build.py slides.md $(FONTS) + +# Pinned sources; the checksums make a changed upstream file fail instead of silently changing the output. +# Inter: Google Fonts repository at a fixed commit (variable font with opsz and wght axes). +INTER_URL := https://raw.githubusercontent.com/google/fonts/0b58fb370093f9a9f4ff785d94405710b79de67c/ofl/inter/Inter%5Bopsz%2Cwght%5D.ttf +# JetBrains Mono: release 2.304 (the variable font in fonts/variable/ of the zip). +JBM_URL := https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip +JBM_ZIP := fonts/JetBrainsMono-2.304.zip + +SHA256_Inter.ttf := 29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031 +SHA256_JetBrainsMono.ttf := 662a196d58f1183bf2d77428b6d5283fe3f45161ab021bea4036bc98e5cac016 +SHA256_JetBrainsMono-2.304.zip := 6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf + +# Check $@.tmp against its pinned checksum, then move it into place. +define verify + echo '$(SHA256_$(notdir $@)) $@.tmp' | shasum -a 256 -c --quiet - || { rm -f $@.tmp; exit 1; } + mv $@.tmp $@ +endef + +.PHONY: all fonts clean distclean +.INTERMEDIATE: $(JBM_ZIP) + +all: $(LIGHT) $(DARK) + +fonts: $(FONTS) + +# fixed colours for the docs: an cannot see Material's theme toggle, so the home page includes both +# and shows the one that matches +$(LIGHT): $(DEPS) + $(RUN) $@.tmp $(SECONDS) light + mv $@.tmp $@ + +$(DARK): $(DEPS) + $(RUN) $@.tmp $(SECONDS) dark + mv $@.tmp $@ + +fonts/Inter.ttf: + mkdir -p fonts + curl -fsSL -m 300 -o $@.tmp '$(INTER_URL)' + $(verify) + +$(JBM_ZIP): + mkdir -p fonts + curl -fsSL -m 300 -o $@.tmp '$(JBM_URL)' + $(verify) + +fonts/JetBrainsMono.ttf: | $(JBM_ZIP) + unzip -p $(JBM_ZIP) 'fonts/variable/JetBrainsMono[[]wght].ttf' > $@.tmp + $(verify) + +clean: + rm -f $(LIGHT) $(DARK) $(IMAGES)/*.svg.tmp + +distclean: clean + rm -f $(FONTS) fonts/*.tmp $(JBM_ZIP) diff --git a/docs/mkdocs/scripts/slideshow/README.md b/docs/mkdocs/scripts/slideshow/README.md new file mode 100644 index 000000000..cd3ede5c8 --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/README.md @@ -0,0 +1,27 @@ +# Feature slideshow of the home page + +`make` builds `docs/images/json-light.svg` and `json-dark.svg`, the animated feature tour on the home page. They replace +`json.gif` there. All text is outlined, so the SVGs need no fonts. Material's theme toggle cannot be seen from an +``, so the home page includes both files (`#only-light`, `#only-dark`) and Material shows the one that matches. + +The slides are in [`slides.md`](slides.md): one `# Title` per slide (`{badge: TEXT}` at the end adds a red chip), `- ` +bullets, and one fenced code block. `build.py` adds the title and closing slides around them, so the slide count follows +the file. Every snippet should compile against the current `develop`. Use a non-breaking space to keep words together +("16 bytes"). + +```bash +make # download missing fonts, build both SVGs +make SECONDS=12 +make clean # remove the SVGs; make distclean also removes the fonts +``` + +Run it from this directory. It needs [uv](https://docs.astral.sh/uv/), plus curl, unzip and shasum for the fonts: +`build.py` declares its dependencies inline (PEP 723, pinned, so the output stays byte-identical) and `uv run --script` +provides them. `fonts/` is git-ignored: Inter (variable, Google Fonts repository) and JetBrains Mono 2.304 (variable) +are downloaded from pinned URLs and checked against their SHA-256; a changed upstream file stops the build. +`./build.py OUT.svg [seconds] [auto|light|dark]` works directly too. A mistake in `slides.md` stops the build with +`slides.md:LINE` and leaves the old SVGs in place. + +Layout: title 48 px, bullets 24 px, code 19 px; the build warns when a slide's code would drop below 18 px. Syntax colours +are the `code-*` tokens of the brand (also used by `css/brand.css`). A CSS animation dissolves each slide over the +previous one and fills the progress bar; with reduced motion only the first slide shows. diff --git a/docs/mkdocs/scripts/slideshow/build.py b/docs/mkdocs/scripts/slideshow/build.py new file mode 100755 index 000000000..1bd45197a --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/build.py @@ -0,0 +1,461 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.9" +# dependencies = ["fonttools==4.67.0", "uharfbuzz==0.57.0"] +# /// +"""Animated SVG slideshow prototype for nlohmann/json, all text outlined (harfbuzz shaping, glyphs as + ). + +Usage: uv run --script build.py OUT.svg [seconds-per-slide] [auto|light|dark] + +auto (the default) follows prefers-color-scheme. light and dark fix the colours, for pages with their own theme +toggle (MkDocs Material), which an cannot see. + +The pinned versions keep the output byte-identical; a different harfbuzz may shape slightly differently. +""" +import re, sys, os +import uharfbuzz as hb +from fontTools.pens.svgPathPen import SVGPathPen + +HERE = os.path.dirname(os.path.abspath(__file__)) +FONTS = os.path.join(HERE, "fonts") +W, H = 1200, 675 +TOTAL = 0 # number of slides, set once slides.md is read (for the "02 / 20" labels and the progress segments) + +# ---------------------------------------------------------------- glyphs + +class Face: + def __init__(self, file, key, feats=None): + blob = hb.Blob.from_file_path(os.path.join(FONTS, file)) + self.face = hb.Face(blob) + self.upem = self.face.upem + self.key = key + self.feats = feats or {} + + def font(self, var): + f = hb.Font(self.face) + f.set_variations(var) + return f + + +INTER = Face("Inter.ttf", "i", {"cv11": True, "ss03": True, "kern": True}) +MONO = Face("JetBrainsMono.ttf", "m", {"calt": False, "liga": False}) + +GLYPHS = {} # (face, var, gid) -> id +DEFS = [] + + +def gid_ref(face, font, var, gid): + k = (face.key, tuple(sorted(var.items())), gid) + if k not in GLYPHS: + pen = SVGPathPen(None, ntos=lambda v: str(round(v))) + font.draw_glyph_with_pen(gid, pen) + d = pen.getCommands() + gidname = f"g{len(GLYPHS):x}" + GLYPHS[k] = gidname if d else None + if d: + DEFS.append(f'') + return GLYPHS[k] + + +def shape(face, text, wght, size, track=0.0): + if not text: + return [], 0, face.upem + var = {"wght": wght} + if face is INTER: + var["opsz"] = max(14, min(32, size)) + font = face.font(var) + buf = hb.Buffer() + buf.add_str(text) + buf.guess_segment_properties() + hb.shape(font, buf, face.feats) + out, x = [], 0 + tr = track * face.upem + for info, pos in zip(buf.glyph_infos, buf.glyph_positions): + out.append((gid_ref(face, font, var, info.codepoint), x + pos.x_offset, pos.y_offset)) + x += pos.x_advance + tr + return out, x - tr, face.upem + + +def width(text, face=INTER, wght=400, size=16, track=0.0): + _, w, upem = shape(face, text, wght, size, track) + return w * size / upem + + +def run(text, x, y, size, wght=400, cls="tx", face=INTER, track=0.0): + """One line of text, baseline at y. Returns (svg, width_px).""" + glyphs, w, upem = shape(face, text, wght, size, track) + s = size / upem + uses = "".join(f'" + for g, gx, gy in glyphs if g) + return (f'{uses}', w * s) + + +def runs(parts, x, y, size, face=INTER, track=0.0): + """Several (text, wght, cls) pieces on one line.""" + out = [] + for text, wght, cls in parts: + svg, w = run(text, x, y, size, wght, cls, face, track) + out.append(svg) + x += w + track * size + return "".join(out), x + + +def wrap(text, maxw, size, wght=400): + lines = _wrap(text, maxw, size, wght) + # no one-word widow: pull the last word of the previous line down if it still fits + if len(lines) > 1 and " " not in lines[-1] and " " in lines[-2]: + head, last = lines[-2].rsplit(" ", 1) + if width(last + " " + lines[-1], size=size, wght=wght) <= maxw: + lines[-2:] = [head, last + " " + lines[-1]] + return lines + + +def _wrap(text, maxw, size, wght=400): + lines, cur = [], "" + for word in text.split(" "): + t = (cur + " " + word).strip() + if cur and width(t, size=size, wght=wght) > maxw: + lines.append(cur) + cur = word + else: + cur = t + return lines + [cur] + +# ---------------------------------------------------------------- pieces + + +def wordmark(x, y, size, on="page"): + """The wordmark with hand kerning around the slash (see the logo in the brand kit). Below 32 px it switches to the + small optical master (nlohmann 250 / slash 450), because Thin falls apart at footer size.""" + a, sl = ("wm-a", "wm-s") if on == "page" else ("on-a", "on-s") + small = size < 32 + w = (250, 450, 800) if small else (100, 400, 800) + k = (0.004, 0.024) if small else (-0.006, 0.012) + out = [] + for i, (t, wg, c) in enumerate([("nlohmann", w[0], a), ("/", w[1], sl), ("json", w[2], a)]): + svg, wd = run(t, x, y, size, wg, c, track=-0.02) + out.append(svg) + x += wd - 0.02 * size + (k[i] * size if i < 2 else 0) + return "".join(out), x + + +KW = {"using", "auto", "return", "const", "struct", "for", "override"} +TYPES = {"int", "void", "bool", "double", "char", "float"} +CONSTANTS = {"true", "false", "nullptr"} +TOKEN = re.compile(r'(//.*$)|(R"\(.*?\)"|R"\(.*$|"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)\')|(<[\w/.]+>(?=\s*$))|(#\w+)' + r'|(\b\d+(?:\.\d+)?\b)|(\b[A-Za-z_]\w*\b)|(\s+)|(.)') + + +def code_block(lines, x, y, size, lh): + """Lines of C++; a raw string literal may span lines.""" + out, in_raw = [], False + for line in lines: + if line: + svg, in_raw = code_line(line, x, y, size, in_raw) + out.append(svg) + y += lh + else: + y += lh * BLANK + return "".join(out) + + +BLANK = 0.55 # an empty code line is a paragraph break, not a full line + + +def code_height(lines, size, lh): + n_full = sum(1 for l in lines if l) + n_blank = len(lines) - n_full + return lh * (n_full - 1) + lh * BLANK * n_blank + size + + +def code_line(line, x, y, size, in_raw=False): + if line.startswith("$ "): + return runs([("$ ", 400, "tx3"), (line[2:], 400, "tx")], x, y, size, MONO)[0], False + out, cx = [], x + if in_raw: + end = line.find(')"') + head = line if end < 0 else line[:end + 2] + svg, w = run(head, cx, y, size, 400, "str", MONO) + out.append(svg) + if end < 0: + return "".join(out), True + cx += w + line = line[end + 2:] + in_raw = False + for m in TOKEN.finditer(line): + com, st, inc, pp, num, ident, ws, other = m.groups() + t = m.group(0) + if st and st.startswith('R"(') and not st.endswith(')"'): + in_raw = True + face, wght, cls = MONO, 400, "tx" + if com: + cls = "com" # upright: JetBrains Mono Italic's cursive f reads as a florin at README size + elif st or inc: + cls = "str" + elif pp: + cls = "pp" + elif ident in KW: + wght, cls = 600, "kw" + elif ident in TYPES: + cls = "ty" + elif num or ident in CONSTANTS: + cls = "num" + elif ident and line[m.end():m.end() + 1] == "(": + cls = "fn" # a call or definition: name directly followed by "(" + svg, w = run(t, cx, y, size, wght, cls, face) + if not ws: + out.append(svg) + cx += w + return "".join(out), in_raw + + +SEG, SEG_GAP = 20, 5 + + +def progress(idx, n_proto, y): + """TOTAL segments at the bottom right; earlier slides full, current one fills over the slide's time.""" + seg, gap = SEG, SEG_GAP + x0 = W - 64 - TOTAL * seg - (TOTAL - 1) * gap + out = [] + for k in range(TOTAL): + x = x0 + k * (seg + gap) + out.append(f'') + if k < idx: + out.append(f'') + elif k == idx: + out.append(f'') + return "".join(out) + + +def pattern_def(): + tile = [("{", 14, 34), ("]", 92, 22), (":", 136, 60), ("[", 52, 82), (",", 118, 112), + ("}", 20, 140), (":", 78, 150), ("{", 140, 158)] + glyphs = "".join(run(c, x, y, 26, 600, "pat", MONO)[0] for c, x, y in tile) + return f'{glyphs}' + +# ---------------------------------------------------------------- slides + + +CODE_SIZES = [] +SLOGAN = "What if JSON was part of modern C++?" + + +def band_bg(): + """Hero gradient plus the syntax pattern, faded out towards the text side (left).""" + return ('' + '') + + +def footer_band(idx, n, left=None): + out = [] + if left: + out.append(run(left, 64, FOOT_Y, 20, 600, "on-tx")[0]) + out.append(progress(idx, n, FOOT_Y - 7.5).replace('class="seg"', 'class="seg on-band"').replace('class="seg-on', 'class="seg-on on-band')) + return "".join(out) + + +FOOT_Y = 640 # one footer baseline on every slide: wordmark or URL left, progress right + + +def slide_title(idx, n): + out = [band_bg()] + x = 96 + label = "VERSION 3.13.0" + lw = width(label, MONO, 500, 15, 0.04) + out.append(f'') + out.append(run(label, x + 14, 208.5, 15, 500, "on-tx", MONO, 0.04)[0]) + out.append(wordmark(x - 6, 336, 112, on="band")[0]) + out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 404, 26, MONO)[0]) + out.append(footer_band(idx, n, "github.com/nlohmann/json")) + return "".join(out) + + +def slide_feature(idx, n, title, bullets, code, badge=None): + out = [''] + x, top, bottom = 64, 136, 596 + # title row: 48/800, -3 % + svg, tw = run(title, x - 2, 98, 48, 800, "tx", track=-0.03) + out.append(svg) + if badge: + cx = x + tw + 16 + cw = width(badge, MONO, 600, 13, 0.06) + 18 + out.append(f'') + out.append(run(badge, cx + 9, 86.2, 13, 600, "on-tx", MONO, 0.06)[0]) + # code panel: fixed left edge, fixed width, fixed top on every slide; only its height follows the code + pad, lf = 28, 1.45 + px, pw = 488, 1136 - 488 + em = max(width(l, MONO, 400, 1) for l in code) + cs = 19.0 + while cs > 14 and (em * cs > pw - 2 * pad or code_height(code, cs, lf * cs) > bottom - top - 2 * pad): + cs -= 0.5 + CODE_SIZES.append((title, cs)) + if cs < 18: + print(f" warning: {title!r} code at {cs} px ({em * 19:.0f} px wide at 19 px)", file=sys.stderr) + lh = lf * cs + ph = max(2 * pad + code_height(code, cs, lh), 240) + out.append(f'') + first = top + pad + cs * 0.78 + out.append(code_block(code, px + pad, first, cs, lh)) + # bullets: 24/400 on 34, first baseline level with the first code baseline + colw = px - x - 40 + by = first + for b in bullets: + out.append(f'') + for line in wrap(b, colw - 24, 24): + out.append(run(line, x + 24, by, 24, 400, "tx2")[0]) + by += 34 + by += 16 + # footer: small-master wordmark left, progress right, no rule, no numeral + out.append(wordmark(64, FOOT_Y, 22)[0]) + out.append(progress(idx, n, FOOT_Y - 7.5)) + return "".join(out) + + +def slide_closing(idx, n): + out = [band_bg()] + x = 96 + out.append(wordmark(x - 4, 146, 80, on="band")[0]) + out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 200, 22, MONO)[0]) + info = [ + ("{", None), + (' "copyright": ', "(c) 2013-2026 Niels Lohmann"), + (' "license": ', "MIT"), + (' "docs": ', "json.nlohmann.me"), + (' "github": ', "github.com/nlohmann/json"), + (' "sponsor": ', "github.com/sponsors/nlohmann"), + (' "mail": ', "mail@nlohmann.me"), + ("}", None), + ] + cs, lh, pad = 21, 33, 28 + py = 244 + pw = max(width(k + '"' + (v or "") + '",', MONO, 400, cs) for k, v in info) + 2 * pad + out.append(f'') + y = py + pad + cs * 0.8 + for k, (key, val) in enumerate(info): + if val is None: + out.append(run(key, x + pad, y, cs, 400, "on-tx", MONO)[0]) + else: + comma = "," if k < len(info) - 2 else "" + out.append(runs([(key, 400, "on-tx"), (f'"{val}"', 400, "on-s"), (comma, 400, "on-tx")], x + pad, y, cs, MONO)[0]) + y += lh + out.append(footer_band(idx, n)) + return "".join(out) + + +def load_slides(path): + """Feature slides from Markdown: '# Title {badge: TEXT}', '- bullet' lines, one fenced code block.""" + text = re.sub(r"", "", open(path).read(), flags=re.S) + slides, cur, in_code = [], None, False + for no, line in enumerate(text.split("\n"), 1): + if in_code: + if line.startswith("```"): + in_code = False + else: + cur["code"].append(line) + elif line.startswith("```"): + in_code = True + elif line.startswith("# "): + title, badge = line[2:].strip(), None + m = re.search(r"\s*\{badge:\s*(.*?)\}$", title) + if m: + title, badge = title[:m.start()], m.group(1) + cur = {"title": title, "bullets": [], "code": [], "badge": badge} + slides.append(cur) + elif line.startswith("- ") and cur: + cur["bullets"].append(line[2:].strip()) + elif line[:1] in " \t" and line.strip() and cur and cur["bullets"]: + cur["bullets"][-1] += " " + line.strip() + elif line.strip(): + raise SystemExit(f"{path}:{no}: unexpected line: {line!r}") + for sl in slides: + while sl["code"] and not sl["code"][-1].strip(): + sl["code"].pop() + if not sl["code"]: + raise SystemExit(f"{path}: slide {sl['title']!r} has no code block") + return slides + + +FEATURES = load_slides(os.path.join(HERE, "slides.md")) + + +def _feature(f): + return lambda i, n: slide_feature(i, n, f["title"], f["bullets"], f["code"], f["badge"]) + + +SLIDES = [slide_title] + [_feature(f) for f in FEATURES] + [slide_closing] +TOTAL = len(SLIDES) + + +# ---------------------------------------------------------------- assemble + + +def build(sec): + n = len(SLIDES) + T = n * sec + fade = 0.6 + a, b, c = fade / T * 100, sec / T * 100, (sec + fade) / T * 100 + def cls(i): + return "s s0" if i == 0 else "s sl" if i == n - 1 else "s" + bodies = [f'{fn(i, n)}' for i, fn in enumerate(SLIDES)] + css = f""" +.bg{{fill:#fff}} .tx{{fill:#0f1419}} .tx2{{fill:#3d444f}} .tx3{{fill:#8a919c}} .com{{fill:#646d7a}} +.bl{{fill:#1747a6}} .blk{{fill:#1747a6}} .kw{{fill:#1747a6}} .ty{{fill:#0d6e72}} .fn{{fill:#6b3fb8}} .str{{fill:#23793a}} .num{{fill:#b04a07}} .pp{{fill:#a3247a}} +.panel{{fill:#f5f7fa;stroke:#e3e6eb;stroke-width:1}} .rule{{stroke:#e3e6eb;stroke-width:1}} +.wm-a{{fill:#0f1419}} .wm-s{{fill:#1747a6}} +.on-a,.on-tx{{fill:#fff}} .red{{fill:#d93a3f}} .on-panel{{fill:#fff;fill-opacity:.08;stroke:#fff;stroke-opacity:.16}} .on-s{{fill:#bcd3fb}} .w70{{fill:#fff;fill-opacity:.7}} .on-tx2{{fill:#dbe6fb}} .chip{{fill:#fff;fill-opacity:.14}} +.pat{{fill:#fff;fill-opacity:.09}} +.h1{{stop-color:#1d55c4}} .h2{{stop-color:#123a8a}} +.seg{{fill:#e3e6eb}} .seg-on{{fill:#1747a6}} +.seg.on-band{{fill:#fff;fill-opacity:.25}} .seg-on.on-band{{fill:#fff;fill-opacity:1}} +@media (prefers-color-scheme:dark){{ + .bg{{fill:#0f1218}} .tx,.wm-a{{fill:#f2f4f7}} .tx2{{fill:#c3c8d1}} .tx3{{fill:#7b828e}} .com{{fill:#8b929e}} + .bl,.blk,.wm-s,.seg-on,.kw{{fill:#7aa7f5}} .ty{{fill:#4fd1c5}} .fn{{fill:#c4a3ff}} .str{{fill:#7ed491}} .num{{fill:#f5a65b}} .pp{{fill:#f48fc8}} + .panel{{fill:#171b23;stroke:#272c36}} .rule{{stroke:#272c36}} .seg{{fill:#272c36}} + .red{{fill:#c8383d}} .h1{{stop-color:#163f93}} .h2{{stop-color:#0c2556}} +}} +.s{{opacity:0;animation:show {T:g}s linear infinite backwards}} +.fill{{transform-box:fill-box;transform-origin:left;animation:fill {T:g}s linear infinite backwards;animation-delay:inherit}} +.s0{{animation-name:show0}} .sl{{animation-name:showl}} +@keyframes show{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}} +@keyframes show0{{0%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}} +@keyframes showl{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {b:.4f}%{{opacity:1}} {c:.4f}%{{opacity:0}} 100%{{opacity:0}}}} +@keyframes fill{{0%{{transform:scaleX(0)}} {b:.4f}%{{transform:scaleX(1)}} 100%{{transform:scaleX(1)}}}} +@media (prefers-reduced-motion:reduce){{.s,.fill{{animation:none}} .s:first-of-type{{opacity:1}}}} +""" + css = re.sub(r"\n\s*", "", css) + pat = pattern_def() + return (f'' + f'JSON for Modern C++: feature slideshow (animated, {n} slides, {sec:g} s each)' + f'' + f'{"".join(DEFS)}{pat}' + f'' + f'' + f'' + f'' + f'{"".join(bodies)}') + + +def fix_scheme(svg, scheme): + """Drop the dark-mode block (light) or apply it unconditionally (dark).""" + if scheme == "auto": + return svg + start = svg.index("@media (prefers-color-scheme:dark){") + i, depth = svg.index("{", start), 0 + for j in range(i, len(svg)): + depth += {"{": 1, "}": -1}.get(svg[j], 0) + if depth == 0: + break + inner = svg[i + 1:j] if scheme == "dark" else "" + return svg[:start] + inner + svg[j + 1:] + + +if __name__ == "__main__": + out = sys.argv[1] + sec = float(sys.argv[2]) if len(sys.argv) > 2 else 15 + scheme = sys.argv[3] if len(sys.argv) > 3 else "auto" + if scheme not in ("auto", "light", "dark"): + sys.exit(f"unknown colour scheme {scheme!r}: use auto, light or dark") + svg = fix_scheme(build(sec), scheme) + # defs are appended while slides render, so build the slides first (done inside build via f-string order) + open(out, "w").write(svg) + print(out, len(svg.encode()) // 1024, "KB,", len(GLYPHS), "glyphs; code px:", " ".join(f"{c:g}" for _, c in CODE_SIZES)) diff --git a/docs/mkdocs/scripts/slideshow/slides.md b/docs/mkdocs/scripts/slideshow/slides.md new file mode 100644 index 000000000..fcfe55d75 --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/slides.md @@ -0,0 +1,412 @@ + + +# Trivial integration + +- single header file +- no build system needed +- vanilla C++11 +- works with GCC 4.8+, Clang 3.4+ and MSVC 2015+ + +```cpp +#include + +// for convenience +using json = nlohmann::json; + +int main() +{ + // a JSON pretty-printer in 3 lines + json j; + std::cin >> j; + std::cout << std::setw(4) << j << std::endl; +} + +$ g++ pretty.cpp -std=c++11 -o pretty +``` + +# First-class datatype + +- write JSON like any other C++ literal +- initializer lists become arrays and objects + +```cpp +json j = { + {"pi", 3.141}, + {"happy", true}, + {"name", "Niels"}, + {"nothing", nullptr}, + {"answer", { + {"everything", 42} + }}, + {"list", {1, 0, 2}}, + {"object", { + {"currency", "USD"}, + {"value", 42.99} + }} +}; +``` + +# Array construction + +- arrays work the way you expect +- adding to a null value makes it an array + +```cpp +// create null value +json j; + +// add via member function +j.push_back("Hello"); +j.emplace_back("World"); + +// add via += operator +j += false; + +// add via index +j[2] = 42.3; + +// gaps are filled with null +j[4] = "World"; +``` + +# Object construction + +- objects work the way you expect +- adding to a null value makes it an object + +```cpp +// create null value +json j; + +// add via member function +j.emplace("key2", "value"); +j.push_back({"key", "value"}); + +// add via += operator +j += {"another key", false}; + +// add via key +j["pi"] = 3.1415926; +j["answer"]["everything"] = 42; +``` + +# Container operations + +- use JSON like an STL container +- satisfies the ReversibleContainer requirements + +```cpp +// container empty? +j.empty(); + +// current number of elements +j.size(); + +// maximal number of elements +j.max_size(); + +// swap elements +j.swap(j2); +std::swap(j, j2); + +// clear a container +j.clear(); +``` + +# STL containers + +- switch between STL and JSON containers +- conversion works in both directions + +```cpp +// some STL containers +std::vector v; +std::unordered_map m; + +// automatic transformation +json j_array(v); +json j_object(m); + +// works both ways +auto l = j_array.get>(); +auto u = j_object + .get>(); +``` + +# Type conversion + +- convert your own types to and from JSON +- one macro writes to_json and from_json +- or write both functions for full control + +```cpp +// some user-defined type +struct person { + std::string name; + int age; +}; + +// defines to_json and from_json +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, + name, age) + +// conversion in action +person p {"Ned Flanders", 60}; +json j = p; +// {"age": 60, "name": "Ned Flanders"} + +auto p2 = j.get(); +``` + +# Type deduction + +- types are deduced automatically +- member functions tell you the type + +```cpp +// type deduction +json j1 = nullptr; +json j2 = false; +json j3 = 12.23; +json j4 = {1, 2, 3}; +json j5 = {{"key", "value"}}; +json j6 = "Hello"; + +// quick checks +j1.is_null(); +j2.is_boolean(); +j3.is_number(); +j4.is_array(); +j5.is_object(); +j6.is_string(); +``` + +# Serialization + +- to a string or a stream +- pretty-printing included + +```cpp +// default serialization +auto s1 = j.dump(); + +// pretty-printed +int indent = 4; +std::string s2 = j.dump(indent); + +// to stream +std::cout << j << std::endl; + +// pretty-printed +std::cout << std::setw(indent) << j << std::endl; +``` + +# Deserialization + +- parse strings, streams and files +- the _json literal puts plain JSON in your code +- comments and trailing commas on request + +```cpp +// from string +json j1 = json::parse("[1,null,false]"); + +// from file +std::ifstream f("example.json"); +json j2 = json::parse(f); + +// from string literal +json j3 = "[3.14159]"_json; + +// from raw string literal +json j4 = R"( + {"one": 1, "two": 2, "three": [1, 2, 3]} +)"_json; +``` + +# SAX parsing + +- the parser can emit SAX events +- process input without building a JSON value + +```cpp +// define a SAX event processor +struct sax_processor : json::json_sax_t +{ + bool boolean(bool val) override + { + return true; + } + + // implement other functions... +}; + +// create object +sax_processor my_processor; + +// use it to process input +json::sax_parse(input, &my_processor); +``` + +# Zero-copy views {badge: NEW IN 3.13} + +- parse without building a tree: 16 bytes per value +- strings and numbers stay in the source text +- materialize a json value only where you need one + +```cpp +#include + +// parse into a flat index, not a tree +std::ifstream f("big.json"); +auto doc = nlohmann::json_document::parse(f); + +// read-only views with familiar access +auto view = doc.root(); +double pi = view["pi"].get(); +for (auto v : view["list"]) + std::cout << v.get() << '\n'; + +// build a json value only where needed +json part = view["list"].materialize(); +``` + +# JSON Pointer + +- address any element with a JSON Pointer +- flatten a value to pointer/value pairs, and back + +```cpp +json j = {"good", "bad", "ugly"}; + +// JSON pointer to the 3rd list element +auto ptr = json::json_pointer("/2"); + +// use JSON pointer to access element +j[ptr] = {{"it", "cattivo"}}; + +// user-defined string literal +j["/2/en"_json_pointer] = "ugly"; +// ["good","bad",{"en":"ugly","it":"cattivo"}] + +json flat = j.flatten(); +// {"/0":"good","/1":"bad","/2/en":"ugly", +// "/2/it":"cattivo"} + +json j2 = flat.unflatten(); // j == j2 +``` + +# JSON Patches + +- use JSON Patch to describe edit operations +- create diffs from two JSON values +- or use a JSON Merge Patch that looks like the document + +```cpp +json source = R"({"F": "Paris", "D": "Berlin"})"_json; + +// a JSON Patch (RFC 6902) +json p1 = R"([ + {"op": "add", "path": "/GB", "value": "London"} +])"_json; +json target = source.patch(p1); + +// create a diff from two JSON values +json p2 = json::diff(target, source); +// [{"op": "remove", "path": "/GB"}] + +// a JSON Merge Patch (RFC 7396) +target.merge_patch(R"({"F": null})"_json); +// {"D": "Berlin", "GB": "London"} +``` + +# Binary formats + +- compact binary representations +- BJData, BON8, BSON, CBOR, MessagePack and UBJSON + +```cpp +// a JSON value +json j = R"({"compact": true, "schema": 0})"_json; + +// serialize to CBOR +std::vector v = json::to_cbor(j); +// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61, +// 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68, +// 0x65, 0x6D, 0x61, 0x00 + +// roundtrip +json j_from_cbor = json::from_cbor(v); + +// the same for the other formats +auto b = json::to_bon8(j); +auto m = json::to_msgpack(j); +``` + +# Algorithms + +- the standard algorithms just work +- sort, find, count, merge and more + +```cpp +// sort JSON containers +std::sort(j.begin(), j.end()); + +// find element +auto pos = std::find(j.begin(), j.end(), + json("needle")); + +// count all numbers +auto n = std::count_if(j.begin(), j.end(), + [](const json& v) { return v.is_number(); }); + +// merge two sorted containers +std::merge(j1.begin(), j1.end(), + j2.begin(), j2.end(), + std::back_inserter(j3)); +``` + +# Iterators + +- iterators, const iterators, and reverse iterators +- random access for arrays +- items() gives you keys and values + +```cpp +json j = {1, 2, 3, 4, 5}; + +// range for +for (auto& el : j) +{ + // print all values + std::cerr << el << '\n'; +} + +// classical iterators +for (auto it = j.begin(); it != j.end(); ++it) +{ + // double all values + *it = it->get() * 2; +} +``` + +# Structured bindings + +- since C++17, unpack keys and values with structured bindings + +```cpp +// JSON object +auto j = R"({"foo": 23, "bar": 42})"_json; + +// structured bindings +for (auto& [key, val] : j.items()) +{ + std::cerr << key << ':' << val << '\n'; +} +``` From ebf5a176c4c1bcc9356ee62d2bb02bcde0814eaf Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 14:31:31 +0100 Subject: [PATCH 22/63] Fix remaining red CI on develop: Infer null dereferences, clang-tidy redundant init (#5943) --- tests/src/unit-bson.cpp | 2 +- tests/src/unit-pointer_access.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 73b277d56..b4756c9b5 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1311,7 +1311,7 @@ TEST_CASE("BSON declared document size bounds the content") // records the events that a SAX parser receives struct recorder : nlohmann::json_sax { - std::string events{}; + std::string events; bool null() override { diff --git a/tests/src/unit-pointer_access.cpp b/tests/src/unit-pointer_access.cpp index 2b84ceede..08b331b3a 100644 --- a/tests/src/unit-pointer_access.cpp +++ b/tests/src/unit-pointer_access.cpp @@ -291,6 +291,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -320,6 +321,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly const test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -349,6 +351,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == value.get()); const test_type* p2 = value.get_ptr(); @@ -378,6 +381,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly const test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == Approx(value.get())); const test_type* p2 = value.get_ptr(); @@ -407,6 +411,7 @@ TEST_CASE("pointer access") // check if pointers are returned correctly test_type* p1 = value.get_ptr(); CHECK(p1 == value.get_ptr()); + // @infer-ignore NULLPTR_DEREFERENCE CHECK(*p1 == Approx(value.get())); const test_type* p2 = value.get_ptr(); From 78a0687147acaca2c8484df38bfaae33ec91fc70 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 17:15:55 +0100 Subject: [PATCH 23/63] Strong exception guarantee for operator[](json_pointer) (#5940) * Keep the value unchanged when operator[] with a JSON pointer throws The non-const operator[](json_pointer) is documented with strong exception safety, but get_unchecked created intermediate values while walking the pointer and kept them when a later reference token failed, e.g. json j; j["/a/99999999999999999999999"_json_pointer] threw 404 and left {"a":[]}; padding an existing array or appending with "-" behaved alike, and so did std::length_error/std::bad_alloc from a huge index. get_unchecked now only walks existing values up to the last reference token. The last token's key insertion or array growth is strongly exception safe by itself. Any other change goes through get_unchecked_create, which records the one change it makes to an existing value (null converted, key inserted, array grown) in a RAII guard and reverts it if a later step throws; everything else it creates lies below that change. The guard needs no try/catch, so JSON_NOEXCEPTION builds are unaffected. Successful calls create the same values as before. Docs: state the guarantee in the exceptions list and version history of operator[], fix the complexity of the pointer overloads of operator[] and at, and warn that padding arrays up to an untrusted index allocates memory proportional to the index (operator[].md, untrusted_input.md). Signed-off-by: Niels Lohmann * Make the strongly exception-safe pointer operator[] faster than before The previous commit made get_unchecked strongly exception safe but slower with GCC 16 when it creates values (up to +6 % instructions for appending with "-"). This restructures it so that no benchmark row is slower than before the strong guarantee, with GCC 16, Clang 22 and Apple Clang. - get_unchecked walks existing values in one loop that inserts nothing. The last reference token is resolved in that walk: "-" appends with emplace_back(), an index grows the array with operator[], and a missing key is inserted with emplace_hint() at the position the walk's lower_bound() found, so the key is searched once. - With std::less or std::less<> as comparator, the walk checks the key at the lower bound for equality instead of equivalence, which is the same for these comparators and cheaper. - get_unchecked_create (not inlined) handles all other changes. Its guard swaps a null value with a fresh null value instead of copying it and reverts the change in a non-inlined function, so a successful call pays one compare for it. As every null value converted afterwards is a fresh one, it is converted in place instead of assigning a new array or object. - Keys are added to new objects with emplace(), which also replaces the basic_json::operator[] call for ordered_json. Signed-off-by: Niels Lohmann * Fix rollback of pointer operator[] for unusual object and string types Follow-up to the strong exception guarantee of the non-const operator[](json_pointer): - get_unchecked_create assumed that emplace() always inserts the key that the walk did not find. For object types whose lookup and emplace() disagree (e.g. a copied fifo_map), nothing was inserted, the rollback erased the existing member, and the walk continued with a value of the wrong type (assertion, type_error.305, or a crash). It now checks the result of emplace(), arms the rollback only if a key was inserted, and otherwise continues the walk with the member found, as basic_json's operator[] does: such pointers throw out_of_range.404 and leave the value unchanged, as before the strong guarantee. - The rollback erased the inserted key by key, which converts string_t to object_t::key_type and may allocate inside a noexcept function (std::terminate on bad_alloc with custom key types). It now erases by the iterator emplace() returned, and shrinks a grown array with erase() at the end instead of resize(). The documentation states the remaining requirement for custom template parameters. - Creating values no longer calls string_t::begin(), so the non-const operator[](json_pointer) compiles with string types that lack it (as the alternative string type of the tests). - Correct the @throw lists of get_unchecked and get_unchecked_create. Signed-off-by: Niels Lohmann * Address review comments on operator[](json_pointer) rollback - undo() shrinks the array with resize() instead of erase() - comments say what "will be" done in get_unchecked_create - tests: avoid a clang-tidy performance-unnecessary-copy-initialization hit (unit-alt-string) and the word "access" in section names, which Flawfinder flags as race/access Signed-off-by: Niels Lohmann * Let the pointer rollback guard call undo() unconditionally The check in the destructor and JSON_HEDLEY_NEVER_INLINE on undo() made no measurable difference: instruction counts of j[ptr] are identical with GCC 13 -O3 and within 0.4 % with clang, timings are within noise. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/at.md | 3 +- docs/mkdocs/docs/api/basic_json/operator[].md | 22 +- .../docs/features/parsing/untrusted_input.md | 13 + include/nlohmann/detail/json_pointer.hpp | 515 ++++++++++++++++- single_include/nlohmann/json.hpp | 516 +++++++++++++++++- tests/src/unit-alt-string.cpp | 17 + tests/src/unit-json_pointer.cpp | 72 +++ tests/src/unit-regression1.cpp | 17 + 8 files changed, 1112 insertions(+), 63 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/at.md b/docs/mkdocs/docs/api/basic_json/at.md index f707b9835..b698443b3 100644 --- a/docs/mkdocs/docs/api/basic_json/at.md +++ b/docs/mkdocs/docs/api/basic_json/at.md @@ -90,7 +90,8 @@ Strong exception safety: if an exception occurs, the original value stays intact 1. Constant. 2. Logarithmic in the size of the container. 3. Logarithmic in the size of the container. -4. Logarithmic in the size of the container. +4. Linear in the number of reference tokens of `ptr`; for each of them, logarithmic in the size of the object, or + constant for an array. ## Notes diff --git a/docs/mkdocs/docs/api/basic_json/operator[].md b/docs/mkdocs/docs/api/basic_json/operator[].md index 59e48d984..a2b2acf5a 100644 --- a/docs/mkdocs/docs/api/basic_json/operator[].md +++ b/docs/mkdocs/docs/api/basic_json/operator[].md @@ -92,12 +92,20 @@ Strong exception safety: if an exception occurs, the original value stays intact For the **const** version, an object key or array index in `ptr` that does not exist is not reported by an exception, but is undefined behavior (see the notes below). Use [`at`](at.md) for checked access. + If the **non-const** version throws, the value is left unchanged: values it created for earlier reference tokens + of `ptr` are removed again. This also holds for exceptions such as `#!cpp std::bad_alloc` or + `#!cpp std::length_error` thrown while padding an array up to a very large index. With custom + [template parameters](../../features/types/template_parameters.md), this requires that `erase()` of the object and + array types and the move operations of the custom base class do not throw. + ## Complexity 1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`. 2. Logarithmic in the size of the container. 3. Logarithmic in the size of the container. -4. Logarithmic in the size of the container. +4. Linear in the number of reference tokens of `ptr`; for each of them, logarithmic in the size of the object, or + constant for an array. When the non-const version pads an array with `#!json null` values, it is additionally + linear in `idx - size()` for that array. ## Notes @@ -145,6 +153,14 @@ Strong exception safety: if an exception occurs, the original value stays intact specified by the JSON Pointer RFC; it is this library's own, intentional disambiguation rule. See also [JSON Pointer](../../features/json_pointer.md). + !!! warning "Large array indices" + + As the array is filled up with `#!json null` values to the given index, the memory used grows with the index + and not with the size of the pointer: on a 64-bit platform, the index `10000000` allocates about 160 MB. + The library does not limit the index, so check the array indices of a JSON pointer from untrusted input + before using it with the non-const version. See + [Parsing untrusted input](../../features/parsing/untrusted_input.md#json-pointers-and-flattened-keys). + !!! warning "Deprecation" Overload (4) also accepts a [`json_pointer`](../json_pointer/index.md) whose template argument is a `basic_json` @@ -296,4 +312,6 @@ Strong exception safety: if an exception occurs, the original value stays intact 4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since version 3.13.0. Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is not -a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. +a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. Fixed in version 3.13.0 to leave the value +unchanged if the non-const version throws (strong exception safety, as documented); before, values created for earlier +reference tokens of `ptr` were kept. diff --git a/docs/mkdocs/docs/features/parsing/untrusted_input.md b/docs/mkdocs/docs/features/parsing/untrusted_input.md index e89a7a7a3..1fe7b64aa 100644 --- a/docs/mkdocs/docs/features/parsing/untrusted_input.md +++ b/docs/mkdocs/docs/features/parsing/untrusted_input.md @@ -130,6 +130,17 @@ parameters of [`parse()`](../../api/basic_json/parse.md) or [`accept()`](../../a enable either for input whose conformance you cannot otherwise control, since interoperability with strictly conforming JSON consumers is exactly what the default rejects. +## JSON Pointers and flattened keys + +A [JSON Pointer](../json_pointer.md) can come from untrusted input as well, for instance as a path in a request or as a +key of a flattened object. The non-const [`operator[]`](../../api/basic_json/operator%5B%5D.md) with a JSON Pointer and +[`unflatten`](../../api/basic_json/unflatten.md) create missing values, and when an array index lies beyond the end of +an array, they fill the array with `#!json null` values up to that index. The memory used therefore grows with the +index, not with the size of the pointer: on a 64-bit platform, the index `10000000` allocates about 160 MB, the index +`100000000` about 1.6 GB. The library does not limit the index. For pointers from untrusted input, check the array +indices (or the pointer as a whole) before using them, or use [`at`](../../api/basic_json/at.md) or +[`contains`](../../api/basic_json/contains.md), which never add values. + ## Checklist - Wrap parsing in a `#!cpp try`/`#!cpp catch` block, or use `allow_exceptions=false`/`accept()` if your environment @@ -145,6 +156,8 @@ conforming JSON consumers is exactly what the default rejects. `JSON_STRICT_NUL_HANDLING`. - Decide whether duplicate object keys should be an error for your application, and add a callback if so. - Do not enable `ignore_comments` or `ignore_trailing_commas` for input that must be strictly conforming JSON. +- Check the array indices of untrusted JSON Pointers and flattened keys before passing them to the non-const + `operator[]` or `unflatten()`, which fill arrays with `#!json null` values up to the index. For the broader design rationale and how it is tested (fuzzing, sanitizers, static analysis), see the [assurance case](../../community/assurance_case.md) and [quality assurance](../../community/quality_assurance.md). To diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 457985480..eef4ce0a4 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -8,7 +8,7 @@ #pragma once -#include // all_of, copy +#include // copy #include // array #include // isdigit #include // errno, ERANGE @@ -16,9 +16,11 @@ #include // uint64_t #include // strtoull #include // memchr, memcmp, memcpy +#include // less #ifndef JSON_NO_IO #include // ostream #endif // JSON_NO_IO +#include // next #include // max #include // string #include // move @@ -28,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -134,6 +137,26 @@ JSON_HEDLEY_ALWAYS_INLINE array_index_status parse_array_index(const StringType& return array_index_status::ok; } +/// whether two keys of type @a StringType are equivalent under @a Compare +/// exactly if they are equal: std::less and std::less<> +template +struct is_less_comparator : std::false_type {}; + +template +struct is_less_comparator, StringType> : std::true_type {}; + +template +struct is_less_comparator, StringType> : std::true_type {}; + +/// whether the objects of type @a ObjectType find a key of type @a StringType +/// exactly if it equals a stored key +template +struct finds_equal_keys : std::false_type {}; + +template +struct finds_equal_keys> + : conjunction, std::is_same> {}; + } // namespace detail /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document @@ -589,6 +612,225 @@ class json_pointer return object_member_impl(object, key, detail::priority_tag<1> {}); } + /// @brief append a null value to @a array, like operator[] with the size of @a array + template + static BasicJsonType& array_append(BasicJsonType& array) + { + auto& elements = *array.m_data.m_value.array; + const auto old_capacity = array.container_capacity(); + elements.emplace_back(); + return array.set_parent_after_insert(elements.back(), old_capacity); + } + + /// @brief add @a key, which is missing, to @a object + template + static auto object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(object.m_data.m_value.object->emplace(key, nullptr), std::declval()) + { + const auto old_capacity = object.container_capacity(); + const auto it = object.m_data.m_value.object->emplace(key, nullptr).first; + return object.set_parent_after_insert(it->second, old_capacity); + } + + template + static BasicJsonType& object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/) + { + return object.operator[](key); + } + + template + static BasicJsonType& object_add(BasicJsonType& object, const string_t& key) + { + return object_add_impl(object, key, detail::priority_tag<1> {}); + } + + /// @brief search @a key in @a members: the result is where the key is or, + /// for objects that can insert at a position (std::map), where it belongs + template + static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(members.key_comp()(key, members.lower_bound(key)->first), members.lower_bound(key)) + { + return members.lower_bound(key); + } + + template + static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<0> /*unused*/) + -> decltype(members.find(key)) + { + return members.find(key); + } + + template + static auto object_search(ObjectType& members, const string_t& key) + -> decltype(object_search_impl(members, key, detail::priority_tag<1> {})) + { + return object_search_impl(members, key, detail::priority_tag<1> {}); + } + + /// @brief whether @a it, the result of @ref object_search for @a key, points to @a key + /// + /// With std::less, the key at the lower bound is equivalent to @a key + /// exactly if it is equal to it, and comparing for equality is cheaper. + template::value, int> = 0> + static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<2> /*unused*/) + -> decltype(members.lower_bound(key), it->first == key) + { + return it != members.end() && it->first == key; + } + + template + static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(members.key_comp()(key, it->first), members.lower_bound(key), bool()) + { + return it != members.end() && !members.key_comp()(key, it->first); + } + + template + static bool object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& /*key*/, detail::priority_tag<0> /*unused*/) + { + return it != members.end(); + } + + template + static bool object_search_found(ObjectType& members, const Iterator& it, const string_t& key) + { + return object_search_found_impl(members, it, key, detail::priority_tag<2> {}); + } + + /// @brief insert @a key, which is missing, into @a object at @a it, the + /// result of @ref object_search for @a key + template + static auto object_insert_impl(BasicJsonType& object, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(object.m_data.m_value.object->emplace_hint(it, key, nullptr), std::declval()) + { + const auto old_capacity = object.container_capacity(); + const auto pos = object.m_data.m_value.object->emplace_hint(it, key, nullptr); + return object.set_parent_after_insert(pos->second, old_capacity); + } + + template + static BasicJsonType& object_insert_impl(BasicJsonType& object, const Iterator& /*it*/, const string_t& key, detail::priority_tag<0> /*unused*/) + { + return object_add(object, key); + } + + template + static BasicJsonType& object_insert(BasicJsonType& object, const Iterator& it, const string_t& key) + { + return object_insert_impl(object, it, key, detail::priority_tag<1> {}); + } + + /*! + @brief undoes the change @ref get_unchecked_create made to an existing value + + @ref get_unchecked walks existing values without changing them. At the + first reference token that does not select an existing value, @ref + get_unchecked_create makes exactly one change to an existing value: it + converts a null value, inserts a key into an object, or grows an array. + Everything it creates afterwards lies below that change. If a later step + throws, the destructor of this guard reverts that one change, which + removes everything created after it, so the value is left as it was + (strong exception safety). The guard needs no try/catch and therefore also + compiles with JSON_NOEXCEPTION. + + Reverting the change must not throw, as it happens in a destructor. It + erases the inserted member by its iterator (no key is converted or + compared, nothing is allocated), erases the added elements at the end of + the array, or swaps the null value back. This holds for std::map, + std::vector, ordered_map, and every object_t, array_t, and + json_base_class_t whose erase() and move operations do not throw. + Between recording and reverting, nothing is inserted into or erased from + the object or array, so the iterator stays valid. + */ + template + class get_unchecked_rollback + { + public: + using object_iterator = typename BasicJsonType::object_t::iterator; + + /// record that @a member was inserted into the object @a value + get_unchecked_rollback(BasicJsonType* value, const object_iterator& member) + : m_value(value) + , m_member(member) + , m_change(change::key_inserted) + {} + + /// record the change that the next reference token makes to the null value or array @a value + explicit get_unchecked_rollback(BasicJsonType* value) + : m_value(value) + { + if (value->is_null()) + { + // keep the null value (with its base class and positions) and + // continue with a fresh null value in its place, which is what + // assigning an array or object to it would have left there + m_saved_null.swap(*value); + m_change = change::null_converted; + } + else + { + JSON_ASSERT(value->is_array()); + m_old_size = value->m_data.m_value.array->size(); + m_change = change::array_grown; + } + } + + get_unchecked_rollback(const get_unchecked_rollback&) = delete; + get_unchecked_rollback(get_unchecked_rollback&&) = delete; + get_unchecked_rollback& operator=(const get_unchecked_rollback&) = delete; + get_unchecked_rollback& operator=(get_unchecked_rollback&&) = delete; + + ~get_unchecked_rollback() + { + undo(); + } + + /// keep the change + void commit() noexcept + { + m_change = change::none; + } + + private: + void undo() noexcept + { + switch (m_change) + { + case change::null_converted: + // the values created since go away with m_saved_null + m_value->swap(m_saved_null); + break; + case change::key_inserted: + m_value->m_data.m_value.object->erase(m_member); + break; + case change::array_grown: + { + auto& elements = *m_value->m_data.m_value.array; + elements.resize(m_old_size); + break; + } + case change::none: + default: + break; + } + } + + enum class change + { + none, + null_converted, + key_inserted, + array_grown + }; + + BasicJsonType* m_value; + object_iterator m_member {}; + typename BasicJsonType::size_type m_old_size = 0; + change m_change = change::none; + BasicJsonType m_saved_null {}; + }; + JSON_PRIVATE_UNLESS_TESTED: json_pointer top() const { @@ -972,47 +1214,245 @@ class json_pointer Complexity: Linear in the length of the JSON pointer. - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.404 if the JSON pointer can not be resolved + Exception safety: Strong guarantee. This function walks existing values + until the first reference token that does not select one. If that is the + last reference token, inserting the key into the object or growing the + array is strongly exception safe by itself; any other change is made by + @ref get_unchecked_create, which undoes it if a later step throws. + + @throw parse_error.106 if an array index of an existing array begins with '0' + @throw parse_error.109 if an array index of an existing array was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved, or if an + array index is too large for strtoull + @throw out_of_range.410 if an array index exceeds size_type + @throw std::length_error if padding an array exceeds its maximal size + @throw std::bad_alloc if an allocation fails */ template BasicJsonType& get_unchecked(BasicJsonType* ptr) const { - for (const auto& reference_token : reference_tokens) + const auto* token = reference_tokens.data(); + const auto* const end = token + reference_tokens.size(); + if (token == end) { + return *ptr; + } + if (ptr->is_null()) + { + // the null value is converted to an array or an object + return get_unchecked_create(ptr, token, end); + } + + // walk existing values; the walk itself inserts nothing, which keeps + // the loop fast (insertions are done below or in get_unchecked_create) + typename BasicJsonType::object_t::iterator it; + do + { + if (ptr->is_object()) + { + auto& members = *ptr->m_data.m_value.object; + it = object_search(members, *token); + if (!object_search_found(members, it, *token)) + { + // the key is inserted below or in get_unchecked_create + break; + } + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (is_dash(*token)) + { + if (token + 1 == end) + { + // explicitly treat "-" as index beyond the end + return array_append(*ptr); + } + // an element will be appended in get_unchecked_create + break; + } + if (token + 1 == end) + { + // convert array index to number; unchecked access + return ptr->operator[](array_index(*token)); + } + const auto idx = array_index(*token); + if (idx >= ptr->m_data.m_value.array->size()) + { + // the array will be padded in get_unchecked_create + break; + } + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else if (ptr->is_null()) + { + // the null value will be converted in get_unchecked_create + break; + } + else + { + // primitive value with a reference token left + throw_unresolved(*token, ptr); + } + } + while (++token != end); + + if (token == end) + { + return *ptr; + } + if (token + 1 == end && ptr->is_object()) + { + // insert the missing key where the walk found it belongs + return object_insert(*ptr, it, *token); + } + return get_unchecked_create(ptr, token, end); + } + + /*! + @brief create the values for the reference tokens from @a first on + + Continues @ref get_unchecked at the first reference token that does not + select an existing value. That token changes @a ptr, an existing value (see + @ref get_unchecked_rollback); all later tokens only change values created + here. + + If the emplace() of the object type finds a member that the lookup in @ref + get_unchecked did not find (the two disagree, e.g. for fifo_map), nothing + is inserted, and the walk continues with that member as in @ref + get_unchecked, which is what basic_json::operator[] does. + + @param[in] ptr the value the reference token @a first is applied to + @param[in] first the first reference token to resolve (an element of + @ref reference_tokens) + @param[in] end the end of @ref reference_tokens + + @throw parse_error.106 if an array index of an existing array begins with + '0' (only after emplace() found a member, see above) + @throw parse_error.109 if an array index of an existing array was not a + number (only after emplace() found a member) + @throw out_of_range.404 if an array index is too large for strtoull, or (only + after emplace() found a member) if the JSON pointer + can not be resolved + @throw out_of_range.410 if an array index exceeds size_type + @throw std::length_error if padding an array exceeds its maximal size + @throw std::bad_alloc if an allocation fails + */ + template + JSON_HEDLEY_NEVER_INLINE static BasicJsonType& get_unchecked_create(BasicJsonType* ptr, const string_t* first, const string_t* end) + { + const auto* token = first; + while (ptr->is_object()) + { + const auto old_capacity = ptr->container_capacity(); + const auto inserted = ptr->m_data.m_value.object->emplace(*token, nullptr); + if (JSON_HEDLEY_LIKELY(inserted.second)) + { + get_unchecked_rollback rollback(ptr, inserted.first); + BasicJsonType& result = create_values(&ptr->set_parent_after_insert(inserted.first->second, old_capacity), token + 1, end); + rollback.commit(); + return result; + } + + // nothing was inserted: walk existing values from the member + // emplace() found on, like get_unchecked + ptr = &inserted.first->second; + while (++token != end) + { + if (ptr->is_object()) + { + auto& members = *ptr->m_data.m_value.object; + const auto it = object_search(members, *token); + if (!object_search_found(members, it, *token)) + { + break; + } + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (is_dash(*token)) + { + break; + } + const auto idx = array_index(*token); + if (idx >= ptr->m_data.m_value.array->size()) + { + break; + } + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else if (ptr->is_null()) + { + break; + } + else + { + // primitive value with a reference token left + throw_unresolved(*token, ptr); + } + } + if (token == end) + { + return *ptr; + } + } + + get_unchecked_rollback rollback(ptr); + BasicJsonType& result = create_values(ptr, token, end); + rollback.commit(); + return result; + } + + /*! + @brief the loop of @ref get_unchecked_create + + @a ptr is a null value or an array. Every value created here is a fresh + one, so every object a key is added to is empty. + + @throw out_of_range.404 if an array index is too large for strtoull + @throw out_of_range.410 if an array index exceeds size_type + @throw std::length_error if padding an array exceeds its maximal size + @throw std::bad_alloc if an allocation fails + */ + template + static BasicJsonType& create_values(BasicJsonType* ptr, const string_t* first, const string_t* end) + { + for (const auto* token = first; token != end; ++token) + { + const auto& reference_token = *token; + // convert null values to arrays or objects before continuing if (ptr->is_null()) { - // check if the reference token is a valid array index, that is - // a nonempty sequence of digits without a leading '0' - // (cf. RFC 6901, Sect. 4); tokens that could never be a valid - // array index (such as "01" or "") are treated as object keys - const bool nums = !reference_token.empty() - && (reference_token.size() == 1 || reference_token[0] != '0') - && std::all_of(reference_token.begin(), reference_token.end(), - [](const unsigned char x) + // change value to an array for array indices or "-" or to an + // object otherwise; every null value here is a fresh one (see + // get_unchecked_rollback), so converting it in place leaves + // the same value as assigning an array or object to it + if (is_array_index_token(reference_token) || is_dash(reference_token)) { - return std::isdigit(x); - }); - - // change value to an array for array indices or "-" or to object otherwise - *ptr = (nums || reference_token == "-") - ? detail::value_t::array - : detail::value_t::object; + ptr->template convert_null_to(); + } + else + { + ptr->template convert_null_to(); + } } if (ptr->is_object()) { - // use unchecked object access - ptr = &object_member(*ptr, reference_token); + // the object is new and empty + ptr = &object_add(*ptr, reference_token); } - else if (ptr->is_array()) + else { - if (reference_token == "-") + // only the first value is not created here, and it is a null + // value or an array (see get_unchecked_create) + JSON_ASSERT(ptr->is_array()); + if (is_dash(reference_token)) { // explicitly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_data.m_value.array->size()); + ptr = &array_append(*ptr); } else { @@ -1020,16 +1460,31 @@ class json_pointer ptr = &ptr->operator[](array_index(reference_token)); } } - else - { - // primitive value with a reference token left - throw_unresolved(reference_token, ptr); - } } return *ptr; } + /// whether @a s is a valid array index, that is a nonempty sequence of + /// digits without a leading '0' (cf. RFC 6901, Sect. 4); tokens that could + /// never be a valid array index (such as "01" or "") are object keys + static bool is_array_index_token(const string_t& s) + { + const std::size_t size = s.size(); + if (size == 0 || (size > 1 && s[0] == '0')) + { + return false; + } + for (std::size_t i = 0; i < size; ++i) + { + if (std::isdigit(static_cast(s[i])) == 0) + { + return false; + } + } + return true; + } + /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0c4753d41..085b55afb 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -24064,7 +24064,7 @@ NLOHMANN_JSON_NAMESPACE_END -#include // all_of, copy +#include // copy #include // array #include // isdigit #include // errno, ERANGE @@ -24072,9 +24072,11 @@ NLOHMANN_JSON_NAMESPACE_END #include // uint64_t #include // strtoull #include // memchr, memcmp, memcpy +#include // less #ifndef JSON_NO_IO #include // ostream #endif // JSON_NO_IO +#include // next #include // max #include // string #include // move @@ -24088,6 +24090,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -24198,6 +24202,26 @@ JSON_HEDLEY_ALWAYS_INLINE array_index_status parse_array_index(const StringType& return array_index_status::ok; } +/// whether two keys of type @a StringType are equivalent under @a Compare +/// exactly if they are equal: std::less and std::less<> +template +struct is_less_comparator : std::false_type {}; + +template +struct is_less_comparator, StringType> : std::true_type {}; + +template +struct is_less_comparator, StringType> : std::true_type {}; + +/// whether the objects of type @a ObjectType find a key of type @a StringType +/// exactly if it equals a stored key +template +struct finds_equal_keys : std::false_type {}; + +template +struct finds_equal_keys> + : conjunction, std::is_same> {}; + } // namespace detail /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document @@ -24653,6 +24677,225 @@ class json_pointer return object_member_impl(object, key, detail::priority_tag<1> {}); } + /// @brief append a null value to @a array, like operator[] with the size of @a array + template + static BasicJsonType& array_append(BasicJsonType& array) + { + auto& elements = *array.m_data.m_value.array; + const auto old_capacity = array.container_capacity(); + elements.emplace_back(); + return array.set_parent_after_insert(elements.back(), old_capacity); + } + + /// @brief add @a key, which is missing, to @a object + template + static auto object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(object.m_data.m_value.object->emplace(key, nullptr), std::declval()) + { + const auto old_capacity = object.container_capacity(); + const auto it = object.m_data.m_value.object->emplace(key, nullptr).first; + return object.set_parent_after_insert(it->second, old_capacity); + } + + template + static BasicJsonType& object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/) + { + return object.operator[](key); + } + + template + static BasicJsonType& object_add(BasicJsonType& object, const string_t& key) + { + return object_add_impl(object, key, detail::priority_tag<1> {}); + } + + /// @brief search @a key in @a members: the result is where the key is or, + /// for objects that can insert at a position (std::map), where it belongs + template + static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(members.key_comp()(key, members.lower_bound(key)->first), members.lower_bound(key)) + { + return members.lower_bound(key); + } + + template + static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<0> /*unused*/) + -> decltype(members.find(key)) + { + return members.find(key); + } + + template + static auto object_search(ObjectType& members, const string_t& key) + -> decltype(object_search_impl(members, key, detail::priority_tag<1> {})) + { + return object_search_impl(members, key, detail::priority_tag<1> {}); + } + + /// @brief whether @a it, the result of @ref object_search for @a key, points to @a key + /// + /// With std::less, the key at the lower bound is equivalent to @a key + /// exactly if it is equal to it, and comparing for equality is cheaper. + template::value, int> = 0> + static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<2> /*unused*/) + -> decltype(members.lower_bound(key), it->first == key) + { + return it != members.end() && it->first == key; + } + + template + static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(members.key_comp()(key, it->first), members.lower_bound(key), bool()) + { + return it != members.end() && !members.key_comp()(key, it->first); + } + + template + static bool object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& /*key*/, detail::priority_tag<0> /*unused*/) + { + return it != members.end(); + } + + template + static bool object_search_found(ObjectType& members, const Iterator& it, const string_t& key) + { + return object_search_found_impl(members, it, key, detail::priority_tag<2> {}); + } + + /// @brief insert @a key, which is missing, into @a object at @a it, the + /// result of @ref object_search for @a key + template + static auto object_insert_impl(BasicJsonType& object, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/) + -> decltype(object.m_data.m_value.object->emplace_hint(it, key, nullptr), std::declval()) + { + const auto old_capacity = object.container_capacity(); + const auto pos = object.m_data.m_value.object->emplace_hint(it, key, nullptr); + return object.set_parent_after_insert(pos->second, old_capacity); + } + + template + static BasicJsonType& object_insert_impl(BasicJsonType& object, const Iterator& /*it*/, const string_t& key, detail::priority_tag<0> /*unused*/) + { + return object_add(object, key); + } + + template + static BasicJsonType& object_insert(BasicJsonType& object, const Iterator& it, const string_t& key) + { + return object_insert_impl(object, it, key, detail::priority_tag<1> {}); + } + + /*! + @brief undoes the change @ref get_unchecked_create made to an existing value + + @ref get_unchecked walks existing values without changing them. At the + first reference token that does not select an existing value, @ref + get_unchecked_create makes exactly one change to an existing value: it + converts a null value, inserts a key into an object, or grows an array. + Everything it creates afterwards lies below that change. If a later step + throws, the destructor of this guard reverts that one change, which + removes everything created after it, so the value is left as it was + (strong exception safety). The guard needs no try/catch and therefore also + compiles with JSON_NOEXCEPTION. + + Reverting the change must not throw, as it happens in a destructor. It + erases the inserted member by its iterator (no key is converted or + compared, nothing is allocated), erases the added elements at the end of + the array, or swaps the null value back. This holds for std::map, + std::vector, ordered_map, and every object_t, array_t, and + json_base_class_t whose erase() and move operations do not throw. + Between recording and reverting, nothing is inserted into or erased from + the object or array, so the iterator stays valid. + */ + template + class get_unchecked_rollback + { + public: + using object_iterator = typename BasicJsonType::object_t::iterator; + + /// record that @a member was inserted into the object @a value + get_unchecked_rollback(BasicJsonType* value, const object_iterator& member) + : m_value(value) + , m_member(member) + , m_change(change::key_inserted) + {} + + /// record the change that the next reference token makes to the null value or array @a value + explicit get_unchecked_rollback(BasicJsonType* value) + : m_value(value) + { + if (value->is_null()) + { + // keep the null value (with its base class and positions) and + // continue with a fresh null value in its place, which is what + // assigning an array or object to it would have left there + m_saved_null.swap(*value); + m_change = change::null_converted; + } + else + { + JSON_ASSERT(value->is_array()); + m_old_size = value->m_data.m_value.array->size(); + m_change = change::array_grown; + } + } + + get_unchecked_rollback(const get_unchecked_rollback&) = delete; + get_unchecked_rollback(get_unchecked_rollback&&) = delete; + get_unchecked_rollback& operator=(const get_unchecked_rollback&) = delete; + get_unchecked_rollback& operator=(get_unchecked_rollback&&) = delete; + + ~get_unchecked_rollback() + { + undo(); + } + + /// keep the change + void commit() noexcept + { + m_change = change::none; + } + + private: + void undo() noexcept + { + switch (m_change) + { + case change::null_converted: + // the values created since go away with m_saved_null + m_value->swap(m_saved_null); + break; + case change::key_inserted: + m_value->m_data.m_value.object->erase(m_member); + break; + case change::array_grown: + { + auto& elements = *m_value->m_data.m_value.array; + elements.resize(m_old_size); + break; + } + case change::none: + default: + break; + } + } + + enum class change + { + none, + null_converted, + key_inserted, + array_grown + }; + + BasicJsonType* m_value; + object_iterator m_member {}; + typename BasicJsonType::size_type m_old_size = 0; + change m_change = change::none; + BasicJsonType m_saved_null {}; + }; + JSON_PRIVATE_UNLESS_TESTED: json_pointer top() const { @@ -25036,47 +25279,245 @@ class json_pointer Complexity: Linear in the length of the JSON pointer. - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.404 if the JSON pointer can not be resolved + Exception safety: Strong guarantee. This function walks existing values + until the first reference token that does not select one. If that is the + last reference token, inserting the key into the object or growing the + array is strongly exception safe by itself; any other change is made by + @ref get_unchecked_create, which undoes it if a later step throws. + + @throw parse_error.106 if an array index of an existing array begins with '0' + @throw parse_error.109 if an array index of an existing array was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved, or if an + array index is too large for strtoull + @throw out_of_range.410 if an array index exceeds size_type + @throw std::length_error if padding an array exceeds its maximal size + @throw std::bad_alloc if an allocation fails */ template BasicJsonType& get_unchecked(BasicJsonType* ptr) const { - for (const auto& reference_token : reference_tokens) + const auto* token = reference_tokens.data(); + const auto* const end = token + reference_tokens.size(); + if (token == end) { + return *ptr; + } + if (ptr->is_null()) + { + // the null value is converted to an array or an object + return get_unchecked_create(ptr, token, end); + } + + // walk existing values; the walk itself inserts nothing, which keeps + // the loop fast (insertions are done below or in get_unchecked_create) + typename BasicJsonType::object_t::iterator it; + do + { + if (ptr->is_object()) + { + auto& members = *ptr->m_data.m_value.object; + it = object_search(members, *token); + if (!object_search_found(members, it, *token)) + { + // the key is inserted below or in get_unchecked_create + break; + } + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (is_dash(*token)) + { + if (token + 1 == end) + { + // explicitly treat "-" as index beyond the end + return array_append(*ptr); + } + // an element will be appended in get_unchecked_create + break; + } + if (token + 1 == end) + { + // convert array index to number; unchecked access + return ptr->operator[](array_index(*token)); + } + const auto idx = array_index(*token); + if (idx >= ptr->m_data.m_value.array->size()) + { + // the array will be padded in get_unchecked_create + break; + } + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else if (ptr->is_null()) + { + // the null value will be converted in get_unchecked_create + break; + } + else + { + // primitive value with a reference token left + throw_unresolved(*token, ptr); + } + } + while (++token != end); + + if (token == end) + { + return *ptr; + } + if (token + 1 == end && ptr->is_object()) + { + // insert the missing key where the walk found it belongs + return object_insert(*ptr, it, *token); + } + return get_unchecked_create(ptr, token, end); + } + + /*! + @brief create the values for the reference tokens from @a first on + + Continues @ref get_unchecked at the first reference token that does not + select an existing value. That token changes @a ptr, an existing value (see + @ref get_unchecked_rollback); all later tokens only change values created + here. + + If the emplace() of the object type finds a member that the lookup in @ref + get_unchecked did not find (the two disagree, e.g. for fifo_map), nothing + is inserted, and the walk continues with that member as in @ref + get_unchecked, which is what basic_json::operator[] does. + + @param[in] ptr the value the reference token @a first is applied to + @param[in] first the first reference token to resolve (an element of + @ref reference_tokens) + @param[in] end the end of @ref reference_tokens + + @throw parse_error.106 if an array index of an existing array begins with + '0' (only after emplace() found a member, see above) + @throw parse_error.109 if an array index of an existing array was not a + number (only after emplace() found a member) + @throw out_of_range.404 if an array index is too large for strtoull, or (only + after emplace() found a member) if the JSON pointer + can not be resolved + @throw out_of_range.410 if an array index exceeds size_type + @throw std::length_error if padding an array exceeds its maximal size + @throw std::bad_alloc if an allocation fails + */ + template + JSON_HEDLEY_NEVER_INLINE static BasicJsonType& get_unchecked_create(BasicJsonType* ptr, const string_t* first, const string_t* end) + { + const auto* token = first; + while (ptr->is_object()) + { + const auto old_capacity = ptr->container_capacity(); + const auto inserted = ptr->m_data.m_value.object->emplace(*token, nullptr); + if (JSON_HEDLEY_LIKELY(inserted.second)) + { + get_unchecked_rollback rollback(ptr, inserted.first); + BasicJsonType& result = create_values(&ptr->set_parent_after_insert(inserted.first->second, old_capacity), token + 1, end); + rollback.commit(); + return result; + } + + // nothing was inserted: walk existing values from the member + // emplace() found on, like get_unchecked + ptr = &inserted.first->second; + while (++token != end) + { + if (ptr->is_object()) + { + auto& members = *ptr->m_data.m_value.object; + const auto it = object_search(members, *token); + if (!object_search_found(members, it, *token)) + { + break; + } + ptr = &it->second; + } + else if (ptr->is_array()) + { + if (is_dash(*token)) + { + break; + } + const auto idx = array_index(*token); + if (idx >= ptr->m_data.m_value.array->size()) + { + break; + } + ptr = &(*ptr->m_data.m_value.array)[idx]; + } + else if (ptr->is_null()) + { + break; + } + else + { + // primitive value with a reference token left + throw_unresolved(*token, ptr); + } + } + if (token == end) + { + return *ptr; + } + } + + get_unchecked_rollback rollback(ptr); + BasicJsonType& result = create_values(ptr, token, end); + rollback.commit(); + return result; + } + + /*! + @brief the loop of @ref get_unchecked_create + + @a ptr is a null value or an array. Every value created here is a fresh + one, so every object a key is added to is empty. + + @throw out_of_range.404 if an array index is too large for strtoull + @throw out_of_range.410 if an array index exceeds size_type + @throw std::length_error if padding an array exceeds its maximal size + @throw std::bad_alloc if an allocation fails + */ + template + static BasicJsonType& create_values(BasicJsonType* ptr, const string_t* first, const string_t* end) + { + for (const auto* token = first; token != end; ++token) + { + const auto& reference_token = *token; + // convert null values to arrays or objects before continuing if (ptr->is_null()) { - // check if the reference token is a valid array index, that is - // a nonempty sequence of digits without a leading '0' - // (cf. RFC 6901, Sect. 4); tokens that could never be a valid - // array index (such as "01" or "") are treated as object keys - const bool nums = !reference_token.empty() - && (reference_token.size() == 1 || reference_token[0] != '0') - && std::all_of(reference_token.begin(), reference_token.end(), - [](const unsigned char x) + // change value to an array for array indices or "-" or to an + // object otherwise; every null value here is a fresh one (see + // get_unchecked_rollback), so converting it in place leaves + // the same value as assigning an array or object to it + if (is_array_index_token(reference_token) || is_dash(reference_token)) { - return std::isdigit(x); - }); - - // change value to an array for array indices or "-" or to object otherwise - *ptr = (nums || reference_token == "-") - ? detail::value_t::array - : detail::value_t::object; + ptr->template convert_null_to(); + } + else + { + ptr->template convert_null_to(); + } } if (ptr->is_object()) { - // use unchecked object access - ptr = &object_member(*ptr, reference_token); + // the object is new and empty + ptr = &object_add(*ptr, reference_token); } - else if (ptr->is_array()) + else { - if (reference_token == "-") + // only the first value is not created here, and it is a null + // value or an array (see get_unchecked_create) + JSON_ASSERT(ptr->is_array()); + if (is_dash(reference_token)) { // explicitly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_data.m_value.array->size()); + ptr = &array_append(*ptr); } else { @@ -25084,16 +25525,31 @@ class json_pointer ptr = &ptr->operator[](array_index(reference_token)); } } - else - { - // primitive value with a reference token left - throw_unresolved(reference_token, ptr); - } } return *ptr; } + /// whether @a s is a valid array index, that is a nonempty sequence of + /// digits without a leading '0' (cf. RFC 6901, Sect. 4); tokens that could + /// never be a valid array index (such as "01" or "") are object keys + static bool is_array_index_token(const string_t& s) + { + const std::size_t size = s.size(); + if (size == 0 || (size > 1 && s[0] == '0')) + { + return false; + } + for (std::size_t i = 0; i < size; ++i) + { + if (std::isdigit(static_cast(s[i])) == 0) + { + return false; + } + } + return true; + } + /*! @throw parse_error.106 if an array index begins with '0' @throw parse_error.109 if an array index was not a number diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index 961cd742b..75feb74e8 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -373,6 +373,23 @@ TEST_CASE("alternative string type") CHECK_FALSE(j.contains(alt_json::json_pointer("/foo/bar"))); } + SECTION("non-const operator[](json_pointer)") + { + // creating values must compile without string_t::begin() + alt_json j = alt_json::parse(R"({"a":{"b":[1]},"c":null})"); + const alt_json before = j; + j[alt_json::json_pointer("/a/b/3/x/-/y")] = 1; // pad, append, create + j[alt_json::json_pointer("/a/z")] = 2; // insert a key + j[alt_json::json_pointer("/c/0/q")] = 3; // convert null + j[alt_json::json_pointer("/a/b/-")] = 4; // append + CHECK(j.dump() == R"({"a":{"b":[1,null,null,{"x":[{"y":1}]},4],"z":2},"c":[{"q":3}]})"); + + // a failing access leaves the value unchanged + j = before; + CHECK_THROWS_AS(j[alt_json::json_pointer("/a/n/99999999999999999999999")], alt_json::out_of_range&); + CHECK(j == before); + } + SECTION("operator/(std::size_t)") { // json_pointer::operator/=(std::size_t) must compile without string_t diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index b28ac4023..81c588544 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -29,9 +29,64 @@ using nlohmann::json; #include #include #include +#include #include #include +namespace +{ +// the non-const operator[] must throw for pointer and leave the value parsed +// from init unchanged (strong exception safety) +template +void check_strong_guarantee(const char* init, const std::string& pointer, const char* message) +{ + CAPTURE(init) + CAPTURE(pointer) + Json j = Json::parse(init); + const Json before = Json::parse(init); + const typename Json::json_pointer ptr(pointer); + CHECK_THROWS_WITH_AS(j[ptr], message, typename Json::exception&); + CHECK(j == before); +} + +// as above, for an index that fits into size_type, but not into an array +template +void check_strong_guarantee_length_error(const char* init, const std::string& pointer) +{ + CAPTURE(init) + CAPTURE(pointer) + Json j = Json::parse(init); + const Json before = Json::parse(init); + const typename Json::json_pointer ptr(pointer); + CHECK_THROWS_AS(j[ptr], std::length_error&); + CHECK(j == before); +} + +template +void check_strong_guarantee_cases() +{ + const char* const unresolved = "[json.exception.out_of_range.404] unresolved reference token '99999999999999999999999'"; + + // the first change converts a null value + check_strong_guarantee("null", "/99999999999999999999999", unresolved); + check_strong_guarantee("null", "/a/99999999999999999999999", unresolved); + check_strong_guarantee(R"({"a":null})", "/a/b/-/99999999999999999999999", unresolved); + // the first change inserts a key into an object + check_strong_guarantee("{}", "/a/99999999999999999999999", unresolved); + check_strong_guarantee(R"({"a":{"b":1}})", "/a/c/d/99999999999999999999999", unresolved); + // the first change pads or appends to an array + check_strong_guarantee(R"({"a":[1]})", "/a/5/99999999999999999999999", unresolved); + check_strong_guarantee(R"({"a":[1]})", "/a/-/x/1/99999999999999999999999", unresolved); + // an index that does not fit into size_type + check_strong_guarantee("null", "/x/18446744073709551615", + "[json.exception.out_of_range.410] array index 18446744073709551615 exceeds size_type"); + // an index that fits into size_type, but not into an array + const auto too_large = std::to_string((std::numeric_limits::max)() - 1); + check_strong_guarantee_length_error("null", "/" + too_large); + check_strong_guarantee_length_error(R"({"a":[[1]]})", "/a/0/3/" + too_large); +} +} // namespace + TEST_CASE("JSON pointers") { SECTION("errors") @@ -557,6 +612,23 @@ TEST_CASE("JSON pointers") CHECK_THROWS_WITH_AS(j.at("/01"_json_pointer), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&); } + + SECTION("a failing operator[] leaves the value unchanged") + { + check_strong_guarantee_cases(); + check_strong_guarantee_cases(); + } + + SECTION("a successful operator[] still creates the same values") + { + json j = {{"a", {1}}}; + j["/a/3/x/-/y"_json_pointer] = 1; + CHECK(j == json::parse(R"({"a":[1,null,null,{"x":[{"y":1}]}]})")); + j["/a/-"_json_pointer] = 2; + j["/a/0"_json_pointer] = 3; + j["/b"_json_pointer] = 4; + CHECK(j == json::parse(R"({"a":[3,null,null,{"x":[{"y":1}]},2],"b":4})")); + } } SECTION("flatten") diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index 084eb79fd..fae261ebf 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -1499,6 +1499,23 @@ TEST_CASE("regression tests 1") CHECK(order.dump() == R"({"a":2,"m":{"y":1,"b":2},"new_key":3})"); } + SECTION("operator[](json_pointer) when emplace() finds a key the lookup does not") + { + // a copied fifo_map restarts its timestamps, so a new key "k" gets + // the timestamp of "x": the lookup does not find "k", but emplace() + // finds "x" and inserts nothing; the pointer then continues with the + // member of "x", as basic_json::operator[] does + const my_json orig = my_json::parse(R"({"x":1})"); + const std::array pointers = {{"/k/0", "/k/a/b", "/k/-"}}; + for (const char* pointer : pointers) + { + CAPTURE(pointer) + my_json j = orig; + CHECK_THROWS_AS(j[my_json::json_pointer(pointer)], my_json::out_of_range&); + CHECK(j.dump() == R"({"x":1})"); + } + } + SECTION("issue #977 - Assigning between different json types") { foo_json lj = ns::foo{3}; From eb8f675bc9f28a3c9edc2708a4e5dfabb379edbc Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 17:16:08 +0100 Subject: [PATCH 24/63] docs: explain signed zero in the diff() round-trip exceptions (#5944) Spell out that numbers comparing equal produce no operation even if one is zero and the other negative zero, in diff.md and features/json_patch.md. Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json/diff.md | 7 ++++--- docs/mkdocs/docs/features/json_patch.md | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/diff.md b/docs/mkdocs/docs/api/basic_json/diff.md index 655261a3d..162882cdb 100644 --- a/docs/mkdocs/docs/api/basic_json/diff.md +++ b/docs/mkdocs/docs/api/basic_json/diff.md @@ -19,9 +19,10 @@ The equality holds up to [`operator==`](operator_eq.md), so it has the following - A value that is not equal to itself cannot round-trip. This applies to `NaN` (also nested in an array or object) and to [discarded](is_discarded.md) values: `diff` emits a `replace` operation for them. A `NaN` in the patch is serialized as `#!json null`, so a patch that was dumped and parsed again would set `#!json null`. -- Numbers that compare equal produce no operation, even if their types or signs differ: `diff(1, 1.0)`, - `diff(1u, 1)` (unsigned and signed integer) and `diff(0.0, -0.0)` all return `#!json []`. The patched value is then - the `source` and keeps its number type. +- Numbers that compare equal produce no operation, even if their types differ or if one is zero and the other is + negative zero: `diff(1, 1.0)` (integer and floating-point), `diff(1u, 1)` (unsigned and signed integer) and + `diff(0.0, -0.0)` (positive and negative zero) all return `#!json []`. The patched value is then the `source` and + keeps its number type and sign. ## Parameters diff --git a/docs/mkdocs/docs/features/json_patch.md b/docs/mkdocs/docs/features/json_patch.md index 3329c6cba..ea22b4e15 100644 --- a/docs/mkdocs/docs/features/json_patch.md +++ b/docs/mkdocs/docs/features/json_patch.md @@ -78,8 +78,9 @@ flowchart LR - `NaN` and [discarded](../api/basic_json/is_discarded.md) values are not equal to themselves, so the invariant cannot hold for them. `diff` emits a `replace` operation for such a value, and a `NaN` in a patch is serialized as `#!json null`. - - Numbers that compare equal produce no operation even if their types differ: `diff(1, 1.0)`, `diff(1u, 1)`, and - `diff(0.0, -0.0)` return `#!json []`, so the patched value keeps the number type of *source*. + - Numbers that compare equal produce no operation even if their types differ or if one is zero and the other is + negative zero: `diff(1, 1.0)`, `diff(1u, 1)`, and `diff(0.0, -0.0)` return `#!json []`, so the patched value + keeps the number type and sign of *source*. - A [binary value](binary_values.md) in a patch is serialized as an object (`#!json {"bytes":[...],"subtype":null}`), so a patch that was dumped and parsed again no longer holds a binary value. From 8617d9c94cc9f72ded869ac9c2e5cfda540a69da Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 17:25:18 +0100 Subject: [PATCH 25/63] Fix test lints that are red on develop (effc++, clang-tidy) (#5946) - unit-bson: initialize the SAX recorder's member (-Weffc++) - unit-json_patch: build the reference document independently instead of copying it (performance-unnecessary-copy-initialization) - unit-json_view_builder: use node_kind::link instead of an out-of-range enum cast (clang-analyzer-optin.core.EnumCastOutOfRange) Signed-off-by: Niels Lohmann --- tests/src/unit-bson.cpp | 2 +- tests/src/unit-json_patch.cpp | 11 ++++++++--- tests/src/unit-json_view_builder.cpp | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index b4756c9b5..09321e3d5 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1311,7 +1311,7 @@ TEST_CASE("BSON declared document size bounds the content") // records the events that a SAX parser receives struct recorder : nlohmann::json_sax { - std::string events; + std::string events {}; bool null() override { diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index b6c78532d..cc1b26a40 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -2178,8 +2178,13 @@ TEST_CASE("JSON patch - behavior documented in the API reference") SECTION("patch() has the strong guarantee") { - const json doc = R"({"a": [1, 2], "o": {"k": "v"}})"_json; - const json before = doc; + // independent values, so that the comparison below checks the untouched document + const auto make_doc = [] + { + return R"({"a": [1, 2], "o": {"k": "v"}})"_json; + }; + const json doc = make_doc(); + const json before = make_doc(); const std::vector failing = { @@ -2202,7 +2207,7 @@ TEST_CASE("JSON patch - behavior documented in the API reference") CHECK_THROWS_AS(doc.patch(patch), json::exception&); CHECK(doc == before); - json copy = doc; + json copy = make_doc(); CHECK_THROWS_AS(copy.patch(patch), json::exception&); CHECK(copy == before); } diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 1074f7018..4023c7848 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -592,7 +592,7 @@ TEST_CASE("json_view node integer bits") }) { CAPTURE(v) - n.kind = static_cast(0x5A); + n.kind = nlohmann::detail::view::node_kind::link; n.flags = 0xA5; n.extra = 0x1234; n.off = 0x89ABCDEFu; @@ -601,7 +601,7 @@ TEST_CASE("json_view node integer bits") CHECK(n.len == static_cast(v)); CHECK(n.next == static_cast(v >> 32)); // the other fields are untouched - CHECK(static_cast(n.kind) == 0x5A); + CHECK(n.kind == nlohmann::detail::view::node_kind::link); CHECK(n.flags == 0xA5); CHECK(n.extra == 0x1234); CHECK(n.off == 0x89ABCDEFu); From 84c5421644314a301ecb1312bbb4e1ff7e1a729d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Tue, 27 Oct 2026 20:31:53 +0100 Subject: [PATCH 26/63] Fix regression1 operator[](json_pointer) test under JSON_NO_THREAD_LOCAL (#5952) The test relies on a copied fifo_map restarting its timestamps. Copying a basic_json does that only on the recursive path; with JSON_NO_THREAD_LOCAL the copy is built member by member and keeps correct timestamps, so the premise was missing and the test failed (ci_test_no_thread_local, MinGW clang). Copy the object_t explicitly to create the state on every path. Signed-off-by: Niels Lohmann --- tests/src/unit-regression1.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index fae261ebf..b98f2b6f3 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -1504,13 +1504,16 @@ TEST_CASE("regression tests 1") // a copied fifo_map restarts its timestamps, so a new key "k" gets // the timestamp of "x": the lookup does not find "k", but emplace() // finds "x" and inserts nothing; the pointer then continues with the - // member of "x", as basic_json::operator[] does + // member of "x", as basic_json::operator[] does. The object_t is + // copied explicitly: copying the my_json would build the object + // member by member (with fresh timestamps) when built with + // JSON_NO_THREAD_LOCAL. const my_json orig = my_json::parse(R"({"x":1})"); const std::array pointers = {{"/k/0", "/k/a/b", "/k/-"}}; for (const char* pointer : pointers) { CAPTURE(pointer) - my_json j = orig; + my_json j(orig.get_ref()); CHECK_THROWS_AS(j[my_json::json_pointer(pointer)], my_json::out_of_range&); CHECK(j.dump() == R"({"x":1})"); } From da83aa92c88aa4bcacc3bc29cef59fec5d257543 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 28 Oct 2026 07:11:04 +0100 Subject: [PATCH 27/63] Reconcile -Weffc++ and readability-redundant-member-init in unit-bson.cpp (#5953) --- tests/src/unit-bson.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 09321e3d5..8e2d00cbf 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1311,6 +1311,8 @@ TEST_CASE("BSON declared document size bounds the content") // records the events that a SAX parser receives struct recorder : nlohmann::json_sax { + // GCC's -Weffc++ wants the member initialized in the member initialization list + // NOLINTNEXTLINE(readability-redundant-member-init) std::string events {}; bool null() override From 282079ba8206dc17c1fa2e3e4da4ce4cab7401f9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 28 Oct 2026 19:44:53 +0100 Subject: [PATCH 28/63] Fix clang-tidy findings in unit-json_view_from_json.cpp (#5954) * Fix clang-tidy findings in unit-json_view_from_json.cpp Silence misc-use-internal-linkage on the macro-generated ADL functions, reconcile -Weffc++ with readability-redundant-member-init, use auto for get() initializers, and use value() on the optional access. Signed-off-by: Niels Lohmann * Satisfy bugprone-unchecked-optional-access in unit-json_view_from_json.cpp clang-tidy 22 also flags value(); check has_value() in the same expression. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- tests/src/unit-json_view_from_json.cpp | 31 +++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index 4a776dd52..c1c8925d8 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -69,9 +69,12 @@ struct point struct shape { // initialized for GCC -Weffc++ + // NOLINTNEXTLINE(readability-redundant-member-init) std::string name{}; + // NOLINTNEXTLINE(readability-redundant-member-init) std::vector points{}; point center{}; + // NOLINTNEXTLINE(readability-redundant-member-init) std::map named{}; NLOHMANN_DEFINE_TYPE_INTRUSIVE(shape, name, points, center, named) }; @@ -82,13 +85,17 @@ struct with_defaults std::string b = "default"; std::vector c = {1, 2}; }; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(with_defaults, a, b, c) struct with_names { int value = 0; - std::string text{}; // initialized for GCC -Weffc++ + // initialized for GCC -Weffc++ + // NOLINTNEXTLINE(readability-redundant-member-init) + std::string text{}; }; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(with_names, "Value", value, "Text", text) struct empty @@ -102,6 +109,7 @@ enum class color green, blue }; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users NLOHMANN_JSON_SERIALIZE_ENUM(color, { {color::red, "red"}, @@ -114,6 +122,7 @@ enum class strict_color red, green }; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_color, { {strict_color::red, "red"}, @@ -154,6 +163,7 @@ struct derived_from_legacy : legacy_base { int extra = 0; }; +// NOLINTNEXTLINE(misc-use-internal-linkage): found by ADL, like the from_json() functions of users NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(derived_from_legacy, legacy_base, extra) // from_json() for json only: converted through materialize() @@ -301,18 +311,19 @@ TEST_CASE("json_view from_json") CHECK(same(v, j, "text")); CHECK(same(v, j, "nothing")); CHECK(same(v, j, "object")); - const std::valarray va = v["ints"].get>(); + const auto va = v["ints"].get>(); CHECK(va.size() == 4); CHECK(va[0] == 3); int c_array[2][2] = {{0, 0}, {0, 0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): the conversion to C arrays v["nested"][0].get_to(c_array[0]); CHECK(c_array[0][1] == 2); #ifdef JSON_HAS_CPP_17 - CHECK(v["points"][0].get>()->y == 2); + const auto first = v["points"][0].get>(); + CHECK((first.has_value() && first->y == 2)); CHECK(!v["nothing"].get>().has_value()); #endif // containers of views keep views of the elements - const std::vector views = v["points"].get>(); + const auto views = v["points"].get>(); CHECK(views.size() == 2); CHECK(views[1]["x"].get() == 3); CHECK(v["object"].get>().at("a").get() == 2); @@ -352,7 +363,7 @@ TEST_CASE("json_view from_json") { const json_document d = json_document::parse(R"({"name": "s", "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], "center": {"x": 5, "y": 6}, "named": {"a": {"x": 7, "y": 8}}})"); - const shape s = d.root().get(); + const auto s = d.root().get(); CHECK(s.name == "s"); CHECK(s.points.size() == 2); CHECK(s.points[1].y == 4); @@ -370,13 +381,13 @@ TEST_CASE("json_view from_json") SECTION("defaults and names") { const json_document d = json_document::parse(R"({"a": 5, "Value": 3, "Text": "t"})"); - const with_defaults w = d.root().get(); + const auto w = d.root().get(); CHECK(w.a == 5); CHECK(w.b == "default"); CHECK(w.c == std::vector {1, 2}); const json_document null_document = json_document::parse("null"); CHECK(null_document.root().get().a == 1); - const with_names n = d.root().get(); + const auto n = d.root().get(); CHECK(n.value == 3); CHECK(n.text == "t"); } @@ -401,13 +412,13 @@ TEST_CASE("json_view from_json") SECTION("derived types") { const json_document d = json_document::parse(R"({"id": 7, "c": "green", "extra": 3})"); - const derived x = d.root().get(); + const auto x = d.root().get(); CHECK(x.id == 7); CHECK(x.c == color::green); - const derived_with_defaults y = d.root().get(); + const auto y = d.root().get(); CHECK(y.id == 7); CHECK(y.label == "none"); - const derived_from_legacy z = d.root().get(); + const auto z = d.root().get(); CHECK(z.id == 7); CHECK(z.extra == 3); } From 2120927a7abebe2ad53736c4a27d563b132e62f4 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 28 Oct 2026 19:46:28 +0100 Subject: [PATCH 29/63] Fix the VS2015 build of unit-json_view_edit_values.cpp (#5947) MSVC 2015 treats two function templates that differ only in a decltype return type as a redefinition (C2995), which breaks all three Visual Studio 2015 jobs on AppVeyor. Overload on the view's mode in the parameter type instead; template argument deduction ignores the read-only-to-editable conversion, so each view still selects the overload of its own mode. Signed-off-by: Niels Lohmann --- tests/src/unit-json_view_edit_values.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/src/unit-json_view_edit_values.cpp b/tests/src/unit-json_view_edit_values.cpp index 39db8d0ba..881d957f8 100644 --- a/tests/src/unit-json_view_edit_values.cpp +++ b/tests/src/unit-json_view_edit_values.cpp @@ -54,14 +54,18 @@ int accepts_any(nlohmann::basic_json_view /*unused*/) return Mode == nlohmann::view_mode::editable ? 3 : 4; } -template -auto overload(View v) -> decltype(accepts_editable(v)) +// overloads that differ only in the mode; deduction ignores the conversion from +// read-only to editable views, so each view picks the overload of its own mode +// (two templates that differ only in a decltype return type are a redefinition +// for MSVC 2015) +template +int overload(nlohmann::basic_json_view v) { return accepts_editable(v); } -template -auto overload(View v) -> decltype(accepts_read_only(v)) +template +int overload(nlohmann::basic_json_view v) { return accepts_read_only(v); } From 091c10d4a5602e327c5513a1d0732e05178824df Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 28 Oct 2026 20:23:55 +0100 Subject: [PATCH 30/63] Fix self-aliasing in json_pointer::operator/= and patch_inplace (#5936) * Fix self-aliasing in json_pointer::operator/= and patch_inplace json_pointer::operator/=(const json_pointer&) passed iterators into its own token vector to insert() when a pointer was appended to itself (p /= p), which is undefined behavior; libstdc++ produced "/a/b//" for "/a/b". The pointer now appends a copy in that case. basic_json::patch_inplace iterated the patch while changing *this, so j.patch_inplace(j) read freed memory. It now applies a copy of the patch when the patch is the value itself. A patch stored inside the value remains undefined behavior and is documented as such; copying the patch unconditionally would double the time of a large patch. Signed-off-by: Niels Lohmann * Fix JSON_DIAGNOSTICS test crash and clang-tidy hit in patch_inplace self-aliasing The spare-capacity test reallocated non-empty elements through get_ref(), which leaves stale parent pointers under JSON_DIAGNOSTICS; reserve while the array is empty instead. Suppress performance-unnecessary-copy-initialization for the copy that is needed because *this is modified. Signed-off-by: Niels Lohmann * Detect a patch stored inside the patched value with JSON_DIAGNOSTICS patch_inplace applied a copy only when the patch was the value itself. With JSON_DIAGNOSTICS the parent pointers make it cheap to also detect a patch nested inside the value, so that case is handled the same way. Without diagnostics it stays documented undefined behavior. Signed-off-by: Niels Lohmann * Silence misc-const-correctness for patch_aliases_this Signed-off-by: Niels Lohmann * Fix clang-tidy findings in the operator/= self-append test Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../docs/api/basic_json/patch_inplace.md | 15 +++ .../docs/api/json_pointer/operator_slasheq.md | 6 +- include/nlohmann/detail/json_pointer.hpp | 7 + include/nlohmann/json.hpp | 19 +++ single_include/nlohmann/json.hpp | 26 ++++ tests/src/unit-json_patch.cpp | 123 ++++++++++++++++++ tests/src/unit-json_pointer.cpp | 40 ++++++ 7 files changed, 234 insertions(+), 2 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json/patch_inplace.md b/docs/mkdocs/docs/api/basic_json/patch_inplace.md index 67c6e8814..ff65e8dbd 100644 --- a/docs/mkdocs/docs/api/basic_json/patch_inplace.md +++ b/docs/mkdocs/docs/api/basic_json/patch_inplace.md @@ -69,6 +69,19 @@ Duplicate member names inside an operation object are resolved by the parser bef is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a `remove` operation. +The JSON patch may be the JSON value itself (`#!cpp j.patch_inplace(j)`): the operations are then read from a copy of +the patch made before the first operation is applied, so the result is the same as that of +`#!cpp j = j.patch(j)`, and the exception safety is the same as above. + +If [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) is enabled, the same holds for a JSON patch stored inside the +JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`): it is detected via the parent pointers. + +!!! danger "Undefined behavior" + + Without [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md), the JSON patch must not be a value stored inside the + JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`), as the operations may change or destroy it while it is + read. Pass a copy (`#!cpp j.patch_inplace(json(j["patch"]))`) or use [`patch`](patch.md) instead. + ## Examples ??? example "Example: apply a JSON patch in place" @@ -122,3 +135,5 @@ is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": - Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. +- Applying a JSON value as patch to itself (or, with `JSON_DIAGNOSTICS`, to a value stored inside it) no longer results + in undefined behavior in version 3.13.0. diff --git a/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md b/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md index e0d4560ec..2f2820d4f 100644 --- a/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md +++ b/docs/mkdocs/docs/api/json_pointer/operator_slasheq.md @@ -18,7 +18,8 @@ json_pointer& operator/=(std::size_t array_idx) ## Parameters `ptr` (in) -: JSON pointer to append +: JSON pointer to append; may be the JSON pointer itself (`#!cpp p /= p`), which then appends a copy of its + reference tokens `token` (in) : reference token to append @@ -69,6 +70,7 @@ json_pointer& operator/=(std::size_t array_idx) ## Version history -1. Added in version 3.6.0. +1. Added in version 3.6.0. Appending a JSON pointer to itself no longer results in undefined behavior in version + 3.13.0. 2. Added in version 3.6.0. Changed type of `token` to `string_t` in version 3.11.0. 3. Added in version 3.6.0. diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index eef4ce0a4..6df49fbc0 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -266,6 +266,13 @@ class json_pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ json_pointer& operator/=(const json_pointer& ptr) { + // insert() must not be given iterators into the vector it inserts + // into: append a copy when a pointer is appended to itself + if (JSON_HEDLEY_UNLIKELY(&ptr == this)) + { + return *this /= json_pointer(ptr); + } + reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index c2dddd55a..607fd1e5a 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -6950,6 +6950,25 @@ public: /// @sa https://json.nlohmann.me/api/basic_json/patch/ void patch_inplace(const basic_json& json_patch) { + // the operations below change *this while iterating json_patch: apply + // a copy when a value is applied as patch to itself or, with + // JSON_DIAGNOSTICS, to a value stored inside it + // NOLINTNEXTLINE(misc-const-correctness): only the JSON_DIAGNOSTICS build modifies it + bool patch_aliases_this = (&json_patch == this); +#if JSON_DIAGNOSTICS + for (const basic_json* p = json_patch.m_parent; !patch_aliases_this && p != nullptr; p = p->m_parent) + { + patch_aliases_this = (p == this); + } +#endif + if (JSON_HEDLEY_UNLIKELY(patch_aliases_this)) + { + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): *this is modified below + const basic_json json_patch_copy = json_patch; + patch_inplace(json_patch_copy); + return; + } + basic_json& result = *this; // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 085b55afb..0ebaebaf5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -24331,6 +24331,13 @@ class json_pointer /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ json_pointer& operator/=(const json_pointer& ptr) { + // insert() must not be given iterators into the vector it inserts + // into: append a copy when a pointer is appended to itself + if (JSON_HEDLEY_UNLIKELY(&ptr == this)) + { + return *this /= json_pointer(ptr); + } + reference_tokens.insert(reference_tokens.end(), ptr.reference_tokens.begin(), ptr.reference_tokens.end()); @@ -43701,6 +43708,25 @@ public: /// @sa https://json.nlohmann.me/api/basic_json/patch/ void patch_inplace(const basic_json& json_patch) { + // the operations below change *this while iterating json_patch: apply + // a copy when a value is applied as patch to itself or, with + // JSON_DIAGNOSTICS, to a value stored inside it + // NOLINTNEXTLINE(misc-const-correctness): only the JSON_DIAGNOSTICS build modifies it + bool patch_aliases_this = (&json_patch == this); +#if JSON_DIAGNOSTICS + for (const basic_json* p = json_patch.m_parent; !patch_aliases_this && p != nullptr; p = p->m_parent) + { + patch_aliases_this = (p == this); + } +#endif + if (JSON_HEDLEY_UNLIKELY(patch_aliases_this)) + { + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): *this is modified below + const basic_json json_patch_copy = json_patch; + patch_inplace(json_patch_copy); + return; + } + basic_json& result = *this; // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; diff --git a/tests/src/unit-json_patch.cpp b/tests/src/unit-json_patch.cpp index cc1b26a40..83df15350 100644 --- a/tests/src/unit-json_patch.cpp +++ b/tests/src/unit-json_patch.cpp @@ -826,6 +826,129 @@ TEST_CASE("JSON patch") CHECK(doc.at("foo") == "bar"); } #endif // !defined(JSON_NOEXCEPTION) + + SECTION("applying a value as patch to itself") + { + // a JSON patch is an array, so an array of operations can be + // applied to itself; the operations are read from the patch as + // it was before the first operation, as patch() does it + const json copy_op = {{"op", "copy"}, {"from", "/0"}, {"path", "/-"}}; + json copy_ops = json::array(); + for (int i = 0; i < 5; ++i) + { + copy_ops.push_back(copy_op); + } + + json expected_copies = copy_ops; + for (int i = 0; i < 5; ++i) + { + expected_copies.push_back(copy_op); + } + + SECTION("copy operations without spare capacity") + { + // a copy has exactly as much capacity as elements + json doc = copy_ops; + CHECK(doc.patch(doc) == expected_copies); + + doc.patch_inplace(doc); + CHECK(doc == expected_copies); + } + +#if JSON_DIAGNOSTICS + SECTION("patch stored inside the patched value") + { + // the parent pointers of JSON_DIAGNOSTICS make the aliasing detectable + const json op = {{"op", "replace"}, {"path", "/patch"}, {"value", 1}}; + json doc = {{"a", 1}, {"patch", json::array({op})}}; + const json expected = doc.patch(doc["patch"]); + + doc.patch_inplace(doc["patch"]); + CHECK(doc == expected); + } +#endif + + SECTION("copy operations with spare capacity") + { + // reserve while the array is empty: reallocating non-empty + // elements behind the library's back leaves stale parent + // pointers with JSON_DIAGNOSTICS + json doc = json::array(); + doc.get_ref().reserve(100); + for (int i = 0; i < 5; ++i) + { + doc.push_back(copy_op); + } + CHECK(doc.patch(doc) == expected_copies); + + doc.patch_inplace(doc); + CHECK(doc == expected_copies); + } + + SECTION("remove operations") + { + json doc = R"([ + {"op": "remove", "path": "/0"}, + {"op": "remove", "path": "/0"}, + {"op": "remove", "path": "/0"} + ])"_json; + CHECK(doc.patch(doc) == json::array()); + + doc.patch_inplace(doc); + CHECK(doc == json::array()); + } + + SECTION("operations that read a changed element") + { + // the second operation is still a "test" operation, although + // the first one replaced it in the document + json doc = R"([ + {"op": "replace", "path": "/1", "value": 1}, + {"op": "test", "path": "/1", "value": 1}, + {"op": "add", "path": "/-", "value": 2} + ])"_json; + const json expected = R"([ + {"op": "replace", "path": "/1", "value": 1}, + 1, + {"op": "add", "path": "/-", "value": 2}, + 2 + ])"_json; + CHECK(doc.patch(doc) == expected); + + doc.patch_inplace(doc); + CHECK(doc == expected); + } + + SECTION("replacing the root") + { + json doc = R"([ + {"op": "add", "path": "", "value": [1, 2]}, + {"op": "add", "path": "/-", "value": 3}, + {"op": "replace", "path": "", "value": {"a": 4}} + ])"_json; + const json expected = {{"a", 4}}; + CHECK(doc.patch(doc) == expected); + + doc.patch_inplace(doc); + CHECK(doc == expected); + } + +#if !defined(JSON_NOEXCEPTION) + SECTION("failing operation") + { + // the operations before the failing one remain applied + json doc = R"([ + {"op": "add", "path": "/-", "value": 1}, + {"op": "test", "path": "/0/op", "value": "remove"} + ])"_json; + CHECK_THROWS_AS(doc.patch(doc), json::other_error&); + + CHECK_THROWS_AS(doc.patch_inplace(doc), json::other_error&); + CHECK(doc.size() == 3); + CHECK(doc.back() == 1); + } +#endif // !defined(JSON_NOEXCEPTION) + } } SECTION("errors") diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 81c588544..0e225b86e 100644 --- a/tests/src/unit-json_pointer.cpp +++ b/tests/src/unit-json_pointer.cpp @@ -917,6 +917,46 @@ TEST_CASE("JSON pointers") CHECK(ptr.to_string() == "/object/~1"); } + SECTION("appending a pointer to itself") + { + // operator/= must not pass iterators into its own token vector to + // insert(); each pointer is appended to itself and must yield the + // pointer whose string representation is the original one twice + const std::vector pointers = + { + "", + "/", + "/a", + "/a/b", + "/foo/0/-", + "/a~1b/~0c/~01", + "/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17" + }; + + for (const auto& s : pointers) + { + CAPTURE(s) + json::json_pointer ptr(s); + const json::json_pointer expected(s + s); + + const json::json_pointer& ret = (ptr /= ptr); + CHECK(&ret == &ptr); + CHECK(ptr == expected); + CHECK(ptr.to_string() == s + s); + + // appending to itself again doubles the pointer once more + ptr /= ptr; + std::string quadruple = s + s; + quadruple += quadruple; + CHECK(ptr.to_string() == quadruple); + + // operator/ appends to a copy and leaves the operand unchanged + const json::json_pointer original(s); + CHECK((original / original) == expected); + CHECK(original.to_string() == s); + } + } + SECTION("equality comparison") { std::string ptr_string{"/foo/bar"}; From 54f6b3ef06c6f600897aa26bb44490d570e975be Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 28 Oct 2026 20:24:49 +0100 Subject: [PATCH 31/63] json_view: source positions in materialize() with JSON_DIAGNOSTIC_POSITIONS (#5945) * json_view: source positions in materialize() with JSON_DIAGNOSTIC_POSITIONS With JSON_DIAGNOSTIC_POSITIONS, materialize() now gives every value the start_pos()/end_pos() that basic_json::parse() would give it, so exceptions about materialized values name the same bytes (#5864). - json_sax_dom_parser takes the lexer type as a defaulted template parameter; materialize() passes position_source, which reports the positions it derives from the index. diagnostic_positions treats npos as "no position" and keeps ranges all-or-nothing. - Strings with escapes keep the offset of their opening quote plus one in `next` (only with the macro); materialize() scans from there to the closing quote. The end of an array or object is found by scanning from the end of its last element over whitespace, comments and a trailing comma. - In editable documents, values written by edits, arrays and objects whose elements changed, and containers whose last element has no position get none. Images keep the flag is_new, and load() checks that `next` of a string is within the text. Nothing changes on the parse path without the macro. The view's own exceptions still carry no byte range, so the tests that compare them with those of materialized values skip it under the macro, as they skip the path under JSON_DIAGNOSTICS. Signed-off-by: Niels Lohmann * Indent comment in unit-json_view_from_json.cpp Signed-off-by: Niels Lohmann * Address review: clearer position code and docs - position_source gets clear(); set_scalar() returns early for new values, switches on node_kind, and uses no local copies; the one position_source per materialize() call is documented - no npos + 1 for containers without a position; a comment on why the end of a container is reported as its closing bracket - less duplication under #if in materialize() and the builder - diagnostic_positions sets npos explicitly - docs: positions of basic_json values after changes, positions in images depend on how the document was parsed, when next is set, and the cost of positions while parsing Signed-off-by: Niels Lohmann * Share the comment scanners of the view builder with materialize() skip_line_comment() and find_block_comment_end() in scan.hpp are used by the builder's comment() and by position_source::container_end(), which had its own copy. Signed-off-by: Niels Lohmann * Address review: if/else for container positions, clearer next field - the opening bracket of a container sets positions with an if/else instead of a ternary, and the frame takes its start from positions - the closing bracket of a container is found in nested if/else; the parent's last_end is taken from positions, so it is npos whenever no range is set - architecture.md: the next field reads "or 0 if it was not; all other values: 0" instead of "else 0; otherwise 0" Signed-off-by: Niels Lohmann * Fix GCC unused-result and clang-tidy raw-string findings in unit-json_view_positions.cpp Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../docs/api/basic_json_view/materialize.md | 24 +- .../api/macros/json_diagnostic_positions.md | 1 + docs/mkdocs/docs/features/json_view.md | 8 +- docs/mkdocs/docs/home/architecture.md | 10 +- include/nlohmann/detail/input/json_sax.hpp | 25 +- include/nlohmann/detail/input/parser.hpp | 4 +- include/nlohmann/detail/view/builder.hpp | 34 +- include/nlohmann/detail/view/image.hpp | 30 +- include/nlohmann/detail/view/materialize.hpp | 268 ++++++++++++- include/nlohmann/detail/view/node.hpp | 2 +- include/nlohmann/detail/view/scan.hpp | 26 ++ include/nlohmann/json.hpp | 2 +- single_include/nlohmann/json.hpp | 31 +- single_include/nlohmann/json_view.hpp | 361 ++++++++++++++++-- tests/src/unit-json_view.cpp | 32 +- tests/src/unit-json_view_from_json.cpp | 10 +- tests/src/unit-json_view_positions.cpp | 346 +++++++++++++++++ 17 files changed, 1101 insertions(+), 113 deletions(-) create mode 100644 tests/src/unit-json_view_positions.cpp diff --git a/docs/mkdocs/docs/api/basic_json_view/materialize.md b/docs/mkdocs/docs/api/basic_json_view/materialize.md index f29a25cda..982b1597f 100644 --- a/docs/mkdocs/docs/api/basic_json_view/materialize.md +++ b/docs/mkdocs/docs/api/basic_json_view/materialize.md @@ -32,9 +32,24 @@ uses, so the result matches it exactly — including, for an object, that a repe last value. The replay is iterative, so it is not limited by the call stack the way a naive recursive conversion would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself. -Unlike parsing with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values -produced by `materialize()` do not carry source positions. The index has the start offset of every value, but not the -end of arrays, objects, and escaped strings, which the positions also need. +With [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values get the source +positions [`BasicJsonType::parse()`](../basic_json/parse.md) would give them, also for the subtree of a view (the +positions are those in the document's whole source text), and exceptions about them name the same bytes. For values of +an [editable document](../json_editable_document.md), positions are all-or-nothing, and [`start_pos()`](../basic_json/start_pos.md) +and [`end_pos()`](../basic_json/end_pos.md) return `std::string::npos` for + +- a value written by an edit (a copy of an array or object of the document is also new; copies of its scalars keep + their positions), +- an array or object whose elements were changed (its elements keep theirs), and +- an array or object whose last element has no positions (its end is found from that element). + +This differs from a `basic_json` value, whose positions are not updated when it is changed: they stay as they were +parsed, even if they no longer match the value (see [`start_pos()`](../basic_json/start_pos.md)). + +Documents [loaded](../basic_json_document/load.md) from an image have the positions of the saved document. Its strings +with escapes have positions only if the document was parsed with `JSON_DIAGNOSTIC_POSITIONS`: the position of their +opening quote is recorded while parsing, and `save()` and `load()` keep it whether or not they are compiled with the +macro. Calling `materialize()` on the same view repeatedly builds a new, independent `BasicJsonType` value each time; it never caches the result. @@ -62,8 +77,7 @@ never caches the result. - [`root`](../basic_json_document/root.md) - the view of a document's root value - [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text -- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values (not - produced by `materialize()`) +- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values - [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - when to build a basic_json value from a view ## Version history diff --git a/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md b/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md index 56886ba86..8a772791a 100644 --- a/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md +++ b/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md @@ -113,6 +113,7 @@ When the macro is not defined, the library will define it to its default value. - [:simple-cmake: JSON_Diagnostic_Positions](../../integration/cmake.md#json_diagnostic_positions) - CMake option to control the macro - [`JSON_DIAGNOSTICS`](json_diagnostics.md) - macro to control extended diagnostics +- [`materialize()`](../basic_json_view/materialize.md) - values of a JSON view, with the same positions - [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input ## Version history diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index f2660f539..09779dba2 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -149,10 +149,12 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile. end must still fit. - **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream is read to its end into a buffer the document owns, and parsing starts after that. -- **No source positions on `materialize()`.** Even with +- **Fewer source positions after edits.** With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled, - [`materialize()`](../api/basic_json_view/materialize.md) does not set them. The index has the start offset of every - value, but not the end of arrays, objects, and escaped strings, which the positions also need. + [`materialize()`](../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`. + In an [editable document](../api/json_editable_document.md), a value written by an edit has none, and neither has + an array or object whose elements changed or whose last element has none; a `basic_json` value keeps its positions + when it is changed. See [`materialize()`](../api/basic_json_view/materialize.md#notes). - **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/ [`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which diff --git a/docs/mkdocs/docs/home/architecture.md b/docs/mkdocs/docs/home/architecture.md index 82a660e3a..1b0d84dd7 100644 --- a/docs/mkdocs/docs/home/architecture.md +++ b/docs/mkdocs/docs/home/architecture.md @@ -203,7 +203,7 @@ packet-beta | 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 | | 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object | | 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements | -| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself | +| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself; strings with escapes: the position of the opening quote in the source plus one if the document was parsed with [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), or 0 if it was not; all other values: 0 |

    Fields of a node, by their byte offset within the 16-byte node. Integers use bytes 8-15 differently, see below.

    @@ -218,6 +218,12 @@ packet-beta step from element to element this way and skip whole subtrees in constant time. - **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below 2^32 for positions one scanner step past the end of the text; `out_of_range.416`). +- **Source positions** ([`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md)) are derived by + [`materialize()`](../api/basic_json_view/materialize.md) and cost almost nothing while parsing: the only work is + storing the offset of the opening quote of a string with escapes in its `next`, which is otherwise unused. A + scalar's range follows from `off` and its length, a string with escapes is scanned from its opening quote (`next`) to the closing one, + and the end of an array or object is found by scanning from the end of its last element over whitespace, comments, + and a trailing comma. `materialize()` hands these positions to the SAX builder of `parse()` in place of its lexer. - **Large objects** (128 members or more) get a hash index for their keys after parsing ([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)): an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does @@ -277,7 +283,7 @@ Images ([`save`](../api/basic_json_document/save.md) and [`load`](../api/basic_j the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load` -rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`, +rebuilds the indexes. The flag *new* is kept, as these values have no position in the text. So a change of the node layout is a change of the image format: it must raise `image_version`, and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them. ## Input adapters diff --git a/include/nlohmann/detail/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 04201efbe..0ceec61b7 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -230,7 +230,10 @@ struct diagnostic_positions { // Lexer has read the first character of the container, so // subtract 1 from the position to get the correct start position. - v.start_position = lexer->get_chars_read_total() - 1; + // (npos: a value without a source position, see materialize() of + // json_view) + const std::size_t read = lexer->get_chars_read_total(); + v.start_position = read == std::string::npos ? std::string::npos : read - 1; } } @@ -245,6 +248,12 @@ struct diagnostic_positions { // Lexer's position is past the closing brace or bracket, so set that as the end position. v.end_position = lexer->get_chars_read_total(); + if (v.end_position == std::string::npos) + { + // a range has both ends or none (exception::diagnostics() + // prints only complete ones) + v.start_position = std::string::npos; + } } } @@ -261,6 +270,12 @@ struct diagnostic_positions // The start position will be set below based on the length of the string representation // of the value. v.end_position = lexer->get_chars_read_total(); + if (v.end_position == std::string::npos) + { + // a value without a source position + v.start_position = std::string::npos; + return; + } switch (v.type()) { @@ -473,8 +488,12 @@ read up to it. @tparam Collect whether to collect the values of containers of unknown size on a stack (only for a caller that does not use the value after a parse error) +@tparam LexerType the type of the lexer that reports the source positions + (JSON_DIAGNOSTIC_POSITIONS); only diagnostic_positions uses + it, so any type with its three functions works (as + materialize() of json_view uses it) */ -template +template> class json_sax_dom_parser : public json_sax_dom_error_state { public: @@ -483,7 +502,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; - using lexer_t = lexer; + using lexer_t = LexerType; /*! @param[in,out] r reference to a JSON value that is manipulated while diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index 665052d73..5bf8f1b6b 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -56,8 +56,8 @@ using parser_callback_t = template struct sax_ignores_number_text : std::false_type {}; -template -struct sax_ignores_number_text> : std::true_type {}; +template +struct sax_ignores_number_text> : std::true_type {}; template struct sax_ignores_number_text> : std::true_type {}; diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index a0446a1e1..89b5af2ab 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -173,31 +173,16 @@ class builder } if (p[1] == '/') { - p += 2; - // (as in parse(), a null byte is the end of the input, so it is - // left for the caller to see) - while (p != m_end && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0)) - { - ++p; - } - return p; + return skip_line_comment(p + 2, m_end); } if (p[1] == '*') { - p += 2; - for (;;) + p = find_block_comment_end(p + 2, m_end); + if (p == m_end || *p != '*') { - if (p == m_end || (NulIsEnd && *p == 0)) - { - return fail_at(error_code::comment_unterminated, p); - } - if (*p == '*' && p + 1 != m_end && p[1] == '/') - { - p += 2; - return p; - } - ++p; + return fail_at(error_code::comment_unterminated, p); } + return p + 2; } ++p; return fail_at(error_code::comment_start, p); @@ -1193,7 +1178,14 @@ indent_done: return false; } p = r.p; - push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len); +#if JSON_DIAGNOSTIC_POSITIONS + // the decoded string is in the arena: next keeps the source offset + // of the opening quote plus one (0: unknown) for materialize() + const std::uint64_t second = static_cast(r.len) | (static_cast(s - b) << 32); +#else + const std::uint64_t second = r.len; +#endif + push_node(node_kind::string, node_flags::escaped, 0, r.start, second); return true; } }; diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index 637350949..545e04a6a 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -80,7 +80,8 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u; /// Copy the current structure of an edited document into nodes in document /// order, as the parser would have written them. Text written by edits is /// appended to text_tail (number tokens) and arena_tail (strings); floats that -/// are not finite become null, as dump() writes them. +/// are not finite become null, as dump() writes them. Nodes keep the flag +/// is_new: they have no source position (JSON_DIAGNOSTIC_POSITIONS). inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector& out, std::string& text_tail, std::string& arena_tail) { struct frame @@ -103,16 +104,18 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v arena_tail.append(d.str(s), s.len); r.flags = node_flags::escaped; } + r.flags = static_cast(r.flags | (s.flags & node_flags::is_new)); return r; }; const auto copy_node = [&](const node * v) { node r = *v; + const auto is_new = static_cast(v->flags & node_flags::is_new); switch (static_cast(v->kind)) { case value_t::object: case value_t::array: - r.flags = 0; + r.flags = is_new; r.extra = 0; r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.len = 0; // counted below @@ -129,7 +132,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v r.off = static_cast(d.size + text_tail.size()); text_tail.append(d.str(*v), number_length(*v)); } - r.flags = 0; + r.flags = is_new; break; case value_t::number_float: if ((v->flags & node_flags::storage) == node_flags::edited) @@ -139,22 +142,23 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v { r = node{}; // nan and infinity: null, as dump() writes them r.kind = node_kind::null; + r.flags = is_new; break; } r.off = static_cast(d.size + text_tail.size()); text_tail.append(t, v->len); r.extra = 0xFFFFu; // the digit layout is not recorded } - r.flags = 0; + r.flags = is_new; break; case value_t::boolean: - r.flags = static_cast(v->flags & node_flags::is_true); + r.flags = static_cast(v->flags & (node_flags::is_true | node_flags::is_new)); break; case value_t::null: case value_t::binary: case value_t::discarded: default: - r.flags = 0; + r.flags = is_new; break; } out.push_back(r); @@ -515,13 +519,15 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha std::vector floats; const auto check_string = [&](const node & n) -> bool { - if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0) + if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0) { return false; } const bool decoded = (n.flags & node_flags::escaped) != 0; const std::size_t limit = decoded ? arena_size : text_size; - if (n.off > limit || n.len > limit - n.off) + // (next: the source offset of a decoded string's opening quote plus + // one, or 0; see node) + if (n.off > limit || n.len > limit - n.off || n.next > text_size) { return false; } @@ -593,13 +599,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { case value_t::null: // (the offset of a literal is read to size the output of dump()) - if (n.flags != 0 || n.extra != 0 || n.off > text_size) + if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size) { return false; } break; case value_t::boolean: - if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size) + if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size) { return false; } @@ -613,7 +619,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: - if (n.flags != 0 || !number_in_bounds(n)) + if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n)) { return false; } @@ -635,7 +641,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha case value_t::object: { const std::size_t limit = stack.empty() ? count : stack.back().end; - if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size) + if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size) { return false; } diff --git a/include/nlohmann/detail/view/materialize.hpp b/include/nlohmann/detail/view/materialize.hpp index 1bab0e896..1dcd1940c 100644 --- a/include/nlohmann/detail/view/materialize.hpp +++ b/include/nlohmann/detail/view/materialize.hpp @@ -19,6 +19,7 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -26,6 +27,189 @@ namespace detail namespace view { +#if JSON_DIAGNOSTIC_POSITIONS +/*! +@brief the source positions of the values materialize() replays + +Stands in for the lexer of json_sax_dom_parser, which reads the positions of +the values it stores from these three functions (diagnostic_positions). npos +is a value without a source position. One position_source serves a whole +materialize() call: before each value is handed to the handler, the range of +that value is set (or cleared), overwriting that of the value before. +*/ +struct position_source +{ + /// the token, of which diagnostic_positions only reads the length + struct token + { + std::size_t length; + std::size_t size() const noexcept + { + return length; + } + }; + + std::size_t start = std::string::npos; ///< the first byte of the next value + std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket) + + std::size_t get_chars_read_total() const noexcept + { + return end; + } + std::size_t get_token_start_position() const noexcept + { + return start; + } + token get_string() const noexcept + { + return token{end - start}; + } + + /// the range [first, last), or none if either is npos + void set(std::size_t first, std::size_t last) noexcept + { + if (first == std::string::npos || last == std::string::npos) + { + clear(); + return; + } + start = first; + end = last; + } + + /// no range: the next value has no source position + void clear() noexcept + { + start = std::string::npos; + end = std::string::npos; + } + + /// one past the closing quote of the string whose opening quote is at + /// src[quote], or npos (the scan is bounded by size, as images can be + /// loaded without a check) + static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept + { + for (std::size_t i = quote + 1; i < size; ++i) + { + if (src[i] == '\\') + { + ++i; + } + else if (src[i] == '"') + { + return i + 1; + } + } + return std::string::npos; + } + + /// one past the closing bracket of the array or object whose last element + /// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos; + /// in between, there can only be whitespace, comments, and a trailing comma + /// (the source was validated when it was parsed) + static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept + { + const auto* const b = reinterpret_cast(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it + const unsigned char* const e = b + size; + while (pos < size) + { + switch (src[pos]) + { + case ' ': + case '\t': + case '\n': + case '\r': + case ',': + ++pos; + break; + case ']': + case '}': + return pos + 1; + case '/': + if (pos + 1 < size && src[pos + 1] == '/') + { + pos = static_cast(skip_line_comment(b + pos + 2, e) - b); + break; + } + if (pos + 1 < size && src[pos + 1] == '*') + { + // (unterminated: pos is size, which ends the loop) + pos = static_cast(find_block_comment_end(b + pos + 2, e) - b) + 2; + break; + } + return std::string::npos; + default: + return std::string::npos; + } + } + return std::string::npos; + } + + /// the range of the scalar n of document d + void set_scalar(const document_data& d, const node& n) noexcept + { + if ((n.flags & node_flags::is_new) != 0) + { + clear(); + return; + } + std::size_t first = std::string::npos; + std::size_t last = std::string::npos; + switch (n.kind) + { + case node_kind::string: + if ((n.flags & node_flags::storage) == 0) + { + // off is the first byte after the opening quote + if (n.off != 0) + { + first = n.off - 1; + last = n.off + std::size_t{n.len} + 1; + } + } + else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0) + { + // next is the offset of the opening quote plus one + first = n.next - 1; + last = string_end(d.base[0], d.size, first); + } + break; + case node_kind::number_integer: + case node_kind::number_unsigned: + case node_kind::number_float: + if ((n.flags & node_flags::storage) == 0) + { + first = n.off; + last = n.off + number_length(n); + } + break; + case node_kind::boolean: + first = n.off; + last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u); + break; + case node_kind::null: + first = n.off; + last = n.off + 4u; + break; + case node_kind::object: + case node_kind::array: + case node_kind::link: + default: + break; + } + // (a crafted image can point past the text) + if (last == std::string::npos || last > d.size) + { + clear(); + } + else + { + set(first, last); + } + } +}; +#endif + /*! @brief the basic_json value of the subtree at n @@ -33,14 +217,24 @@ The subtree is replayed into the SAX handler that parse() uses to build its values, so the result is the value parse() would produce: duplicate keys keep the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is iterative, so the nesting depth is limited by memory only, as for parse(). -Without a lexer the handler records no source positions -(JSON_DIAGNOSTIC_POSITIONS). + +With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with +parse(): position_source stands in for the lexer. Values written by edits have +none (npos), and neither has an array or object whose elements changed, or +whose last element has none (the end of its closing bracket is found by +scanning from the end of the last element). */ template BasicJsonType materialize(const document_data& d, const node* n) { using string_t = typename BasicJsonType::string_t; - using sax_t = json_sax_dom_parser, true>; + using input_t = iterator_input_adapter; +#if JSON_DIAGNOSTIC_POSITIONS + using lexer_t = position_source; +#else + using lexer_t = lexer; // (the default: unused, as no lexer is passed) +#endif + using sax_t = json_sax_dom_parser; using nav = navigation; // the open containers: their next element (or key of the next member), @@ -52,9 +246,15 @@ BasicJsonType materialize(const document_data& d, const node* n) const node* pos; const node* end; bool object; +#if JSON_DIAGNOSTIC_POSITIONS + std::size_t start; ///< the opening bracket (npos: unknown) + std::size_t last_end; ///< the end of the last element read (npos: unknown) +#endif }; +#if !JSON_DIAGNOSTIC_POSITIONS // a scalar needs neither the handler nor the stack of open containers + // (with positions, it goes through the handler, which sets them) switch (static_cast(n->kind)) { case value_t::string: @@ -76,10 +276,16 @@ BasicJsonType materialize(const document_data& d, const node* n) default: break; } +#endif BasicJsonType result; +#if JSON_DIAGNOSTIC_POSITIONS + position_source positions; + sax_t sax(result, true, &positions); +#else sax_t sax(result, true); - const string_t no_token{}; +#endif + const string_t no_token {}; // (shallow subtrees on the stack: a call for a small value then allocates only for the value) std::array shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open std::vector deep{}; @@ -94,6 +300,16 @@ BasicJsonType materialize(const document_data& d, const node* n) std::size_t depth = 0; for (;;) { +#if JSON_DIAGNOSTIC_POSITIONS + if (!is_container(*n)) + { + positions.set_scalar(d, *n); + if (depth != 0) + { + open[depth - 1].last_end = positions.end; + } + } +#endif // false positive: n comes from nav::value(), which never returns null for a valid index // @infer-ignore NULLPTR_DEREFERENCE switch (static_cast(n->kind)) @@ -102,6 +318,17 @@ BasicJsonType materialize(const document_data& d, const node* n) case value_t::array: { const bool object = n->kind == node_kind::object; +#if JSON_DIAGNOSTIC_POSITIONS + // (moved containers are also new) + if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size) + { + positions.clear(); + } + else + { + positions.set(std::size_t{n->off}, std::size_t{n->off} + 1); + } +#endif if (object) { sax.start_object(n->len); @@ -118,7 +345,13 @@ BasicJsonType materialize(const document_data& d, const node* n) open = deep.data(); capacity = deep.size(); } - open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; + // (with positions: until an element is read, the container's + // own bracket is the last thing read) + open[depth++] = frame{nav::first(d, n), nav::end(d, n), object +#if JSON_DIAGNOSTIC_POSITIONS + , positions.start, positions.end +#endif + }; break; } case value_t::string: @@ -159,6 +392,31 @@ BasicJsonType materialize(const document_data& d, const node* n) frame& f = open[depth - 1]; if (f.pos == f.end) { +#if JSON_DIAGNOSTIC_POSITIONS + if (f.start == std::string::npos || f.last_end == std::string::npos) + { + positions.clear(); + } + else + { + const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end); + if (end == std::string::npos) + { + positions.clear(); + } + else + { + // the closing bracket, as the lexer reports it (end_object() + // and end_array() only read its end; the start of the + // container was set when it was opened) + positions.set(end - 1, end); + } + } + if (depth > 1) + { + open[depth - 2].last_end = positions.end; + } +#endif if (f.object) { sax.end_object(); diff --git a/include/nlohmann/detail/view/node.hpp b/include/nlohmann/detail/view/node.hpp index 8987a8860..e00ef9eb6 100644 --- a/include/nlohmann/detail/view/node.hpp +++ b/include/nlohmann/detail/view/node.hpp @@ -69,7 +69,7 @@ struct node std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count - std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence) + std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence); escaped string: source offset of the opening quote plus one (JSON_DIAGNOSTIC_POSITIONS; 0: unknown) }; static_assert(sizeof(node) == 16, "node must stay 16 bytes"); diff --git a/include/nlohmann/detail/view/scan.hpp b/include/nlohmann/detail/view/scan.hpp index e230afbdb..b8d862203 100644 --- a/include/nlohmann/detail/view/scan.hpp +++ b/include/nlohmann/detail/view/scan.hpp @@ -172,6 +172,32 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char return p; } +/// advance over the text of a line comment (after its "//") to the line +/// break or the end (with NulIsEnd also a null byte, which is left for the +/// caller to see, as in parse()) +template +inline const unsigned char* skip_line_comment(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0)) + { + ++p; + } + return p; +} + +/// the "*/" that ends a block comment whose text starts at p (after its "/*"); +/// if it is unterminated, the end (with NulIsEnd also a null byte), which is +/// where the caller reports the error +template +inline const unsigned char* find_block_comment_end(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && !(NulIsEnd && *p == 0) && !(*p == '*' && p + 1 != e && p[1] == '/')) + { + ++p; + } + return p; +} + /// powers of ten up to 10^19 as integers inline std::uint64_t int_pow10(unsigned k) noexcept { diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 607fd1e5a..4d4f0a1cd 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -159,7 +159,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; - template + template friend class ::nlohmann::detail::json_sax_dom_parser; template friend class ::nlohmann::detail::json_sax_dom_binary_parser; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0ebaebaf5..98d6045fa 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -15629,7 +15629,10 @@ struct diagnostic_positions { // Lexer has read the first character of the container, so // subtract 1 from the position to get the correct start position. - v.start_position = lexer->get_chars_read_total() - 1; + // (npos: a value without a source position, see materialize() of + // json_view) + const std::size_t read = lexer->get_chars_read_total(); + v.start_position = read == std::string::npos ? std::string::npos : read - 1; } } @@ -15644,6 +15647,12 @@ struct diagnostic_positions { // Lexer's position is past the closing brace or bracket, so set that as the end position. v.end_position = lexer->get_chars_read_total(); + if (v.end_position == std::string::npos) + { + // a range has both ends or none (exception::diagnostics() + // prints only complete ones) + v.start_position = std::string::npos; + } } } @@ -15660,6 +15669,12 @@ struct diagnostic_positions // The start position will be set below based on the length of the string representation // of the value. v.end_position = lexer->get_chars_read_total(); + if (v.end_position == std::string::npos) + { + // a value without a source position + v.start_position = std::string::npos; + return; + } switch (v.type()) { @@ -15872,8 +15887,12 @@ read up to it. @tparam Collect whether to collect the values of containers of unknown size on a stack (only for a caller that does not use the value after a parse error) +@tparam LexerType the type of the lexer that reports the source positions + (JSON_DIAGNOSTIC_POSITIONS); only diagnostic_positions uses + it, so any type with its three functions works (as + materialize() of json_view uses it) */ -template +template> class json_sax_dom_parser : public json_sax_dom_error_state { public: @@ -15882,7 +15901,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; using binary_t = typename BasicJsonType::binary_t; - using lexer_t = lexer; + using lexer_t = LexerType; /*! @param[in,out] r reference to a JSON value that is manipulated while @@ -22515,8 +22534,8 @@ using parser_callback_t = template struct sax_ignores_number_text : std::false_type {}; -template -struct sax_ignores_number_text> : std::true_type {}; +template +struct sax_ignores_number_text> : std::true_type {}; template struct sax_ignores_number_text> : std::true_type {}; @@ -36917,7 +36936,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; - template + template friend class ::nlohmann::detail::json_sax_dom_parser; template friend class ::nlohmann::detail::json_sax_dom_binary_parser; diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 73323e38b..90593b2d1 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -269,7 +269,7 @@ struct node std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count - std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence) + std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence); escaped string: source offset of the opening quote plus one (JSON_DIAGNOSTIC_POSITIONS; 0: unknown) }; static_assert(sizeof(node) == 16, "node must stay 16 bytes"); @@ -1185,6 +1185,32 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char return p; } +/// advance over the text of a line comment (after its "//") to the line +/// break or the end (with NulIsEnd also a null byte, which is left for the +/// caller to see, as in parse()) +template +inline const unsigned char* skip_line_comment(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0)) + { + ++p; + } + return p; +} + +/// the "*/" that ends a block comment whose text starts at p (after its "/*"); +/// if it is unterminated, the end (with NulIsEnd also a null byte), which is +/// where the caller reports the error +template +inline const unsigned char* find_block_comment_end(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && !(NulIsEnd && *p == 0) && !(*p == '*' && p + 1 != e && p[1] == '/')) + { + ++p; + } + return p; +} + /// powers of ten up to 10^19 as integers inline std::uint64_t int_pow10(unsigned k) noexcept { @@ -1382,31 +1408,16 @@ class builder } if (p[1] == '/') { - p += 2; - // (as in parse(), a null byte is the end of the input, so it is - // left for the caller to see) - while (p != m_end && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0)) - { - ++p; - } - return p; + return skip_line_comment(p + 2, m_end); } if (p[1] == '*') { - p += 2; - for (;;) + p = find_block_comment_end(p + 2, m_end); + if (p == m_end || *p != '*') { - if (p == m_end || (NulIsEnd && *p == 0)) - { - return fail_at(error_code::comment_unterminated, p); - } - if (*p == '*' && p + 1 != m_end && p[1] == '/') - { - p += 2; - return p; - } - ++p; + return fail_at(error_code::comment_unterminated, p); } + return p + 2; } ++p; return fail_at(error_code::comment_start, p); @@ -2402,7 +2413,14 @@ indent_done: return false; } p = r.p; - push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len); +#if JSON_DIAGNOSTIC_POSITIONS + // the decoded string is in the arena: next keeps the source offset + // of the opening quote plus one (0: unknown) for materialize() + const std::uint64_t second = static_cast(r.len) | (static_cast(s - b) << 32); +#else + const std::uint64_t second = r.len; +#endif + push_node(node_kind::string, node_flags::escaped, 0, r.start, second); return true; } }; @@ -4943,7 +4961,8 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u; /// Copy the current structure of an edited document into nodes in document /// order, as the parser would have written them. Text written by edits is /// appended to text_tail (number tokens) and arena_tail (strings); floats that -/// are not finite become null, as dump() writes them. +/// are not finite become null, as dump() writes them. Nodes keep the flag +/// is_new: they have no source position (JSON_DIAGNOSTIC_POSITIONS). inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector& out, std::string& text_tail, std::string& arena_tail) { struct frame @@ -4966,16 +4985,18 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v arena_tail.append(d.str(s), s.len); r.flags = node_flags::escaped; } + r.flags = static_cast(r.flags | (s.flags & node_flags::is_new)); return r; }; const auto copy_node = [&](const node * v) { node r = *v; + const auto is_new = static_cast(v->flags & node_flags::is_new); switch (static_cast(v->kind)) { case value_t::object: case value_t::array: - r.flags = 0; + r.flags = is_new; r.extra = 0; r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.len = 0; // counted below @@ -4992,7 +5013,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v r.off = static_cast(d.size + text_tail.size()); text_tail.append(d.str(*v), number_length(*v)); } - r.flags = 0; + r.flags = is_new; break; case value_t::number_float: if ((v->flags & node_flags::storage) == node_flags::edited) @@ -5002,22 +5023,23 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v { r = node{}; // nan and infinity: null, as dump() writes them r.kind = node_kind::null; + r.flags = is_new; break; } r.off = static_cast(d.size + text_tail.size()); text_tail.append(t, v->len); r.extra = 0xFFFFu; // the digit layout is not recorded } - r.flags = 0; + r.flags = is_new; break; case value_t::boolean: - r.flags = static_cast(v->flags & node_flags::is_true); + r.flags = static_cast(v->flags & (node_flags::is_true | node_flags::is_new)); break; case value_t::null: case value_t::binary: case value_t::discarded: default: - r.flags = 0; + r.flags = is_new; break; } out.push_back(r); @@ -5378,13 +5400,15 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha std::vector floats; const auto check_string = [&](const node & n) -> bool { - if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0) + if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0) { return false; } const bool decoded = (n.flags & node_flags::escaped) != 0; const std::size_t limit = decoded ? arena_size : text_size; - if (n.off > limit || n.len > limit - n.off) + // (next: the source offset of a decoded string's opening quote plus + // one, or 0; see node) + if (n.off > limit || n.len > limit - n.off || n.next > text_size) { return false; } @@ -5456,13 +5480,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha { case value_t::null: // (the offset of a literal is read to size the output of dump()) - if (n.flags != 0 || n.extra != 0 || n.off > text_size) + if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size) { return false; } break; case value_t::boolean: - if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size) + if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size) { return false; } @@ -5476,7 +5500,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha case value_t::number_integer: case value_t::number_unsigned: case value_t::number_float: - if (n.flags != 0 || !number_in_bounds(n)) + if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n)) { return false; } @@ -5498,7 +5522,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha case value_t::object: { const std::size_t limit = stack.empty() ? count : stack.back().end; - if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size) + if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size) { return false; } @@ -6101,6 +6125,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -6108,6 +6134,189 @@ namespace detail namespace view { +#if JSON_DIAGNOSTIC_POSITIONS +/*! +@brief the source positions of the values materialize() replays + +Stands in for the lexer of json_sax_dom_parser, which reads the positions of +the values it stores from these three functions (diagnostic_positions). npos +is a value without a source position. One position_source serves a whole +materialize() call: before each value is handed to the handler, the range of +that value is set (or cleared), overwriting that of the value before. +*/ +struct position_source +{ + /// the token, of which diagnostic_positions only reads the length + struct token + { + std::size_t length; + std::size_t size() const noexcept + { + return length; + } + }; + + std::size_t start = std::string::npos; ///< the first byte of the next value + std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket) + + std::size_t get_chars_read_total() const noexcept + { + return end; + } + std::size_t get_token_start_position() const noexcept + { + return start; + } + token get_string() const noexcept + { + return token{end - start}; + } + + /// the range [first, last), or none if either is npos + void set(std::size_t first, std::size_t last) noexcept + { + if (first == std::string::npos || last == std::string::npos) + { + clear(); + return; + } + start = first; + end = last; + } + + /// no range: the next value has no source position + void clear() noexcept + { + start = std::string::npos; + end = std::string::npos; + } + + /// one past the closing quote of the string whose opening quote is at + /// src[quote], or npos (the scan is bounded by size, as images can be + /// loaded without a check) + static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept + { + for (std::size_t i = quote + 1; i < size; ++i) + { + if (src[i] == '\\') + { + ++i; + } + else if (src[i] == '"') + { + return i + 1; + } + } + return std::string::npos; + } + + /// one past the closing bracket of the array or object whose last element + /// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos; + /// in between, there can only be whitespace, comments, and a trailing comma + /// (the source was validated when it was parsed) + static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept + { + const auto* const b = reinterpret_cast(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it + const unsigned char* const e = b + size; + while (pos < size) + { + switch (src[pos]) + { + case ' ': + case '\t': + case '\n': + case '\r': + case ',': + ++pos; + break; + case ']': + case '}': + return pos + 1; + case '/': + if (pos + 1 < size && src[pos + 1] == '/') + { + pos = static_cast(skip_line_comment(b + pos + 2, e) - b); + break; + } + if (pos + 1 < size && src[pos + 1] == '*') + { + // (unterminated: pos is size, which ends the loop) + pos = static_cast(find_block_comment_end(b + pos + 2, e) - b) + 2; + break; + } + return std::string::npos; + default: + return std::string::npos; + } + } + return std::string::npos; + } + + /// the range of the scalar n of document d + void set_scalar(const document_data& d, const node& n) noexcept + { + if ((n.flags & node_flags::is_new) != 0) + { + clear(); + return; + } + std::size_t first = std::string::npos; + std::size_t last = std::string::npos; + switch (n.kind) + { + case node_kind::string: + if ((n.flags & node_flags::storage) == 0) + { + // off is the first byte after the opening quote + if (n.off != 0) + { + first = n.off - 1; + last = n.off + std::size_t{n.len} + 1; + } + } + else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0) + { + // next is the offset of the opening quote plus one + first = n.next - 1; + last = string_end(d.base[0], d.size, first); + } + break; + case node_kind::number_integer: + case node_kind::number_unsigned: + case node_kind::number_float: + if ((n.flags & node_flags::storage) == 0) + { + first = n.off; + last = n.off + number_length(n); + } + break; + case node_kind::boolean: + first = n.off; + last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u); + break; + case node_kind::null: + first = n.off; + last = n.off + 4u; + break; + case node_kind::object: + case node_kind::array: + case node_kind::link: + default: + break; + } + // (a crafted image can point past the text) + if (last == std::string::npos || last > d.size) + { + clear(); + } + else + { + set(first, last); + } + } +}; +#endif + /*! @brief the basic_json value of the subtree at n @@ -6115,14 +6324,24 @@ The subtree is replayed into the SAX handler that parse() uses to build its values, so the result is the value parse() would produce: duplicate keys keep the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is iterative, so the nesting depth is limited by memory only, as for parse(). -Without a lexer the handler records no source positions -(JSON_DIAGNOSTIC_POSITIONS). + +With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with +parse(): position_source stands in for the lexer. Values written by edits have +none (npos), and neither has an array or object whose elements changed, or +whose last element has none (the end of its closing bracket is found by +scanning from the end of the last element). */ template BasicJsonType materialize(const document_data& d, const node* n) { using string_t = typename BasicJsonType::string_t; - using sax_t = json_sax_dom_parser, true>; + using input_t = iterator_input_adapter; +#if JSON_DIAGNOSTIC_POSITIONS + using lexer_t = position_source; +#else + using lexer_t = lexer; // (the default: unused, as no lexer is passed) +#endif + using sax_t = json_sax_dom_parser; using nav = navigation; // the open containers: their next element (or key of the next member), @@ -6134,9 +6353,15 @@ BasicJsonType materialize(const document_data& d, const node* n) const node* pos; const node* end; bool object; +#if JSON_DIAGNOSTIC_POSITIONS + std::size_t start; ///< the opening bracket (npos: unknown) + std::size_t last_end; ///< the end of the last element read (npos: unknown) +#endif }; +#if !JSON_DIAGNOSTIC_POSITIONS // a scalar needs neither the handler nor the stack of open containers + // (with positions, it goes through the handler, which sets them) switch (static_cast(n->kind)) { case value_t::string: @@ -6158,10 +6383,16 @@ BasicJsonType materialize(const document_data& d, const node* n) default: break; } +#endif BasicJsonType result; +#if JSON_DIAGNOSTIC_POSITIONS + position_source positions; + sax_t sax(result, true, &positions); +#else sax_t sax(result, true); - const string_t no_token{}; +#endif + const string_t no_token {}; // (shallow subtrees on the stack: a call for a small value then allocates only for the value) std::array shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open std::vector deep{}; @@ -6176,6 +6407,16 @@ BasicJsonType materialize(const document_data& d, const node* n) std::size_t depth = 0; for (;;) { +#if JSON_DIAGNOSTIC_POSITIONS + if (!is_container(*n)) + { + positions.set_scalar(d, *n); + if (depth != 0) + { + open[depth - 1].last_end = positions.end; + } + } +#endif // false positive: n comes from nav::value(), which never returns null for a valid index // @infer-ignore NULLPTR_DEREFERENCE switch (static_cast(n->kind)) @@ -6184,6 +6425,17 @@ BasicJsonType materialize(const document_data& d, const node* n) case value_t::array: { const bool object = n->kind == node_kind::object; +#if JSON_DIAGNOSTIC_POSITIONS + // (moved containers are also new) + if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size) + { + positions.clear(); + } + else + { + positions.set(std::size_t{n->off}, std::size_t{n->off} + 1); + } +#endif if (object) { sax.start_object(n->len); @@ -6200,7 +6452,13 @@ BasicJsonType materialize(const document_data& d, const node* n) open = deep.data(); capacity = deep.size(); } - open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; + // (with positions: until an element is read, the container's + // own bracket is the last thing read) + open[depth++] = frame{nav::first(d, n), nav::end(d, n), object +#if JSON_DIAGNOSTIC_POSITIONS + , positions.start, positions.end +#endif + }; break; } case value_t::string: @@ -6241,6 +6499,31 @@ BasicJsonType materialize(const document_data& d, const node* n) frame& f = open[depth - 1]; if (f.pos == f.end) { +#if JSON_DIAGNOSTIC_POSITIONS + if (f.start == std::string::npos || f.last_end == std::string::npos) + { + positions.clear(); + } + else + { + const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end); + if (end == std::string::npos) + { + positions.clear(); + } + else + { + // the closing bracket, as the lexer reports it (end_object() + // and end_array() only read its end; the start of the + // container was set when it was opened) + positions.set(end - 1, end); + } + } + if (depth > 1) + { + open[depth - 2].last_end = positions.end; + } +#endif if (f.object) { sax.end_object(); diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index ee2300e3e..77f7aa4fa 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -749,6 +749,20 @@ std::string exception_of(F f) } return ""; } + +// the exception a call on a basic_json value throws, without the byte range of +// the value (JSON_DIAGNOSTIC_POSITIONS), which the exceptions of views lack +template +std::string json_exception_of(F f) +{ + std::string what = exception_of(f); + const std::size_t at = what.find("] (bytes "); + if (at != std::string::npos) + { + what.erase(at + 2, what.find(") ", at) - at); + } + return what; +} #endif // compares a view with the ordered_json value materialize() gives for it: @@ -957,24 +971,24 @@ TEST_CASE("json_view element access and iteration") #if !defined(JSON_NOEXCEPTION) if (!j.is_object()) { - CHECK(exception_of([&] { static_cast(v["a"]); }) == exception_of([&] { static_cast(j["a"]); })); + CHECK(exception_of([&] { static_cast(v["a"]); }) == json_exception_of([&] { static_cast(j["a"]); })); } if (!j.is_array()) { - CHECK(exception_of([&] { static_cast(v[0]); }) == exception_of([&] { static_cast(j[0]); })); + CHECK(exception_of([&] { static_cast(v[0]); }) == json_exception_of([&] { static_cast(j[0]); })); } - CHECK(exception_of([&] { static_cast(v.at("a")); }) == exception_of([&] { static_cast(j.at("a")); })); - CHECK(exception_of([&] { static_cast(v.at("missing")); }) == exception_of([&] { static_cast(j.at("missing")); })); - CHECK(exception_of([&] { static_cast(v.at(0)); }) == exception_of([&] { static_cast(j.at(0)); })); - CHECK(exception_of([&] { static_cast(v.at(5)); }) == exception_of([&] { static_cast(j.at(5)); })); + CHECK(exception_of([&] { static_cast(v.at("a")); }) == json_exception_of([&] { static_cast(j.at("a")); })); + CHECK(exception_of([&] { static_cast(v.at("missing")); }) == json_exception_of([&] { static_cast(j.at("missing")); })); + CHECK(exception_of([&] { static_cast(v.at(0)); }) == json_exception_of([&] { static_cast(j.at(0)); })); + CHECK(exception_of([&] { static_cast(v.at(5)); }) == json_exception_of([&] { static_cast(j.at(5)); })); if (!(j.is_object() && j.empty())) // (key() of an end iterator) { - CHECK(exception_of([&] { static_cast(v.begin().key()); }) == exception_of([&] { static_cast(j.begin().key()); })); + CHECK(exception_of([&] { static_cast(v.begin().key()); }) == json_exception_of([&] { static_cast(j.begin().key()); })); } if (!j.empty() || j.is_null()) { - CHECK(exception_of([&] { static_cast(v.front()); }) == exception_of([&] { static_cast(j.front()); })); - CHECK(exception_of([&] { static_cast(v.back()); }) == exception_of([&] { static_cast(j.back()); })); + CHECK(exception_of([&] { static_cast(v.front()); }) == json_exception_of([&] { static_cast(j.front()); })); + CHECK(exception_of([&] { static_cast(v.back()); }) == json_exception_of([&] { static_cast(j.back()); })); } #endif CHECK(v.contains("a") == j.contains("a")); diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index c1c8925d8..7e84a9081 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -337,7 +337,8 @@ TEST_CASE("json_view from_json") CHECK(d.root().get>().at("a") == 3); } -#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS + // (the exceptions of views have neither the path nor the byte range of the value) +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS SECTION("std types: the same errors as from the materialized value") { const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])"); @@ -403,7 +404,7 @@ TEST_CASE("json_view from_json") #if !defined(JSON_NOEXCEPTION) const json j = d.root().materialize(); CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\""); -#if !JSON_DIAGNOSTICS +#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == exception_of_call([&] { static_cast(j[2].get()); })); #endif #endif @@ -451,10 +452,11 @@ TEST_CASE("json_view from_json") CHECK(o.root().get().center.y == 2); } -#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS SECTION("errors are those of the same conversion of a json value") { - // (with JSON_DIAGNOSTICS, those of json name the path of the value) + // (with JSON_DIAGNOSTICS, those of json name the path of the value, + // and with JSON_DIAGNOSTIC_POSITIONS its bytes) const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])"); const json j = d.root().materialize(); for (std::size_t i = 0; i < 3; ++i) diff --git a/tests/src/unit-json_view_positions.cpp b/tests/src/unit-json_view_positions.cpp new file mode 100644 index 000000000..857b0cb8f --- /dev/null +++ b/tests/src/unit-json_view_positions.cpp @@ -0,0 +1,346 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +// source positions of materialize() (JSON_DIAGNOSTIC_POSITIONS, #5864) + +#include "doctest_compatibility.h" + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 1 +#endif +#define JSON_DIAGNOSTIC_POSITIONS 1 +#include +using nlohmann::json; +using nlohmann::ordered_json; +using nlohmann::json_document; +using nlohmann::json_editable_document; + +#include +#include +#include +#include +#include +#include + +#include "json_view_test_helpers.hpp" +#include + +namespace +{ +constexpr std::size_t npos = std::string::npos; + +// whether every value of a has the source positions of the value of b at the +// same place (a and b are equal; shallow test documents only) +template +bool same_positions(const BasicJsonType& a, const BasicJsonType& b) +{ + if (a.start_pos() != b.start_pos() || a.end_pos() != b.end_pos() || a.type() != b.type() || a.size() != b.size()) + { + return false; + } + if (a.is_object()) + { + for (auto it = a.begin(); it != a.end(); ++it) + { + if (!same_positions(it.value(), b.at(it.key()))) + { + return false; + } + } + } + else if (a.is_array()) + { + for (std::size_t i = 0; i < a.size(); ++i) + { + if (!same_positions(a[i], b[i])) + { + return false; + } + } + } + return true; +} + +// the materialized root of text has the positions of parse() +template +bool positions_as_parse(const std::string& text, bool comments = false, bool trailing_commas = false) +{ + const auto j = BasicJsonType::parse(text, nullptr, true, comments, trailing_commas); + const auto d = nlohmann::basic_json_document::parse(text, true, comments, trailing_commas); + const auto e = nlohmann::basic_json_document::parse(text, true, comments, trailing_commas); + return same_positions(d.root().materialize(), j) && same_positions(e.root().materialize(), j); +} + +std::string range(const json& j) +{ + return j.start_pos() == npos && j.end_pos() == npos ? "none" : std::to_string(j.start_pos()) + "-" + std::to_string(j.end_pos()); +} +} // namespace + +TEST_CASE("json_view materialize() source positions") +{ + SECTION("as parse()") + { + const std::vector texts = + { + "null", "true", "false", "0", "-12", "3.25e-2", "18446744073709551615", "-9223372036854775809", R"("a")", + R"("\n")", R"("\u00e9")", R"("\ud83d\ude00")", "\"\xf0\x9f\x98\x80\"", R"("a\"b\\")", R"("x\\")", + " [ ] ", "{}", "[[[]]]", R"({"a":{"b":[1,{"c":[]}]}})", + R"({"\u0041": "v\n", "k": "\"", "\t":[ "\/" ]})", + R"([1, 2.5, true, false, null, "s", "\u0000", -0])", + "\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n", + "\xef\xbb\xbf[1, {\"a\": \"b\"}]", "\xef\xbb\xbf\"\\n\"", + }; + for (const auto& text : texts) + { + CAPTURE(text) + CHECK(positions_as_parse(text)); + CHECK(positions_as_parse(text)); + } + } + + SECTION("comments and trailing commas") + { + const std::vector texts = + { + "[1 /* ] */]", "[1 // ]\n]", "{\"a\": 1 /* } */ , }", "[1, 2, ]", "[[1,],[2 , /**/ ] , ]", + "{\"a\": [] // }\n, \"b\": {} /* } ] */ }", "[ /* empty */ ]", "{ // }\n}", "/* a */ [1] // b", + R"(["\n" /* " */ , ])", + }; + for (const auto& text : texts) + { + CAPTURE(text) + CHECK(positions_as_parse(text, true, true)); + } + } + + SECTION("generated documents") + { + json_view_test::generator gen; + for (int i = 0; i < 300; ++i) + { + std::string text; + gen.value(text, 0); + CAPTURE(text) + CHECK(positions_as_parse(text)); + CHECK(positions_as_parse(text)); + } + } + + SECTION("test data") + { + for (const char* name : + { + "/json_testsuite/sample.json", "/nativejson-benchmark/canada.json", "/nativejson-benchmark/citm_catalog.json", + "/nativejson-benchmark/twitter.json", "/json_tests/pass1.json" + }) + { + CAPTURE(name) + const std::ifstream f(std::string(TEST_DATA_DIRECTORY) + name, std::ios::binary); + std::stringstream ss; + ss << f.rdbuf(); + CHECK(positions_as_parse(ss.str())); + } + } + + SECTION("subtrees") + { + const std::string text = R"({"a": [1, "\n", {"b": null}], "s": "x\ty", "n": -1.5})"; + const json j = json::parse(text); + const json_document d = json_document::parse(text); + CHECK(same_positions(d.root()["a"].materialize(), j["a"])); + CHECK(same_positions(d.root()["a"][2].materialize(), j["a"][2])); + CHECK(same_positions(d.root()["a"][1].materialize(), j["a"][1])); + CHECK(same_positions(d.root()["s"].materialize(), j["s"])); + CHECK(same_positions(d.root()["n"].materialize(), j["n"])); + CHECK(range(d.root()["a"][1].materialize()) == "10-14"); + CHECK(range(d.root()["s"].materialize()) == "35-41"); + } + +#if !defined(JSON_NOEXCEPTION) + SECTION("exception messages") + { + const std::string text = "{\n \"address\": {\"street\": \"Fake\\tStreet\", \"housenumber\": \"1\"},\n \"list\": [1, [true]]\n}"; + const json j = json::parse(text); + const json_document d = json_document::parse(text); + const json m = d.root().materialize(); + const std::string expected = json_view_test::exception_of_call([&] + { + static_cast(j.at("address").at("housenumber").get()); + }); + CHECK(expected.find("(bytes ") != std::string::npos); + CHECK(json_view_test::exception_of_call([&] + { + static_cast(m.at("address").at("housenumber").get()); + }) == expected); + CHECK(json_view_test::exception_of_call([&] + { + static_cast(m.at("address").at("street").get()); + }) == json_view_test::exception_of_call([&] + { + static_cast(j.at("address").at("street").get()); + })); + CHECK(json_view_test::exception_of_call([&] + { + static_cast(m.at("list").at(1).get()); + }) == json_view_test::exception_of_call([&] + { + static_cast(j.at("list").at(1).get()); + })); + } +#endif + + SECTION("edited documents") + { + const std::string text = R"({"a": [1, 2], "b": {"c": "\n", "d": true}, "e": [3, [4]], "f": "x"})"; + const json j = json::parse(text); + + SECTION("new scalars have none") + { + json_editable_document d = json_editable_document::parse(text); + d.set(d.root()["f"], 5); + d.set(d.root()["b"]["d"], "s"); + const json m = d.root().materialize(); + CHECK(range(m["f"]) == "none"); + CHECK(range(m["b"]["d"]) == "none"); + // the object whose last member is new has none; the others keep theirs + CHECK(range(m) == "none"); + CHECK(range(m["b"]) == "none"); + CHECK(same_positions(m["a"], j["a"])); + CHECK(same_positions(m["e"], j["e"])); + CHECK(same_positions(m["b"]["c"], j["b"]["c"])); + } + + SECTION("containers whose elements change have none") + { + json_editable_document d = json_editable_document::parse(text); + d.push_back(d.root()["a"], 7); + d.erase(d.root()["e"], 0); + const json m = d.root().materialize(); + CHECK(range(m["a"]) == "none"); + CHECK(range(m["a"][2]) == "none"); + CHECK(range(m["e"]) == "none"); + // the elements kept keep theirs + CHECK(range(m["a"][0]) == range(j["a"][0])); + CHECK(range(m["a"][1]) == range(j["a"][1])); + CHECK(same_positions(m["e"][0], j["e"][1])); + CHECK(same_positions(m["b"], j["b"])); + // the root's elements did not change + CHECK(range(m) == range(j)); + } + + SECTION("new containers have none") + { + json_editable_document d = json_editable_document::parse(text); + d.set(d.root()["b"], json::parse(R"({"x": [1]})")); + const json m = d.root().materialize(); + CHECK(range(m["b"]) == "none"); + CHECK(range(m["b"]["x"]) == "none"); + CHECK(range(m["b"]["x"][0]) == "none"); + CHECK(range(m) == range(j)); + } + + SECTION("copies of scalars of the document keep theirs") + { + // (a copy of an array or object is a new one; its scalars are the + // parsed ones) + json_editable_document d = json_editable_document::parse(text); + d.set(d.root()["f"], d.root()["e"]); + d.set(d.root()["a"], d.root()["b"]["c"]); + const json m = d.root().materialize(); + CHECK(range(m["a"]) == range(j["b"]["c"])); + CHECK(range(m["f"]) == "none"); + CHECK(range(m["f"][1]) == "none"); + CHECK(range(m["f"][0]) == range(j["e"][0])); + CHECK(range(m["f"][1][0]) == range(j["e"][1][0])); + } + } + +#if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + SECTION("images") + { + const std::string text = R"({"a\n": ["\u00e9", 1.5, {"b": [true, null]}], "c": "d"})"; + const json j = json::parse(text); + + SECTION("round trip") + { + const json_document d = json_document::load(json_document::parse(text).save()); + CHECK(same_positions(d.root().materialize(), j)); + } + + SECTION("edited") + { + json_editable_document e = json_editable_document::parse(text); + e.set(e.root()["c"], 1); + e.push_back(e.root()[std::string("a") + '\n'], false); + e.set(e.root()[std::string("a") + '\n'][2]["b"][0], "\""); + const json_document d = json_document::load(e.save()); + const json m = d.root().materialize(); + CHECK(range(m["c"]) == "none"); + CHECK(range(m) == "none"); + const json& a = m[std::string("a") + '\n']; + const json& ja = j[std::string("a") + '\n']; + CHECK(range(a) == "none"); + CHECK(range(a[3]) == "none"); + CHECK(range(a[0]) == range(ja[0])); + CHECK(range(a[1]) == range(ja[1])); + CHECK(range(a[2]["b"][0]) == "none"); + CHECK(range(a[2]["b"][1]) == range(ja[2]["b"][1])); + // the last element of b is still the parsed one + CHECK(range(a[2]["b"]) == range(ja[2]["b"])); + CHECK(range(a[2]) == range(ja[2])); + } + + SECTION("without positions") + { + // an image saved without JSON_DIAGNOSTIC_POSITIONS: next is 0 for + // the strings with escapes + std::vector image = json_document::parse(text).save(); + std::uint64_t count = 0; + std::memcpy(&count, image.data() + 8, sizeof(count)); + for (std::size_t i = 0; i < count; ++i) + { + nlohmann::detail::view::node n{}; + std::memcpy(&n, image.data() + 64 + (i * sizeof(n)), sizeof(n)); + if (n.kind == nlohmann::detail::view::node_kind::string) + { + n.next = 0; + } + std::memcpy(image.data() + 64 + (i * sizeof(n)), &n, sizeof(n)); + } + const json_document d = json_document::load(image); + const json m = d.root().materialize(); + CHECK(range(m[std::string("a") + '\n'][0]) == "none"); + CHECK(range(m["c"]) == range(j["c"])); + CHECK(same_positions(m[std::string("a") + '\n'][2], j[std::string("a") + '\n'][2])); + } + +#if !defined(JSON_NOEXCEPTION) + SECTION("a decoded string's position must be within the text") + { + std::vector image = json_document::parse(R"(["\n"])").save(); + nlohmann::detail::view::node n{}; + std::memcpy(&n, image.data() + 64 + sizeof(n), sizeof(n)); + REQUIRE(n.next == 2); + n.next = 7; + std::memcpy(image.data() + 64 + sizeof(n), &n, sizeof(n)); + const auto load_image = [&image] + { + // GCC ignores a cast to void for [[nodiscard]]-like results, so keep the document + const json_document d = json_document::load(image); + static_cast(d); + }; + CHECK_THROWS_AS(load_image(), json::parse_error&); + // without the check, the position is out of bounds and ignored + const json_document d = json_document::load(image, json_document::image_check::none); + CHECK(range(d.root()[0].materialize()) == "none"); + } +#endif + } +#endif +} From 46bead6e134f2fe883086ba315dc687ef06210f8 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 28 Oct 2026 22:08:55 +0100 Subject: [PATCH 32/63] Fix the VS2015 build of unit-json_view_positions.cpp (#5957) VS 2015 does not accept std::string::npos in a constant expression (C2131), and the constant is only used at run time. Signed-off-by: Niels Lohmann --- tests/src/unit-json_view_positions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/src/unit-json_view_positions.cpp b/tests/src/unit-json_view_positions.cpp index 857b0cb8f..08ea47a08 100644 --- a/tests/src/unit-json_view_positions.cpp +++ b/tests/src/unit-json_view_positions.cpp @@ -32,7 +32,8 @@ using nlohmann::json_editable_document; namespace { -constexpr std::size_t npos = std::string::npos; +// not constexpr: VS 2015 does not accept std::string::npos in a constant expression (C2131) +const std::size_t npos = std::string::npos; // whether every value of a has the source positions of the value of b at the // same place (a and b are equal; shallow test documents only) From 5eccbbd2bce4fb8b0312ec090179aeaac0fbdd7c Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 29 Oct 2026 07:36:11 +0100 Subject: [PATCH 33/63] Fix clang-tidy misc-redundant-expression in unit-json_view_image.cpp (#5958) Split the combined has_save/has_load static_asserts so clang-tidy no longer sees both sides of && as equivalent (develop ci_clang_tidy failure). Signed-off-by: Niels Lohmann --- tests/src/unit-json_view_image.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index ff1713371..4f3e6319f 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -185,8 +185,10 @@ std::uint32_t rng() TEST_CASE("json_view images: round trips") { - static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); - static_assert(json_view_image_test::has_save::value && json_view_image_test::has_load::value, "save() and load() on a little-endian target"); + static_assert(json_view_image_test::has_save::value, "save() on a little-endian target"); + static_assert(json_view_image_test::has_load::value, "load() on a little-endian target"); + static_assert(json_view_image_test::has_save::value, "save() on a little-endian target"); + static_assert(json_view_image_test::has_load::value, "load() on a little-endian target"); SECTION("small documents") { From 2aecd86b37340e3aec7b169e1c205ff0417b7351 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 29 Oct 2026 07:37:33 +0100 Subject: [PATCH 34/63] Document the contract of object keys passed to json_sax::key and key callbacks (#5956) * Document the contract of object keys passed to json_sax::key and key callbacks Describe what the name passed to json_sax::key and to a parser callback for parse_event_t::key holds when parsing JSON text: escapes are decoded to UTF-8, different spellings of a name yield identical strings, the name is validated before the call (invalid escapes or UTF-8 give a parse error and no key event), no normalization or case conversion is applied, the event precedes the ':' check, and duplicate names are reported once per occurrence. Also note that sax_parse passes binary-format keys unchanged. See discussion #5955. Signed-off-by: Niels Lohmann * Add a test for the object key contract and correct the BON8 note Pin the guarantees documented for json_sax::key and the key event of parser callbacks: decoded names, validation before delivery, delivery before the colon, one event per repeated name, and the binary formats. BON8 rejects ill-formed UTF-8 in a name even with sax_parse, so the note no longer lists it with the formats that pass bytes through. See discussion #5955. Signed-off-by: Niels Lohmann * Fix the GCC build of the object key contract test GCC 11 in C++11 mode rejects a default argument on a lambda parameter (-Wc++14-extensions) and ignores a static_cast on the warn_unused_result of parse(). Pass the input format explicitly and bind the parse result to a variable. Signed-off-by: Niels Lohmann * Add version history for the object key contract Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../docs/api/basic_json/parser_callback_t.md | 9 ++ docs/mkdocs/docs/api/json_sax/key.md | 21 +++ .../docs/features/parsing/sax_interface.md | 3 + tests/src/unit-class_parser.cpp | 136 ++++++++++++++++++ 4 files changed, 169 insertions(+) diff --git a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md index a12634b9d..643610cb2 100644 --- a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md +++ b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md @@ -63,6 +63,15 @@ called: Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In the latter case, it is skipped completely, or replaced by `null` if it is the top-level value. +## Notes + +For a `parse_event_t::key` event, `parsed` holds the decoded object name, with the same guarantees as the `val` +parameter of [`json_sax::key`](../json_sax/key.md#notes): all escape sequences are decoded and `\uXXXX` escapes are +stored as UTF-8, so different spellings of the same name yield identical strings; the name is validated completely +before the callback is called, so a name with an invalid escape sequence or ill-formed UTF-8 causes a parse error and +no `key` event; no Unicode normalization or case conversion is applied; and a name that occurs several times in the +same object yields one `key` event per occurrence. + ## Examples ??? example "Example: skip an object key while parsing" diff --git a/docs/mkdocs/docs/api/json_sax/key.md b/docs/mkdocs/docs/api/json_sax/key.md index 591f590e0..14b1b52a1 100644 --- a/docs/mkdocs/docs/api/json_sax/key.md +++ b/docs/mkdocs/docs/api/json_sax/key.md @@ -19,6 +19,23 @@ Whether parsing should proceed. It is safe to move the passed object key value. +When parsing JSON text, `val` is the decoded object name, not its spelling in the input: + +- All escape sequences (`\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t`, `\uXXXX`, and surrogate pairs) are + decoded, and `\uXXXX` escapes are stored as UTF-8. Different spellings of the same name, such as `#!json "/"` + and `#!json "\/"`, or `#!json "\u00e9"` and `#!json "\u00E9"`, yield identical strings. An escaped `\u0000` yields + an embedded NUL byte. +- The name is scanned and validated completely before `key` is called. If it contains an invalid escape sequence, an + unpaired surrogate, a control character, or ill-formed UTF-8, the parse fails with a + [`parse_error`](../../home/exceptions.md#parse-errors), and `key` is not called for it. +- No Unicode normalization or case conversion is applied: names that differ in any byte are different strings. +- `key` is called before the following `:` is read, so a parse error may still follow for the same member. +- A name that occurs several times in the same object is passed once per occurrence. + +For the binary formats, object keys contain no escape sequences. For CBOR, MessagePack, UBJSON, and BSON, `val` holds +the bytes stored in the input, and [`sax_parse`](../basic_json/sax_parse.md) passes them unchanged, without checking them +for valid UTF-8. BON8 encodes characters itself, so an ill-formed name is a parse error and `key` is not called for it. + ## Examples ??? example @@ -45,3 +62,7 @@ It is safe to move the passed object key value. ## Version history - Added in version 3.2.0. +- The behavior described in the notes above (decoded names, validation before `key` is called, no normalization, `key` + before the `:` check, one call per duplicate, bytes of binary formats passed unchanged) is the same in all versions + from 3.2.0 on, and for the `key` event of a [parser callback](../basic_json/parser_callback_t.md) from 3.0.0 on. BSON + is only supported from version 3.4.0, and BON8 only from version 3.13.0. diff --git a/docs/mkdocs/docs/features/parsing/sax_interface.md b/docs/mkdocs/docs/features/parsing/sax_interface.md index 872105a20..f732f4b2e 100644 --- a/docs/mkdocs/docs/features/parsing/sax_interface.md +++ b/docs/mkdocs/docs/features/parsing/sax_interface.md @@ -64,6 +64,9 @@ bool parse_error(std::size_t position, const std::string& last_token, const json The return value of each function determines whether parsing should proceed. +Object keys are passed to `key` decoded and validated: escape sequences are already resolved, so different spellings of +the same name yield the same string. See [`key`](../../api/json_sax/key.md#notes) for the exact guarantees. + ## Stepping through the events The events for a small input, step by step, and what two different handlers do with them. Press *Play* or step with *Next*: diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index dd78f7ff4..1662fd037 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -3030,3 +3030,139 @@ TEST_CASE("a json_sax_dom_parser keeps the values read before a parse error") CHECK(read_before_error(c.first) == c.second); } } + +TEST_CASE("object keys are delivered decoded and validated") +{ + // json_sax::key and the key event of a parser callback see the decoded name, and + // only after the whole name was scanned and validated + const auto keys_of_format = [](const std::string & text, const json::input_format_t format) + { + SaxEventLogger l; + json::sax_parse(text, &l, format); + std::vector keys; + for (const auto& e : l.events) + { + if (e.compare(0, 4, "key(") == 0) + { + keys.push_back(e.substr(4, e.size() - 5)); + } + } + return keys; + }; + + const auto keys_of = [&keys_of_format](const std::string & text) + { + return keys_of_format(text, json::input_format_t::json); + }; + + const auto callback_keys_of = [](const std::string & text) + { + std::vector keys; + const json::parser_callback_t cb = [&keys](int /*depth*/, json::parse_event_t event, json & parsed) + { + if (event == json::parse_event_t::key) + { + keys.push_back(parsed.get()); + } + return true; + }; + const json parsed = json::parse(text, cb, false); + static_cast(parsed); + return keys; + }; + + SECTION("different spellings of a name yield the same string") + { + const std::string e_acute = "\xc3\xa9"; + const std::string text = std::string(R"({"/":1,"\/":2,"\u002f":3,"\u00e9":4,"\u00E9":5,")") + e_acute + R"(":6})"; + const std::vector expected = {"/", "/", "/", e_acute, e_acute, e_acute}; + CHECK(keys_of(text) == expected); + CHECK(callback_keys_of(text) == expected); + } + + SECTION("escape sequences are decoded") + { + const std::string simple = R"({"\"\\\/\b\f\n\r\t":1})"; + const std::vector simple_expected = {std::string("\"\\/\b\f\n\r\t")}; + CHECK(keys_of(simple) == simple_expected); + CHECK(callback_keys_of(simple) == simple_expected); + + const std::string nul = R"({"\u0000":1})"; + const std::vector nul_expected = {std::string(1, '\0')}; + CHECK(keys_of(nul) == nul_expected); + CHECK(callback_keys_of(nul) == nul_expected); + + const std::string pair = R"({"\ud83d\ude00":1})"; + const std::vector pair_expected = {"\xf0\x9f\x98\x80"}; + CHECK(keys_of(pair) == pair_expected); + CHECK(callback_keys_of(pair) == pair_expected); + } + + SECTION("no normalization and no case conversion") + { + const std::string text = std::string("{\"\xc3\xa9\":1,\"e\xcc\x81\":2,\"A\":3,\"a\":4}"); + const std::vector expected = {"\xc3\xa9", "e\xcc\x81", "A", "a"}; + CHECK(keys_of(text) == expected); + CHECK(callback_keys_of(text) == expected); + } + + SECTION("a name that is not valid is not delivered") + { + const std::vector texts = + { + R"({"\u12G4":1})", // malformed escape + R"({"\udc00":1})", // lone low surrogate + R"({"\ud800":1})", // lone high surrogate + R"({"\ud800\u0041":1})", // high surrogate not followed by a low one + R"({"\x":1})", // unknown escape + std::string("{\"a\x01\":1}"), // control character + std::string("{\"\xff\":1}") // ill-formed UTF-8 + }; + for (const auto& text : texts) + { + CAPTURE(text) + CHECK(!json::accept(text)); + CHECK(keys_of(text).empty()); + CHECK(callback_keys_of(text).empty()); + } + } + + SECTION("a name is delivered before the following colon is read") + { + const std::string text = R"({"a" 1})"; + const std::vector expected = {"a"}; + CHECK(!json::accept(text)); + CHECK(keys_of(text) == expected); + CHECK(callback_keys_of(text) == expected); + } + + SECTION("a repeated name is delivered once per occurrence") + { + const std::string text = R"({"a":1,"a":2})"; + const std::vector expected = {"a", "a"}; + CHECK(keys_of(text) == expected); + CHECK(callback_keys_of(text) == expected); + } + + SECTION("binary formats pass the stored bytes through") + { + const std::string stored = "\xff\\"; + const std::vector expected = {stored}; + const auto keys_of_bytes = [&keys_of_format](const std::vector& v, json::input_format_t format) + { + return keys_of_format(std::string(v.begin(), v.end()), format); + }; + + CHECK(keys_of_bytes({0xA1, 0x62, 0xFF, 0x5C, 0x01}, json::input_format_t::cbor) == expected); + CHECK(keys_of_bytes({0x81, 0xA2, 0xFF, 0x5C, 0x01}, json::input_format_t::msgpack) == expected); + CHECK(keys_of_bytes({'{', 'i', 2, 0xFF, 0x5C, 'i', 1, '}'}, json::input_format_t::ubjson) == expected); + CHECK(keys_of_bytes({0x0D, 0, 0, 0, 0x10, 0xFF, 0x5C, 0, 1, 0, 0, 0, 0}, json::input_format_t::bson) == expected); + } + + SECTION("BON8 rejects ill-formed UTF-8 in a name") + { + const std::vector valid = {"\\"}; + CHECK(keys_of_format(std::string("\x87\x5C\x91"), json::input_format_t::bon8) == valid); + CHECK(keys_of_format(std::string("\x87\xC3\x28\x91"), json::input_format_t::bon8).empty()); + } +} From ea8a05484b97fa3fa686e915f553d71fb7ca4d37 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 30 Oct 2026 09:56:06 +0100 Subject: [PATCH 35/63] Fix implicit-conversion sanitizer failure in unit-class_parser.cpp (#5960) Constructing a std::string from uint8_t iterators converts values >= 0x80 implicitly to char, which ci_test_clang_sanitizer rejects. Convert each byte with an explicit static_cast. Signed-off-by: Niels Lohmann --- tests/src/unit-class_parser.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 1662fd037..090ab4b41 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -3150,7 +3150,12 @@ TEST_CASE("object keys are delivered decoded and validated") const std::vector expected = {stored}; const auto keys_of_bytes = [&keys_of_format](const std::vector& v, json::input_format_t format) { - return keys_of_format(std::string(v.begin(), v.end()), format); + std::string text; + for (const auto byte : v) + { + text.push_back(static_cast(byte)); + } + return keys_of_format(text, format); }; CHECK(keys_of_bytes({0xA1, 0x62, 0xFF, 0x5C, 0x01}, json::input_format_t::cbor) == expected); From 0115bb47b362867add4615aeb8d4aa68f9f142b9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 30 Oct 2026 15:17:24 +0100 Subject: [PATCH 36/63] Name the source and license of adapted code in the file comments (#5965) The files with code adapted from fast_float, Zmij and simdjson carried the authors' SPDX copyright lines, but their file comments did not say what was taken or under which license: number_parse.hpp, to_chars.hpp, simd_scan.hpp and view/simd.hpp now do, as zmij.hpp and the yyjson-based view files already did. README and the license page now also name fast_float's eight-digit parsing and the lexer's use of simdjson's UTF-8 check. Comments only. Signed-off-by: Niels Lohmann --- README.md | 4 ++-- docs/mkdocs/docs/home/license.md | 4 ++-- .../nlohmann/detail/conversions/to_chars.hpp | 5 +++++ .../nlohmann/detail/input/number_parse.hpp | 7 +++++++ include/nlohmann/detail/input/simd_scan.hpp | 7 +++++++ include/nlohmann/detail/view/simd.hpp | 7 +++++++ single_include/nlohmann/json.hpp | 19 +++++++++++++++++++ single_include/nlohmann/json_view.hpp | 7 +++++++ 8 files changed, 56 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 87c70fc96..2551df959 100644 --- a/README.md +++ b/README.md @@ -1408,9 +1408,9 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I - The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, including the conversion of the digits to text by Xiang JunBo and the SIMD instruction sequence of Dougall Johnson, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright © 2025 [Victor Zverovich](https://github.com/vitaut) - The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/). - The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0). -- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors +- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, its digit comparison for long numbers, and its parsing of eight digits at a time from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors - The view's parser (``) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks. -- The view's parser (``) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors +- The class's lexer and the view's parser (``) validate non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors REUSE Software diff --git a/docs/mkdocs/docs/home/license.md b/docs/mkdocs/docs/home/license.md index 714e971ed..c0bb94633 100644 --- a/docs/mkdocs/docs/home/license.md +++ b/docs/mkdocs/docs/home/license.md @@ -22,8 +22,8 @@ The class contains a port of the shortest double-to-decimal conversion of [Żmij The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/). -The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors +The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, its digit comparison for long numbers, and its parsing of eight digits at a time from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors The view's parser (``) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks. -The view's parser (``) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors +The class's lexer and the view's parser (``) validate non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors diff --git a/include/nlohmann/detail/conversions/to_chars.hpp b/include/nlohmann/detail/conversions/to_chars.hpp index a977e797b..6cfb9b8e7 100644 --- a/include/nlohmann/detail/conversions/to_chars.hpp +++ b/include/nlohmann/detail/conversions/to_chars.hpp @@ -19,6 +19,11 @@ #include // conditional +// The shortest-digit conversion of doubles (see zmij.hpp) and the writing of +// its digits, including the conversion of the digits to text by Xiang JunBo +// and the SIMD instruction sequence by Dougall Johnson, are ported from Zmij +// by Victor Zverovich (https://github.com/vitaut/zmij, MIT license). +// // SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16 // digits of a double at once (unless JSON_NO_SIMD selects the portable code) #if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))) diff --git a/include/nlohmann/detail/input/number_parse.hpp b/include/nlohmann/detail/input/number_parse.hpp index dce0105de..852e1a6c8 100644 --- a/include/nlohmann/detail/input/number_parse.hpp +++ b/include/nlohmann/detail/input/number_parse.hpp @@ -44,6 +44,13 @@ // use std::from_chars or std::strtold. They are free functions so the lexer // stays focused on scanning (see lexer::convert_number()) and so that other // parsers of JSON text can convert tokens exactly like it does. +// +// The Eisel-Lemire conversion (eisel_lemire()), the digit comparison for the +// cases it cannot decide, and the eight-digits-at-a-time helpers +// (is_eight_digits(), parse_eight_digits()) are adapted from fast_float +// (https://github.com/fastfloat/fast_float, Daniel Lemire and contributors, +// available under the MIT, Apache 2.0, and Boost Software licenses; used here +// under the MIT license). The table of powers of five is in pow5_table.hpp. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail diff --git a/include/nlohmann/detail/input/simd_scan.hpp b/include/nlohmann/detail/input/simd_scan.hpp index 8caed1814..3abb911ee 100644 --- a/include/nlohmann/detail/input/simd_scan.hpp +++ b/include/nlohmann/detail/input/simd_scan.hpp @@ -30,6 +30,13 @@ // NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target // attribute and then reject the SSSE3 intrinsics) keeps the check portable // without disabling the SSE2 code. +// +// The vector UTF-8 check (utf8_lookup4) is adapted from simdjson +// (https://github.com/simdjson/simdjson, Daniel Lemire, Geoff Langdale, John +// Keiser, and contributors, available under the Apache 2.0 and MIT licenses; +// used here under the MIT license): the "lookup4" algorithm and tables of +// J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per +// Byte" (2021). #if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #include #define JSON_SCAN_NEON 1 diff --git a/include/nlohmann/detail/view/simd.hpp b/include/nlohmann/detail/view/simd.hpp index dbfbdc7a9..9dede9b09 100644 --- a/include/nlohmann/detail/view/simd.hpp +++ b/include/nlohmann/detail/view/simd.hpp @@ -29,6 +29,13 @@ // NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE (set by the GCC module interface, where GCC // ignores the target attribute and then rejects the SSSE3 intrinsics) keeps the // check portable without disabling the SSE2 code. +// +// The vector UTF-8 check (utf8_lookup4) is adapted from simdjson +// (https://github.com/simdjson/simdjson, Daniel Lemire, Geoff Langdale, John +// Keiser, and contributors, available under the Apache 2.0 and MIT licenses; +// used here under the MIT license): the "lookup4" algorithm and tables of +// J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per +// Byte" (2021). #if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN #include #define NLOHMANN_VIEW_NEON 1 diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 98d6045fa..10b42dac1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10970,6 +10970,13 @@ NLOHMANN_JSON_NAMESPACE_END // use std::from_chars or std::strtold. They are free functions so the lexer // stays focused on scanning (see lexer::convert_number()) and so that other // parsers of JSON text can convert tokens exactly like it does. +// +// The Eisel-Lemire conversion (eisel_lemire()), the digit comparison for the +// cases it cannot decide, and the eight-digits-at-a-time helpers +// (is_eight_digits(), parse_eight_digits()) are adapted from fast_float +// (https://github.com/fastfloat/fast_float, Daniel Lemire and contributors, +// available under the MIT, Apache 2.0, and Boost Software licenses; used here +// under the MIT license). The table of powers of five is in pow5_table.hpp. NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -12078,6 +12085,13 @@ NLOHMANN_JSON_NAMESPACE_END // NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target // attribute and then reject the SSSE3 intrinsics) keeps the check portable // without disabling the SSE2 code. +// +// The vector UTF-8 check (utf8_lookup4) is adapted from simdjson +// (https://github.com/simdjson/simdjson, Daniel Lemire, Geoff Langdale, John +// Keiser, and contributors, available under the Apache 2.0 and MIT licenses; +// used here under the MIT license): the "lookup4" algorithm and tables of +// J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per +// Byte" (2021). #if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #include #define JSON_SCAN_NEON 1 @@ -32495,6 +32509,11 @@ NLOHMANN_JSON_NAMESPACE_END #include // conditional +// The shortest-digit conversion of doubles (see zmij.hpp) and the writing of +// its digits, including the conversion of the digits to text by Xiang JunBo +// and the SIMD instruction sequence by Dougall Johnson, are ported from Zmij +// by Victor Zverovich (https://github.com/vitaut/zmij, MIT license). +// // SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16 // digits of a double at once (unless JSON_NO_SIMD selects the portable code) #if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))) diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 90593b2d1..7992e2c78 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -688,6 +688,13 @@ NLOHMANN_JSON_NAMESPACE_END // NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE (set by the GCC module interface, where GCC // ignores the target attribute and then rejects the SSSE3 intrinsics) keeps the // check portable without disabling the SSE2 code. +// +// The vector UTF-8 check (utf8_lookup4) is adapted from simdjson +// (https://github.com/simdjson/simdjson, Daniel Lemire, Geoff Langdale, John +// Keiser, and contributors, available under the Apache 2.0 and MIT licenses; +// used here under the MIT license): the "lookup4" algorithm and tables of +// J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per +// Byte" (2021). #if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN #include #define NLOHMANN_VIEW_NEON 1 From 0b91f9ddd8314b3739f1a9ddf929eb5501e39136 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 30 Oct 2026 15:17:55 +0100 Subject: [PATCH 37/63] json_view: check_utf8 skips well-formed runs with the parser's string scan (#5926) * json_view: check_utf8 skips well-formed runs with the parser's string scan Strings of 32 bytes or more that are written into an editable document are validated with scan_string_run(), which skips well-formed UTF-8 16 bytes at a time; the byte-wise check runs only from where the scan found ill-formed UTF-8, to report the same type_error.316. Shorter strings keep the byte-wise check (inlined with a constant start, so that this path is the code it was), where the vector scan costs more than it saves. set()/push_back() of strings of 32 bytes or more take 0.29-0.76 of the time (x86-64 GCC, averaged over code layouts) and 0.25-0.80 (AArch64 clang); shorter strings take 1.5-4 % fewer instructions; the json_view rows of bench_edit execute at most 0.04 % more instructions. Signed-off-by: Niels Lohmann * tests: avoid sign conversion in json_view_edit UTF-8 check Signed-off-by: Niels Lohmann * json_view: use while in check_utf8_bytewise Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/edit.hpp | 43 ++++++++- single_include/nlohmann/json_view.hpp | 44 +++++++++- tests/src/unit-json_view_edit.cpp | 120 ++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 6 deletions(-) diff --git a/include/nlohmann/detail/view/edit.hpp b/include/nlohmann/detail/view/edit.hpp index f47bf5985..c542c1141 100644 --- a/include/nlohmann/detail/view/edit.hpp +++ b/include/nlohmann/detail/view/edit.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include NLOHMANN_JSON_NAMESPACE_BEGIN @@ -46,10 +47,9 @@ struct first_true : std::integral_constant < int, 1 + firs /// (type_error.316 with the same message), so that an editable document /// only holds valid UTF-8: the error is at the first byte that no /// well-formed sequence can continue with (Unicode, Table 3-7). -inline void check_utf8(const char* s, std::size_t n) +NLOHMANN_VIEW_ALWAYS_INLINE void check_utf8_bytewise(const unsigned char* p, std::size_t n, std::size_t i) { - const auto* const p = reinterpret_cast(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - for (std::size_t i = 0; i < n;) + while (i < n) { const unsigned char c = p[i]; if (c < 0x80) @@ -79,6 +79,43 @@ inline void check_utf8(const char* s, std::size_t n) } } +/// The long-string path of check_utf8(): skips well-formed runs with the +/// parser's string scan (which stops at a quote, a backslash, or a control +/// character, all legal here, and at ill-formed UTF-8); the byte-wise check +/// runs only from where the scan found ill-formed UTF-8, to report it. +NLOHMANN_VIEW_NOINLINE inline void check_utf8_scan(const unsigned char* p, std::size_t n) +{ + const unsigned char* const e = p + n; + const unsigned char* q = p; + for (;;) + { + q = scan_string_run(q, e); + if (q == e) + { + return; + } + if (*q >= 0x80) + { + check_utf8_bytewise(p, n, static_cast(q - p)); + return; // not reached: the scan stopped at ill-formed UTF-8 + } + ++q; // a quote, backslash, or control character + } +} + +inline void check_utf8(const char* s, std::size_t n) +{ + const auto* const p = reinterpret_cast(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + if (NLOHMANN_VIEW_UNLIKELY(n >= 32)) + { + check_utf8_scan(p, n); + return; + } + // short strings: the vector scan costs more than it saves; (the loop is + // inlined with a constant start, so this path is the plain byte-wise check) + check_utf8_bytewise(p, n, 0); +} + /*! @brief the edits of an editable basic_json_document diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 7992e2c78..20de9f188 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -3765,6 +3765,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ @@ -3838,10 +3840,9 @@ struct first_true : std::integral_constant < int, 1 + firs /// (type_error.316 with the same message), so that an editable document /// only holds valid UTF-8: the error is at the first byte that no /// well-formed sequence can continue with (Unicode, Table 3-7). -inline void check_utf8(const char* s, std::size_t n) +NLOHMANN_VIEW_ALWAYS_INLINE void check_utf8_bytewise(const unsigned char* p, std::size_t n, std::size_t i) { - const auto* const p = reinterpret_cast(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - for (std::size_t i = 0; i < n;) + while (i < n) { const unsigned char c = p[i]; if (c < 0x80) @@ -3871,6 +3872,43 @@ inline void check_utf8(const char* s, std::size_t n) } } +/// The long-string path of check_utf8(): skips well-formed runs with the +/// parser's string scan (which stops at a quote, a backslash, or a control +/// character, all legal here, and at ill-formed UTF-8); the byte-wise check +/// runs only from where the scan found ill-formed UTF-8, to report it. +NLOHMANN_VIEW_NOINLINE inline void check_utf8_scan(const unsigned char* p, std::size_t n) +{ + const unsigned char* const e = p + n; + const unsigned char* q = p; + for (;;) + { + q = scan_string_run(q, e); + if (q == e) + { + return; + } + if (*q >= 0x80) + { + check_utf8_bytewise(p, n, static_cast(q - p)); + return; // not reached: the scan stopped at ill-formed UTF-8 + } + ++q; // a quote, backslash, or control character + } +} + +inline void check_utf8(const char* s, std::size_t n) +{ + const auto* const p = reinterpret_cast(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + if (NLOHMANN_VIEW_UNLIKELY(n >= 32)) + { + check_utf8_scan(p, n); + return; + } + // short strings: the vector scan costs more than it saves; (the loop is + // inlined with a constant start, so this path is the plain byte-wise check) + check_utf8_bytewise(p, n, 0); +} + /*! @brief the edits of an editable basic_json_document diff --git a/tests/src/unit-json_view_edit.cpp b/tests/src/unit-json_view_edit.cpp index 4a6fbe69d..1b1a24ec8 100644 --- a/tests/src/unit-json_view_edit.cpp +++ b/tests/src/unit-json_view_edit.cpp @@ -941,3 +941,123 @@ TEST_CASE("json_view edits: duplicate keys") CHECK(d.root()["o"]["d"].get() == 0); } } + +#if !defined(JSON_NOEXCEPTION) +TEST_CASE("json_view edits: check_utf8 agrees with the byte-wise check") +{ + namespace dv = nlohmann::detail::view; + + // "ok" or the exception message + const auto run = [](bool fast, const std::string & s) -> std::string + { + try + { + if (fast) + { + dv::check_utf8(s.data(), s.size()); + } + else + { + dv::check_utf8_bytewise(reinterpret_cast(s.data()), s.size(), 0); + } + } + catch (const json::type_error& e) + { + return {e.what()}; + } + return {"ok"}; + }; + std::size_t errors = 0; + const auto same = [&](const std::string & s) + { + const std::string a = run(true, s); + const std::string b = run(false, s); + if (a != b) + { + INFO("fast: " << a << ", byte-wise: " << b << ", length " << s.size()); + CHECK(a == b); + } + errors += b != "ok" ? 1u : 0u; + }; + + const std::vector pieces = + { + "a", "Z", " ", "\"", "\\", "\n", "\x01", "\x7f", "\xc3\xa4", "\xe4\xb8\xad", "\xf0\x9f\x98\x80", + "\x80", "\xbf", "\xc0\x80", "\xc1\xbf", "\xc2", "\xe0\x80\x80", "\xe0\x9f\xbf", "\xed\xa0\x80", "\xed\xbf\xbf", + "\xf0\x80\x80\x80", "\xf0\x8f\xbf\xbf", "\xf4\x90\x80\x80", "\xf4\x8f\xbf\xbf", "\xf5", "\xf8", "\xff", "\xfe", + "\xe4\xb8", "\xf0\x9f\x98", "\xf0\x9f", "\xf0" + }; + + // every piece behind prefixes of every length, and followed by more bytes + for (std::size_t prefix = 0; prefix <= 64; ++prefix) + { + for (const auto& piece : pieces) + { + same(std::string(prefix, 'x') + piece); + same(std::string(prefix, 'x') + piece + "yz"); + same(std::string(prefix, '"') + piece); + same(std::string(prefix, '\xc3') + piece); + same(std::string(prefix / 2, '\xc3').append(prefix / 2, '\xa4') + piece); + same(std::string(prefix, 'x') + "\xe4\xb8\xad" + piece); + } + } + // an invalid byte at every position of valid strings of every length + for (std::size_t len = 0; len <= 64; ++len) + { + const std::string ascii(len, 'a'); + std::string mixed; + while (mixed.size() < len) + { + if (mixed.size() % 7 == 0) + { + mixed += "\xc3\xa4"; + } + else if (mixed.size() % 11 == 0) + { + mixed += "\xf0\x9f\x98\x80"; + } + else + { + mixed += "a"; + } + } + same(ascii); + same(mixed); + for (std::size_t pos = 0; pos < len; ++pos) + { + for (const char bad : + {'\x80', '\xc0', '\xc2', '\xe0', '\xf4', '\xff', '"', '\\', '\x1f' + }) + { + std::string s = ascii; + s[pos] = bad; + same(s); + s = mixed; + s[pos] = bad; + same(s); + } + } + } + // random bytes, biased to the interesting ones + std::mt19937 gen(5869); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible + static const std::array interesting = {{0x00, 0x1f, 0x22, 0x5c, 0x7f, 0x80, 0x8f, 0x90, 0x9f, 0xa0, 0xbf, 0xc0, 0xc2, 0xdf, 0xe0, 0xe1, 0xed, 0xef, 0xf0, 0xf1, 0xf4, 0xf5, 0xff}}; + for (int i = 0; i < 100000; ++i) + { + std::string s(gen() % 80, 'a'); + for (auto& c : s) + { + const unsigned r = gen() % 8; + if (r == 0) + { + c = static_cast(gen() & 0xff); + } + else if (r == 1) + { + c = static_cast(interesting[gen() % interesting.size()]); + } + } + same(s); + } + CHECK(errors > 1000); +} +#endif From 28f71914b5d7352dbd25074a2e4636d64244cef0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:18:44 +0100 Subject: [PATCH 38/63] Bump egor-tensin/setup-mingw (#5963) Bumps [egor-tensin/setup-mingw](https://github.com/egor-tensin/setup-mingw) from 41b837e47d7f85214629d255b9c4bc3fcbe9fd63 to d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931. - [Release notes](https://github.com/egor-tensin/setup-mingw/releases) - [Changelog](https://github.com/egor-tensin/setup-mingw/blob/master/CHANGELOG.md) - [Commits](https://github.com/egor-tensin/setup-mingw/compare/41b837e47d7f85214629d255b9c4bc3fcbe9fd63...d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931) --- updated-dependencies: - dependency-name: egor-tensin/setup-mingw dependency-version: d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6f1cf8cae..59647ed94 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -28,7 +28,7 @@ jobs: with: persist-credentials: false - name: Set up MinGW - uses: egor-tensin/setup-mingw@41b837e47d7f85214629d255b9c4bc3fcbe9fd63 # v3.0 + uses: egor-tensin/setup-mingw@d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931 # v3.0 with: platform: ${{ matrix.architecture }} version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14 @@ -153,7 +153,7 @@ jobs: - name: Install Clang run: curl -fsSL -o LLVM${{ matrix.version }}.exe https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.version }}/LLVM-${{ matrix.version }}-win64.exe ; 7z x LLVM${{ matrix.version }}.exe -y -o"C:/Program Files/LLVM" - name: Set up MinGW - uses: egor-tensin/setup-mingw@41b837e47d7f85214629d255b9c4bc3fcbe9fd63 # v3.0 + uses: egor-tensin/setup-mingw@d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931 # v3.0 with: platform: x64 version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14 From c451fac1bcbb6f03d62a169c25487ae442ea84da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:18:56 +0100 Subject: [PATCH 39/63] Bump step-security/harden-runner from 2.22.1 to 2.22.2 (#5962) Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.22.1 to 2.22.2. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](https://github.com/step-security/harden-runner/compare/ccd8616d44fd3846e67624a50d5aad6d37bf2d25...dc2769abcc0fcfbadfc1df54081c1608579ef6b2) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.22.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cancel_closed_pr_runs.yml | 2 +- .github/workflows/check_amalgamation.yml | 4 ++-- .github/workflows/check_docs_links.yml | 2 +- .github/workflows/cifuzz.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/comment_check_amalgamation.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/flawfinder.yml | 2 +- .github/workflows/json_view_benchmarks.yml | 2 +- .github/workflows/labeler.yml | 2 +- .github/workflows/publish_documentation.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/semgrep.yml | 2 +- .github/workflows/stale.yml | 2 +- .github/workflows/ubuntu.yml | 10 +++++----- 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/cancel_closed_pr_runs.yml b/.github/workflows/cancel_closed_pr_runs.yml index 99010d81b..93d65312d 100644 --- a/.github/workflows/cancel_closed_pr_runs.yml +++ b/.github/workflows/cancel_closed_pr_runs.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index 910eeef0f..66af126fa 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit @@ -46,7 +46,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/check_docs_links.yml b/.github/workflows/check_docs_links.yml index bf174c245..12f5ad074 100644 --- a/.github/workflows/check_docs_links.yml +++ b/.github/workflows/check_docs_links.yml @@ -20,7 +20,7 @@ jobs: timeout-minutes: 30 steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 43efd0e16..f5feb9a8f 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 48b8db457..b89043b10 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/comment_check_amalgamation.yml b/.github/workflows/comment_check_amalgamation.yml index fc96d5c18..04bfaae88 100644 --- a/.github/workflows/comment_check_amalgamation.yml +++ b/.github/workflows/comment_check_amalgamation.yml @@ -20,7 +20,7 @@ jobs: pull-requests: write steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 333546437..bd2d295a4 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/flawfinder.yml b/.github/workflows/flawfinder.yml index 0c6e5fa9c..84bc4d6ba 100644 --- a/.github/workflows/flawfinder.yml +++ b/.github/workflows/flawfinder.yml @@ -31,7 +31,7 @@ jobs: security-events: write steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/json_view_benchmarks.yml b/.github/workflows/json_view_benchmarks.yml index 305f1ef35..d87357c42 100644 --- a/.github/workflows/json_view_benchmarks.yml +++ b/.github/workflows/json_view_benchmarks.yml @@ -54,7 +54,7 @@ jobs: timeout-minutes: 60 steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 21cf6c99c..35549b4e0 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/publish_documentation.yml b/.github/workflows/publish_documentation.yml index 979f2937f..12c55a0d4 100644 --- a/.github/workflows/publish_documentation.yml +++ b/.github/workflows/publish_documentation.yml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index dbebf3e23..4900d0607 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 203a1416f..6b8713b41 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-26.04 steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 10d527504..41928ab20 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 8e7d8e0b9..a8cd05766 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -80,7 +80,7 @@ jobs: runs-on: ubuntu-26.04 steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit @@ -112,7 +112,7 @@ jobs: target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind] steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit @@ -178,7 +178,7 @@ jobs: runs-on: ubuntu-26.04 steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit @@ -433,7 +433,7 @@ jobs: runs-on: ubuntu-26.04 steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit @@ -456,7 +456,7 @@ jobs: target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid] steps: - name: Harden Runner - uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 + uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 with: egress-policy: audit From e2cb15c479404f0f4590139529d8b97b66c97162 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:19:08 +0100 Subject: [PATCH 40/63] Bump the codeql-action group across 1 directory with 4 updates (#5961) Bumps the codeql-action group with 4 updates in the / directory: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.39.0 to 4.39.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) Updates `github/codeql-action/autobuild` from 4.39.0 to 4.39.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) Updates `github/codeql-action/analyze` from 4.39.0 to 4.39.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) Updates `github/codeql-action/upload-sarif` from 4.39.0 to 4.39.1 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action - dependency-name: github/codeql-action/autobuild dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action - dependency-name: github/codeql-action/init dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: codeql-action ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/flawfinder.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/semgrep.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b89043b10..61036d79d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,14 +38,14 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 + uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: languages: c-cpp # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 + uses: github/codeql-action/autobuild@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 + uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 diff --git a/.github/workflows/flawfinder.yml b/.github/workflows/flawfinder.yml index 84bc4d6ba..b3e2511ae 100644 --- a/.github/workflows/flawfinder.yml +++ b/.github/workflows/flawfinder.yml @@ -48,6 +48,6 @@ jobs: output: 'flawfinder_results.sarif' - name: Upload analysis results to GitHub Security tab - uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: sarif_file: ${{github.workspace}}/flawfinder_results.sarif diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 4900d0607..14b987200 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -80,6 +80,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: sarif_file: results.sarif diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 6b8713b41..8b1e6ed5e 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -65,7 +65,7 @@ jobs: # Upload SARIF file generated in previous step - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: sarif_file: semgrep.sarif if: always() From ea03d601af66c03ce625f73d853968166be6eac4 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 30 Oct 2026 15:19:42 +0100 Subject: [PATCH 41/63] json_view: comparison without temporaries and a faster image float check (#5890) * Compare json_view values without a vector per container and a basic_json per number operator== of views (view/view, view/json, and editable documents) built two vectors of members for every object it entered, sorted them with stable_sort for every object, and converted each number to a basic_json value. It now: - keeps the members of all open objects in one vector (reserved with geometric growth, so that nested objects do not reallocate), and orders them by insertion sort for up to 16 members, or not at all when the keys ascend already; - compares numbers of one type directly, and builds a basic_json only for numbers of different types; - compares short texts (keys, numbers, most strings) with two overlapping loads (bytes_equal(), in node.hpp); - first walks both documents in lockstep, in the order of their text, which finds most pairs of equal values without the work of the comparison. The walk only enters an object if the comparison would visit its members in the same order (the object type keeps the order, the keys ascend, or all values are scalars), so a difference is never found later than before, and it records the containers it found different, so that the comparison does not walk them again; - rejects values of different types and arrays of different sizes before any of this, and, for an object type that keeps the order, objects whose first (up to four) members have different keys, which ends a comparison that differs near the start without preparing the members. Results are unchanged: numbers compare by value across types, duplicate keys resolve as parse() resolves them, and objects compare in document order for ordered_json and by key otherwise. Instructions of one comparison of a document with an equal copy, macOS arm64 (develop -> now), json_document / ordered_json_document / editable: twitter 16.1M -> 7.0M 17.7M -> 1.5M 16.6M -> 7.3M citm 45.0M -> 3.4M 47.0M -> 3.2M 45.7M -> 4.3M canada 111.4M -> 13.7M 111.4M -> 13.7M 113.3M -> 15.0M jeopardy 26.2M -> 3.7M 27.3M -> 3.7M 26.6M -> 12.1M and of a view with a basic_json value: twitter 11.4M -> 5.9M 11.2M -> 7.3M 11.6M -> 5.9M citm 33.9M -> 9.7M 32.6M -> 9.1M 34.6M -> 9.8M canada 92.9M -> 32.1M 92.9M -> 32.1M 93.9M -> 32.8M and Linux instruction counts (GCC 13 / Clang 18) of the same comparisons: document with an equal copy json_document ordered_json_document editable twitter -49 % / -52 % -89 % / -89 % -48 % / -50 % citm -90 % / -89 % -91 % / -90 % -88 % / -87 % jeopardy (4,000 questions) -85 % / -85 % -86 % / -86 % -46 % / -49 % view with a basic_json value twitter -41 % / -45 % -28 % / -35 % -45 % / -44 % citm -63 % / -65 % -63 % / -66 % -65 % / -64 % jeopardy -39 % / -43 % -14 % / -24 % -44 % / -43 % A sweep that places one difference at about 2,100 positions of eight documents (a token at an even spacing, the first and last tokens, the first, middle and last member of the root removed, one appended, the document wrapped) in five kinds of comparison (view/view of read-only and of editable documents, view/json, json_document, editable/view) finds the same results as before and no placement slower on the Mac (Clang); on Linux no placement is slower than 3 % (Clang and GCC). Signed-off-by: Niels Lohmann * Check the float tokens of a json_document image without a sort and a scan of every character load(image_check::full) checked every float token of an image by sorting the token ranges, scanning the token character by character, and comparing the digit layout with the node's. Now: - the ranges are sorted only if they are not sorted already (save() writes them in the order of the nodes, so they are); - a token of the shape [-]digits.digits whose digit counts are the ones the node records (nearly all of them) is accepted by checking its digits eight at a time, without the general scan; - the general scan skips runs of digits eight at a time. Damaged tokens are still rejected as before: the check accepts exactly the tokens that the general scan accepts. Instructions of load(image_check::full) (develop -> now): macOS arm64 canada 44.6M -> 30.9M (-31 %), jeopardy 11.3M -> 10.7M, citm 10.4M -> 10.0M, twitter 5.06M -> 4.94M Linux GCC 13 canada -50 %, jeopardy -43 %, twitter -31 %, citm -28 % Linux Clang 18 canada -42 %, jeopardy -48 %, twitter -35 %, citm -32 % image_check::bounds is unchanged (within 0.4 %). Signed-off-by: Niels Lohmann * Rename names that Flawfinder takes for the file function open() The comparison cursors' open() is now enter(), and a test's open and close strings are head and tail. Signed-off-by: Niels Lohmann * Fix the compiler and lint failures of the json_view comparison - all_digits(): the lambda declares its constant itself; MSVC does not let a lambda without captures read a constexpr local of the function - equal(): the frame's "in document order" index is a namespace-scope constant (document_order); the local constexpr npos in the default member initializers of the local struct crashed GCC 9 and MinGW GCC and was not found by MSVC - keys_differ_early() keeps the first keys in std::arrays (cpplint: variable-length arrays; clang-tidy: C arrays), and the basic_json side's first_keys() is not noexcept (key() may throw) - the numbers of one type are compared in an if chain instead of a nested conditional operator - the dump test avoids C arrays and temporary string concatenations View comparison instructions are unchanged (Apple clang, twitter, citm, canada: -0.1 % to +0.1 %). Signed-off-by: Niels Lohmann * Keep the byte comparison helpers in compare.hpp Address review comments: bytes_equal() and bytes_less() move from node.hpp to compare.hpp, their only user; key_less() no longer splits the comment of resolve_indices() from its function; and the loop counters of bytes_equal() and all_digits() are scoped to their loops. Signed-off-by: Niels Lohmann * Dispatch on the compile-time conditions of the json_view comparison MSVC (/W4 /WX) reported C4127 (conditional expression is constant) for the template parameter EA and the object type's ordering in an if condition; both are now tag-dispatched. Signed-off-by: Niels Lohmann * Remove a nested duplicate guard from the json_view image test clang-tidy reported the inner #if !defined(JSON_NOEXCEPTION) as redundant (the test case is in the same #if) and a find() != npos, which gets the NOLINT the other tests use. Signed-off-by: Niels Lohmann * Mark Infer's null dereference in materialize() as a false positive Infer (ci_infer, also red on develop since #5882) reports a null dereference in json_sax_dom_parser::null() called from materialize(). The member pointer the parser stores a value of an object in is set by the key() before it, so it is not null there. Signed-off-by: Niels Lohmann * Move the json_view comparison tests into their own file With the tests this branch adds, the object file of unit-json_view_dump.cpp got too large for the MinGW x64 assembler ("file too big"). The test case "json_view comparison" is now unit-json_view_compare.cpp, unchanged and also built with C++17. Signed-off-by: Niels Lohmann * Build the DOM parser with Visual Studio 2015 again Since #5882, the three VS 2015 jobs of AppVeyor fail on develop: - the detection of keys that can be indexed instantiated std::hash for every string type, which VS 2015 rejects with a static_assert for a type it does not hash (alt_string): std::hash is now looked at only for objects in a vector - the frame stack copied its frames with std::copy to a raw pointer, which VS 2015 reports as unsafe (C4996, an error with /WX): a loop now Signed-off-by: Niels Lohmann * json_view: no per-object allocations in ordered_json comparisons For an object type that keeps the order of its members (ordered_json), resolve_indices() allocated a vector per compared object, and resolve_members() two. They now use one working vector owned by equal(), whose capacity is reused, and resolve_indices() needs none when the keys are unique. resolve_members() sorts small objects by insertion, as the other paths do. Instructions per comparison (callgrind, GCC 13 and clang 18, x86-64): twitter, ordered_json view == ordered_json: 0.939 / 0.941; 20000 small objects with a duplicate key: 0.741-0.768; all other rows (json, citm_catalog, canada, view == view) within 0.988-1.003. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/compare.hpp | 1022 +++++++++++- include/nlohmann/detail/view/image.hpp | 118 +- include/nlohmann/json_view.hpp | 4 + single_include/nlohmann/json_view.hpp | 1828 ++++++++++++++++------ tests/src/unit-json_view_compare.cpp | 364 +++++ tests/src/unit-json_view_dump.cpp | 95 -- tests/src/unit-json_view_image.cpp | 58 + 7 files changed, 2858 insertions(+), 631 deletions(-) create mode 100644 tests/src/unit-json_view_compare.cpp diff --git a/include/nlohmann/detail/view/compare.hpp b/include/nlohmann/detail/view/compare.hpp index 8ca6058f0..96a14efbb 100644 --- a/include/nlohmann/detail/view/compare.hpp +++ b/include/nlohmann/detail/view/compare.hpp @@ -8,14 +8,22 @@ #pragma once -#include // sort, stable_sort +#include // is_sorted, sort, stable_sort +#include // array #include // size_t +#include // int64_t, uint32_t, uint64_t +#include // memcpy +#include // equal_to #include // string -#include // move, pair +#include // move, pair, swap #include // vector #include +#include #include +#include +#include +#include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -30,17 +38,355 @@ namespace view // value, at the position of the first occurrence), and in document order if // the object type keeps an order (ordered_json), by key otherwise. +/// whether n bytes are equal; the short texts of JSON (keys, numbers, most +/// strings) are compared by two overlapping loads instead of a library call +NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_equal(const char* x, const char* y, std::size_t n) noexcept +{ + const auto load8 = [](const char* p) + { + std::uint64_t v = 0; + std::memcpy(&v, p, sizeof(v)); + return v; + }; + const auto load4 = [](const char* p) + { + std::uint32_t v = 0; + std::memcpy(&v, p, sizeof(v)); + return v; + }; + if (n >= 8) + { + for (std::size_t i = 0; i + 8 < n; i += 8) + { + if (load8(x + i) != load8(y + i)) + { + return false; + } + } + return load8(x + n - 8) == load8(y + n - 8); + } + if (n >= 4) + { + return load4(x) == load4(y) && load4(x + n - 4) == load4(y + n - 4); + } + for (std::size_t i = 0; i < n; ++i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; +} + +/// whether the n1 bytes at x are before the n2 bytes at y (memcmp() order, then the shorter first); +/// the first difference is found eight bytes at a time +NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_less(const char* x, std::size_t n1, const char* y, std::size_t n2) noexcept +{ + const std::size_t n = n1 < n2 ? n1 : n2; + std::size_t i = 0; + for (; i + 8 <= n; i += 8) + { + const std::uint64_t u = read_eight_bytes(x + i); + const std::uint64_t v = read_eight_bytes(y + i); + if (u != v) + { + const auto shift = static_cast(count_trailing_zeros(u ^ v)) & ~7u; // (the first byte that differs) + return ((u >> shift) & 0xFFu) < ((v >> shift) & 0xFFu); + } + } + for (; i < n; ++i) + { + if (x[i] != y[i]) + { + return static_cast(x[i]) < static_cast(y[i]); + } + } + return n1 < n2; +} + +/// whether the key a is before the key b +template +NLOHMANN_VIEW_ALWAYS_INLINE bool key_less(const Key& a, const Key& b) noexcept +{ + return bytes_less(a.data(), a.size(), b.data(), b.size()); +} + +/// sorts the indices order[0..n) of the members m by key; equal keys stay in +/// document order (small objects with an insertion sort, which needs no memory) +template +void sort_by_key(std::uint32_t* order, std::size_t n, const Entry* m) +{ + for (std::size_t i = 0; i < n; ++i) + { + order[i] = static_cast(i); + } + if (n <= 16) + { + for (std::size_t i = 1; i < n; ++i) + { + const std::uint32_t x = order[i]; + std::size_t j = i; + for (; j > 0 && key_less(m[x].first, m[order[j - 1]].first); --j) + { + order[j] = order[j - 1]; + } + order[j] = x; + } + } + else + { + std::stable_sort(order, order + n, [m](std::uint32_t x, std::uint32_t y) + { + return key_less(m[x].first, m[y].first); + }); + } +} + +/// The members of an object as parse() keeps them: one per key, the last +/// value at the position of the first occurrence, in the order of the +/// comparison (sorted by key, or, if the object type keeps the order, the order +/// of first occurrence). The members m[0..n) are in document order; this +/// appends to out the indices of the member of each key that has its value +/// (its last one), in the order of the comparison. Most objects have unique +/// keys. scratch is working memory, kept by the caller so that its capacity +/// is reused. +template +void resolve_indices(const Entry* m, std::size_t n, bool ordered, std::vector& out, std::vector& scratch) +{ + const std::size_t start = out.size(); + out.resize(start + n); + std::uint32_t* const order = out.data() + start; + sort_by_key(order, n, m); + if (!ordered) + { + // the last of each run of equal keys + std::size_t w = 0; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && m[order[last + 1]].first == m[order[i]].first) + { + ++last; + } + order[w++] = order[last]; + i = last + 1; + } + out.resize(start + w); + return; + } + // (document order) the value of the last occurrence at the first occurrence of each key + constexpr std::uint32_t none = 0xFFFFFFFFu; + bool any = false; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && m[order[last + 1]].first == m[order[i]].first) + { + ++last; + } + if (last != i) + { + if (!any) + { + scratch.assign(n, none); + for (std::size_t k = 0; k < i; ++k) + { + scratch[order[k]] = order[k]; + } + any = true; + } + scratch[order[i]] = order[last]; // (stable: order[i] is the first occurrence) + } + else if (any) + { + scratch[order[i]] = order[i]; + } + i = last + 1; + } + if (!any) + { + // unique keys: document order + for (std::size_t i = 0; i < n; ++i) + { + order[i] = static_cast(i); + } + return; + } + std::size_t w = 0; + for (std::size_t i = 0; i < n; ++i) + { + if (scratch[i] != none) + { + order[w++] = scratch[i]; + } + } + out.resize(start + w); +} + +/// whether the keys of n members are the same, in the same order +template +bool same_keys(const EntryA* a, const EntryB* b, std::size_t n) +{ + for (std::size_t i = 0; i < n; ++i) + { + if (!(a[i].first == b[i].first)) + { + return false; + } + } + return true; +} + +/// The members of an object as parse() keeps them (one per key: the last +/// value at the position of the first occurrence), sorted by key or, if the +/// object type keeps the order, in the order of first occurrence. The +/// members are given in document order, at the end of m (from the position +/// base on); most objects have unique keys, in which case they are only sorted +/// (for small objects with an insertion sort, which keeps equal keys in order +/// and needs no memory). scratch is working memory, kept by the caller. +template +void resolve_members(std::vector>& m, std::size_t base, bool ordered, std::vector& scratch) +{ + using entry = std::pair; + const std::size_t n = m.size() - base; + entry* const first = m.data() + base; + if (ordered) + { + // (document order) later values of a key go to its first occurrence; + // scratch holds the order by key, then whether a member is dropped + constexpr std::uint32_t dropped = 1; + scratch.assign(2 * n, 0); + std::uint32_t* const order = scratch.data(); + std::uint32_t* const drop = order + n; + sort_by_key(order, n, first); + bool any = false; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && first[order[last + 1]].first == first[order[i]].first) + { + ++last; + } + if (last != i) + { + first[order[i]].second = first[order[last]].second; + for (std::size_t k = i + 1; k <= last; ++k) + { + drop[order[k]] = dropped; + } + any = true; + } + i = last + 1; + } + if (any) + { + std::size_t w = 0; + for (std::size_t i = 0; i < n; ++i) + { + if (drop[i] != dropped) + { + if (w != i) + { + first[w] = std::move(first[i]); + } + ++w; + } + } + m.erase(m.begin() + static_cast(base + w), m.end()); + } + return; + } + // sorted by key; equal keys stay in document order + if (n <= 16) + { + for (std::size_t i = 1; i < n; ++i) + { + entry x = std::move(first[i]); + std::size_t j = i; + for (; j > 0 && key_less(x.first, first[j - 1].first); --j) + { + first[j] = std::move(first[j - 1]); + } + first[j] = std::move(x); + } + } + else + { + std::stable_sort(first, first + n, [](const entry & x, const entry & y) + { + return key_less(x.first, y.first); + }); + } + // the first key of a run with the last value + std::size_t w = 0; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && first[last + 1].first == first[i].first) + { + ++last; + } + if (w != i || last != i) + { + first[w] = entry(first[i].first, std::move(first[last].second)); + } + ++w; + i = last + 1; + } + m.erase(m.begin() + static_cast(base + w), m.end()); +} + +/// room for k more entries, growing geometrically (reserve(size() + k) alone would +/// reallocate for each object nested in another) +template +void reserve_more(std::vector& v, std::size_t k) +{ + const std::size_t needed = v.size() + k; + if (needed > v.capacity()) + { + v.reserve((std::max)(needed, 2 * v.capacity())); + } +} + /// one side of a comparison: a view template class view_side { public: using string_view_t = typename View::string_view_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using nav = navigation; + + /// the position in the elements of an array + struct cursor + { + const node* pos = nullptr; + }; + + view_side() noexcept + : m_view() + {} explicit view_side(const View& v) noexcept : m_view(v) {} + /// the document and the node of the view (nullptr: a discarded view) + const document_data& document() const noexcept + { + return *m_view.m_doc; + } + + const node* value_node() const noexcept + { + return m_view.m_node; + } + + static constexpr bool is_editable = View::is_editable; + value_t type() const noexcept { return m_view.type(); @@ -56,6 +402,32 @@ class view_side return m_view.get_string(); } + bool boolean() const noexcept + { + return (m_view.m_node->flags & node_flags::is_true) != 0; + } + + number_integer_t integer() const noexcept + { + return static_cast(static_cast(integer_bits(*m_view.m_node))); + } + + number_unsigned_t unsigned_integer() const noexcept + { + return static_cast(integer_bits(*m_view.m_node)); + } + + number_float_t floating() const + { + return float_value(*m_view.m_doc, *m_view.m_node); + } + + /// the token of a float + string_view_t float_token() const noexcept + { + return string_view_t(m_view.m_doc->str(*m_view.m_node), m_view.m_node->len); + } + /// a number, boolean, or null as a basic_json value (no allocation) BasicJsonType scalar() const { @@ -80,60 +452,47 @@ class view_side } } - void elements(std::vector& out) const + /// start at the first element of this array + void enter(cursor& c) const noexcept { - out.reserve(m_view.size()); - for (const View e : m_view) + c.pos = nav::first(*m_view.m_doc, m_view.m_node); + } + + /// the element at c (and advance) + view_side take(cursor& c) const noexcept + { + const node* const v = nav::value(c.pos); + c.pos = document_data::after(c.pos); + return view_side(View(m_view.m_doc, v)); + } + + /// the keys of the first n members of this object (n <= its size), in document order + void first_keys(string_view_t* out, std::size_t n) const noexcept + { + const node* pos = nav::first(*m_view.m_doc, m_view.m_node); + for (std::size_t i = 0; i < n; ++i) { - out.emplace_back(e); + out[i] = string_view_t(m_view.m_doc->str(*pos), pos->len); + pos = document_data::after(pos + 1); } } - /// the members as parse() keeps them: one per key, the last value at the - /// position of the first occurrence; in that order, or sorted by key - void members(std::vector>& out, bool ordered) const + /// append the members to out, in document order; whether their keys ascend + /// (then they are unique, and in the order of the comparison of a std::map) + bool members(std::vector>& out, bool /*ordered*/) const { - struct member + const std::size_t base = out.size(); + reserve_more(out, m_view.m_node->len); + bool sorted = true; + const node* pos = nav::first(*m_view.m_doc, m_view.m_node); + for (std::size_t i = m_view.m_node->len; i != 0; --i) { - string_view_t key; - View value; - std::size_t position; - }; - std::vector all; - all.reserve(m_view.size()); - std::size_t position = 0; - for (auto it = m_view.begin(); it != m_view.end(); ++it) - { - all.push_back(member{it.key(), it.value(), position++}); - } - std::stable_sort(all.begin(), all.end(), [](const member & a, const member & b) - { - return a.key < b.key; - }); - std::vector unique; - unique.reserve(all.size()); - for (std::size_t i = 0; i < all.size();) - { - std::size_t last = i; - while (last + 1 < all.size() && all[last + 1].key == all[i].key) - { - ++last; - } - unique.push_back(member{all[i].key, all[last].value, all[i].position}); - i = last + 1; - } - if (ordered) - { - std::sort(unique.begin(), unique.end(), [](const member & a, const member & b) - { - return a.position < b.position; - }); - } - out.reserve(unique.size()); - for (const member& m : unique) - { - out.emplace_back(m.key, view_side(m.value)); + const string_view_t key(m_view.m_doc->str(*pos), pos->len); + sorted = sorted && (out.size() == base || key_less(out.back().first, key)); + out.emplace_back(key, view_side(View(m_view.m_doc, nav::value(pos + 1)))); + pos = document_data::after(pos + 1); } + return sorted; } private: @@ -146,6 +505,17 @@ class json_side { public: using string_view_t = StringView; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + /// the position in the elements of an array + struct cursor + { + typename BasicJsonType::const_iterator it{}; + }; + + json_side() noexcept = default; explicit json_side(const BasicJsonType& j) noexcept : m_json(&j) @@ -167,45 +537,428 @@ class json_side return string_view_t(s.data(), s.size()); } + bool boolean() const noexcept + { + return *m_json->template get_ptr(); + } + + number_integer_t integer() const noexcept + { + return *m_json->template get_ptr(); + } + + number_unsigned_t unsigned_integer() const noexcept + { + return *m_json->template get_ptr(); + } + + number_float_t floating() const noexcept + { + return *m_json->template get_ptr(); + } + BasicJsonType scalar() const { return *m_json; } - void elements(std::vector& out) const + void enter(cursor& c) const { - out.reserve(m_json->size()); - for (const auto& e : *m_json) + c.it = m_json->cbegin(); + } + + json_side take(cursor& c) const + { + const json_side r(*c.it); + ++c.it; + return r; + } + + /// the keys of the first n members of this object (n <= its size), in document order + void first_keys(string_view_t* out, std::size_t n) const + { + auto it = m_json->cbegin(); + for (std::size_t i = 0; i < n; ++i, ++it) { - out.emplace_back(e); + out[i] = string_view_t(it.key().data(), it.key().size()); } } - void members(std::vector>& out, bool ordered) const + /// append the members to out, in the order of the comparison (the keys of a + /// basic_json are unique); true + bool members(std::vector>& out, bool ordered) const { - out.reserve(m_json->size()); + const std::size_t base = out.size(); + reserve_more(out, m_json->size()); for (auto it = m_json->cbegin(); it != m_json->cend(); ++it) { out.emplace_back(string_view_t(it.key().data(), it.key().size()), json_side(it.value())); } if (!ordered) { - std::sort(out.begin(), out.end(), [](const std::pair& a, const std::pair& b) + const auto less = [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }); + return key_less(a.first, b.first); + }; + const auto first = out.begin() + static_cast(base); + if (!std::is_sorted(first, out.end(), less)) + { + std::sort(first, out.end(), less); + } + } + return true; + } + + private: + const BasicJsonType* m_json = nullptr; +}; + +/*! +@brief the values at two nodes, found equal by walking both documents in the +same order + +Two values are equal if they have the same type, the same number token or +string, the same elements, and the same members with the same keys in the same +order: a value that matches this way is equal under the rules above whatever +the object type and duplicate keys (the last value of a key then wins on +both sides, and their order of first occurrence is the same). The walk does +not find every equal pair (numbers spelled differently, members in another +order, duplicate keys with other values), only the ones that are the same +text; it stops at the first difference and then says nothing. Containers +nested deeper than the budget are not walked. + +The walk goes through the document in its order, the comparison that follows +goes through the members of an object by key. So that the walk never reaches +a difference later than the comparison would, and never costs more than it, +it enters an object only if the two orders agree (the keys ascend, or the +object type keeps the order) or if the walk is cheaper than preparing the +comparison of the object (all values are scalars). +*/ +/// the containers that lockstep found different, from the outermost to the +/// innermost: the comparison that goes on from there does not walk them again +struct lockstep_path +{ + /// the nesting depth up to which lockstep walks (the recursion is bounded) + static constexpr unsigned budget() noexcept + { + return 48; + } + + /// a pair of containers that differ; for arrays, the elements where the walk stopped + /// (the ones before are equal) + struct entry + { + const node* a; + const node* b; + const node* element_a; + const node* element_b; + std::uint32_t index; + }; + + /// the number of entries that a path can hold (a container deeper than the + /// budget of the walk is not entered) + static constexpr std::size_t levels() noexcept + { + return 50; + } + + explicit lockstep_path(entry* storage) noexcept + : nodes(storage) + {} + + entry* nodes; ///< levels() entries, written before they are read + unsigned length = 0; ///< pairs recorded by the last walk + unsigned next = 0; ///< the pair that the comparison enters next + const entry* resume = nullptr; ///< the entry of the pair that was just looked at, if it is in the path +}; + +template +class lockstep +{ + public: + lockstep(const document_data& a, const document_data& b, lockstep_path& path, bool ordered) noexcept + : m_a(a), m_b(b), m_path(path), m_ordered(ordered) + {} + + /// whether the values at a and b are the same (the nodes of the values, + /// not entries of a sequence) + bool same(const node* a, const node* b, unsigned budget) noexcept + { + if (a->kind != b->kind) + { + return false; + } + switch (static_cast(a->kind)) + { + case value_t::null: + return true; + case value_t::boolean: + return ((a->flags ^ b->flags) & node_flags::is_true) == 0; + case value_t::string: + return a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len); + case value_t::number_float: // (the token, and the digit layout that the value is read with: they only differ in a damaged image) + return a->extra == b->extra && a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len); + case value_t::number_integer: + case value_t::number_unsigned: + return integer_bits(*a) == integer_bits(*b); + case value_t::array: + if (budget == 0 || a->len != b->len) + { + return false; + } + { + lockstep_path::entry stop{a, b, nullptr, nullptr, 0}; + if (elements(a, b, budget - 1, stop)) + { + return true; + } + record(stop, budget); + return false; + } + case value_t::object: + if (budget == 0 || a->len != b->len) + { + return false; + } + if (!visits_in_order(a) || !members(a, b, budget - 1)) + { + record(lockstep_path::entry{a, b, nullptr, nullptr, 0}, budget); + return false; + } + return true; + case value_t::binary: // LCOV_EXCL_LINE (not in a document) + case value_t::discarded: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE } } private: - const BasicJsonType* m_json; + using nav_a = navigation; + using nav_b = navigation; + + /// note the containers a and b (at the nesting level that budget stands + /// for) as different + NLOHMANN_VIEW_NOINLINE void record(const lockstep_path::entry& stop, unsigned budget) const noexcept + { + const unsigned level = lockstep_path::budget() - budget; + m_path.nodes[level] = stop; + if (m_path.length == 0) + { + m_path.length = level + 1; // (the innermost container is the first to be left) + } + } + + /// whether the object @a a of a document that cannot be edited has scalar values only + static bool scalar_values_only(const node* a, std::false_type /*editable*/) noexcept + { + return a->next == 1 + (2 * static_cast(a->len)); + } + + /// (the nodes of an editable document can be elsewhere: not decided) + static bool scalar_values_only(const node* /*a*/, std::true_type /*editable*/) noexcept + { + return false; + } + + /// Whether the comparison that follows visits the members of the object a in the + /// order of the document: if the object type keeps the order, if the keys + /// ascend (it sorts by key), or if all values are scalars (then the walk costs + /// less than collecting and sorting the members, whichever member differs). + /// Otherwise a difference could be found by the walk later than by the + /// comparison, which is not worth the walk. + bool visits_in_order(const node* a) const noexcept + { + if (m_ordered || a->len < 2) + { + return true; + } + if (scalar_values_only(a, std::integral_constant {})) + { + return true; + } + const node* k = nav_a::first(m_a, a); + string_ref previous(m_a.str(*k), k->len); + for (std::uint32_t i = a->len - 1; i != 0; --i) + { + k = document_data::after(k + 1); + const string_ref key(m_a.str(*k), k->len); + if (!key_less(previous, key)) + { + return false; + } + previous = key; + } + return true; + } + + bool elements(const node* a, const node* b, unsigned budget, lockstep_path::entry& stop) noexcept + { + const node* pa = nav_a::first(m_a, a); + const node* pb = nav_b::first(m_b, b); + for (std::uint32_t i = a->len; i != 0; --i) + { + if (!same(nav_a::value(pa), nav_b::value(pb), budget)) + { + stop.element_a = pa; + stop.element_b = pb; + stop.index = a->len - i; + return false; + } + pa = document_data::after(pa); + pb = document_data::after(pb); + } + return true; + } + + bool members(const node* a, const node* b, unsigned budget) noexcept + { + const node* pa = nav_a::first(m_a, a); + const node* pb = nav_b::first(m_b, b); + for (std::uint32_t i = a->len; i != 0; --i) + { + if (pa->len != pb->len || !bytes_equal(m_a.str(*pa), m_b.str(*pb), pa->len)) + { + return false; + } + if (!same(nav_a::value(pa + 1), nav_b::value(pb + 1), budget)) + { + return false; + } + pa = document_data::after(pa + 1); + pb = document_data::after(pb + 1); + } + return true; + } + + const document_data& m_a; + const document_data& m_b; + lockstep_path& m_path; + bool m_ordered; ///< the object type keeps the order of the members }; +/// whether two views certainly have equal values (see lockstep); if not, the +/// path says where they differ +template +bool certainly_equal(const view_side& a, const view_side& b, lockstep_path& path, bool ordered) noexcept +{ + const node* const na = a.value_node(); + const node* const nb = b.value_node(); + if (na == nullptr || nb == nullptr) + { + return false; + } + if (na == nb) + { + return true; // (the same node of the same document: documents have distinct nodes) + } + path.resume = nullptr; + if (path.next < path.length && path.nodes[path.next].a == na && path.nodes[path.next].b == nb) + { + path.resume = &path.nodes[path.next]; + ++path.next; // (the walk found these different already) + return false; + } + path.length = 0; + const bool equal = lockstep::is_editable, view_side::is_editable>(a.document(), b.document(), path, ordered).same(na, nb, lockstep_path::budget()); + path.next = path.length != 0 ? 1 : 0; // (this pair is the first in the path) + path.resume = path.length != 0 ? &path.nodes[0] : nullptr; + return equal; +} + +/// (a view and a basic_json value: no walk) +template +bool certainly_equal(const A& /*a*/, const B& /*b*/, lockstep_path& /*path*/, bool /*ordered*/) noexcept +{ + return false; +} + +/// (arrays of two views) the elements where the walk stopped are the next pair: the ones before are equal +template +void resume_array(const lockstep_path& path, const view_side& a, const view_side& b, + typename view_side::cursor& cursor_a, typename view_side::cursor& cursor_b, std::size_t& next) noexcept +{ + if (path.resume != nullptr && path.resume->element_a != nullptr && path.resume->a == a.value_node() && path.resume->b == b.value_node()) + { + cursor_a.pos = path.resume->element_a; + cursor_b.pos = path.resume->element_b; + next = path.resume->index; + } +} + +template +void resume_array(const lockstep_path& /*path*/, const A& /*a*/, const B& /*b*/, CursorA& /*cursor_a*/, CursorB& /*cursor_b*/, std::size_t& /*next*/) noexcept +{} + +/// whether two floats are equal (the tokens of two views: equal text and digit layout is an equal value; they +/// only differ in a damaged image) +template +bool floats_equal(const view_side& a, const view_side& b) +{ + return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token()) || std::equal_to()(a.floating(), b.floating()); +} + +template +bool floats_equal(const A& a, const B& b) +{ + return std::equal_to()(a.floating(), b.floating()); +} + +/// Whether two objects of a type that keeps the order are different because of the keys of +/// their first members, found without the preparation of the comparison: the comparison goes +/// through the members in the order of the first occurrence of their keys, so if the first +/// members have the same distinct keys and then one pair of keys differs, where neither is +/// one of the keys before it, the objects are different. (The keys that are the same as an +/// earlier key are the duplicates that the comparison drops, so those are not decided here.) +template +bool keys_differ_early(const SideA& a, const SideB& b) +{ + using string_view_t = typename SideA::string_view_t; + constexpr std::size_t limit = 4; + const std::size_t n = (std::min)((std::min)(a.size(), b.size()), limit); + std::array ka{}; + std::array kb{}; + a.first_keys(ka.data(), n); + b.first_keys(kb.data(), n); + for (std::size_t i = 0; i < n; ++i) + { + if (ka[i] != kb[i]) + { + for (std::size_t k = 0; k < i; ++k) + { + if (ka[i] == ka[k] || kb[i] == ka[k]) + { + return false; // (a duplicate: not decided) + } + } + return true; + } + } + return false; +} + +/// keys_differ_early() for a type that keeps the order of the members +template +bool keys_differ_early(const SideA& a, const SideB& b, std::true_type /*ordered*/) +{ + return keys_differ_early(a, b); +} + +/// (a type that sorts the members by key: not decided early) +template +bool keys_differ_early(const SideA& /*a*/, const SideB& /*b*/, std::false_type /*ordered*/) noexcept +{ + return false; +} + inline bool is_number(value_t t) noexcept { return t == value_t::number_integer || t == value_t::number_unsigned || t == value_t::number_float; } +/// the index_a/index_b of a frame of equal() whose members are compared in document order +constexpr std::size_t document_order = static_cast(-1); + /// whether two sides are equal; iterative, so that the nesting depth is /// limited by memory only template @@ -216,20 +969,52 @@ bool equal(const A& a0, const B& b0) struct frame { - std::vector elements_a{}; - std::vector elements_b{}; - std::vector> members_a{}; - std::vector> members_b{}; + A array_a{}; + B array_b{}; + typename A::cursor cursor_a{}; + typename B::cursor cursor_b{}; + std::size_t base_a = 0; ///< the members of an object: from this position in members_a + std::size_t base_b = 0; + std::size_t index_a = document_order; ///< their order in the comparison: from this position in indices (document_order: as they are) + std::size_t index_b = document_order; + std::size_t index_mark = 0; ///< the size of indices before this frame bool object = false; std::size_t next = 0; + std::size_t count = 0; }; std::vector stack; + std::vector indices; // the order of the members of the open objects, where it is not their order in the document + std::vector scratch; // working memory of resolve_indices() and resolve_members() + // the members of the open objects (the ones of an object follow those of the objects around it) + std::vector> members_a; + std::vector> members_b; + std::array path_storage; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + lockstep_path path(path_storage.data()); + { + // (values that differ in type or in the size of an array need no walk) + const value_t first = a0.type(); + const value_t second = b0.type(); + if (first != second && !(is_number(first) && is_number(second)) && first != value_t::discarded && second != value_t::discarded) + { + return false; + } + if (first == value_t::array && second == value_t::array && a0.size() != b0.size()) + { + return false; + } + } A a = a0; B b = b0; + // (pairs of views that are certainly equal need no further look) + if (certainly_equal(a, b, path, ordered)) + { + return true; + } for (;;) { const value_t ta = a.type(); const value_t tb = b.type(); + const bool numbers = is_number(ta) && is_number(tb); if (ta == value_t::discarded || tb == value_t::discarded) { // basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) @@ -240,7 +1025,7 @@ bool equal(const A& a0, const B& b0) } else { - if (ta != tb && !(is_number(ta) && is_number(tb))) // numbers of different types can be equal + if (ta != tb && !numbers) // numbers of different types can be equal { return false; } @@ -257,31 +1042,98 @@ bool equal(const A& a0, const B& b0) { return false; } - frame f; + if (ta == value_t::object && keys_differ_early(a, b, std::integral_constant::value> {})) + { + return false; + } + stack.emplace_back(); + frame& f = stack.back(); f.object = ta == value_t::object; if (f.object) { - a.members(f.members_a, ordered); - b.members(f.members_b, ordered); - if (f.members_a.size() != f.members_b.size()) + f.base_a = members_a.size(); + f.base_b = members_b.size(); + f.index_mark = indices.size(); + const bool sorted_a = a.members(members_a, ordered); + const bool sorted_b = b.members(members_b, ordered); + const std::size_t size_a = members_a.size() - f.base_a; + const std::size_t size_b = members_b.size() - f.base_b; + std::size_t count_a = size_a; + std::size_t count_b = size_b; + if (!sorted_a && !sorted_b && size_a == size_b && same_keys(members_a.data() + f.base_a, members_b.data() + f.base_b, size_a)) + { + // the same keys in the same order: one order of the comparison for both + f.index_a = indices.size(); + f.index_b = f.index_a; + resolve_indices(members_a.data() + f.base_a, size_a, ordered, indices, scratch); + count_a = indices.size() - f.index_a; + count_b = count_a; + } + else + { + if (!sorted_a) + { + resolve_members(members_a, f.base_a, ordered, scratch); + count_a = members_a.size() - f.base_a; + } + if (!sorted_b) + { + resolve_members(members_b, f.base_b, ordered, scratch); + count_b = members_b.size() - f.base_b; + } + } + f.count = count_a; + if (count_a != count_b) { return false; } } else { - a.elements(f.elements_a); - b.elements(f.elements_b); + f.array_a = a; + f.array_b = b; + a.enter(f.cursor_a); + b.enter(f.cursor_b); + f.count = a.size(); + resume_array(path, a, b, f.cursor_a, f.cursor_b, f.next); } - stack.push_back(std::move(f)); } - else if (a.scalar() != b.scalar()) // numbers (also of different types), null, boolean + else if (ta == value_t::boolean) + { + if (a.boolean() != b.boolean()) + { + return false; + } + } + else if (numbers && ta == tb) + { + // numbers of one type: as basic_json compares them + bool differ = false; + if (ta == value_t::number_integer) + { + differ = a.integer() != b.integer(); + } + else if (ta == value_t::number_unsigned) + { + differ = a.unsigned_integer() != b.unsigned_integer(); + } + else + { + differ = !floats_equal(a, b); + } + if (differ) + { + return false; + } + } + else if (numbers && !(a.scalar() == b.scalar())) // numbers of different types { return false; } + // (null equals null) } - // the next pair of values + // the next pair of values that is not certainly equal for (;;) { if (stack.empty()) @@ -289,28 +1141,38 @@ bool equal(const A& a0, const B& b0) return true; } frame& f = stack.back(); - const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size(); - if (f.next == count) + if (f.next == f.count) { + if (f.object) + { + members_a.erase(members_a.begin() + static_cast(f.base_a), members_a.end()); + members_b.erase(members_b.begin() + static_cast(f.base_b), members_b.end()); + indices.resize(f.index_mark); + } stack.pop_back(); // (f dangles now, and is bound again after continue) continue; } if (f.object) { - if (!(f.members_a[f.next].first == f.members_b[f.next].first)) + const auto& member_a = members_a[f.base_a + (f.index_a == document_order ? f.next : indices[f.index_a + f.next])]; + const auto& member_b = members_b[f.base_b + (f.index_b == document_order ? f.next : indices[f.index_b + f.next])]; + if (!(member_a.first == member_b.first)) { return false; } - a = f.members_a[f.next].second; - b = f.members_b[f.next].second; + a = member_a.second; + b = member_b.second; } else { - a = f.elements_a[f.next]; - b = f.elements_b[f.next]; + a = f.array_a.take(f.cursor_a); + b = f.array_b.take(f.cursor_b); } ++f.next; - break; + if (!certainly_equal(a, b, path, ordered)) + { + break; + } } } } diff --git a/include/nlohmann/detail/view/image.hpp b/include/nlohmann/detail/view/image.hpp index 545e04a6a..9e57082de 100644 --- a/include/nlohmann/detail/view/image.hpp +++ b/include/nlohmann/detail/view/image.hpp @@ -258,6 +258,81 @@ struct number_token bool is_float; ///< a fraction or an exponent }; +/// advance over ASCII digits, eight bytes at a time +inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept +{ + constexpr std::uint64_t ones = 0x0101010101010101ull; + while (e - p >= 8) + { + // (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry + // out of a byte with bit 7 set only affects the bytes above, after the first one + // that count_trailing_zeros finds.) + const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u); + const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u); + if (not_digit != 0) + { + return p + (static_cast(count_trailing_zeros(not_digit)) / 8); + } + p += 8; + } + while (p != e && is_digit(*p)) + { + ++p; + } + return p; +} + +/// whether the n bytes at p are ASCII digits (n > 0) +inline bool all_digits(const unsigned char* p, std::size_t n) noexcept +{ + const auto digits = [](const unsigned char* q) + { + constexpr std::uint64_t ones = 0x0101010101010101ull; + const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u); + return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0; + }; + if (n >= 8) + { + for (std::size_t i = 0; i + 8 < n; i += 8) + { + if (!digits(p + i)) + { + return false; + } + } + return digits(p + n - 8); // (overlapping the bytes before: they are digits) + } + for (std::size_t i = 0; i < n; ++i) + { + if (!is_digit(p[i])) + { + return false; + } + } + return true; +} + +/// Whether a float token has the shape that the parser records for most of +/// them: [-]digits.digits, with the numbers of digits of the node's layout +/// (extra). Then the token is a number of that layout, and the general scan +/// that follows is not needed. +inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept +{ + const std::size_t int_digits = extra & 0xFFu; + const std::size_t frac_digits = extra >> 8u; + if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0) + { + return false; + } + const std::size_t negative = *s == '-' ? 1 : 0; + if (len != negative + int_digits + 1 + frac_digits) + { + return false; + } + const unsigned char* const p = s + negative; + return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits); +} + /// whether [s, s + len) is a JSON number (the grammar the parser accepts) inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t) { @@ -276,11 +351,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to } else if (*p >= '1' && *p <= '9') { - do - { - ++p; - } - while (p != e && is_digit(*p)); + p = digit_run(p + 1, e); } else { @@ -292,10 +363,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to if (p != e && *p == '.') { const unsigned char* const f0 = ++p; - while (p != e && is_digit(*p)) - { - ++p; - } + p = digit_run(p, e); if (p == f0) { return false; @@ -406,10 +474,15 @@ struct float_range template bool check_distinct_ranges(std::vector& ranges, Check check) { - std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b) + // (save() writes the ranges in the order of the nodes, which is sorted already) + const auto before = [](const byte_range & a, const byte_range & b) { return a.off != b.off ? a.off < b.off : a.len < b.len; - }); + }; + if (!std::is_sorted(ranges.begin(), ranges.end(), before)) + { + std::sort(ranges.begin(), ranges.end(), before); + } std::size_t end = 0; for (std::size_t i = 0; i < ranges.size(); ++i) { @@ -431,7 +504,8 @@ bool check_distinct_ranges(std::vector& ranges, Check check) /// record layouts that match it), tokens must not overlap. inline bool check_float_ranges(std::vector& ranges, const unsigned char* text) { - std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b) + // (save() writes the tokens in the order of the nodes, which is sorted already) + const auto before = [](const float_range & a, const float_range & b) { if (a.off != b.off) { @@ -442,7 +516,11 @@ inline bool check_float_ranges(std::vector& ranges, const unsigned return a.len < b.len; } return a.extra < b.extra; - }); + }; + if (!std::is_sorted(ranges.begin(), ranges.end(), before)) + { + std::sort(ranges.begin(), ranges.end(), before); + } std::size_t end = 0; std::size_t i = 0; while (i < ranges.size()) @@ -452,14 +530,18 @@ inline bool check_float_ranges(std::vector& ranges, const unsigned { return false; } - number_token t{}; const unsigned char* const s = text + r.off; - if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t)) + std::uint16_t layout = r.extra; + if (!plain_float_token(s, r.len, r.extra)) { - return false; + number_token t{}; + if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t)) + { + return false; + } + // the digit layout the parser records (or "many", as compaction writes it) + layout = float_layout(t); } - // the digit layout the parser records (or "many", as compaction writes it) - const std::uint16_t layout = float_layout(t); for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i) { if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu) diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index d413a55b9..0c6c7410b 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -763,8 +763,12 @@ class basic_json_view template friend class basic_json_document; template friend class basic_json_view; template friend class detail::view::editor; + template friend class detail::view::view_side; friend iterator; + /// whether the view is one of an editable document + static constexpr bool is_editable = Editable; + basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 20de9f188..c4636da14 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -2479,15 +2479,367 @@ NLOHMANN_JSON_NAMESPACE_END -#include // sort, stable_sort +#include // is_sorted, sort, stable_sort +#include // array #include // size_t +#include // int64_t, uint32_t, uint64_t +#include // memcpy +#include // equal_to #include // string -#include // move, pair +#include // move, pair, swap #include // vector // #include +// #include + // #include +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // int64_t, uint64_t +#include // numeric_limits +#include // string +#include // integral_constant + +// #include +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/*! +@brief locate the decimal point and the end of the mantissa of a float token + +Also checks that the token is a JSON number. Tokens of the parser and of edits +always are; an image loaded with image_check::bounds can hold any bytes, which +must not reach the conversion (it expects a well-formed token). +*/ +inline bool float_token_layout(const char* first, const char* last, std::size_t& dot, std::size_t& mantissa_end) noexcept +{ + const auto digit = [last](const char* q) + { + return q != last && is_digit(static_cast(*q)); + }; + const char* p = first; + p += (p != last && *p == '-') ? 1 : 0; + if (!digit(p) || (*p == '0' && digit(p + 1))) + { + return false; + } + do + { + ++p; + } + while (digit(p)); + dot = std::string::npos; + if (p != last && *p == '.') + { + dot = static_cast(p - first); + if (!digit(++p)) + { + return false; + } + do + { + ++p; + } + while (digit(p)); + } + mantissa_end = static_cast(p - first); + if (p != last && (*p == 'e' || *p == 'E')) + { + ++p; + p += (p != last && (*p == '+' || *p == '-')) ? 1 : 0; + if (!digit(p)) + { + return false; + } + do + { + ++p; + } + while (digit(p)); + } + return p == last; +} + +/*! +@brief the value of the float token of a node, as parse() converts it + +Uses the lexer's conversion (detail::convert_float), so that the values are +bit-identical to parse(): float and double are converted without allocation +and independent of the locale. A token that is not a JSON number (only in a +damaged image loaded with image_check::bounds) yields 0. +*/ +template +NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n) +{ + const char* const last = first + n.len; + std::size_t dot = 0; + std::size_t mantissa_end = 0; + if (NLOHMANN_VIEW_UNLIKELY(!float_token_layout(first, last, dot, mantissa_end))) + { + return FloatType{}; + } + return convert_float(first, last, dot, mantissa_end); +} + +/*! +@brief the digits of a float token with at most 19 digits, from its layout + +The digit layout recorded while parsing says where the integer digits, the +fraction digits, and the exponent are, so the digits are read eight at a +time without scanning. + +@param[in] p first character of the token +@param[in] e end of the token +@param[in] limit end of the readable memory (the source text) +*/ +NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept +{ + const bool negative = *p == '-'; + p += negative ? 1 : 0; + std::uint64_t w = parse_upto19(p, int_digits, limit); + p += int_digits; + std::int64_t q = 0; + if (frac_digits != 0) + { + w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); + p += 1 + frac_digits; + q = -static_cast(frac_digits); + } + if (p != e) + { + // [eE][+-]digits; huge exponents saturate (the parser rejected + // overflow). The token is not read beyond e, and the digits are taken + // as unsigned, so that a token that is not well-formed (a damaged + // image loaded with image_check::bounds) yields a wrong value, but no + // overflow. + ++p; + const bool exp_negative = p != e && *p == '-'; + p += (p != e && (*p == '-' || *p == '+')) ? 1 : 0; + std::int64_t exp_value = 0; + for (; p != e; ++p) + { + if (exp_value < 0x10000000) + { + exp_value = (exp_value * 10) + static_cast(*p - '0'); + } + } + q += exp_negative ? -exp_value : exp_value; + } + + float_significand d; + d.w = w; + d.exponent = q; + d.negative = negative; + return d; +} + +/*! +@brief the value of a float token with at most 19 digits, from its layout + +The result is correctly rounded by the lexer's conversion +(detail::decimal_to_float(): Clinger's fast path where both operands are +exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19 +digits), so it is the value parse() produces. +*/ +template +NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept +{ + return decimal_to_float(layout_decimal(p, e, int_digits, frac_digits, limit)); +} + +/// the value of a float set by an edit: its token (the shortest round-trip +/// text, or "nan", "inf", "-inf") in the edit arena +template +NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n) +{ + if (token[0] == 'n') + { + return std::numeric_limits::quiet_NaN(); + } + if (token[0] == 'i' || (token[0] == '-' && token[1] == 'i')) + { + return token[0] == 'i' ? std::numeric_limits::infinity() : -std::numeric_limits::infinity(); + } + return float_value(token, n); +} + +/// the value of the float token of a node, as parse() converts it; floats and +/// doubles with at most 19 digits are converted from the digit layout +template +FloatType float_value(const document_data& d, const node& n) +{ + if (NLOHMANN_VIEW_UNLIKELY((n.flags & node_flags::storage) == node_flags::edited)) + { + return edited_float(d.str(n), n); + } + return float_value(d, n, std::integral_constant::value> {}); +} + +template +FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/) +{ + const unsigned int_digits = n.extra & 0xFFu; + const unsigned frac_digits = n.extra >> 8u; + if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many") + { + // (a float token not written by an edit is in the text) + const auto* const first = reinterpret_cast(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + return layout_float(first, first + n.len, int_digits, frac_digits, reinterpret_cast(d.src + d.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + } + return float_value(d.str(n), n); +} + +template +FloatType float_value(const document_data& d, const node& n, std::false_type /*other*/) +{ + return float_value(d.str(n), n); +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // min +#include // size_t +#include // memcmp, strlen +#include // basic_string + +// #include +// #include + + +#if NLOHMANN_VIEW_HAS_CPP_17 + #include // string_view +#endif +#ifndef JSON_NO_IO + #include // ostream +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +#if NLOHMANN_VIEW_HAS_CPP_17 +using string_ref = std::string_view; +#else +/// minimal C++11 stand-in for std::string_view +class string_ref +{ + public: + using size_type = std::size_t; + using const_iterator = const char*; + + string_ref() noexcept = default; + // s must be null-terminated, as for std::string_view(const char*) + // flawfinder: ignore + string_ref(const char* s) : m_data(s), m_size(std::strlen(s)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + string_ref(const char* s, std::size_t n) noexcept : m_data(s), m_size(n) {} + template + string_ref(const std::basic_string& s) noexcept : m_data(s.data()), m_size(s.size()) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) + + const char* data() const noexcept + { + return m_data; + } + std::size_t size() const noexcept + { + return m_size; + } + std::size_t length() const noexcept + { + return m_size; + } + bool empty() const noexcept + { + return m_size == 0; + } + const char* begin() const noexcept + { + return m_data; + } + const char* end() const noexcept + { + return m_data + m_size; + } + char operator[](std::size_t i) const noexcept + { + return m_data[i]; + } + + template + explicit operator std::basic_string() const + { + return std::basic_string(m_data, m_size); + } + + friend bool operator==(string_ref a, string_ref b) noexcept + { + return a.m_size == b.m_size && (a.m_size == 0 || std::memcmp(a.m_data, b.m_data, a.m_size) == 0); + } + friend bool operator!=(string_ref a, string_ref b) noexcept + { + return !(a == b); + } + friend bool operator<(string_ref a, string_ref b) noexcept + { + const int c = std::memcmp(a.m_data, b.m_data, (std::min)(a.m_size, b.m_size)); + return c != 0 ? c < 0 : a.m_size < b.m_size; + } +#ifndef JSON_NO_IO + friend std::ostream& operator<<(std::ostream& o, string_ref s) + { + return o.write(s.m_data, static_cast(s.m_size)); + } +#endif + + private: + const char* m_data = ""; + std::size_t m_size = 0; +}; +#endif + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -2502,17 +2854,355 @@ namespace view // value, at the position of the first occurrence), and in document order if // the object type keeps an order (ordered_json), by key otherwise. +/// whether n bytes are equal; the short texts of JSON (keys, numbers, most +/// strings) are compared by two overlapping loads instead of a library call +NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_equal(const char* x, const char* y, std::size_t n) noexcept +{ + const auto load8 = [](const char* p) + { + std::uint64_t v = 0; + std::memcpy(&v, p, sizeof(v)); + return v; + }; + const auto load4 = [](const char* p) + { + std::uint32_t v = 0; + std::memcpy(&v, p, sizeof(v)); + return v; + }; + if (n >= 8) + { + for (std::size_t i = 0; i + 8 < n; i += 8) + { + if (load8(x + i) != load8(y + i)) + { + return false; + } + } + return load8(x + n - 8) == load8(y + n - 8); + } + if (n >= 4) + { + return load4(x) == load4(y) && load4(x + n - 4) == load4(y + n - 4); + } + for (std::size_t i = 0; i < n; ++i) + { + if (x[i] != y[i]) + { + return false; + } + } + return true; +} + +/// whether the n1 bytes at x are before the n2 bytes at y (memcmp() order, then the shorter first); +/// the first difference is found eight bytes at a time +NLOHMANN_VIEW_ALWAYS_INLINE bool bytes_less(const char* x, std::size_t n1, const char* y, std::size_t n2) noexcept +{ + const std::size_t n = n1 < n2 ? n1 : n2; + std::size_t i = 0; + for (; i + 8 <= n; i += 8) + { + const std::uint64_t u = read_eight_bytes(x + i); + const std::uint64_t v = read_eight_bytes(y + i); + if (u != v) + { + const auto shift = static_cast(count_trailing_zeros(u ^ v)) & ~7u; // (the first byte that differs) + return ((u >> shift) & 0xFFu) < ((v >> shift) & 0xFFu); + } + } + for (; i < n; ++i) + { + if (x[i] != y[i]) + { + return static_cast(x[i]) < static_cast(y[i]); + } + } + return n1 < n2; +} + +/// whether the key a is before the key b +template +NLOHMANN_VIEW_ALWAYS_INLINE bool key_less(const Key& a, const Key& b) noexcept +{ + return bytes_less(a.data(), a.size(), b.data(), b.size()); +} + +/// sorts the indices order[0..n) of the members m by key; equal keys stay in +/// document order (small objects with an insertion sort, which needs no memory) +template +void sort_by_key(std::uint32_t* order, std::size_t n, const Entry* m) +{ + for (std::size_t i = 0; i < n; ++i) + { + order[i] = static_cast(i); + } + if (n <= 16) + { + for (std::size_t i = 1; i < n; ++i) + { + const std::uint32_t x = order[i]; + std::size_t j = i; + for (; j > 0 && key_less(m[x].first, m[order[j - 1]].first); --j) + { + order[j] = order[j - 1]; + } + order[j] = x; + } + } + else + { + std::stable_sort(order, order + n, [m](std::uint32_t x, std::uint32_t y) + { + return key_less(m[x].first, m[y].first); + }); + } +} + +/// The members of an object as parse() keeps them: one per key, the last +/// value at the position of the first occurrence, in the order of the +/// comparison (sorted by key, or, if the object type keeps the order, the order +/// of first occurrence). The members m[0..n) are in document order; this +/// appends to out the indices of the member of each key that has its value +/// (its last one), in the order of the comparison. Most objects have unique +/// keys. scratch is working memory, kept by the caller so that its capacity +/// is reused. +template +void resolve_indices(const Entry* m, std::size_t n, bool ordered, std::vector& out, std::vector& scratch) +{ + const std::size_t start = out.size(); + out.resize(start + n); + std::uint32_t* const order = out.data() + start; + sort_by_key(order, n, m); + if (!ordered) + { + // the last of each run of equal keys + std::size_t w = 0; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && m[order[last + 1]].first == m[order[i]].first) + { + ++last; + } + order[w++] = order[last]; + i = last + 1; + } + out.resize(start + w); + return; + } + // (document order) the value of the last occurrence at the first occurrence of each key + constexpr std::uint32_t none = 0xFFFFFFFFu; + bool any = false; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && m[order[last + 1]].first == m[order[i]].first) + { + ++last; + } + if (last != i) + { + if (!any) + { + scratch.assign(n, none); + for (std::size_t k = 0; k < i; ++k) + { + scratch[order[k]] = order[k]; + } + any = true; + } + scratch[order[i]] = order[last]; // (stable: order[i] is the first occurrence) + } + else if (any) + { + scratch[order[i]] = order[i]; + } + i = last + 1; + } + if (!any) + { + // unique keys: document order + for (std::size_t i = 0; i < n; ++i) + { + order[i] = static_cast(i); + } + return; + } + std::size_t w = 0; + for (std::size_t i = 0; i < n; ++i) + { + if (scratch[i] != none) + { + order[w++] = scratch[i]; + } + } + out.resize(start + w); +} + +/// whether the keys of n members are the same, in the same order +template +bool same_keys(const EntryA* a, const EntryB* b, std::size_t n) +{ + for (std::size_t i = 0; i < n; ++i) + { + if (!(a[i].first == b[i].first)) + { + return false; + } + } + return true; +} + +/// The members of an object as parse() keeps them (one per key: the last +/// value at the position of the first occurrence), sorted by key or, if the +/// object type keeps the order, in the order of first occurrence. The +/// members are given in document order, at the end of m (from the position +/// base on); most objects have unique keys, in which case they are only sorted +/// (for small objects with an insertion sort, which keeps equal keys in order +/// and needs no memory). scratch is working memory, kept by the caller. +template +void resolve_members(std::vector>& m, std::size_t base, bool ordered, std::vector& scratch) +{ + using entry = std::pair; + const std::size_t n = m.size() - base; + entry* const first = m.data() + base; + if (ordered) + { + // (document order) later values of a key go to its first occurrence; + // scratch holds the order by key, then whether a member is dropped + constexpr std::uint32_t dropped = 1; + scratch.assign(2 * n, 0); + std::uint32_t* const order = scratch.data(); + std::uint32_t* const drop = order + n; + sort_by_key(order, n, first); + bool any = false; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && first[order[last + 1]].first == first[order[i]].first) + { + ++last; + } + if (last != i) + { + first[order[i]].second = first[order[last]].second; + for (std::size_t k = i + 1; k <= last; ++k) + { + drop[order[k]] = dropped; + } + any = true; + } + i = last + 1; + } + if (any) + { + std::size_t w = 0; + for (std::size_t i = 0; i < n; ++i) + { + if (drop[i] != dropped) + { + if (w != i) + { + first[w] = std::move(first[i]); + } + ++w; + } + } + m.erase(m.begin() + static_cast(base + w), m.end()); + } + return; + } + // sorted by key; equal keys stay in document order + if (n <= 16) + { + for (std::size_t i = 1; i < n; ++i) + { + entry x = std::move(first[i]); + std::size_t j = i; + for (; j > 0 && key_less(x.first, first[j - 1].first); --j) + { + first[j] = std::move(first[j - 1]); + } + first[j] = std::move(x); + } + } + else + { + std::stable_sort(first, first + n, [](const entry & x, const entry & y) + { + return key_less(x.first, y.first); + }); + } + // the first key of a run with the last value + std::size_t w = 0; + for (std::size_t i = 0; i < n;) + { + std::size_t last = i; + while (last + 1 < n && first[last + 1].first == first[i].first) + { + ++last; + } + if (w != i || last != i) + { + first[w] = entry(first[i].first, std::move(first[last].second)); + } + ++w; + i = last + 1; + } + m.erase(m.begin() + static_cast(base + w), m.end()); +} + +/// room for k more entries, growing geometrically (reserve(size() + k) alone would +/// reallocate for each object nested in another) +template +void reserve_more(std::vector& v, std::size_t k) +{ + const std::size_t needed = v.size() + k; + if (needed > v.capacity()) + { + v.reserve((std::max)(needed, 2 * v.capacity())); + } +} + /// one side of a comparison: a view template class view_side { public: using string_view_t = typename View::string_view_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using nav = navigation; + + /// the position in the elements of an array + struct cursor + { + const node* pos = nullptr; + }; + + view_side() noexcept + : m_view() + {} explicit view_side(const View& v) noexcept : m_view(v) {} + /// the document and the node of the view (nullptr: a discarded view) + const document_data& document() const noexcept + { + return *m_view.m_doc; + } + + const node* value_node() const noexcept + { + return m_view.m_node; + } + + static constexpr bool is_editable = View::is_editable; + value_t type() const noexcept { return m_view.type(); @@ -2528,6 +3218,32 @@ class view_side return m_view.get_string(); } + bool boolean() const noexcept + { + return (m_view.m_node->flags & node_flags::is_true) != 0; + } + + number_integer_t integer() const noexcept + { + return static_cast(static_cast(integer_bits(*m_view.m_node))); + } + + number_unsigned_t unsigned_integer() const noexcept + { + return static_cast(integer_bits(*m_view.m_node)); + } + + number_float_t floating() const + { + return float_value(*m_view.m_doc, *m_view.m_node); + } + + /// the token of a float + string_view_t float_token() const noexcept + { + return string_view_t(m_view.m_doc->str(*m_view.m_node), m_view.m_node->len); + } + /// a number, boolean, or null as a basic_json value (no allocation) BasicJsonType scalar() const { @@ -2552,60 +3268,47 @@ class view_side } } - void elements(std::vector& out) const + /// start at the first element of this array + void enter(cursor& c) const noexcept { - out.reserve(m_view.size()); - for (const View e : m_view) + c.pos = nav::first(*m_view.m_doc, m_view.m_node); + } + + /// the element at c (and advance) + view_side take(cursor& c) const noexcept + { + const node* const v = nav::value(c.pos); + c.pos = document_data::after(c.pos); + return view_side(View(m_view.m_doc, v)); + } + + /// the keys of the first n members of this object (n <= its size), in document order + void first_keys(string_view_t* out, std::size_t n) const noexcept + { + const node* pos = nav::first(*m_view.m_doc, m_view.m_node); + for (std::size_t i = 0; i < n; ++i) { - out.emplace_back(e); + out[i] = string_view_t(m_view.m_doc->str(*pos), pos->len); + pos = document_data::after(pos + 1); } } - /// the members as parse() keeps them: one per key, the last value at the - /// position of the first occurrence; in that order, or sorted by key - void members(std::vector>& out, bool ordered) const + /// append the members to out, in document order; whether their keys ascend + /// (then they are unique, and in the order of the comparison of a std::map) + bool members(std::vector>& out, bool /*ordered*/) const { - struct member + const std::size_t base = out.size(); + reserve_more(out, m_view.m_node->len); + bool sorted = true; + const node* pos = nav::first(*m_view.m_doc, m_view.m_node); + for (std::size_t i = m_view.m_node->len; i != 0; --i) { - string_view_t key; - View value; - std::size_t position; - }; - std::vector all; - all.reserve(m_view.size()); - std::size_t position = 0; - for (auto it = m_view.begin(); it != m_view.end(); ++it) - { - all.push_back(member{it.key(), it.value(), position++}); - } - std::stable_sort(all.begin(), all.end(), [](const member & a, const member & b) - { - return a.key < b.key; - }); - std::vector unique; - unique.reserve(all.size()); - for (std::size_t i = 0; i < all.size();) - { - std::size_t last = i; - while (last + 1 < all.size() && all[last + 1].key == all[i].key) - { - ++last; - } - unique.push_back(member{all[i].key, all[last].value, all[i].position}); - i = last + 1; - } - if (ordered) - { - std::sort(unique.begin(), unique.end(), [](const member & a, const member & b) - { - return a.position < b.position; - }); - } - out.reserve(unique.size()); - for (const member& m : unique) - { - out.emplace_back(m.key, view_side(m.value)); + const string_view_t key(m_view.m_doc->str(*pos), pos->len); + sorted = sorted && (out.size() == base || key_less(out.back().first, key)); + out.emplace_back(key, view_side(View(m_view.m_doc, nav::value(pos + 1)))); + pos = document_data::after(pos + 1); } + return sorted; } private: @@ -2618,6 +3321,17 @@ class json_side { public: using string_view_t = StringView; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + /// the position in the elements of an array + struct cursor + { + typename BasicJsonType::const_iterator it{}; + }; + + json_side() noexcept = default; explicit json_side(const BasicJsonType& j) noexcept : m_json(&j) @@ -2639,45 +3353,428 @@ class json_side return string_view_t(s.data(), s.size()); } + bool boolean() const noexcept + { + return *m_json->template get_ptr(); + } + + number_integer_t integer() const noexcept + { + return *m_json->template get_ptr(); + } + + number_unsigned_t unsigned_integer() const noexcept + { + return *m_json->template get_ptr(); + } + + number_float_t floating() const noexcept + { + return *m_json->template get_ptr(); + } + BasicJsonType scalar() const { return *m_json; } - void elements(std::vector& out) const + void enter(cursor& c) const { - out.reserve(m_json->size()); - for (const auto& e : *m_json) + c.it = m_json->cbegin(); + } + + json_side take(cursor& c) const + { + const json_side r(*c.it); + ++c.it; + return r; + } + + /// the keys of the first n members of this object (n <= its size), in document order + void first_keys(string_view_t* out, std::size_t n) const + { + auto it = m_json->cbegin(); + for (std::size_t i = 0; i < n; ++i, ++it) { - out.emplace_back(e); + out[i] = string_view_t(it.key().data(), it.key().size()); } } - void members(std::vector>& out, bool ordered) const + /// append the members to out, in the order of the comparison (the keys of a + /// basic_json are unique); true + bool members(std::vector>& out, bool ordered) const { - out.reserve(m_json->size()); + const std::size_t base = out.size(); + reserve_more(out, m_json->size()); for (auto it = m_json->cbegin(); it != m_json->cend(); ++it) { out.emplace_back(string_view_t(it.key().data(), it.key().size()), json_side(it.value())); } if (!ordered) { - std::sort(out.begin(), out.end(), [](const std::pair& a, const std::pair& b) + const auto less = [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }); + return key_less(a.first, b.first); + }; + const auto first = out.begin() + static_cast(base); + if (!std::is_sorted(first, out.end(), less)) + { + std::sort(first, out.end(), less); + } + } + return true; + } + + private: + const BasicJsonType* m_json = nullptr; +}; + +/*! +@brief the values at two nodes, found equal by walking both documents in the +same order + +Two values are equal if they have the same type, the same number token or +string, the same elements, and the same members with the same keys in the same +order: a value that matches this way is equal under the rules above whatever +the object type and duplicate keys (the last value of a key then wins on +both sides, and their order of first occurrence is the same). The walk does +not find every equal pair (numbers spelled differently, members in another +order, duplicate keys with other values), only the ones that are the same +text; it stops at the first difference and then says nothing. Containers +nested deeper than the budget are not walked. + +The walk goes through the document in its order, the comparison that follows +goes through the members of an object by key. So that the walk never reaches +a difference later than the comparison would, and never costs more than it, +it enters an object only if the two orders agree (the keys ascend, or the +object type keeps the order) or if the walk is cheaper than preparing the +comparison of the object (all values are scalars). +*/ +/// the containers that lockstep found different, from the outermost to the +/// innermost: the comparison that goes on from there does not walk them again +struct lockstep_path +{ + /// the nesting depth up to which lockstep walks (the recursion is bounded) + static constexpr unsigned budget() noexcept + { + return 48; + } + + /// a pair of containers that differ; for arrays, the elements where the walk stopped + /// (the ones before are equal) + struct entry + { + const node* a; + const node* b; + const node* element_a; + const node* element_b; + std::uint32_t index; + }; + + /// the number of entries that a path can hold (a container deeper than the + /// budget of the walk is not entered) + static constexpr std::size_t levels() noexcept + { + return 50; + } + + explicit lockstep_path(entry* storage) noexcept + : nodes(storage) + {} + + entry* nodes; ///< levels() entries, written before they are read + unsigned length = 0; ///< pairs recorded by the last walk + unsigned next = 0; ///< the pair that the comparison enters next + const entry* resume = nullptr; ///< the entry of the pair that was just looked at, if it is in the path +}; + +template +class lockstep +{ + public: + lockstep(const document_data& a, const document_data& b, lockstep_path& path, bool ordered) noexcept + : m_a(a), m_b(b), m_path(path), m_ordered(ordered) + {} + + /// whether the values at a and b are the same (the nodes of the values, + /// not entries of a sequence) + bool same(const node* a, const node* b, unsigned budget) noexcept + { + if (a->kind != b->kind) + { + return false; + } + switch (static_cast(a->kind)) + { + case value_t::null: + return true; + case value_t::boolean: + return ((a->flags ^ b->flags) & node_flags::is_true) == 0; + case value_t::string: + return a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len); + case value_t::number_float: // (the token, and the digit layout that the value is read with: they only differ in a damaged image) + return a->extra == b->extra && a->len == b->len && bytes_equal(m_a.str(*a), m_b.str(*b), a->len); + case value_t::number_integer: + case value_t::number_unsigned: + return integer_bits(*a) == integer_bits(*b); + case value_t::array: + if (budget == 0 || a->len != b->len) + { + return false; + } + { + lockstep_path::entry stop{a, b, nullptr, nullptr, 0}; + if (elements(a, b, budget - 1, stop)) + { + return true; + } + record(stop, budget); + return false; + } + case value_t::object: + if (budget == 0 || a->len != b->len) + { + return false; + } + if (!visits_in_order(a) || !members(a, b, budget - 1)) + { + record(lockstep_path::entry{a, b, nullptr, nullptr, 0}, budget); + return false; + } + return true; + case value_t::binary: // LCOV_EXCL_LINE (not in a document) + case value_t::discarded: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE } } private: - const BasicJsonType* m_json; + using nav_a = navigation; + using nav_b = navigation; + + /// note the containers a and b (at the nesting level that budget stands + /// for) as different + NLOHMANN_VIEW_NOINLINE void record(const lockstep_path::entry& stop, unsigned budget) const noexcept + { + const unsigned level = lockstep_path::budget() - budget; + m_path.nodes[level] = stop; + if (m_path.length == 0) + { + m_path.length = level + 1; // (the innermost container is the first to be left) + } + } + + /// whether the object @a a of a document that cannot be edited has scalar values only + static bool scalar_values_only(const node* a, std::false_type /*editable*/) noexcept + { + return a->next == 1 + (2 * static_cast(a->len)); + } + + /// (the nodes of an editable document can be elsewhere: not decided) + static bool scalar_values_only(const node* /*a*/, std::true_type /*editable*/) noexcept + { + return false; + } + + /// Whether the comparison that follows visits the members of the object a in the + /// order of the document: if the object type keeps the order, if the keys + /// ascend (it sorts by key), or if all values are scalars (then the walk costs + /// less than collecting and sorting the members, whichever member differs). + /// Otherwise a difference could be found by the walk later than by the + /// comparison, which is not worth the walk. + bool visits_in_order(const node* a) const noexcept + { + if (m_ordered || a->len < 2) + { + return true; + } + if (scalar_values_only(a, std::integral_constant {})) + { + return true; + } + const node* k = nav_a::first(m_a, a); + string_ref previous(m_a.str(*k), k->len); + for (std::uint32_t i = a->len - 1; i != 0; --i) + { + k = document_data::after(k + 1); + const string_ref key(m_a.str(*k), k->len); + if (!key_less(previous, key)) + { + return false; + } + previous = key; + } + return true; + } + + bool elements(const node* a, const node* b, unsigned budget, lockstep_path::entry& stop) noexcept + { + const node* pa = nav_a::first(m_a, a); + const node* pb = nav_b::first(m_b, b); + for (std::uint32_t i = a->len; i != 0; --i) + { + if (!same(nav_a::value(pa), nav_b::value(pb), budget)) + { + stop.element_a = pa; + stop.element_b = pb; + stop.index = a->len - i; + return false; + } + pa = document_data::after(pa); + pb = document_data::after(pb); + } + return true; + } + + bool members(const node* a, const node* b, unsigned budget) noexcept + { + const node* pa = nav_a::first(m_a, a); + const node* pb = nav_b::first(m_b, b); + for (std::uint32_t i = a->len; i != 0; --i) + { + if (pa->len != pb->len || !bytes_equal(m_a.str(*pa), m_b.str(*pb), pa->len)) + { + return false; + } + if (!same(nav_a::value(pa + 1), nav_b::value(pb + 1), budget)) + { + return false; + } + pa = document_data::after(pa + 1); + pb = document_data::after(pb + 1); + } + return true; + } + + const document_data& m_a; + const document_data& m_b; + lockstep_path& m_path; + bool m_ordered; ///< the object type keeps the order of the members }; +/// whether two views certainly have equal values (see lockstep); if not, the +/// path says where they differ +template +bool certainly_equal(const view_side& a, const view_side& b, lockstep_path& path, bool ordered) noexcept +{ + const node* const na = a.value_node(); + const node* const nb = b.value_node(); + if (na == nullptr || nb == nullptr) + { + return false; + } + if (na == nb) + { + return true; // (the same node of the same document: documents have distinct nodes) + } + path.resume = nullptr; + if (path.next < path.length && path.nodes[path.next].a == na && path.nodes[path.next].b == nb) + { + path.resume = &path.nodes[path.next]; + ++path.next; // (the walk found these different already) + return false; + } + path.length = 0; + const bool equal = lockstep::is_editable, view_side::is_editable>(a.document(), b.document(), path, ordered).same(na, nb, lockstep_path::budget()); + path.next = path.length != 0 ? 1 : 0; // (this pair is the first in the path) + path.resume = path.length != 0 ? &path.nodes[0] : nullptr; + return equal; +} + +/// (a view and a basic_json value: no walk) +template +bool certainly_equal(const A& /*a*/, const B& /*b*/, lockstep_path& /*path*/, bool /*ordered*/) noexcept +{ + return false; +} + +/// (arrays of two views) the elements where the walk stopped are the next pair: the ones before are equal +template +void resume_array(const lockstep_path& path, const view_side& a, const view_side& b, + typename view_side::cursor& cursor_a, typename view_side::cursor& cursor_b, std::size_t& next) noexcept +{ + if (path.resume != nullptr && path.resume->element_a != nullptr && path.resume->a == a.value_node() && path.resume->b == b.value_node()) + { + cursor_a.pos = path.resume->element_a; + cursor_b.pos = path.resume->element_b; + next = path.resume->index; + } +} + +template +void resume_array(const lockstep_path& /*path*/, const A& /*a*/, const B& /*b*/, CursorA& /*cursor_a*/, CursorB& /*cursor_b*/, std::size_t& /*next*/) noexcept +{} + +/// whether two floats are equal (the tokens of two views: equal text and digit layout is an equal value; they +/// only differ in a damaged image) +template +bool floats_equal(const view_side& a, const view_side& b) +{ + return (a.value_node()->extra == b.value_node()->extra && a.float_token() == b.float_token()) || std::equal_to()(a.floating(), b.floating()); +} + +template +bool floats_equal(const A& a, const B& b) +{ + return std::equal_to()(a.floating(), b.floating()); +} + +/// Whether two objects of a type that keeps the order are different because of the keys of +/// their first members, found without the preparation of the comparison: the comparison goes +/// through the members in the order of the first occurrence of their keys, so if the first +/// members have the same distinct keys and then one pair of keys differs, where neither is +/// one of the keys before it, the objects are different. (The keys that are the same as an +/// earlier key are the duplicates that the comparison drops, so those are not decided here.) +template +bool keys_differ_early(const SideA& a, const SideB& b) +{ + using string_view_t = typename SideA::string_view_t; + constexpr std::size_t limit = 4; + const std::size_t n = (std::min)((std::min)(a.size(), b.size()), limit); + std::array ka{}; + std::array kb{}; + a.first_keys(ka.data(), n); + b.first_keys(kb.data(), n); + for (std::size_t i = 0; i < n; ++i) + { + if (ka[i] != kb[i]) + { + for (std::size_t k = 0; k < i; ++k) + { + if (ka[i] == ka[k] || kb[i] == ka[k]) + { + return false; // (a duplicate: not decided) + } + } + return true; + } + } + return false; +} + +/// keys_differ_early() for a type that keeps the order of the members +template +bool keys_differ_early(const SideA& a, const SideB& b, std::true_type /*ordered*/) +{ + return keys_differ_early(a, b); +} + +/// (a type that sorts the members by key: not decided early) +template +bool keys_differ_early(const SideA& /*a*/, const SideB& /*b*/, std::false_type /*ordered*/) noexcept +{ + return false; +} + inline bool is_number(value_t t) noexcept { return t == value_t::number_integer || t == value_t::number_unsigned || t == value_t::number_float; } +/// the index_a/index_b of a frame of equal() whose members are compared in document order +constexpr std::size_t document_order = static_cast(-1); + /// whether two sides are equal; iterative, so that the nesting depth is /// limited by memory only template @@ -2688,20 +3785,52 @@ bool equal(const A& a0, const B& b0) struct frame { - std::vector elements_a{}; - std::vector elements_b{}; - std::vector> members_a{}; - std::vector> members_b{}; + A array_a{}; + B array_b{}; + typename A::cursor cursor_a{}; + typename B::cursor cursor_b{}; + std::size_t base_a = 0; ///< the members of an object: from this position in members_a + std::size_t base_b = 0; + std::size_t index_a = document_order; ///< their order in the comparison: from this position in indices (document_order: as they are) + std::size_t index_b = document_order; + std::size_t index_mark = 0; ///< the size of indices before this frame bool object = false; std::size_t next = 0; + std::size_t count = 0; }; std::vector stack; + std::vector indices; // the order of the members of the open objects, where it is not their order in the document + std::vector scratch; // working memory of resolve_indices() and resolve_members() + // the members of the open objects (the ones of an object follow those of the objects around it) + std::vector> members_a; + std::vector> members_b; + std::array path_storage; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + lockstep_path path(path_storage.data()); + { + // (values that differ in type or in the size of an array need no walk) + const value_t first = a0.type(); + const value_t second = b0.type(); + if (first != second && !(is_number(first) && is_number(second)) && first != value_t::discarded && second != value_t::discarded) + { + return false; + } + if (first == value_t::array && second == value_t::array && a0.size() != b0.size()) + { + return false; + } + } A a = a0; B b = b0; + // (pairs of views that are certainly equal need no further look) + if (certainly_equal(a, b, path, ordered)) + { + return true; + } for (;;) { const value_t ta = a.type(); const value_t tb = b.type(); + const bool numbers = is_number(ta) && is_number(tb); if (ta == value_t::discarded || tb == value_t::discarded) { // basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) @@ -2712,7 +3841,7 @@ bool equal(const A& a0, const B& b0) } else { - if (ta != tb && !(is_number(ta) && is_number(tb))) // numbers of different types can be equal + if (ta != tb && !numbers) // numbers of different types can be equal { return false; } @@ -2729,31 +3858,98 @@ bool equal(const A& a0, const B& b0) { return false; } - frame f; + if (ta == value_t::object && keys_differ_early(a, b, std::integral_constant::value> {})) + { + return false; + } + stack.emplace_back(); + frame& f = stack.back(); f.object = ta == value_t::object; if (f.object) { - a.members(f.members_a, ordered); - b.members(f.members_b, ordered); - if (f.members_a.size() != f.members_b.size()) + f.base_a = members_a.size(); + f.base_b = members_b.size(); + f.index_mark = indices.size(); + const bool sorted_a = a.members(members_a, ordered); + const bool sorted_b = b.members(members_b, ordered); + const std::size_t size_a = members_a.size() - f.base_a; + const std::size_t size_b = members_b.size() - f.base_b; + std::size_t count_a = size_a; + std::size_t count_b = size_b; + if (!sorted_a && !sorted_b && size_a == size_b && same_keys(members_a.data() + f.base_a, members_b.data() + f.base_b, size_a)) + { + // the same keys in the same order: one order of the comparison for both + f.index_a = indices.size(); + f.index_b = f.index_a; + resolve_indices(members_a.data() + f.base_a, size_a, ordered, indices, scratch); + count_a = indices.size() - f.index_a; + count_b = count_a; + } + else + { + if (!sorted_a) + { + resolve_members(members_a, f.base_a, ordered, scratch); + count_a = members_a.size() - f.base_a; + } + if (!sorted_b) + { + resolve_members(members_b, f.base_b, ordered, scratch); + count_b = members_b.size() - f.base_b; + } + } + f.count = count_a; + if (count_a != count_b) { return false; } } else { - a.elements(f.elements_a); - b.elements(f.elements_b); + f.array_a = a; + f.array_b = b; + a.enter(f.cursor_a); + b.enter(f.cursor_b); + f.count = a.size(); + resume_array(path, a, b, f.cursor_a, f.cursor_b, f.next); } - stack.push_back(std::move(f)); } - else if (a.scalar() != b.scalar()) // numbers (also of different types), null, boolean + else if (ta == value_t::boolean) + { + if (a.boolean() != b.boolean()) + { + return false; + } + } + else if (numbers && ta == tb) + { + // numbers of one type: as basic_json compares them + bool differ = false; + if (ta == value_t::number_integer) + { + differ = a.integer() != b.integer(); + } + else if (ta == value_t::number_unsigned) + { + differ = a.unsigned_integer() != b.unsigned_integer(); + } + else + { + differ = !floats_equal(a, b); + } + if (differ) + { + return false; + } + } + else if (numbers && !(a.scalar() == b.scalar())) // numbers of different types { return false; } + // (null equals null) } - // the next pair of values + // the next pair of values that is not certainly equal for (;;) { if (stack.empty()) @@ -2761,28 +3957,38 @@ bool equal(const A& a0, const B& b0) return true; } frame& f = stack.back(); - const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size(); - if (f.next == count) + if (f.next == f.count) { + if (f.object) + { + members_a.erase(members_a.begin() + static_cast(f.base_a), members_a.end()); + members_b.erase(members_b.begin() + static_cast(f.base_b), members_b.end()); + indices.resize(f.index_mark); + } stack.pop_back(); // (f dangles now, and is bound again after continue) continue; } if (f.object) { - if (!(f.members_a[f.next].first == f.members_b[f.next].first)) + const auto& member_a = members_a[f.base_a + (f.index_a == document_order ? f.next : indices[f.index_a + f.next])]; + const auto& member_b = members_b[f.base_b + (f.index_b == document_order ? f.next : indices[f.index_b + f.next])]; + if (!(member_a.first == member_b.first)) { return false; } - a = f.members_a[f.next].second; - b = f.members_b[f.next].second; + a = member_a.second; + b = member_b.second; } else { - a = f.elements_a[f.next]; - b = f.elements_b[f.next]; + a = f.array_a.take(f.cursor_a); + b = f.array_b.take(f.cursor_b); } ++f.next; - break; + if (!certainly_equal(a, b, path, ordered)) + { + break; + } } } } @@ -4720,232 +5926,6 @@ NLOHMANN_JSON_NAMESPACE_END // #include // #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // size_t -#include // int64_t, uint64_t -#include // numeric_limits -#include // string -#include // integral_constant - -// #include -// #include - -// #include - -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -namespace view -{ - -/*! -@brief locate the decimal point and the end of the mantissa of a float token - -Also checks that the token is a JSON number. Tokens of the parser and of edits -always are; an image loaded with image_check::bounds can hold any bytes, which -must not reach the conversion (it expects a well-formed token). -*/ -inline bool float_token_layout(const char* first, const char* last, std::size_t& dot, std::size_t& mantissa_end) noexcept -{ - const auto digit = [last](const char* q) - { - return q != last && is_digit(static_cast(*q)); - }; - const char* p = first; - p += (p != last && *p == '-') ? 1 : 0; - if (!digit(p) || (*p == '0' && digit(p + 1))) - { - return false; - } - do - { - ++p; - } - while (digit(p)); - dot = std::string::npos; - if (p != last && *p == '.') - { - dot = static_cast(p - first); - if (!digit(++p)) - { - return false; - } - do - { - ++p; - } - while (digit(p)); - } - mantissa_end = static_cast(p - first); - if (p != last && (*p == 'e' || *p == 'E')) - { - ++p; - p += (p != last && (*p == '+' || *p == '-')) ? 1 : 0; - if (!digit(p)) - { - return false; - } - do - { - ++p; - } - while (digit(p)); - } - return p == last; -} - -/*! -@brief the value of the float token of a node, as parse() converts it - -Uses the lexer's conversion (detail::convert_float), so that the values are -bit-identical to parse(): float and double are converted without allocation -and independent of the locale. A token that is not a JSON number (only in a -damaged image loaded with image_check::bounds) yields 0. -*/ -template -NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n) -{ - const char* const last = first + n.len; - std::size_t dot = 0; - std::size_t mantissa_end = 0; - if (NLOHMANN_VIEW_UNLIKELY(!float_token_layout(first, last, dot, mantissa_end))) - { - return FloatType{}; - } - return convert_float(first, last, dot, mantissa_end); -} - -/*! -@brief the digits of a float token with at most 19 digits, from its layout - -The digit layout recorded while parsing says where the integer digits, the -fraction digits, and the exponent are, so the digits are read eight at a -time without scanning. - -@param[in] p first character of the token -@param[in] e end of the token -@param[in] limit end of the readable memory (the source text) -*/ -NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept -{ - const bool negative = *p == '-'; - p += negative ? 1 : 0; - std::uint64_t w = parse_upto19(p, int_digits, limit); - p += int_digits; - std::int64_t q = 0; - if (frac_digits != 0) - { - w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); - p += 1 + frac_digits; - q = -static_cast(frac_digits); - } - if (p != e) - { - // [eE][+-]digits; huge exponents saturate (the parser rejected - // overflow). The token is not read beyond e, and the digits are taken - // as unsigned, so that a token that is not well-formed (a damaged - // image loaded with image_check::bounds) yields a wrong value, but no - // overflow. - ++p; - const bool exp_negative = p != e && *p == '-'; - p += (p != e && (*p == '-' || *p == '+')) ? 1 : 0; - std::int64_t exp_value = 0; - for (; p != e; ++p) - { - if (exp_value < 0x10000000) - { - exp_value = (exp_value * 10) + static_cast(*p - '0'); - } - } - q += exp_negative ? -exp_value : exp_value; - } - - float_significand d; - d.w = w; - d.exponent = q; - d.negative = negative; - return d; -} - -/*! -@brief the value of a float token with at most 19 digits, from its layout - -The result is correctly rounded by the lexer's conversion -(detail::decimal_to_float(): Clinger's fast path where both operands are -exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19 -digits), so it is the value parse() produces. -*/ -template -NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept -{ - return decimal_to_float(layout_decimal(p, e, int_digits, frac_digits, limit)); -} - -/// the value of a float set by an edit: its token (the shortest round-trip -/// text, or "nan", "inf", "-inf") in the edit arena -template -NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n) -{ - if (token[0] == 'n') - { - return std::numeric_limits::quiet_NaN(); - } - if (token[0] == 'i' || (token[0] == '-' && token[1] == 'i')) - { - return token[0] == 'i' ? std::numeric_limits::infinity() : -std::numeric_limits::infinity(); - } - return float_value(token, n); -} - -/// the value of the float token of a node, as parse() converts it; floats and -/// doubles with at most 19 digits are converted from the digit layout -template -FloatType float_value(const document_data& d, const node& n) -{ - if (NLOHMANN_VIEW_UNLIKELY((n.flags & node_flags::storage) == node_flags::edited)) - { - return edited_float(d.str(n), n); - } - return float_value(d, n, std::integral_constant::value> {}); -} - -template -FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/) -{ - const unsigned int_digits = n.extra & 0xFFu; - const unsigned frac_digits = n.extra >> 8u; - if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many") - { - // (a float token not written by an edit is in the text) - const auto* const first = reinterpret_cast(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - return layout_float(first, first + n.len, int_digits, frac_digits, reinterpret_cast(d.src + d.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) - } - return float_value(d.str(n), n); -} - -template -FloatType float_value(const document_data& d, const node& n, std::false_type /*other*/) -{ - return float_value(d.str(n), n); -} - -} // namespace view -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END // #include @@ -5184,6 +6164,81 @@ struct number_token bool is_float; ///< a fraction or an exponent }; +/// advance over ASCII digits, eight bytes at a time +inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept +{ + constexpr std::uint64_t ones = 0x0101010101010101ull; + while (e - p >= 8) + { + // (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry + // out of a byte with bit 7 set only affects the bytes above, after the first one + // that count_trailing_zeros finds.) + const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u); + const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u); + if (not_digit != 0) + { + return p + (static_cast(count_trailing_zeros(not_digit)) / 8); + } + p += 8; + } + while (p != e && is_digit(*p)) + { + ++p; + } + return p; +} + +/// whether the n bytes at p are ASCII digits (n > 0) +inline bool all_digits(const unsigned char* p, std::size_t n) noexcept +{ + const auto digits = [](const unsigned char* q) + { + constexpr std::uint64_t ones = 0x0101010101010101ull; + const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u); + return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0; + }; + if (n >= 8) + { + for (std::size_t i = 0; i + 8 < n; i += 8) + { + if (!digits(p + i)) + { + return false; + } + } + return digits(p + n - 8); // (overlapping the bytes before: they are digits) + } + for (std::size_t i = 0; i < n; ++i) + { + if (!is_digit(p[i])) + { + return false; + } + } + return true; +} + +/// Whether a float token has the shape that the parser records for most of +/// them: [-]digits.digits, with the numbers of digits of the node's layout +/// (extra). Then the token is a number of that layout, and the general scan +/// that follows is not needed. +inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept +{ + const std::size_t int_digits = extra & 0xFFu; + const std::size_t frac_digits = extra >> 8u; + if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0) + { + return false; + } + const std::size_t negative = *s == '-' ? 1 : 0; + if (len != negative + int_digits + 1 + frac_digits) + { + return false; + } + const unsigned char* const p = s + negative; + return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits); +} + /// whether [s, s + len) is a JSON number (the grammar the parser accepts) inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t) { @@ -5202,11 +6257,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to } else if (*p >= '1' && *p <= '9') { - do - { - ++p; - } - while (p != e && is_digit(*p)); + p = digit_run(p + 1, e); } else { @@ -5218,10 +6269,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to if (p != e && *p == '.') { const unsigned char* const f0 = ++p; - while (p != e && is_digit(*p)) - { - ++p; - } + p = digit_run(p, e); if (p == f0) { return false; @@ -5332,10 +6380,15 @@ struct float_range template bool check_distinct_ranges(std::vector& ranges, Check check) { - std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b) + // (save() writes the ranges in the order of the nodes, which is sorted already) + const auto before = [](const byte_range & a, const byte_range & b) { return a.off != b.off ? a.off < b.off : a.len < b.len; - }); + }; + if (!std::is_sorted(ranges.begin(), ranges.end(), before)) + { + std::sort(ranges.begin(), ranges.end(), before); + } std::size_t end = 0; for (std::size_t i = 0; i < ranges.size(); ++i) { @@ -5357,7 +6410,8 @@ bool check_distinct_ranges(std::vector& ranges, Check check) /// record layouts that match it), tokens must not overlap. inline bool check_float_ranges(std::vector& ranges, const unsigned char* text) { - std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b) + // (save() writes the tokens in the order of the nodes, which is sorted already) + const auto before = [](const float_range & a, const float_range & b) { if (a.off != b.off) { @@ -5368,7 +6422,11 @@ inline bool check_float_ranges(std::vector& ranges, const unsigned return a.len < b.len; } return a.extra < b.extra; - }); + }; + if (!std::is_sorted(ranges.begin(), ranges.end(), before)) + { + std::sort(ranges.begin(), ranges.end(), before); + } std::size_t end = 0; std::size_t i = 0; while (i < ranges.size()) @@ -5378,14 +6436,18 @@ inline bool check_float_ranges(std::vector& ranges, const unsigned { return false; } - number_token t{}; const unsigned char* const s = text + r.off; - if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t)) + std::uint16_t layout = r.extra; + if (!plain_float_token(s, r.len, r.extra)) { - return false; + number_token t{}; + if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t)) + { + return false; + } + // the digit layout the parser records (or "many", as compaction writes it) + layout = float_layout(t); } - // the digit layout the parser records (or "many", as compaction writes it) - const std::uint16_t layout = float_layout(t); for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i) { if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu) @@ -7910,120 +8972,6 @@ class view_serializer NLOHMANN_JSON_NAMESPACE_END // #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // min -#include // size_t -#include // memcmp, strlen -#include // basic_string - -// #include -// #include - - -#if NLOHMANN_VIEW_HAS_CPP_17 - #include // string_view -#endif -#ifndef JSON_NO_IO - #include // ostream -#endif - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -namespace view -{ - -#if NLOHMANN_VIEW_HAS_CPP_17 -using string_ref = std::string_view; -#else -/// minimal C++11 stand-in for std::string_view -class string_ref -{ - public: - using size_type = std::size_t; - using const_iterator = const char*; - - string_ref() noexcept = default; - // s must be null-terminated, as for std::string_view(const char*) - // flawfinder: ignore - string_ref(const char* s) : m_data(s), m_size(std::strlen(s)) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - string_ref(const char* s, std::size_t n) noexcept : m_data(s), m_size(n) {} - template - string_ref(const std::basic_string& s) noexcept : m_data(s.data()), m_size(s.size()) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions) - - const char* data() const noexcept - { - return m_data; - } - std::size_t size() const noexcept - { - return m_size; - } - std::size_t length() const noexcept - { - return m_size; - } - bool empty() const noexcept - { - return m_size == 0; - } - const char* begin() const noexcept - { - return m_data; - } - const char* end() const noexcept - { - return m_data + m_size; - } - char operator[](std::size_t i) const noexcept - { - return m_data[i]; - } - - template - explicit operator std::basic_string() const - { - return std::basic_string(m_data, m_size); - } - - friend bool operator==(string_ref a, string_ref b) noexcept - { - return a.m_size == b.m_size && (a.m_size == 0 || std::memcmp(a.m_data, b.m_data, a.m_size) == 0); - } - friend bool operator!=(string_ref a, string_ref b) noexcept - { - return !(a == b); - } - friend bool operator<(string_ref a, string_ref b) noexcept - { - const int c = std::memcmp(a.m_data, b.m_data, (std::min)(a.m_size, b.m_size)); - return c != 0 ? c < 0 : a.m_size < b.m_size; - } -#ifndef JSON_NO_IO - friend std::ostream& operator<<(std::ostream& o, string_ref s) - { - return o.write(s.m_data, static_cast(s.m_size)); - } -#endif - - private: - const char* m_data = ""; - std::size_t m_size = 0; -}; -#endif - -} // namespace view -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ @@ -8856,8 +9804,12 @@ class basic_json_view template friend class basic_json_document; template friend class basic_json_view; template friend class detail::view::editor; + template friend class detail::view::view_side; friend iterator; + /// whether the view is one of an editable document + static constexpr bool is_editable = Editable; + basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} diff --git a/tests/src/unit-json_view_compare.cpp b/tests/src/unit-json_view_compare.cpp new file mode 100644 index 000000000..48527adb4 --- /dev/null +++ b/tests/src/unit-json_view_compare.cpp @@ -0,0 +1,364 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; +using nlohmann::ordered_json; +using nlohmann::json_document; +using nlohmann::ordered_json_editable_document; +using nlohmann::json_view; +using nlohmann::ordered_json_document; + +#include "json_view_test_helpers.hpp" +using json_view_test::generator; + +#include +#include +#include +#include + +// These tests were split off unit-json_view_dump.cpp, whose object file got too +// large for the MinGW assembler ("file too big"). Like that file, this one is +// also built with C++17, as it mentions JSON_HAS_CPP_17. + +TEST_CASE("json_view comparison") +{ + SECTION("equality of the values parse() produces") + { + generator g; + std::vector texts; + for (int i = 0; i < 600; ++i) + { + std::string text; + g.value(text, 0); + texts.push_back(text); + // the same value written differently: sorted keys, canonical numbers + texts.push_back(json::parse(text).dump(1)); + } + for (std::size_t i = 0; i + 2 < texts.size(); ++i) + { + for (std::size_t k = i; k < i + 3; ++k) + { + CAPTURE(texts[i]) + CAPTURE(texts[k]) + const json_document a = json_document::parse(texts[i]); + const json_document b = json_document::parse(texts[k]); + const json ja = json::parse(texts[i]); + const json jb = json::parse(texts[k]); + CHECK((a.root() == b.root()) == (ja == jb)); + CHECK((a.root() != b.root()) == (ja != jb)); + CHECK((a.root() == jb) == (ja == jb)); + CHECK((jb == a.root()) == (ja == jb)); + CHECK((a.root() != jb) == (ja != jb)); + CHECK((jb != a.root()) == (ja != jb)); + + // ordered_json compares members in order + const ordered_json_document oa = ordered_json_document::parse(texts[i]); + const ordered_json_document ob = ordered_json_document::parse(texts[k]); + const ordered_json oja = ordered_json::parse(texts[i]); + const ordered_json ojb = ordered_json::parse(texts[k]); + CHECK((oa.root() == ob.root()) == (oja == ojb)); + CHECK((oa.root() == ojb) == (oja == ojb)); + } + } + } + + SECTION("objects whose first keys differ or repeat") + { + const std::array texts = + { + { + R"({"a":1,"b":2})", R"({"a":1,"c":2})", R"({"b":1,"a":2})", R"({"a":1,"a":2,"b":3})", R"({"a":2,"b":3})", + R"({"a":1,"b":1,"a":2})", R"({"a":2,"b":1})", R"({"a":1,"a":2})", R"({"b":1})", R"({"a":1,"b":2,"c":3,"d":4,"e":5})", + R"({"a":1,"b":2,"c":3,"d":4,"f":5})", R"({"a":1,"b":2,"c":3,"a":4,"e":5})", R"({"a":4,"b":2,"c":3,"e":5})", + R"({"\u0061":1,"b":2})", R"({})", R"([{"a":1,"b":2}])", R"([{"a":1,"c":2}])" + } + }; + for (const char* const x : texts) + { + for (const char* const y : texts) + { + CAPTURE(x) + CAPTURE(y) + const ordered_json_document dx = ordered_json_document::parse(x); + const ordered_json_document dy = ordered_json_document::parse(y); + const ordered_json_editable_document ex = ordered_json_editable_document::parse(x); + const ordered_json jx = ordered_json::parse(x); + const ordered_json jy = ordered_json::parse(y); + CHECK((dx.root() == dy.root()) == (jx == jy)); + CHECK((dx.root() == jy) == (jx == jy)); + CHECK((ex.root() == dy.root()) == (jx == jy)); + } + } + } + + SECTION("objects with duplicate keys, small and large") + { + // n members with keys in descending order; with duplicates, some keys + // repeat later (the first after unique ones in key order); in reverse, + // the members are in ascending key order + const auto object = [](int n, bool duplicates, bool reverse, int value) + { + std::string s = "{"; + for (int i = 0; i < n; ++i) + { + const int k = reverse ? i : n - 1 - i; + s += (i != 0 ? ",\"k" : "\"k") + std::to_string(100 + k) + "\":" + std::to_string(value + k); + } + if (duplicates) + { + for (int k = n / 2; k < n; k += 3) + { + s += ",\"k" + std::to_string(100 + k) + "\":" + std::to_string(value + k); + } + } + return s + "}"; + }; + std::vector texts; + for (const int n : + { + 3, 16, 17, 40 + }) + { + for (const int flags : + { + 0, 1, 2, 3 + }) + { + texts.push_back(object(n, (flags & 1) != 0, (flags & 2) != 0, 0)); + } + texts.push_back(object(n, true, false, 1)); // other values + } + // many objects in one comparison (working memory reused across objects) + std::string array = "["; + std::string array_other = "["; + for (std::size_t i = 0; i < texts.size(); ++i) + { + array += (i != 0 ? "," : "") + texts[i]; + array_other += (i != 0 ? "," : "") + texts[texts.size() - 1 - i]; + } + texts.push_back(array + "]"); + texts.push_back(array_other + "]"); + for (const std::string& x : texts) + { + for (const std::string& y : texts) + { + CAPTURE(x) + CAPTURE(y) + const ordered_json_document dx = ordered_json_document::parse(x); + const ordered_json_document dy = ordered_json_document::parse(y); + const ordered_json jx = ordered_json::parse(x); + const ordered_json jy = ordered_json::parse(y); + CHECK((dx.root() == dy.root()) == (jx == jy)); + CHECK((dx.root() == jy) == (jx == jy)); + const json_document ux = json_document::parse(x); + const json uy = json::parse(y); + CHECK((ux.root() == uy) == (json::parse(x) == uy)); + } + } + } + + SECTION("numbers, duplicate keys, member order") + { + const auto same = [](const char* x, const char* y) + { + const json_document dx = json_document::parse(x); + const json_document dy = json_document::parse(y); + return dx.root() == dy.root(); + }; + CHECK(same("1", "1.0")); + CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]")); + CHECK(!same("1", "1.5")); + CHECK(same("18446744073709551615", "18446744073709551615")); + CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})")); + CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})")); + CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})")); + CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})")); + CHECK(!same("[1, 2]", "[2, 1]")); + CHECK(!same("\"a\"", "\"b\"")); + CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\"")); + CHECK(!same("null", "false")); + CHECK(!same("[]", "{}")); + const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})"); + const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})"); + CHECK(dup.root() == last.root()); + // (== compares as parse() resolves duplicates, a lookup finds the first) + CHECK(dup.root()["a"].materialize() == 1); + CHECK(dup.root()["a"] != last.root()["a"]); + const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})"); + const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})"); + CHECK(ab.root() != ba.root()); + + // discarded values compare as basic_json's do + const json discarded(json::value_t::discarded); + CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested + CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty) + const json_document null_document = json_document::parse("null"); + CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty) + CHECK(!(null_document.root() == discarded)); + } + + SECTION("values that are the same text, or the same value written differently, at any depth") + { + // nested arrays and objects around depths where the walk over equal text gives up + for (const int depth : + { + 1, 5, 47, 48, 49, 50, 60, 300 + }) + { + CAPTURE(depth) + std::string head; + std::string tail; + for (int i = 0; i < depth; ++i) + { + head += i % 2 == 0 ? R"({"k":[1,"s",{"x":true},)" : R"([null,2.5,{"y":)"; + tail.insert(0, i % 2 == 0 ? "]}" : "}]"); + } + // the same text, one number changed, and one number written differently + const auto with_number = [&](const char* number) + { + std::string result = head; + result += number; + result += tail; + return result; + }; + const std::string text = with_number("7"); + const std::string changed = with_number("8"); + const std::string spelled = with_number("7.0"); + const json_document a = json_document::parse(text); + const json_document b = json_document::parse(text); + const json_document c = json_document::parse(changed); + const json_document d = json_document::parse(spelled); + CHECK(a.root() == b.root()); + CHECK(a.root() != c.root()); + CHECK(c.root() != a.root()); + CHECK(a.root() == d.root()); + CHECK(d.root() == a.root()); + CHECK(a.root() == json::parse(text)); + CHECK(a.root() != json::parse(changed)); + CHECK(json::parse(spelled) == a.root()); + const nlohmann::json_editable_document e = nlohmann::json_editable_document::parse(text); + CHECK(a.root() == e.root()); + CHECK(e.root() == a.root()); + CHECK(c.root() != e.root()); + CHECK(e.root() == e.root()); + const ordered_json_document oa = ordered_json_document::parse(text); + const ordered_json_document oc = ordered_json_document::parse(changed); + CHECK(oa.root() != oc.root()); + CHECK(oa.root() == ordered_json::parse(text)); + CHECK(oc.root() != ordered_json::parse(text)); + } + + // not the same text, but the same value + const auto same = [](const char* x, const char* y) + { + const json_document dx = json_document::parse(x); + const json_document dy = json_document::parse(y); + const bool equal = dx.root() == dy.root(); + CHECK(equal == (json::parse(x) == json::parse(y))); + CHECK(equal == (dx.root() == json::parse(y))); + return equal; + }; + CHECK(same(R"({"a":1,"a":2})", R"({"a":3,"a":2})")); + CHECK(same(R"({"a":1,"b":[1,{"c":2,"c":3}]})", R"({"b":[1,{"c":9,"c":3}],"a":1})")); + CHECK(!same(R"({"a":1,"a":2})", R"({"a":2,"a":3})")); + CHECK(same("[1.0,-0,0,1e2]", "[1.00,0,-0,100.0]")); + CHECK(same(R"(["\u00e9","\n"])", "[\"\xc3\xa9\",\"\\n\"]")); + CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":false}]})")); + CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"c":null}]})")); + CHECK(!same(R"({"a":[1,2,{"b":null}]})", R"({"a":[1,2,{"b":null,"c":1}]})")); + + // edited documents: moved sequences, links, and values of new nodes + nlohmann::json_editable_document edited = nlohmann::json_editable_document::parse(R"({"a":[1,2,3],"b":{"c":"d"},"e":0.5})"); + const json_document plain = json_document::parse(R"({"a":[1,2,3,4],"b":{"c":"x","y":null},"e":0.5,"z":[]})"); + edited.push_back(edited.root()["a"], 4); + edited.set(edited.root()["b"], "c", "x"); + edited.set(edited.root()["b"], "y", nullptr); + edited.set(edited.root(), "z", json::array()); + CHECK(edited.root() == plain.root()); + CHECK(plain.root() == edited.root()); + edited.erase(edited.root()["a"], 0); + CHECK(edited.root() != plain.root()); + CHECK(edited.root()["a"] == json::parse("[2,3,4]")); + const json_document tail = json_document::parse("[2,3,4]"); + CHECK(edited.root()["a"] == tail.root()); + } + + SECTION("large documents, with a difference at any place") + { + const auto make = [](std::size_t count, std::size_t changed, bool swap_members) + { + std::string text = R"({"b":1,"list":[)"; + for (std::size_t i = 0; i < count; ++i) + { + text += (i != 0 ? "," : ""); + const std::string id = std::to_string(i == changed ? i + 1 : i); + if (swap_members && i % 3 == 0) + { + text += R"({"v":[1.5,"s",{"k":)"; + text += id; + text += R"(}],"id":)"; + text += id; + text += "}"; + } + else + { + text += R"({"id":)"; + text += id; + text += R"(,"v":[1.5,"s",{"k":)"; + text += id; + text += "}]}"; + } + } + return text + R"(],"a":{"y":2,"x":3}})"; + }; + const std::size_t count = 3000; + for (const std::size_t changed : + { + count, std::size_t{0}, std::size_t{1}, count / 2, count - 1 + }) + { + for (const bool swap : + { + false, true + }) + { + CAPTURE(changed) + CAPTURE(swap) + const std::string text_a = make(count, count, false); + const std::string text_b = make(count, changed, swap); + const json_document a = json_document::parse(text_a); + const json_document b = json_document::parse(text_b); + const bool equal = changed == count; + CHECK((a.root() == b.root()) == equal); + CHECK((b.root() == a.root()) == equal); + CHECK((a.root() == json::parse(text_b)) == equal); + CHECK((json::parse(text_b) == a.root()) == equal); + CHECK((a.root()["list"] == b.root()["list"]) == equal); + const ordered_json_document oa = ordered_json_document::parse(text_a); + const ordered_json_document ob = ordered_json_document::parse(text_b); + CHECK((oa.root() == ob.root()) == (equal && !swap)); + CHECK((oa.root() == ordered_json::parse(text_b)) == (equal && !swap)); + } + } + } + + SECTION("deep nesting") + { + const std::string deep = std::string(100000, '[') + std::string(100000, ']'); + const json_document a = json_document::parse(deep); + const json_document b = json_document::parse(deep); + CHECK(a.root() == b.root()); + CHECK(a.root() == json::parse(deep)); + const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']'); + const json_document c = json_document::parse(other); + CHECK(a.root() != c.root()); + } +} diff --git a/tests/src/unit-json_view_dump.cpp b/tests/src/unit-json_view_dump.cpp index f3ccc004e..6470ab680 100644 --- a/tests/src/unit-json_view_dump.cpp +++ b/tests/src/unit-json_view_dump.cpp @@ -432,101 +432,6 @@ TEST_CASE("json_view dump") } } -TEST_CASE("json_view comparison") -{ - SECTION("equality of the values parse() produces") - { - generator g; - std::vector texts; - for (int i = 0; i < 600; ++i) - { - std::string text; - g.value(text, 0); - texts.push_back(text); - // the same value written differently: sorted keys, canonical numbers - texts.push_back(json::parse(text).dump(1)); - } - for (std::size_t i = 0; i + 2 < texts.size(); ++i) - { - for (std::size_t k = i; k < i + 3; ++k) - { - CAPTURE(texts[i]) - CAPTURE(texts[k]) - const json_document a = json_document::parse(texts[i]); - const json_document b = json_document::parse(texts[k]); - const json ja = json::parse(texts[i]); - const json jb = json::parse(texts[k]); - CHECK((a.root() == b.root()) == (ja == jb)); - CHECK((a.root() != b.root()) == (ja != jb)); - CHECK((a.root() == jb) == (ja == jb)); - CHECK((jb == a.root()) == (ja == jb)); - CHECK((a.root() != jb) == (ja != jb)); - CHECK((jb != a.root()) == (ja != jb)); - - // ordered_json compares members in order - const ordered_json_document oa = ordered_json_document::parse(texts[i]); - const ordered_json_document ob = ordered_json_document::parse(texts[k]); - const ordered_json oja = ordered_json::parse(texts[i]); - const ordered_json ojb = ordered_json::parse(texts[k]); - CHECK((oa.root() == ob.root()) == (oja == ojb)); - CHECK((oa.root() == ojb) == (oja == ojb)); - } - } - } - - SECTION("numbers, duplicate keys, member order") - { - const auto same = [](const char* x, const char* y) - { - const json_document dx = json_document::parse(x); - const json_document dy = json_document::parse(y); - return dx.root() == dy.root(); - }; - CHECK(same("1", "1.0")); - CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]")); - CHECK(!same("1", "1.5")); - CHECK(same("18446744073709551615", "18446744073709551615")); - CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})")); - CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})")); - CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})")); - CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})")); - CHECK(!same("[1, 2]", "[2, 1]")); - CHECK(!same("\"a\"", "\"b\"")); - CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\"")); - CHECK(!same("null", "false")); - CHECK(!same("[]", "{}")); - const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})"); - const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})"); - CHECK(dup.root() == last.root()); - // (== compares as parse() resolves duplicates, a lookup finds the first) - CHECK(dup.root()["a"].materialize() == 1); - CHECK(dup.root()["a"] != last.root()["a"]); - const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})"); - const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})"); - CHECK(ab.root() != ba.root()); - - // discarded values compare as basic_json's do - const json discarded(json::value_t::discarded); - CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested - CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty) - const json_document null_document = json_document::parse("null"); - CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty) - CHECK(!(null_document.root() == discarded)); - } - - SECTION("deep nesting") - { - const std::string deep = std::string(100000, '[') + std::string(100000, ']'); - const json_document a = json_document::parse(deep); - const json_document b = json_document::parse(deep); - CHECK(a.root() == b.root()); - CHECK(a.root() == json::parse(deep)); - const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']'); - const json_document c = json_document::parse(other); - CHECK(a.root() != c.root()); - } -} - TEST_CASE("json_view large objects") { // objects with 128 members or more are looked up with a hash index diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index 4f3e6319f..5e392559d 100644 --- a/tests/src/unit-json_view_image.cpp +++ b/tests/src/unit-json_view_image.cpp @@ -701,6 +701,64 @@ TEST_CASE("json_view images: ownership") #if !defined(JSON_NOEXCEPTION) TEST_CASE("json_view images: errors") { + SECTION("float tokens: a changed byte is accepted if the text is still a number with the digits the node records") + { + for (const char* const token : + { + "-12.375", "0.5", "123456.0", "-0.0", "9999999999999999999.5", "0.000000000000000000000001", "1.25e10", "5E-3" + }) + { + CAPTURE(token) + const std::string text = std::string("[") + token + "]"; + const json_document d = json_document::parse(text); + const std::vector image = d.save(); + const std::size_t at = text_at(image) + 1; // the first byte of the token + const std::uint16_t recorded = node_at(image, 1).extra; + CHECK(loaded_dump(image) == json::parse(text).dump()); + for (std::size_t pos = 0; token[pos] != '\0'; ++pos) + { + for (unsigned int byte = 0; byte < 256; ++byte) + { + std::vector changed = image; + changed[at + pos] = static_cast(byte); + std::string t = token; + t[pos] = static_cast(byte); + // the digits of the integer part and of the fraction of the changed token + std::size_t integer = 0; + std::size_t fraction = 0; + std::size_t k = (t[0] == '-') ? 1 : 0; + for (; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k) + { + ++integer; + } + if (k < t.size() && t[k] == '.') + { + for (++k; k < t.size() && t[k] >= '0' && t[k] <= '9'; ++k) + { + ++fraction; + } + } + // (as a JSON text, the token may have white space around it) + const bool float_token = json::accept(t) && t.find_first_of(std::string(" \t\r\n\0", 5)) == std::string::npos && (t.find('.') != std::string::npos || t.find_first_of("eE") != std::string::npos) // NOLINT(abseil-string-find-str-contains) + && ((integer | (fraction << 8U)) == recorded); + CAPTURE(pos) + CAPTURE(byte) + std::string result; + try + { + result = loaded_dump(changed); + } + catch (const json::parse_error& e) + { + CHECK(std::string(e.what()) == check_failed); + result = "rejected"; + } + CHECK((result != "rejected") == float_token); + } + } + } + } + SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw") { const json_document d = json_document::parse(R"({"k\n":"\u00e9x"})"); From d20c4dcd894728cd6a25e8e96533adf85fd17223 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 30 Oct 2026 15:20:12 +0100 Subject: [PATCH 42/63] json_view benchmarks: compare fairly and state what each row means (#5914) - bench_view: add "(editable)" rows for json_editable_document and a parse+dump workload, so the cost of lazy float conversion shows next to the parse row (the traverse workload already includes the parse). Check that all engines' dumps agree where outputs are comparable. - compare.py: add --aligned, which also builds and runs with aligned code and reports both tables, so code-layout artifacts become visible. Off by default; the default output is unchanged. Not run end to end yet. - README: say which engines are like-for-like peers (yyjson, simdjson DOM) and which are mutable reference points (Boost.JSON, json::parse, the editable json_view); explain the lazy float conversion and the eager prototype that was measured and dropped. Signed-off-by: Niels Lohmann --- tests/benchmarks/json_view/README.md | 28 +++++- tests/benchmarks/json_view/bench_view.cpp | 116 +++++++++++++++++----- tests/benchmarks/json_view/compare.py | 72 +++++++++----- 3 files changed, 166 insertions(+), 50 deletions(-) diff --git a/tests/benchmarks/json_view/README.md b/tests/benchmarks/json_view/README.md index a0bd2afbc..8f56c2693 100644 --- a/tests/benchmarks/json_view/README.md +++ b/tests/benchmarks/json_view/README.md @@ -21,6 +21,10 @@ python3 tests/benchmarks/json_view/compare.py --data - The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With `--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or with `--no-boost`), the Boost.JSON columns are skipped, and the results say so. +- `--aligned` additionally builds and runs everything with all functions aligned to 64 bytes (`-mllvm + -align-all-functions=6` for Clang, `-falign-functions=64 -falign-loops=64` for GCC), and reports both result + tables as "default build" and "aligned build". Code layout alone can move a result by a few percent; a difference + that appears in only one of the two tables is a layout artifact. Off by default, which doubles the run time. - `--corpus file...` adds files to the corpus benchmark, e.g. those of [simdjson-data](https://github.com/simdjson/simdjson-data) or the [yyjson benchmark](https://github.com/ibireme/yyjson_benchmark). @@ -39,7 +43,7 @@ where `json_view` stands on another architecture; they are not meant for publica ## What is measured -`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a +`bench_view.cpp` runs these workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a JSON-RPC request (`rpc`): | workload | what it does | @@ -48,6 +52,7 @@ JSON-RPC request (`rpc`): | traverse | parse, then visit every value, convert every number, touch every string and key | | select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) | | dump | serialize a parsed document (compact) | +| parse+dump | build a fresh document, then serialize it (compact), in one timed call | `bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and @@ -72,11 +77,32 @@ call follows an untimed call of the same engine: otherwise the engine after `jso cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times slower on citm_catalog traverse). +`json_view` is an index into the source text, not a tree of owning nodes. Its like-for-like peers are yyjson and +simdjson DOM (immutable, indexed, no per-node allocation). Boost.JSON and `json::parse` build mutable, owning DOMs and +are reference points for what that costs, not equals; simdjson On-Demand is lazy and forward-only, so it is the other +way round: it has no random access to pay for. All engines validate UTF-8 and syntax by default. + +Number conversion differs: `json_view` converts integers while parsing but converts floating-point numbers lazily, +when they are read or dumped; yyjson (default flags) and simdjson DOM convert every number while parsing. The cost of +float conversion therefore appears in the traverse and dump rows for `json_view`, and in the parse row for the others. +For float-heavy documents (canada), the fair reading is a sum of parse and use: the `traverse` workload already +includes the parse, and `parse+dump` measures a fresh document and its dump in one timed call; the `dump` row alone +leaves the conversion in `json_view` and out of the others. Eager conversion at parse time was prototyped as an opt-in +and measured: canada traverse (with its parse) was 4-7 % faster, but parse+dump 11-30 % slower and parse 36-90 % slower. It was +dropped, so lazy conversion is the only mode. + +`json_view` can also be edited: `json_editable_document` has the same read API, and the `(editable)` rows in +`bench_view` measure it on the same workloads. They are the fair counterpart of Boost.JSON and `json::parse`, which are +mutable too; the plain `json_view` rows are the counterpart of yyjson and simdjson DOM, which are not. `bench_edit` adds +the read-modify-write workloads. Read the tables with that in mind: an immutable-only library is not compared with an +editable `json_view` unless it says so. + The engines do not all offer the same features, which the numbers should be read with: | engine | document | random access | editable | notes | |---|---|---|---|---| | `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document | +| `json_view` (editable) | index into the text plus edits | yes | yes, in place | `json_editable_document`; keeps member order and number spellings | | yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | | | simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse | | simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select | diff --git a/tests/benchmarks/json_view/bench_view.cpp b/tests/benchmarks/json_view/bench_view.cpp index 32d038649..71e19d68b 100644 --- a/tests/benchmarks/json_view/bench_view.cpp +++ b/tests/benchmarks/json_view/bench_view.cpp @@ -8,8 +8,9 @@ // Same-feature-set benchmark: read-only JSON documents with random access. // -// json_view nlohmann/json_view.hpp (fresh document per parse / reused) -// yyjson yyjson_read(): immutable document, random access +// json_view nlohmann/json_view.hpp (fresh document per parse / reused; +// "(editable)": json_editable_document, same read API) +// yyjson yyjson_read(): immutable document, random access // simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser // per parse): immutable, random access // references (different feature sets): @@ -19,7 +20,11 @@ // // Workloads: parse (build + free), traverse (visit everything, convert every // number, touch every string and key), select (a few fields per document), -// dump (compact serialization of the parsed document). +// dump (compact serialization of the parsed document), and parse+dump (a fresh +// document, then its dump, in one timed call). json_view converts floating-point +// numbers lazily (when read or dumped), yyjson and simdjson DOM while parsing: +// for float-heavy documents, traverse (which includes the parse) and parse+dump +// are the fair reading of the parse and dump rows. // All engines run interleaved in every round, each timed call after an untimed // one of the same engine; the best round is reported. #include @@ -43,6 +48,8 @@ using nlohmann::json; using nlohmann::json_document; +using nlohmann::json_editable_document; +using nlohmann::json_editable_view; using nlohmann::json_view; static volatile double g_sink; @@ -55,7 +62,8 @@ struct stats // ---------------- traversal ---------------- -static void walk(json_view v, stats& st) +template +static void walk_view(V v, stats& st) { ++st.nodes; switch (v.type()) @@ -64,32 +72,42 @@ static void walk(json_view v, stats& st) for (auto it = v.begin(); it != v.end(); ++it) { st.str += it.key().size(); - walk(*it, st); + walk_view(*it, st); } break; case json::value_t::array: - for (const json_view e : v) + for (const V e : v) { - walk(e, st); + walk_view(e, st); } break; case json::value_t::string: st.str += v.get_string().size(); break; case json::value_t::number_integer: - st.num += static_cast(v.get()); + st.num += static_cast(v.template get()); break; case json::value_t::number_unsigned: - st.num += static_cast(v.get()); + st.num += static_cast(v.template get()); break; case json::value_t::number_float: - st.num += v.get(); + st.num += v.template get(); break; default: break; } } +static void walk(json_view v, stats& st) +{ + walk_view(v, st); +} + +static void walk(json_editable_view v, stats& st) +{ + walk_view(v, st); +} + static void walk(const json& j, stats& st) { ++st.nodes; @@ -300,20 +318,21 @@ static void walk(const boost::json::value& v, stats& st) // status (one tweet): id, user.screen_name, retweet_count // rpc: method, params.minuend, id -static double pick(const std::string& name, json_view r) +template +static double pick_view(const std::string& name, V r) { double acc = 0; if (name == "twitter" || name == "status") { - auto one = [&](json_view s) + auto one = [&](V s) { - acc += static_cast(s["id"].get()); + acc += static_cast(s["id"].template get()); acc += static_cast(s["user"]["screen_name"].get_string().size()); - acc += static_cast(s["retweet_count"].get()); + acc += static_cast(s["retweet_count"].template get()); }; if (name == "twitter") { - for (const json_view s : r["statuses"]) + for (const V s : r["statuses"]) { one(s); } @@ -325,20 +344,20 @@ static double pick(const std::string& name, json_view r) } else if (name == "citm_catalog") { - for (const json_view p : r["performances"]) + for (const V p : r["performances"]) { - acc += static_cast(p["id"].get() + p["eventId"].get() + p["seatCategories"].size()); + acc += static_cast(p["id"].template get() + p["eventId"].template get() + p["seatCategories"].size()); } acc += static_cast(r["events"].size()); } else if (name == "canada") { - const json_view g = r["features"][0]["geometry"]; + const V g = r["features"][0]["geometry"]; acc += static_cast(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size()); } else if (name == "jeopardy") { - for (const json_view q : r) + for (const V q : r) { acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0; acc += static_cast(q["category"].get_string().size()); @@ -347,11 +366,21 @@ static double pick(const std::string& name, json_view r) else if (name == "rpc") { acc += static_cast(r["method"].get_string().size()); - acc += static_cast(r["params"]["minuend"].get() + r["id"].get()); + acc += static_cast(r["params"]["minuend"].template get() + r["id"].template get()); } return acc; } +static double pick(const std::string& name, json_view r) +{ + return pick_view(name, r); +} + +static double pick(const std::string& name, json_editable_view r) +{ + return pick_view(name, r); +} + static double pick(const std::string& name, const json& r) { double acc = 0; @@ -634,15 +663,35 @@ int main(int argc, char** argv) walk(json::parse(dc.text), a); auto d = json_document::parse(dc.text); walk(d.root(), b); + auto ed = json_editable_document::parse(dc.text); + stats e; + walk(ed.root(), e); yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0); walk(yyjson_doc_get_root(y), c); simdjson::dom::parser p; walk(p.parse(dc.text).value(), dd); - const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str + const bool ok = a.nodes == b.nodes && a.nodes == e.nodes && a.str == e.str && std::fabs(a.num - e.num) <= 1e-9 * std::fabs(a.num) && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str && std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num); - const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value()); - std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH"); + const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pe = pick(dc.name, ed.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value()); + std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pe && pa == pc && pa == pd) ? "OK" : "MISMATCH"); yyjson_doc_free(y); + + // the dumps of the engines may format floats differently, so only: every dump is non-empty, and the + // dump of json_view (also editable) and of json::parse parse back to the same number of nodes + const std::string dj = json::parse(dc.text).dump(); + const std::string dv = d.root().dump(); + const std::string de = ed.root().dump(); + std::size_t yn = 0; + yyjson_doc* y2 = yyjson_read(dc.text.data(), dc.text.size(), 0); + char* yo = yyjson_write(y2, 0, &yn); + const bool dumps_ok = !dj.empty() && !dv.empty() && !de.empty() && yo != nullptr && yn > 0 && !simdjson::to_string(p.parse(dc.text).value()).empty(); + std::free(yo); + yyjson_doc_free(y2); + stats sj_nodes, sv_nodes, se_nodes; + walk(json::parse(dj), sj_nodes); + walk(json::parse(dv), sv_nodes); + walk(json::parse(de), se_nodes); + std::printf("check %-13s dump %s\n", dc.name.c_str(), (dumps_ok && sj_nodes.nodes == a.nodes && sv_nodes.nodes == a.nodes && se_nodes.nodes == a.nodes) ? "OK" : "MISMATCH"); } std::FILE* csv = std::fopen("bench_view.csv", "w"); @@ -669,6 +718,7 @@ int main(int argc, char** argv) workloads.push_back({"parse", { {"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast(d.node_count()); }}, {"json_view (reused)", [&] { reused.read(s); g_sink = static_cast(reused.node_count()); }}, + {"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = static_cast(d.root().size()); }}, {"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }}, {"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }}, {"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }}, @@ -680,6 +730,7 @@ int main(int argc, char** argv) workloads.push_back({"traverse", { {"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }}, {"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }}, + {"json_view (editable)", [&] { auto d = json_editable_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }}, {"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }}, {"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }}, {"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }}, @@ -691,15 +742,31 @@ int main(int argc, char** argv) workloads.push_back({"select", { {"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }}, {"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }}, + {"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = pick(name, d.root()); }}, {"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }}, {"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }}, {"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }}, {"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }}, }}); + // pipeline: a fresh document, then a dump of it, in one timed call + // (simdjson DOM reuses its parser, as in the parse row); the traverse + // workload above is already parse+traverse + workloads.push_back({"parse+dump", { + {"json_view", [&] { auto d = json_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast(o.size()); }}, + {"json_view (editable)", [&] { auto d = json_editable_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast(o.size()); }}, + {"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); std::size_t n = 0; char* o = yyjson_write(d, 0, &n); g_sink = static_cast(n); std::free(o); yyjson_doc_free(d); }}, + {"simdjson DOM", [&] { std::string o = simdjson::to_string(sj.parse(ps).value_unsafe()); g_sink = static_cast(o.size()); }}, +#if JSON_VIEW_BENCH_BOOST + {"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); std::string o = boost::json::serialize(v); g_sink = static_cast(o.size()); }}, +#endif + {"json::parse", [&] { json j = json::parse(s); std::string o = j.dump(); g_sink = static_cast(o.size()); }}, + }}); { // serialization of an already parsed document static json_document vd; vd.read(s); + static json_editable_document ed = json_editable_document::parse(s); + ed = json_editable_document::parse(s); static yyjson_doc* yd = nullptr; if (yd) { @@ -713,6 +780,7 @@ int main(int argc, char** argv) jd = json::parse(s); workloads.push_back({"dump", { {"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast(o.size()); }}, + {"json_view (editable)", [&] { std::string o = ed.root().dump(); g_sink = static_cast(o.size()); }}, {"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast(n); std::free(o); }}, {"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast(o.size()); }}, {"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast(o.size()); }}, @@ -740,7 +808,7 @@ int main(int argc, char** argv) } } const double ref = best[0]; - std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str()); + std::printf("%-13s %-14s", dc.name.c_str(), wl.first.c_str()); for (std::size_t k = 0; k < wl.second.size(); ++k) { const double us = best[k] / 1e3; diff --git a/tests/benchmarks/json_view/compare.py b/tests/benchmarks/json_view/compare.py index 6573c88af..1e8ae2494 100755 --- a/tests/benchmarks/json_view/compare.py +++ b/tests/benchmarks/json_view/compare.py @@ -239,6 +239,8 @@ def main(): ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries') ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON') ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)') + ap.add_argument('--aligned', action='store_true', + help='also build and run with all functions (and loops) aligned, to make code layout artifacts visible') ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)') ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus') ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)') @@ -282,32 +284,46 @@ def main(): + ['-I' + d for d in lib.include]) objects.append(obj) - binaries = {} - for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: - exe = os.path.join(args.build_dir, bench) - run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe]) - binaries[bench] = exe + cxx_version = output(cxx + ['--version']) + is_clang = 'clang' in cxx_version.lower() + # alignment flags: functions and (gcc) loops on 64-byte boundaries + align_flags = ['-mllvm', '-align-all-functions=6'] if is_clang else ['-falign-functions=64', '-falign-loops=64'] + + # the builds: the default one and, with --aligned, one with aligned code + variants = [('default build', args.build_dir, [])] + if args.aligned: + variants.append(('aligned build', os.path.join(args.build_dir, 'aligned'), align_flags)) - # run: bench_view on its documents, bench_corpus on those and the given files corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus - outputs = {} - outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir, - capture_output=True, text=True).stdout - outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir, - capture_output=True, text=True).stdout - outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir, - capture_output=True, text=True).stdout - outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir, - capture_output=True, text=True).stdout - for name, text in outputs.items(): - print(text) + # label -> {benchmark: output} + all_outputs = {} + for label, build_dir, extra in variants: + os.makedirs(build_dir, exist_ok=True) + binaries = {} + for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: + exe = os.path.join(build_dir, bench) + run(cxx + flags + extra + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe]) + binaries[bench] = exe + + # run: bench_view on its documents, bench_corpus on those and the given files + outputs = {} + outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=build_dir, + capture_output=True, text=True).stdout + outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=build_dir, + capture_output=True, text=True).stdout + outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=build_dir, + capture_output=True, text=True).stdout + outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))], + cwd=build_dir, capture_output=True, text=True).stdout + for name, text in outputs.items(): + print(text) + all_outputs[label] = outputs # results with their metadata now = datetime.datetime.now() host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host' stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}') os.makedirs(os.path.dirname(stem), exist_ok=True) - cxx_version = output(cxx + ['--version']) compiler_version = cxx_version.splitlines()[0] if cxx_version else ' '.join(cxx) meta = [ ('date', f'{now:%Y-%m-%d %H:%M}'), @@ -316,6 +332,7 @@ def main(): ('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'), ('compiler', compiler_version), ('flags', ' '.join(flags)), + ('aligned results', ('included (flags: ' + ' '.join(align_flags) + ')') if args.aligned else 'not included'), ('yyjson', libs['yyjson'].version), ('simdjson', libs['simdjson'].version), ('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'), @@ -328,15 +345,20 @@ def main(): f.write('| | |\n|---|---|\n') for key, value in meta: f.write(f'| {key} | {value} |\n') - for name, text in outputs.items(): - f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n') + for label, outputs in all_outputs.items(): + # (without --aligned there is only the default build, and no extra heading level) + prefix = f'{label}: ' if args.aligned else '' + for name, text in outputs.items(): + f.write(f'\n## {prefix}{name}\n\n```\n{text.rstrip()}\n```\n') with open(stem + '.csv', 'w', encoding='utf-8') as out: out.write(''.join(f'# {key}: {value}\n' for key, value in meta)) - for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: - path = os.path.join(args.build_dir, name + '.csv') - if os.path.isfile(path): - with open(path, encoding='utf-8') as f: - out.write(f'# {name}\n' + f.read()) + for label, build_dir, _ in variants: + for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']: + path = os.path.join(build_dir, name + '.csv') + if os.path.isfile(path): + with open(path, encoding='utf-8') as f: + # (the default build keeps its plain section names) + out.write(f'# {name}' + (f' ({label})' if args.aligned else '') + '\n' + f.read()) print(f'results: {stem}.md, {stem}.csv') From 755bb16d7a80436646380b5f871b93c6a443a3b7 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 30 Oct 2026 16:48:31 +0100 Subject: [PATCH 43/63] Fix the red CI on develop: remove the duplicate is_editable in basic_json_view (#5966) #5919 replaced the bool template parameter Editable of basic_json_view by view_mode Mode and defined is_editable from Mode. #5890 was written before that and added a second is_editable = Editable to the private section, which no longer compiles ("duplicate member", "undeclared identifier Editable"). It broke every job that builds the json_view tests (macOS, MSVC, clang-cl, clang on Windows, the C++20 module job, CodeQL). Signed-off-by: Niels Lohmann --- include/nlohmann/json_view.hpp | 3 --- single_include/nlohmann/json_view.hpp | 3 --- 2 files changed, 6 deletions(-) diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 0c6c7410b..9d26d12ea 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -766,9 +766,6 @@ class basic_json_view template friend class detail::view::view_side; friend iterator; - /// whether the view is one of an editable document - static constexpr bool is_editable = Editable; - basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index c4636da14..8ff21c77f 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -9807,9 +9807,6 @@ class basic_json_view template friend class detail::view::view_side; friend iterator; - /// whether the view is one of an editable document - static constexpr bool is_editable = Editable; - basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} From 4fe670b5f4ce045a31bf108dca389755cb3b3563 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 31 Oct 2026 13:54:02 +0100 Subject: [PATCH 44/63] json_view: decode chains of unicode escapes in a loop (#5969) After the first \uXXXX escape of a string, slow_string() now decodes the escapes that follow it directly, while the next six bytes are \u and four hex digits that are not a surrogate, instead of going round its main loop for each of them (that is how Python's json.dumps writes non-ASCII text by default). A surrogate, a bad digit, or fewer than six bytes leave the position at the backslash: the main loop handles them as before and reports the same error at the same position. twitterescaped.json (simdjson-data, 549 KB, 33,036 escapes), json_document::parse(), A/B in one binary, 6 code layouts, time after / before: - Apple M1 Max, Apple clang 21: 0.897 - x86-64 Haswell, GCC 16.2: 0.907 - x86-64 Haswell, Clang 23: 0.910 The other files of the benchmark set (twitter, citm_catalog, canada, gsoc-2018, apache_builds, instruments, tree-pretty, github_events, random, update-center, semanticscholar-corpus, twitter_timeline) stay within the noise of each machine. New test: chains of every length from 1 to 40 of escapes that give one, two, and three bytes of UTF-8, followed by the end of the string, text, escapes, surrogates, bad digits, and cut in every place, in keys and in values, against json::parse(). Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/builder.hpp | 15 +++++++ single_include/nlohmann/json_view.hpp | 15 +++++++ tests/src/unit-json_view_builder.cpp | 53 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 89b5af2ab..daf0d59bb 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -343,6 +343,21 @@ class builder } arena_ensure(w, w_end, 4); w = put_utf8(w, cp); + // A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is + // decoded here, without going round the loop above for each of them. Anything else (a surrogate, a + // bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports + // the same error at the same position as it would without this loop. + while (end - p >= 6 && p[0] == '\\' && p[1] == 'u') + { + const int next = hex_codepoint(p + 2); + if (next < 0 || (next & 0xF800) == 0xD800) + { + break; + } + p += 6; + arena_ensure(w, w_end, 4); + w = put_utf8(w, next); + } } else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) { diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 8ff21c77f..e490f61cc 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -1585,6 +1585,21 @@ class builder } arena_ensure(w, w_end, 4); w = put_utf8(w, cp); + // A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is + // decoded here, without going round the loop above for each of them. Anything else (a surrogate, a + // bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports + // the same error at the same position as it would without this loop. + while (end - p >= 6 && p[0] == '\\' && p[1] == 'u') + { + const int next = hex_codepoint(p + 2); + if (next < 0 || (next & 0xF800) == 0xD800) + { + break; + } + p += 6; + arena_ensure(w, w_end, 4); + w = put_utf8(w, next); + } } else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) { diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 4023c7848..7ac7a7d40 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -575,6 +575,59 @@ TEST_CASE("json_view builder: escapes between runs of text") } } +TEST_CASE("json_view builder: chains of unicode escapes") +{ + // Consecutive \uXXXX escapes are decoded in a loop of their own. Chains of + // every length from 1 to 40 of escapes that give one, two, and three + // bytes of UTF-8, in upper and lower case, followed by what ends a chain: + // the end of the string, text, another escape, UTF-8, and everything that + // is not a valid escape (a bad digit at every position of the last + // escape, a lone surrogate, a surrogate pair, a cut in every place). + const std::array chain = {{"\\u0041", "\\u00e4", "\\u00E4", "\\u20ac", "\\u3042", "\\uFFFF", "\\u0000", "\\u07ff"}}; + const std::array ends = {{"", "abc", "\\n", "\\u00e9", "\\ud83d\\ude00", "\\ud83d", "\\ude00", "\\ud83d\\u0041", "\\u12G4", "\\u12", "\xc3\xa4", "\\"}}; + std::mt19937 rng(2027); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible + for (std::size_t length = 1; length <= 40; ++length) + { + for (int i = 0; i < 20; ++i) + { + std::string content = std::string(rng() % 20, 'a'); + for (std::size_t k = 0; k < length; ++k) + { + content += chain[rng() % chain.size()]; + } + const std::string whole = content; + for (const char* end : ends) + { + check_string(whole + end); + check_string(whole + end + std::string(rng() % 19, 'z')); + } + // a bad digit at every position of every escape near the end of the chain + for (std::size_t back = 1; back <= 6 && back <= length; ++back) + { + for (std::size_t digit = 2; digit < 6; ++digit) + { + std::string broken = whole; + broken[broken.size() - (6 * back) + digit] = "gG\"\x01 z"[rng() % 6]; + check_string(broken); + } + } + // the text cut in every place of the last three escapes + for (std::size_t cut = 0; cut <= 18 && cut <= whole.size(); ++cut) + { + check_string(whole.substr(0, whole.size() - cut)); + } + } + } + // a chain longer than the first growth of the arena, in a key and in a value + std::string long_chain; + for (int i = 0; i < 3000; ++i) + { + long_chain += chain[static_cast(i) % chain.size()]; + } + check_string(long_chain); + check_string(long_chain + "\\ud83d\\ude00" + long_chain); +} + TEST_CASE("json_view node integer bits") { using nlohmann::detail::view::integer_bits; From 367963f6bb6d9b7e066ef8365e82993f7fcc47d2 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 31 Oct 2026 13:54:20 +0100 Subject: [PATCH 45/63] Test escaped object keys decoded through NLOHMANN_DEFINE_TYPE from a json_view (#5972) A json_view or json_document decodes a macro-defined struct directly and looks the members up by their decoded keys. Only plain keys were covered so far. The new section decodes structs defined with NLOHMANN_DEFINE_TYPE_INTRUSIVE, ..._WITH_DEFAULT and ..._WITH_NAMES from documents whose keys contain escape sequences, and compares the result with the conversion of the materialized value. It also pins the difference for a duplicate key written once plain and once escaped (the view takes the first member, the materialized value keeps the last one), and that an escaped backslash is part of the decoded key. Signed-off-by: Niels Lohmann --- tests/src/unit-json_view_from_json.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index 7e84a9081..a1d94d586 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -443,6 +443,30 @@ TEST_CASE("json_view from_json") CHECK(d.root().materialize().get().x == 3); } + SECTION("escaped keys: members are found by their decoded names") + { + // keys are decoded before they are compared, as by parse(); a lookup + // that compares the raw bytes of the source would miss these members + const json_document d = json_document::parse(R"([{"\u0078": 1, "y": 2}, {"V\u0061lue": 3, "Te\u0078t": "t"}, {"\u0062": "escaped"}, {"x": 1, "\u0078": 3, "y": 2}])"); + const json j = d.root().materialize(); + CHECK(d.root()[0].get().x == 1); + CHECK(d.root()[0].get().y == 2); + CHECK((d.root()[0].get() == j[0].get())); + CHECK(d.root()[1].get().value == 3); + CHECK(d.root()[1].get().text == "t"); + CHECK(d.root()[2].get().b == "escaped"); + CHECK(d.root()[2].get().a == 1); + // a duplicate key written once plain and once escaped: still the first member + CHECK(d.root()[3].get().x == 1); + CHECK(j[3].get().x == 3); +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS + // an escaped backslash is part of the decoded key: "x\\" is not "x" + const json_document e = json_document::parse(R"({"x\\": 1, "y": 2})"); + CHECK(exception_of_call([&] { static_cast(e.root().get()); }) == exception_of_call([&] { static_cast(e.root().materialize().get()); })); + CHECK(exception_of_call([&] { static_cast(e.root().get()); }) == "[json.exception.out_of_range.403] key 'x' not found"); +#endif + } + SECTION("editable documents and ordered_json") { json_editable_document e = json_editable_document::parse(R"({"x": 1, "y": 2})"); From 9c349b1d7c393c6bc6dbbe4191e332e3bb4100b8 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 31 Oct 2026 13:54:45 +0100 Subject: [PATCH 46/63] Write integers of 17 to 20 digits eight digits at a time in dump() (#5964) #5634 made dump() write integers below 10^16 eight digits at a time; larger ones still took the two-digits-per-division loop and count_digits(). Random 64-bit integers almost all have 17 to 20 digits (99.9 % of signed_ints.json and unsigned_ints.json), so Dump/signed_ints and Dump/unsigned_ints gained only 1.4x over 3.12.0, against 2.3x for small integers. A 64-bit magnitude is now written as its first one to four digits and two groups of eight. Wider magnitudes (__int128 and class types, #5805) keep the loop: number types of at most 64 bits are selected by type, so no comparison is reported as always true; for others the value is compared. The sign handling of #1447/#5815 (remove_sign() into number_magnitude_t) is unchanged. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/output/serializer.hpp | 34 ++++++++++++++ single_include/nlohmann/json.hpp | 34 ++++++++++++++ tests/src/unit-serialization.cpp | 46 +++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 598fda98a..4656dbdf1 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -1977,6 +1977,23 @@ class serializer return; } + // 17 to 20 digits: the first one to four, then twice eight + if (magnitude_fits_64_bits(abs_value, std::integral_constant())) + { + const auto value = static_cast(abs_value); + const std::uint64_t top = value / 10000000000000000u; // 1 to 1844 + const std::uint64_t low16 = value - (top * 10000000000000000u); + const std::uint64_t middle = low16 / 100000000u; + const std::uint64_t first = dtoa_impl::eight_digit_bytes(top); + const auto leading = static_cast(count_leading_zeros(first) / 8); // (first is not 0) + char* const p = buffer_ptr + n_chars; + dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u); + dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u); + dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u); + write_buffer_pos += n_chars + 24 - leading; + return; + } + n_chars += count_digits(abs_value); // spare 1 byte for '\0' @@ -2122,6 +2139,23 @@ class serializer } } + /// whether every number_magnitude_t fits 64 bits + static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits::is_specialized + && std::numeric_limits::digits <= 64; + + /// whether @a x fits 64 bits: always if number_magnitude_t has at most + /// 64 bits (selected by type, as the comparison would be reported as + /// always true), else compared + static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept + { + return true; + } + + static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept + { + return x <= 18446744073709551615u; + } + /* * Helper function for dump_integer * diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 10b42dac1..83a7090cc 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -36224,6 +36224,23 @@ class serializer return; } + // 17 to 20 digits: the first one to four, then twice eight + if (magnitude_fits_64_bits(abs_value, std::integral_constant())) + { + const auto value = static_cast(abs_value); + const std::uint64_t top = value / 10000000000000000u; // 1 to 1844 + const std::uint64_t low16 = value - (top * 10000000000000000u); + const std::uint64_t middle = low16 / 100000000u; + const std::uint64_t first = dtoa_impl::eight_digit_bytes(top); + const auto leading = static_cast(count_leading_zeros(first) / 8); // (first is not 0) + char* const p = buffer_ptr + n_chars; + dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u); + dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u); + dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u); + write_buffer_pos += n_chars + 24 - leading; + return; + } + n_chars += count_digits(abs_value); // spare 1 byte for '\0' @@ -36369,6 +36386,23 @@ class serializer } } + /// whether every number_magnitude_t fits 64 bits + static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits::is_specialized + && std::numeric_limits::digits <= 64; + + /// whether @a x fits 64 bits: always if number_magnitude_t has at most + /// 64 bits (selected by type, as the comparison would be reported as + /// always true), else compared + static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept + { + return true; + } + + static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept + { + return x <= 18446744073709551615u; + } + /* * Helper function for dump_integer * diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index 1173b851b..e2a41f272 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -23,6 +23,7 @@ using nlohmann::json; #include #include #include +#include #include #include @@ -248,6 +249,51 @@ TEST_CASE_TEMPLATE("serialization for extreme integer values", T, int32_t, uint3 } } +TEST_CASE("integers of every digit count are written like std::to_string") +{ + // dump_integer() writes up to 16 digits in two groups of eight and 17 to + // 20 digits as one to four digits and two groups of eight + std::vector values; + std::uint64_t power = 1; + for (int digits = 1; digits <= 20; ++digits) + { + values.push_back(power); // 1 followed by zeros + values.push_back(power + 1); + values.push_back((power * 2) - 1); + if (digits < 20) + { + values.push_back((power * 10) - 1); // all 9s + values.push_back((power * 10) - power); // 9 followed by zeros + power *= 10; + } + } + values.push_back((std::numeric_limits::max)()); + values.push_back(static_cast((std::numeric_limits::max)())); + values.push_back(static_cast((std::numeric_limits::max)()) + 1); + std::uint64_t state = 0x9E3779B97F4A7C15u; + for (int i = 0; i < 20000; ++i) + { + state = (state * 6364136223846793005u) + 1442695040888963407u; + values.push_back(state >> (state % 61)); // every bit length + } + + for (const auto v : values) + { + CAPTURE(v) + CHECK(json(v).dump() == std::to_string(v)); + if (v <= static_cast((std::numeric_limits::max)())) + { + const auto s = static_cast(v); + CHECK(json(s).dump() == std::to_string(s)); + CHECK(json(-s).dump() == std::to_string(-s)); + } + } + CHECK(json((std::numeric_limits::min)()).dump() == "-9223372036854775808"); + + // in a container, so the digits are followed by other output + CHECK(json::array({std::uint64_t{18446744073709551615u}, std::int64_t{-1234567890123456789}, 1}).dump() == "[18446744073709551615,-1234567890123456789,1]"); +} + TEST_CASE("dump with binary values") { auto binary = json::binary({1, 2, 3, 4}); From 3fd3e0fdaa54201e1b98cb523423bb548dcb0a38 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:20:29 +0100 Subject: [PATCH 47/63] json_view: dump and read canada.json-style floats faster (batched float decisions) (#5908) Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/number.hpp | 171 ++++- include/nlohmann/detail/view/serializer.hpp | 404 ++++++++++- single_include/nlohmann/json_view.hpp | 576 ++++++++++++++- tests/src/unit-json_view_dump.cpp | 757 +++----------------- tests/src/unit-json_view_dump_escape.cpp | 298 ++++++++ tests/src/unit-json_view_large.cpp | 544 ++++++++++++++ 6 files changed, 2060 insertions(+), 690 deletions(-) create mode 100644 tests/src/unit-json_view_dump_escape.cpp create mode 100644 tests/src/unit-json_view_large.cpp diff --git a/include/nlohmann/detail/view/number.hpp b/include/nlohmann/detail/view/number.hpp index 10e2aa85d..7fc1b946f 100644 --- a/include/nlohmann/detail/view/number.hpp +++ b/include/nlohmann/detail/view/number.hpp @@ -19,6 +19,7 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -103,12 +104,56 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n) return convert_float(first, last, dot, mantissa_end); } +#if NLOHMANN_VIEW_VECTOR +/*! +@brief the value of the first 16 digits of a run of digits with a point after +int_digits of them (or none, if int_digits >= 16) + +The 17 bytes at p are read at once: the bytes after the point move down by one, +then pairs, quadruples, and octets of digits are combined by multiplications +(after simdjson). The bytes [p, p + 32) must be readable. Bytes that are not +digits (only in a damaged image loaded with image_check::bounds) give a wrong +value, but no undefined behavior. +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t sixteen_digits(const unsigned char* p, unsigned int_digits) noexcept +{ +#if NLOHMANN_VIEW_NEON + const uint8x16_t low = vld1q_u8(p); + const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u)); + const uint8x16_t before_point = vcltq_u8(index, vdupq_n_u8(static_cast(int_digits))); + const uint8x16_t digits = vsubq_u8(vbslq_u8(before_point, low, vextq_u8(low, vld1q_u8(p + 16), 1)), vdupq_n_u8('0')); + const uint16x8_t twos = vpaddlq_u8(vmulq_u8(digits, vreinterpretq_u8_u16(vdupq_n_u16(0x010A)))); + const uint32x4_t fours = vpaddlq_u16(vmulq_u16(twos, vreinterpretq_u16_u32(vdupq_n_u32(0x00010064)))); + const uint64x2_t eights = vpaddlq_u32(vmulq_u32(fours, vreinterpretq_u32_u64(vdupq_n_u64(0x0000000100002710u)))); + return (vgetq_lane_u64(eights, 0) * 100000000u) + vgetq_lane_u64(eights, 1); +#else + // NOLINTBEGIN(portability-simd-intrinsics) + const __m128i low = _mm_loadu_si128(static_cast(static_cast(p))); // NOLINT(bugprone-casting-through-void) + const __m128i high = _mm_loadu_si128(static_cast(static_cast(p + 16))); // NOLINT(bugprone-casting-through-void) + const __m128i before_point = _mm_cmpgt_epi8(_mm_set1_epi8(static_cast(int_digits)), _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); + const __m128i moved = _mm_or_si128(_mm_srli_si128(low, 1), _mm_slli_si128(high, 15)); + const __m128i digits = _mm_sub_epi8(_mm_or_si128(_mm_and_si128(before_point, low), _mm_andnot_si128(before_point, moved)), _mm_set1_epi8('0')); + // (16-bit lanes: SSE2 has no multiply-add of bytes) + const __m128i tens = _mm_setr_epi16(10, 1, 10, 1, 10, 1, 10, 1); + const __m128i twos = _mm_packs_epi32(_mm_madd_epi16(_mm_unpacklo_epi8(digits, _mm_setzero_si128()), tens), + _mm_madd_epi16(_mm_unpackhi_epi8(digits, _mm_setzero_si128()), tens)); + const __m128i fours = _mm_madd_epi16(twos, _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1)); + const __m128i eights = _mm_madd_epi16(_mm_packs_epi32(fours, fours), _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1)); + const auto upper = static_cast(_mm_cvtsi128_si32(eights)); + const auto lower = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(eights, 4))); + // NOLINTEND(portability-simd-intrinsics) + return (std::uint64_t{upper} * 100000000u) + lower; +#endif +} +#endif + /*! @brief the digits of a float token with at most 19 digits, from its layout The digit layout recorded while parsing says where the integer digits, the fraction digits, and the exponent are, so the digits are read eight at a -time without scanning. +time without scanning (with SIMD, a token of 16 to 19 digits sixteen at a +time). @param[in] p first character of the token @param[in] e end of the token @@ -118,15 +163,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char { const bool negative = *p == '-'; p += negative ? 1 : 0; - std::uint64_t w = parse_upto19(p, int_digits, limit); - p += int_digits; + std::uint64_t w = 0; std::int64_t q = 0; - if (frac_digits != 0) +#if NLOHMANN_VIEW_VECTOR + const unsigned count = int_digits + frac_digits; + if (count >= 16 && limit - p >= 32) { - w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); - p += 1 + frac_digits; + // (the point, if any, follows the integer digits; the digits after + // the first 16 are at most three) + w = sixteen_digits(p, int_digits); + for (unsigned i = 16; i < count; ++i) + { + w = (w * 10) + static_cast(p[i + (i >= int_digits ? 1 : 0)] - '0'); + } + p += count + (frac_digits != 0 ? 1 : 0); q = -static_cast(frac_digits); } + else /* NOLINT(readability/braces) */ +#endif + { + w = parse_upto19(p, int_digits, limit); + p += int_digits; + if (frac_digits != 0) + { + w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); + p += 1 + frac_digits; + q = -static_cast(frac_digits); + } + } if (p != e) { // [eE][+-]digits; huge exponents saturate (the parser rejected @@ -161,12 +225,22 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char The result is correctly rounded by the lexer's conversion (detail::decimal_to_float(): Clinger's fast path where both operands are exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19 -digits), so it is the value parse() produces. +digits), so it is the value parse() produces. Tokens of 16 or more digits go +to Eisel-Lemire directly: the fast path takes few of them (none of 17 digits), +and its check is a branch that data like GeoJSON coordinates mispredicts. */ template NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept { - return decimal_to_float(layout_decimal(p, e, int_digits, frac_digits, limit)); + const float_significand d = layout_decimal(p, e, int_digits, frac_digits, limit); + if (int_digits + frac_digits >= 16) + { + using format = ieee_binary_format::digits>; + native_float_t value{}; + float_from_bits(eisel_lemire(d.exponent, d.w) | (d.negative ? (std::uint64_t{1} << format::sign_bit()) : 0u), value); + return value; + } + return decimal_to_float(d); } /// the value of a float set by an edit: its token (the shortest round-trip @@ -185,6 +259,80 @@ NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n) return float_value(token, n); } +#if NLOHMANN_VIEW_VECTOR +/// eisel_lemire() for the token w * 10^q of coordinate_float() +template +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, Format /*format*/) noexcept +{ + return eisel_lemire(q, w); +} + +/*! +@brief (binary64) eisel_lemire() without the cases that tokens of 16 or 17 +digits with 5 or more after the point cannot reach: a value of 0, a +subnormal, an infinity, or a tie (q < -4) +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, ieee_binary_format<53> format) noexcept +{ + if (NLOHMANN_VIEW_UNLIKELY(w == 0 || q > -5)) + { + return eisel_lemire>(q, w); + } + static_cast(format); + const int lz = count_leading_zeros(w); + const std::uint64_t normalized = w << static_cast(lz); + const auto index = static_cast(2 * (q - pow5_128_smallest_power)); + const uint128_parts product = full_multiplication(normalized, pow5_128()[index]); + std::uint64_t high = product.high; + if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu)) + { + // (the lower bits may carry into the result) + const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high; + high += product.low + second < product.low ? 1u : 0u; + } + const auto upperbit = static_cast(high >> 63u); + std::uint64_t mantissa = high >> (upperbit + 9); + auto power2 = static_cast(((217706 * q) >> 16) + 63 + static_cast(upperbit) - lz + 1023); + mantissa = (mantissa + (mantissa & 1u)) >> 1u; + const bool carry = mantissa >= (std::uint64_t{2} << 52u); + mantissa = carry ? (std::uint64_t{1} << 52u) : mantissa; + power2 += carry ? 1u : 0u; + return (mantissa & ~(std::uint64_t{1} << 52u)) | (power2 << 52u); +} + +/*! +@brief (binary32 and binary64) the value of a float token of the source with +16 or 17 digits, a point, and no exponent, the shape of most coordinates +written with full precision; whether the token of n has this shape + +These tokens skip the general layout of layout_decimal(): no digits beyond +the 17th, no exponent (and Eisel-Lemire directly, as in layout_float()). +*/ +template +NLOHMANN_VIEW_ALWAYS_INLINE bool coordinate_float(const document_data& d, const node& n, FloatType& value) noexcept +{ + const unsigned int_digits = n.extra & 0xFFu; + const unsigned frac_digits = n.extra >> 8u; + const unsigned count = int_digits + frac_digits; + const auto* const first = reinterpret_cast(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + const unsigned negative = first[0] == '-' ? 1u : 0u; + if (count - 16u > 1u || frac_digits == 0 || n.len != negative + count + 1 || d.size - n.off < 33) + { + return false; + } + const unsigned char* const digits = first + negative; + const std::uint64_t w = sixteen_digits(digits, int_digits); + // (the 17th digit follows the point) + const std::uint64_t w17 = (w * 10) + static_cast(digits[17] - '0'); + using format = ieee_binary_format::digits>; + native_float_t result{}; + float_from_bits(coordinate_bits(-static_cast(frac_digits), count == 17 ? w17 : w, format{}) | (static_cast(negative) << format::sign_bit()), result); + value = result; + return true; +} + +#endif + /// the value of the float token of a node, as parse() converts it; floats and /// doubles with at most 19 digits are converted from the digit layout template @@ -200,6 +348,13 @@ FloatType float_value(const document_data& d, const node& n) template FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/) { +#if NLOHMANN_VIEW_VECTOR + FloatType value {}; + if (coordinate_float(d, n, value)) + { + return value; + } +#endif const unsigned int_digits = n.extra & 0xFFu; const unsigned frac_digits = n.extra >> 8u; if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many") diff --git a/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index b41ac683f..2bfa2d2c0 100644 --- a/include/nlohmann/detail/view/serializer.hpp +++ b/include/nlohmann/detail/view/serializer.hpp @@ -14,7 +14,9 @@ #include // size_t #include // uint8_t, uint32_t #include // memcpy, memset +#include // less #include // numeric_limits +#include // unique_ptr #include // integral_constant #include // vector @@ -384,6 +386,143 @@ struct depth_counter } }; +/*! +@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w < +10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position) + +dump() writes the shortest decimal in the rounding interval of x (the closest +one if there are several, see zmij::to_shortest()). Its digits are those of a +multiple of 10^g of the token's last digit, with g = dec_exp + 1 - q for x's +decimal exponent dec_exp: the multiple of 10^g nearest to x if it lies in the +interval (at most one does), else the multiple of 10^(g - 1) nearest to x +(see token_digits_1() and token_digits_2()). For g <= 0, the token itself is +in the interval. + +The position is exact in a 64-bit fixed-point number with 56 bits after the +point (of the token's last digit): x / 10^q = 2c * 5^-q / 2^t for x = c * 2^e2 +and t = q + 1 - e2, with at most 56 bits after the point here, and |x / 10^q - +w| is at most half the spacing of the doubles, below 12 units. So only the low +64 bits of 2c * 5^-q are needed. c and e2 come from Eisel-Lemire's product +directly (as eisel_lemire() rounds it: no tie is possible for q < -4, and its +second product is added where it can carry into the result). + +@param[out] offset x / 10^q - w + 32, with 56 bits after the point +@param[out] radius half the spacing of the doubles at x, likewise (+ 1 if + the interval is closed) +@return g; 3 if the digits are not decided here (a power of two, whose + interval is not symmetric, or out of range) +*/ +NLOHMANN_VIEW_ALWAYS_INLINE int locate_token(std::uint64_t w, int q, std::uint64_t& offset, std::uint64_t& radius) noexcept +{ + const int lz = count_leading_zeros(w); + const std::uint64_t normalized = w << static_cast(lz); + const auto index = static_cast(2 * (q - pow5_128_smallest_power)); + const uint128_parts product = full_multiplication(normalized, pow5_128()[index]); + std::uint64_t high = product.high; + if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu)) + { + // (as eisel_lemire(): the lower bits may carry into the result) + const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high; + high += product.low + second < product.low ? 1u : 0u; + } + const auto upperbit = static_cast(high >> 63u); + const std::uint64_t mantissa = high >> (upperbit + 9); + const std::uint64_t c = (mantissa + (mantissa & 1u)) >> 1u; + // e2 as eisel_lemire() computes the exponent (log2(10) ~ 217706 / 2^16) + const int e2 = static_cast((217706 * static_cast(q)) >> 16) + 63 + static_cast(upperbit) - lz - 52; + const int t = q + 1 - e2; + const int g = ((e2 * 315653) >> 20) + 1 - q; // (dec_exp as in zmij::to_shortest(), for a regular x) + static const std::array fives = {{1u, 5u, 25u, 125u, 625u, 3125u, 15625u, 78125u, 390625u, 1953125u, 9765625u, 48828125u, 244140625u, 1220703125u, 6103515625u, 30517578125u, 152587890625u}}; + const std::uint64_t five = fives[static_cast(-q)]; // 5^-q + const unsigned s = static_cast(56 - t) & 63u; + offset = ((2 * c * five) << s) - (w << 56u) + (std::uint64_t{32} << 56u); + radius = (five << s) + ((c & 1u) == 0 ? 1u : 0u); + // (c = 2^52: a power of two; c = 2^53: rounded up to one. With 8 to 16 + // fraction digits, as the batch's tokens have, t is in [15, 40] and g at + // most 2. x lies halfway between two candidates only if its distance from + // the token is a whole unit (g == 2) or a half (g == 1): such tokens are + // left to the full conversion) + const bool decided = c - ((std::uint64_t{1} << 52u) + 1) < (std::uint64_t{1} << 52u) - 1 + && (offset & ((std::uint64_t{1} << 55u) - 1)) != 0; + return decided ? g : 3; +} + +/// all bits set if b, else none +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t mask_if(bool b) noexcept +{ + return std::uint64_t{0} - static_cast(b); +} + +/// a if b, else c, without a branch (the conditions of token_digits_1/2() are +/// as good as random, and GCC compiled their ?: as branches: one misprediction +/// per float of canada.json on x86-64) +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t select_if(bool b, std::uint64_t a, std::uint64_t c) noexcept +{ + return c ^ ((a ^ c) & mask_if(b)); +} + +/// the distance of a position (see locate_token()) from the biased point 32 +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t biased_distance(std::uint64_t position) noexcept +{ + constexpr std::uint64_t center = std::uint64_t{32} << 56u; + // |position - center|, without a branch: negated (complemented, plus 1) if below + const std::uint64_t below = mask_if(position < center); + return ((position - center) ^ below) - below; +} + +/*! +@brief (g == 1) the digits dump() writes for a token, from the position of its +double (see locate_token()): the multiple of 10 nearest to x if it is in the +interval, else w rounded to x (an exact tie between two candidates, like +754570942.33984373, whose double is 754570942.33984375, is excluded by +locate_token()) + +With |x - w| at most the radius, which is below half the spacing of the +candidates, the multiple of 10 nearest to x is w - r10 or the one above it. +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_1(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10) noexcept +{ + constexpr std::uint64_t one = std::uint64_t{1} << 56u; + const std::uint64_t from = offset + (r10 * one); // x - (w - r10) (+ 32) + const bool up = from > 37 * one; // (nearer to the multiple above) + const bool shorter = biased_distance(from - (mask_if(up) & (10 * one))) < radius; + const std::uint64_t a = w - r10 + (mask_if(up) & 10u); + const std::uint64_t b = w + ((offset + (one / 2)) >> 56u) - 32; // w rounded to x + return select_if(shorter, a, b); +} + +/*! +@brief (g == 2) the digits dump() writes for a token, as token_digits_1(): the +multiple of 100 nearest to x if it is in the interval, else the multiple of 10 +nearest to x +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_2(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10, std::uint64_t r100) noexcept +{ + constexpr std::uint64_t one = std::uint64_t{1} << 56u; + const std::uint64_t from = offset + (r100 * one); + const bool up = from > 82 * one; + const bool shorter = biased_distance(from - (mask_if(up) & (100 * one))) < radius; + const std::uint64_t a = w - r100 + (mask_if(up) & 100u); + // x - (w - r10), in [-12, 21]: the multiple of 10 nearest to x is that one, + // or one or two above, or one below + const std::uint64_t y = offset + (r10 * one); + const std::uint64_t steps = static_cast(y > 37 * one) + static_cast(y > 47 * one); + const bool down = y < 27 * one; + const std::uint64_t b = w - r10 + (10 * steps) - (mask_if(down) & 10u); + return select_if(shorter, a, b); +} + +/// the four ASCII digits of x < 10^4, the first in the lowest byte (as +/// little-endian memory holds them) +NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t four_digit_text(std::uint32_t x) noexcept +{ + const std::uint32_t ab = (x * 5243u) >> 19u; // x / 100 + std::uint32_t v = ab | ((x - (ab * 100u)) << 16u); + const std::uint32_t a = ((v * 103u) >> 10u) & 0x000F000Fu; // / 10 per 16-bit lane + v = a | ((v - (a * 10u)) << 8u); + return v + 0x30303030u; +} + /// how the view's dump() writes a value struct dump_style { @@ -711,6 +850,7 @@ class view_serializer std::array buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read std::vector heap; small_stack stack(buffer.data(), buffer.size(), heap); + float_batch batch; const char* const src = m_doc.src; const char* const src_end = src + m_doc.size; char* w = m_out.cursor(); @@ -850,7 +990,7 @@ class view_serializer else if (enabled(fast_double::value)) { room(64); - w = write_double_at(w, *n); + w = write_double_ahead(w, src, n, batch); } else { @@ -875,6 +1015,20 @@ class view_serializer case value_t::array: { const bool object = n->kind == node_kind::object; + // (read-only documents, compact output) a pair of floats, + // like the coordinates of GeoJSON, without a frame + if (enabled(!Editable && !Pretty && !SourceNumbers && fast_double::value) && n->next == 3 && n->len == 2 + && n[1].kind == node_kind::number_float && n[2].kind == node_kind::number_float) + { + room(2 * 64); + *w++ = '['; + w = write_double_ahead(w, src, n + 1, batch); + *w++ = ','; + w = write_double_ahead(w, src, n + 2, batch); + *w++ = ']'; + n += 2; // (the element before the next node) + break; + } room(8); if (n->len == 0) { @@ -984,6 +1138,254 @@ class view_serializer } } + /// (float_batch) the mark of digits whose rounding carried beyond the + /// token's last four (see mark()) + static constexpr std::uint64_t carried() noexcept + { + return std::uint64_t{1} << 63u; + } + + /// floats of 16 or 17 digits ahead of the walk, converted together (see + /// convert_ahead()), with their digits as mark() keeps them + struct float_batch + { + static constexpr std::size_t capacity() noexcept + { + return 256; + } + /// (on the heap, allocated by the first conversion: a frame of more + /// than a page made every dump of a small document slower) + struct entries + { + std::array at; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + }; + std::unique_ptr storage{}; + const node** at = nullptr; + std::uint64_t* digits = nullptr; + std::size_t count = 0; + std::size_t next = 0; + const node* scanned = nullptr; ///< where the last scan of the node array ended + }; + + /// (doubles) the float at n as dump() writes it, at w (64 bytes of room): + /// from the batch if it holds n + NLOHMANN_VIEW_ALWAYS_INLINE char* write_double_ahead(char* w, const char* src, const node* n, float_batch& batch) + { +#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN + // (digits 0: not written by the batch; carried(): rounding carried + // beyond the last four digits) + if (batch.next != batch.count && batch.at[batch.next] == n && batch.digits[batch.next] - 1 < carried() - 1) + { + return write_token_digits(w, src, *n, batch.digits[batch.next++]); + } + return write_double_beside(w, n, batch); +#else + static_cast(src); + static_cast(batch); + return write_double_at(w, *n); +#endif + } + +#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN + /// write_double_ahead() for a float that is not the batch's next one (out + /// of line: the walk is faster with less code in its loop) + NLOHMANN_VIEW_NOINLINE char* write_double_beside(char* w, const node* n, float_batch& batch) + { + // (an editable document's walk can skip nodes of the array) + const std::less before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<> + while (batch.next != batch.count && before(batch.at[batch.next], n)) + { + ++batch.next; + } + if ((batch.next != batch.count && batch.at[batch.next] == n) || convert_ahead(n, batch)) + { + const std::uint64_t digits = batch.digits[batch.next++]; + if ((digits & carried()) != 0) + { + return write_carried_digits(w, *n, digits & ~carried()); + } + if (digits != 0) + { + return write_token_digits(w, m_doc.src, *n, digits); + } + } + return write_double_at(w, *n); + } + + /*! + @brief convert the floats of 16 or 17 digits among the next nodes of the + array at once; whether n is the first of them + + Such tokens (like the coordinates of GeoJSON, written with 17 digits) are + written as the token with its last four digits replaced + (write_token_digits()). Each step (the digits, locate_token(), + token_digits_1/2()) is a long chain of dependent operations; one loop per + step over many floats lets the processor overlap the floats, which it does + not across the steps of one float (on Apple M1, one loop for the digits and + locate_token() made the dump of canada.json 12 % slower). Each node of the array + is scanned once; a node that the walk does not reach in order (in an + editable document) is written without the batch. + */ + NLOHMANN_VIEW_NOINLINE bool convert_ahead(const node* n, float_batch& batch) + { + const std::less before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<> + const node* const last = m_doc.nodes + m_doc.node_count; + if (before(n, batch.scanned) || before(n, m_doc.nodes) || !before(n, last)) + { + return false; + } + const char* const src = m_doc.src; + const char* const src_end = src + m_doc.size; + const node* const stop = last - n > 1024 ? n + 1024 : last; + batch.count = 0; + batch.next = 0; + // the digits of each token (and its last four from the text) + std::array digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array exponent; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array last_four; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + const node* p = n; + if (!batch.storage) + { + batch.storage.reset(new typename float_batch::entries); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): C++11 + batch.at = batch.storage->at.data(); + batch.digits = batch.storage->digits.data(); + } + for (; p != stop && batch.count != float_batch::capacity(); ++p) + { + // a token of the source: 16 or 17 digits, at least 8 after the + // point, no exponent, 32 bytes to read + const unsigned frac_digits = p->extra >> 8u; + const unsigned int_digits = p->extra & 0xFFu; + const unsigned count = int_digits + frac_digits; + if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0 + && count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33) + { + const auto* const token = reinterpret_cast(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + const unsigned sign = token[0] == '-' ? 1 : 0; + const unsigned char* const first = token + sign; + const std::uint64_t w = sixteen_digits(first, int_digits); + // (the 17th digit follows the point; a token with an exponent + // gets digits outside the range of locate_token(), and is not + // written by the batch. A token 0.xxx with 16 significant + // digits is: its double is written as "0." and its digits too) + const std::uint64_t w17 = (w * 10) + static_cast(first[17] - '0'); + const std::size_t i = batch.count++; + batch.at[i] = p; + if (p->len != sign + count + 1) + { + digits[i] = 0; + } + else + { + digits[i] = count == 17 ? w17 : w; + } + exponent[i] = -static_cast(frac_digits); + // (byte 0: the last digit, byte 1: the last two, bits 16-29: the last four) + std::uint32_t four = 0; + std::memcpy(&four, token + p->len - 4, 4); + four -= 0x30303030u; + const std::uint32_t pairs = ((four & 0x0F0F0F0Fu) * 2561u) >> 8u; // (byte 0, 2: the pairs) + last_four[i] = (four >> 24u) | ((pairs >> 16u & 0xFFu) << 8u) | ((((pairs & 0x00FF00FFu) * 6553601u) >> 16u) << 16u); + } + } + batch.scanned = p; + // the position of each double, and the floats of either kind + std::array offset; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array radius; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array kind_1; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array kind_2; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::size_t count_1 = 0; + std::size_t count_2 = 0; + for (std::size_t i = 0; i != batch.count; ++i) + { + const std::uint64_t w = digits[i]; + // (the range excludes the values of bytes that are not digits, only + // in a damaged image loaded with image_check::bounds) + const int g = w - 1000000000000000u < 99000000000000000u ? locate_token(w, exponent[i], offset[i], radius[i]) : 3; + batch.digits[i] = mask_if(g <= 0) & ((last_four[i] >> 16u) + 1); + kind_1[count_1] = static_cast(i); + count_1 += static_cast(g == 1); + kind_2[count_2] = static_cast(i); + count_2 += static_cast(g == 2); + } + // the digits to write, in one loop per kind (see mark()) + for (std::size_t k = 0; k != count_1; ++k) + { + const std::size_t i = kind_1[k]; + const std::uint64_t s = token_digits_1(digits[i], offset[i], radius[i], last_four[i] & 0xFFu); + batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u); + } + for (std::size_t k = 0; k != count_2; ++k) + { + const std::size_t i = kind_2[k]; + const std::uint64_t s = token_digits_2(digits[i], offset[i], radius[i], last_four[i] & 0xFFu, (last_four[i] >> 8u) & 0xFFu); + batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u); + } + return batch.count != 0 && batch.at[0] == n; + } + + /*! + @brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes + of room), from the digits of its double (see locate_token()) whose + rounding carried beyond the token's last four digits + + (like 43.42027299999999 -> 43.420273): the digits go to the writer of + Zmij's results as its 15 or 16 leading digits and one more, which omits + the trailing zeros. + */ + NLOHMANN_VIEW_NOINLINE char* write_carried_digits(char* w, const node& n, std::uint64_t digits) const noexcept + { + const int exponent = -(n.extra >> 8u); + const std::uint64_t leading = digits / 10; + const auto last = static_cast(digits - (leading * 10)); + const bool seventeen = digits >= 10000000000000000u; + *w = '-'; + w += m_doc.src[n.off] == '-' ? 1 : 0; + return ::nlohmann::detail::dtoa_impl::write_shortest(w, seventeen ? ::nlohmann::detail::zmij::shortest_decimal{leading, exponent, last, last != 0} + : ::nlohmann::detail::zmij::shortest_decimal{digits, exponent - 1, 0, false}); + } + + /// the digits s of a token with the digits w and the last four digits + /// r4, as the batch keeps them: the new last four digits + 1 (only those + /// are written over the token's), or s marked if it differs from w beyond + /// them (0 stays for a float the batch does not write) + static std::uint64_t mark(std::uint64_t w, std::uint64_t s, std::uint64_t r4) noexcept + { + const std::uint64_t last = s - (w - r4); + return last < 10000 ? last + 1 : s | carried(); + } + + /*! + @brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes + of room): the token with its last four digits replaced by digits - 1 (see + mark(); the others are the token's), and without trailing zeros after the + point, but one + */ + char* write_token_digits(char* w, const char* src, const node& n, std::uint64_t digits) + { + const char* const token = src + n.off; + std::memcpy(w, token, 32); + char* const end = w + n.len; + const std::uint32_t last = four_digit_text(static_cast(digits - 1)); + std::memcpy(end - 4, &last, 4); + // (the token's text before the new digits, from the source: reading + // the output back would wait for the stores) + std::uint64_t before = 0; + std::memcpy(&before, token + n.len - 12, 8); + const std::uint32_t last_zeros = last ^ 0x30303030u; + const std::uint64_t before_zeros = before ^ 0x3030303030303030u; + if (NLOHMANN_VIEW_UNLIKELY(last_zeros == 0 && before_zeros == 0)) + { + return write_double_at(w, n); // (twelve or more trailing zeros) + } + const unsigned zeros = last_zeros != 0 ? (static_cast(count_leading_zeros(last_zeros)) / 8) - 4 + : 4 + (static_cast(count_leading_zeros(before_zeros)) / 8); + const unsigned frac_digits = n.extra >> 8u; + return end - (zeros < frac_digits ? zeros : frac_digits - 1); + } +#endif + /// a float node as dump() writes it void write_float_node(const node& n) { diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index e490f61cc..7e20544ba 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -2537,6 +2537,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -2621,12 +2623,56 @@ NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n) return convert_float(first, last, dot, mantissa_end); } +#if NLOHMANN_VIEW_VECTOR +/*! +@brief the value of the first 16 digits of a run of digits with a point after +int_digits of them (or none, if int_digits >= 16) + +The 17 bytes at p are read at once: the bytes after the point move down by one, +then pairs, quadruples, and octets of digits are combined by multiplications +(after simdjson). The bytes [p, p + 32) must be readable. Bytes that are not +digits (only in a damaged image loaded with image_check::bounds) give a wrong +value, but no undefined behavior. +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t sixteen_digits(const unsigned char* p, unsigned int_digits) noexcept +{ +#if NLOHMANN_VIEW_NEON + const uint8x16_t low = vld1q_u8(p); + const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u)); + const uint8x16_t before_point = vcltq_u8(index, vdupq_n_u8(static_cast(int_digits))); + const uint8x16_t digits = vsubq_u8(vbslq_u8(before_point, low, vextq_u8(low, vld1q_u8(p + 16), 1)), vdupq_n_u8('0')); + const uint16x8_t twos = vpaddlq_u8(vmulq_u8(digits, vreinterpretq_u8_u16(vdupq_n_u16(0x010A)))); + const uint32x4_t fours = vpaddlq_u16(vmulq_u16(twos, vreinterpretq_u16_u32(vdupq_n_u32(0x00010064)))); + const uint64x2_t eights = vpaddlq_u32(vmulq_u32(fours, vreinterpretq_u32_u64(vdupq_n_u64(0x0000000100002710u)))); + return (vgetq_lane_u64(eights, 0) * 100000000u) + vgetq_lane_u64(eights, 1); +#else + // NOLINTBEGIN(portability-simd-intrinsics) + const __m128i low = _mm_loadu_si128(static_cast(static_cast(p))); // NOLINT(bugprone-casting-through-void) + const __m128i high = _mm_loadu_si128(static_cast(static_cast(p + 16))); // NOLINT(bugprone-casting-through-void) + const __m128i before_point = _mm_cmpgt_epi8(_mm_set1_epi8(static_cast(int_digits)), _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); + const __m128i moved = _mm_or_si128(_mm_srli_si128(low, 1), _mm_slli_si128(high, 15)); + const __m128i digits = _mm_sub_epi8(_mm_or_si128(_mm_and_si128(before_point, low), _mm_andnot_si128(before_point, moved)), _mm_set1_epi8('0')); + // (16-bit lanes: SSE2 has no multiply-add of bytes) + const __m128i tens = _mm_setr_epi16(10, 1, 10, 1, 10, 1, 10, 1); + const __m128i twos = _mm_packs_epi32(_mm_madd_epi16(_mm_unpacklo_epi8(digits, _mm_setzero_si128()), tens), + _mm_madd_epi16(_mm_unpackhi_epi8(digits, _mm_setzero_si128()), tens)); + const __m128i fours = _mm_madd_epi16(twos, _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1)); + const __m128i eights = _mm_madd_epi16(_mm_packs_epi32(fours, fours), _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1)); + const auto upper = static_cast(_mm_cvtsi128_si32(eights)); + const auto lower = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(eights, 4))); + // NOLINTEND(portability-simd-intrinsics) + return (std::uint64_t{upper} * 100000000u) + lower; +#endif +} +#endif + /*! @brief the digits of a float token with at most 19 digits, from its layout The digit layout recorded while parsing says where the integer digits, the fraction digits, and the exponent are, so the digits are read eight at a -time without scanning. +time without scanning (with SIMD, a token of 16 to 19 digits sixteen at a +time). @param[in] p first character of the token @param[in] e end of the token @@ -2636,15 +2682,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char { const bool negative = *p == '-'; p += negative ? 1 : 0; - std::uint64_t w = parse_upto19(p, int_digits, limit); - p += int_digits; + std::uint64_t w = 0; std::int64_t q = 0; - if (frac_digits != 0) +#if NLOHMANN_VIEW_VECTOR + const unsigned count = int_digits + frac_digits; + if (count >= 16 && limit - p >= 32) { - w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); - p += 1 + frac_digits; + // (the point, if any, follows the integer digits; the digits after + // the first 16 are at most three) + w = sixteen_digits(p, int_digits); + for (unsigned i = 16; i < count; ++i) + { + w = (w * 10) + static_cast(p[i + (i >= int_digits ? 1 : 0)] - '0'); + } + p += count + (frac_digits != 0 ? 1 : 0); q = -static_cast(frac_digits); } + else /* NOLINT(readability/braces) */ +#endif + { + w = parse_upto19(p, int_digits, limit); + p += int_digits; + if (frac_digits != 0) + { + w = (w * int_pow10(frac_digits)) + parse_upto19(p + 1, frac_digits, limit); + p += 1 + frac_digits; + q = -static_cast(frac_digits); + } + } if (p != e) { // [eE][+-]digits; huge exponents saturate (the parser rejected @@ -2679,12 +2744,22 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char The result is correctly rounded by the lexer's conversion (detail::decimal_to_float(): Clinger's fast path where both operands are exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19 -digits), so it is the value parse() produces. +digits), so it is the value parse() produces. Tokens of 16 or more digits go +to Eisel-Lemire directly: the fast path takes few of them (none of 17 digits), +and its check is a branch that data like GeoJSON coordinates mispredicts. */ template NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const unsigned char* e, unsigned int_digits, unsigned frac_digits, const unsigned char* limit) noexcept { - return decimal_to_float(layout_decimal(p, e, int_digits, frac_digits, limit)); + const float_significand d = layout_decimal(p, e, int_digits, frac_digits, limit); + if (int_digits + frac_digits >= 16) + { + using format = ieee_binary_format::digits>; + native_float_t value{}; + float_from_bits(eisel_lemire(d.exponent, d.w) | (d.negative ? (std::uint64_t{1} << format::sign_bit()) : 0u), value); + return value; + } + return decimal_to_float(d); } /// the value of a float set by an edit: its token (the shortest round-trip @@ -2703,6 +2778,80 @@ NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n) return float_value(token, n); } +#if NLOHMANN_VIEW_VECTOR +/// eisel_lemire() for the token w * 10^q of coordinate_float() +template +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, Format /*format*/) noexcept +{ + return eisel_lemire(q, w); +} + +/*! +@brief (binary64) eisel_lemire() without the cases that tokens of 16 or 17 +digits with 5 or more after the point cannot reach: a value of 0, a +subnormal, an infinity, or a tie (q < -4) +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t coordinate_bits(std::int64_t q, std::uint64_t w, ieee_binary_format<53> format) noexcept +{ + if (NLOHMANN_VIEW_UNLIKELY(w == 0 || q > -5)) + { + return eisel_lemire>(q, w); + } + static_cast(format); + const int lz = count_leading_zeros(w); + const std::uint64_t normalized = w << static_cast(lz); + const auto index = static_cast(2 * (q - pow5_128_smallest_power)); + const uint128_parts product = full_multiplication(normalized, pow5_128()[index]); + std::uint64_t high = product.high; + if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu)) + { + // (the lower bits may carry into the result) + const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high; + high += product.low + second < product.low ? 1u : 0u; + } + const auto upperbit = static_cast(high >> 63u); + std::uint64_t mantissa = high >> (upperbit + 9); + auto power2 = static_cast(((217706 * q) >> 16) + 63 + static_cast(upperbit) - lz + 1023); + mantissa = (mantissa + (mantissa & 1u)) >> 1u; + const bool carry = mantissa >= (std::uint64_t{2} << 52u); + mantissa = carry ? (std::uint64_t{1} << 52u) : mantissa; + power2 += carry ? 1u : 0u; + return (mantissa & ~(std::uint64_t{1} << 52u)) | (power2 << 52u); +} + +/*! +@brief (binary32 and binary64) the value of a float token of the source with +16 or 17 digits, a point, and no exponent, the shape of most coordinates +written with full precision; whether the token of n has this shape + +These tokens skip the general layout of layout_decimal(): no digits beyond +the 17th, no exponent (and Eisel-Lemire directly, as in layout_float()). +*/ +template +NLOHMANN_VIEW_ALWAYS_INLINE bool coordinate_float(const document_data& d, const node& n, FloatType& value) noexcept +{ + const unsigned int_digits = n.extra & 0xFFu; + const unsigned frac_digits = n.extra >> 8u; + const unsigned count = int_digits + frac_digits; + const auto* const first = reinterpret_cast(d.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + const unsigned negative = first[0] == '-' ? 1u : 0u; + if (count - 16u > 1u || frac_digits == 0 || n.len != negative + count + 1 || d.size - n.off < 33) + { + return false; + } + const unsigned char* const digits = first + negative; + const std::uint64_t w = sixteen_digits(digits, int_digits); + // (the 17th digit follows the point) + const std::uint64_t w17 = (w * 10) + static_cast(digits[17] - '0'); + using format = ieee_binary_format::digits>; + native_float_t result{}; + float_from_bits(coordinate_bits(-static_cast(frac_digits), count == 17 ? w17 : w, format{}) | (static_cast(negative) << format::sign_bit()), result); + value = result; + return true; +} + +#endif + /// the value of the float token of a node, as parse() converts it; floats and /// doubles with at most 19 digits are converted from the digit layout template @@ -2718,6 +2867,13 @@ FloatType float_value(const document_data& d, const node& n) template FloatType float_value(const document_data& d, const node& n, std::true_type /*binary32 or binary64*/) { +#if NLOHMANN_VIEW_VECTOR + FloatType value {}; + if (coordinate_float(d, n, value)) + { + return value; + } +#endif const unsigned int_digits = n.extra & 0xFFu; const unsigned frac_digits = n.extra >> 8u; if (NLOHMANN_VIEW_LIKELY(int_digits + frac_digits <= 19)) // (255 marks "many") @@ -7837,7 +7993,9 @@ NLOHMANN_JSON_NAMESPACE_END #include // size_t #include // uint8_t, uint32_t #include // memcpy, memset +#include // less #include // numeric_limits +#include // unique_ptr #include // integral_constant #include // vector @@ -8212,6 +8370,143 @@ struct depth_counter } }; +/*! +@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w < +10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position) + +dump() writes the shortest decimal in the rounding interval of x (the closest +one if there are several, see zmij::to_shortest()). Its digits are those of a +multiple of 10^g of the token's last digit, with g = dec_exp + 1 - q for x's +decimal exponent dec_exp: the multiple of 10^g nearest to x if it lies in the +interval (at most one does), else the multiple of 10^(g - 1) nearest to x +(see token_digits_1() and token_digits_2()). For g <= 0, the token itself is +in the interval. + +The position is exact in a 64-bit fixed-point number with 56 bits after the +point (of the token's last digit): x / 10^q = 2c * 5^-q / 2^t for x = c * 2^e2 +and t = q + 1 - e2, with at most 56 bits after the point here, and |x / 10^q - +w| is at most half the spacing of the doubles, below 12 units. So only the low +64 bits of 2c * 5^-q are needed. c and e2 come from Eisel-Lemire's product +directly (as eisel_lemire() rounds it: no tie is possible for q < -4, and its +second product is added where it can carry into the result). + +@param[out] offset x / 10^q - w + 32, with 56 bits after the point +@param[out] radius half the spacing of the doubles at x, likewise (+ 1 if + the interval is closed) +@return g; 3 if the digits are not decided here (a power of two, whose + interval is not symmetric, or out of range) +*/ +NLOHMANN_VIEW_ALWAYS_INLINE int locate_token(std::uint64_t w, int q, std::uint64_t& offset, std::uint64_t& radius) noexcept +{ + const int lz = count_leading_zeros(w); + const std::uint64_t normalized = w << static_cast(lz); + const auto index = static_cast(2 * (q - pow5_128_smallest_power)); + const uint128_parts product = full_multiplication(normalized, pow5_128()[index]); + std::uint64_t high = product.high; + if (NLOHMANN_VIEW_UNLIKELY((high & 0x1FFu) == 0x1FFu)) + { + // (as eisel_lemire(): the lower bits may carry into the result) + const std::uint64_t second = full_multiplication(normalized, pow5_128()[index + 1]).high; + high += product.low + second < product.low ? 1u : 0u; + } + const auto upperbit = static_cast(high >> 63u); + const std::uint64_t mantissa = high >> (upperbit + 9); + const std::uint64_t c = (mantissa + (mantissa & 1u)) >> 1u; + // e2 as eisel_lemire() computes the exponent (log2(10) ~ 217706 / 2^16) + const int e2 = static_cast((217706 * static_cast(q)) >> 16) + 63 + static_cast(upperbit) - lz - 52; + const int t = q + 1 - e2; + const int g = ((e2 * 315653) >> 20) + 1 - q; // (dec_exp as in zmij::to_shortest(), for a regular x) + static const std::array fives = {{1u, 5u, 25u, 125u, 625u, 3125u, 15625u, 78125u, 390625u, 1953125u, 9765625u, 48828125u, 244140625u, 1220703125u, 6103515625u, 30517578125u, 152587890625u}}; + const std::uint64_t five = fives[static_cast(-q)]; // 5^-q + const unsigned s = static_cast(56 - t) & 63u; + offset = ((2 * c * five) << s) - (w << 56u) + (std::uint64_t{32} << 56u); + radius = (five << s) + ((c & 1u) == 0 ? 1u : 0u); + // (c = 2^52: a power of two; c = 2^53: rounded up to one. With 8 to 16 + // fraction digits, as the batch's tokens have, t is in [15, 40] and g at + // most 2. x lies halfway between two candidates only if its distance from + // the token is a whole unit (g == 2) or a half (g == 1): such tokens are + // left to the full conversion) + const bool decided = c - ((std::uint64_t{1} << 52u) + 1) < (std::uint64_t{1} << 52u) - 1 + && (offset & ((std::uint64_t{1} << 55u) - 1)) != 0; + return decided ? g : 3; +} + +/// all bits set if b, else none +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t mask_if(bool b) noexcept +{ + return std::uint64_t{0} - static_cast(b); +} + +/// a if b, else c, without a branch (the conditions of token_digits_1/2() are +/// as good as random, and GCC compiled their ?: as branches: one misprediction +/// per float of canada.json on x86-64) +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t select_if(bool b, std::uint64_t a, std::uint64_t c) noexcept +{ + return c ^ ((a ^ c) & mask_if(b)); +} + +/// the distance of a position (see locate_token()) from the biased point 32 +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t biased_distance(std::uint64_t position) noexcept +{ + constexpr std::uint64_t center = std::uint64_t{32} << 56u; + // |position - center|, without a branch: negated (complemented, plus 1) if below + const std::uint64_t below = mask_if(position < center); + return ((position - center) ^ below) - below; +} + +/*! +@brief (g == 1) the digits dump() writes for a token, from the position of its +double (see locate_token()): the multiple of 10 nearest to x if it is in the +interval, else w rounded to x (an exact tie between two candidates, like +754570942.33984373, whose double is 754570942.33984375, is excluded by +locate_token()) + +With |x - w| at most the radius, which is below half the spacing of the +candidates, the multiple of 10 nearest to x is w - r10 or the one above it. +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_1(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10) noexcept +{ + constexpr std::uint64_t one = std::uint64_t{1} << 56u; + const std::uint64_t from = offset + (r10 * one); // x - (w - r10) (+ 32) + const bool up = from > 37 * one; // (nearer to the multiple above) + const bool shorter = biased_distance(from - (mask_if(up) & (10 * one))) < radius; + const std::uint64_t a = w - r10 + (mask_if(up) & 10u); + const std::uint64_t b = w + ((offset + (one / 2)) >> 56u) - 32; // w rounded to x + return select_if(shorter, a, b); +} + +/*! +@brief (g == 2) the digits dump() writes for a token, as token_digits_1(): the +multiple of 100 nearest to x if it is in the interval, else the multiple of 10 +nearest to x +*/ +NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_2(std::uint64_t w, std::uint64_t offset, std::uint64_t radius, std::uint64_t r10, std::uint64_t r100) noexcept +{ + constexpr std::uint64_t one = std::uint64_t{1} << 56u; + const std::uint64_t from = offset + (r100 * one); + const bool up = from > 82 * one; + const bool shorter = biased_distance(from - (mask_if(up) & (100 * one))) < radius; + const std::uint64_t a = w - r100 + (mask_if(up) & 100u); + // x - (w - r10), in [-12, 21]: the multiple of 10 nearest to x is that one, + // or one or two above, or one below + const std::uint64_t y = offset + (r10 * one); + const std::uint64_t steps = static_cast(y > 37 * one) + static_cast(y > 47 * one); + const bool down = y < 27 * one; + const std::uint64_t b = w - r10 + (10 * steps) - (mask_if(down) & 10u); + return select_if(shorter, a, b); +} + +/// the four ASCII digits of x < 10^4, the first in the lowest byte (as +/// little-endian memory holds them) +NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t four_digit_text(std::uint32_t x) noexcept +{ + const std::uint32_t ab = (x * 5243u) >> 19u; // x / 100 + std::uint32_t v = ab | ((x - (ab * 100u)) << 16u); + const std::uint32_t a = ((v * 103u) >> 10u) & 0x000F000Fu; // / 10 per 16-bit lane + v = a | ((v - (a * 10u)) << 8u); + return v + 0x30303030u; +} + /// how the view's dump() writes a value struct dump_style { @@ -8539,6 +8834,7 @@ class view_serializer std::array buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read std::vector heap; small_stack stack(buffer.data(), buffer.size(), heap); + float_batch batch; const char* const src = m_doc.src; const char* const src_end = src + m_doc.size; char* w = m_out.cursor(); @@ -8678,7 +8974,7 @@ class view_serializer else if (enabled(fast_double::value)) { room(64); - w = write_double_at(w, *n); + w = write_double_ahead(w, src, n, batch); } else { @@ -8703,6 +8999,20 @@ class view_serializer case value_t::array: { const bool object = n->kind == node_kind::object; + // (read-only documents, compact output) a pair of floats, + // like the coordinates of GeoJSON, without a frame + if (enabled(!Editable && !Pretty && !SourceNumbers && fast_double::value) && n->next == 3 && n->len == 2 + && n[1].kind == node_kind::number_float && n[2].kind == node_kind::number_float) + { + room(2 * 64); + *w++ = '['; + w = write_double_ahead(w, src, n + 1, batch); + *w++ = ','; + w = write_double_ahead(w, src, n + 2, batch); + *w++ = ']'; + n += 2; // (the element before the next node) + break; + } room(8); if (n->len == 0) { @@ -8812,6 +9122,254 @@ class view_serializer } } + /// (float_batch) the mark of digits whose rounding carried beyond the + /// token's last four (see mark()) + static constexpr std::uint64_t carried() noexcept + { + return std::uint64_t{1} << 63u; + } + + /// floats of 16 or 17 digits ahead of the walk, converted together (see + /// convert_ahead()), with their digits as mark() keeps them + struct float_batch + { + static constexpr std::size_t capacity() noexcept + { + return 256; + } + /// (on the heap, allocated by the first conversion: a frame of more + /// than a page made every dump of a small document slower) + struct entries + { + std::array at; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + }; + std::unique_ptr storage{}; + const node** at = nullptr; + std::uint64_t* digits = nullptr; + std::size_t count = 0; + std::size_t next = 0; + const node* scanned = nullptr; ///< where the last scan of the node array ended + }; + + /// (doubles) the float at n as dump() writes it, at w (64 bytes of room): + /// from the batch if it holds n + NLOHMANN_VIEW_ALWAYS_INLINE char* write_double_ahead(char* w, const char* src, const node* n, float_batch& batch) + { +#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN + // (digits 0: not written by the batch; carried(): rounding carried + // beyond the last four digits) + if (batch.next != batch.count && batch.at[batch.next] == n && batch.digits[batch.next] - 1 < carried() - 1) + { + return write_token_digits(w, src, *n, batch.digits[batch.next++]); + } + return write_double_beside(w, n, batch); +#else + static_cast(src); + static_cast(batch); + return write_double_at(w, *n); +#endif + } + +#if NLOHMANN_VIEW_VECTOR && NLOHMANN_VIEW_LITTLE_ENDIAN + /// write_double_ahead() for a float that is not the batch's next one (out + /// of line: the walk is faster with less code in its loop) + NLOHMANN_VIEW_NOINLINE char* write_double_beside(char* w, const node* n, float_batch& batch) + { + // (an editable document's walk can skip nodes of the array) + const std::less before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<> + while (batch.next != batch.count && before(batch.at[batch.next], n)) + { + ++batch.next; + } + if ((batch.next != batch.count && batch.at[batch.next] == n) || convert_ahead(n, batch)) + { + const std::uint64_t digits = batch.digits[batch.next++]; + if ((digits & carried()) != 0) + { + return write_carried_digits(w, *n, digits & ~carried()); + } + if (digits != 0) + { + return write_token_digits(w, m_doc.src, *n, digits); + } + } + return write_double_at(w, *n); + } + + /*! + @brief convert the floats of 16 or 17 digits among the next nodes of the + array at once; whether n is the first of them + + Such tokens (like the coordinates of GeoJSON, written with 17 digits) are + written as the token with its last four digits replaced + (write_token_digits()). Each step (the digits, locate_token(), + token_digits_1/2()) is a long chain of dependent operations; one loop per + step over many floats lets the processor overlap the floats, which it does + not across the steps of one float (on Apple M1, one loop for the digits and + locate_token() made the dump of canada.json 12 % slower). Each node of the array + is scanned once; a node that the walk does not reach in order (in an + editable document) is written without the batch. + */ + NLOHMANN_VIEW_NOINLINE bool convert_ahead(const node* n, float_batch& batch) + { + const std::less before{}; // NOLINT(modernize-use-transparent-functors) C++11 has no std::less<> + const node* const last = m_doc.nodes + m_doc.node_count; + if (before(n, batch.scanned) || before(n, m_doc.nodes) || !before(n, last)) + { + return false; + } + const char* const src = m_doc.src; + const char* const src_end = src + m_doc.size; + const node* const stop = last - n > 1024 ? n + 1024 : last; + batch.count = 0; + batch.next = 0; + // the digits of each token (and its last four from the text) + std::array digits; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array exponent; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array last_four; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + const node* p = n; + if (!batch.storage) + { + batch.storage.reset(new typename float_batch::entries); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): C++11 + batch.at = batch.storage->at.data(); + batch.digits = batch.storage->digits.data(); + } + for (; p != stop && batch.count != float_batch::capacity(); ++p) + { + // a token of the source: 16 or 17 digits, at least 8 after the + // point, no exponent, 32 bytes to read + const unsigned frac_digits = p->extra >> 8u; + const unsigned int_digits = p->extra & 0xFFu; + const unsigned count = int_digits + frac_digits; + if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0 + && count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33) + { + const auto* const token = reinterpret_cast(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + const unsigned sign = token[0] == '-' ? 1 : 0; + const unsigned char* const first = token + sign; + const std::uint64_t w = sixteen_digits(first, int_digits); + // (the 17th digit follows the point; a token with an exponent + // gets digits outside the range of locate_token(), and is not + // written by the batch. A token 0.xxx with 16 significant + // digits is: its double is written as "0." and its digits too) + const std::uint64_t w17 = (w * 10) + static_cast(first[17] - '0'); + const std::size_t i = batch.count++; + batch.at[i] = p; + if (p->len != sign + count + 1) + { + digits[i] = 0; + } + else + { + digits[i] = count == 17 ? w17 : w; + } + exponent[i] = -static_cast(frac_digits); + // (byte 0: the last digit, byte 1: the last two, bits 16-29: the last four) + std::uint32_t four = 0; + std::memcpy(&four, token + p->len - 4, 4); + four -= 0x30303030u; + const std::uint32_t pairs = ((four & 0x0F0F0F0Fu) * 2561u) >> 8u; // (byte 0, 2: the pairs) + last_four[i] = (four >> 24u) | ((pairs >> 16u & 0xFFu) << 8u) | ((((pairs & 0x00FF00FFu) * 6553601u) >> 16u) << 16u); + } + } + batch.scanned = p; + // the position of each double, and the floats of either kind + std::array offset; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array radius; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array kind_1; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::array kind_2; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read + std::size_t count_1 = 0; + std::size_t count_2 = 0; + for (std::size_t i = 0; i != batch.count; ++i) + { + const std::uint64_t w = digits[i]; + // (the range excludes the values of bytes that are not digits, only + // in a damaged image loaded with image_check::bounds) + const int g = w - 1000000000000000u < 99000000000000000u ? locate_token(w, exponent[i], offset[i], radius[i]) : 3; + batch.digits[i] = mask_if(g <= 0) & ((last_four[i] >> 16u) + 1); + kind_1[count_1] = static_cast(i); + count_1 += static_cast(g == 1); + kind_2[count_2] = static_cast(i); + count_2 += static_cast(g == 2); + } + // the digits to write, in one loop per kind (see mark()) + for (std::size_t k = 0; k != count_1; ++k) + { + const std::size_t i = kind_1[k]; + const std::uint64_t s = token_digits_1(digits[i], offset[i], radius[i], last_four[i] & 0xFFu); + batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u); + } + for (std::size_t k = 0; k != count_2; ++k) + { + const std::size_t i = kind_2[k]; + const std::uint64_t s = token_digits_2(digits[i], offset[i], radius[i], last_four[i] & 0xFFu, (last_four[i] >> 8u) & 0xFFu); + batch.digits[i] = mark(digits[i], s, last_four[i] >> 16u); + } + return batch.count != 0 && batch.at[0] == n; + } + + /*! + @brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes + of room), from the digits of its double (see locate_token()) whose + rounding carried beyond the token's last four digits + + (like 43.42027299999999 -> 43.420273): the digits go to the writer of + Zmij's results as its 15 or 16 leading digits and one more, which omits + the trailing zeros. + */ + NLOHMANN_VIEW_NOINLINE char* write_carried_digits(char* w, const node& n, std::uint64_t digits) const noexcept + { + const int exponent = -(n.extra >> 8u); + const std::uint64_t leading = digits / 10; + const auto last = static_cast(digits - (leading * 10)); + const bool seventeen = digits >= 10000000000000000u; + *w = '-'; + w += m_doc.src[n.off] == '-' ? 1 : 0; + return ::nlohmann::detail::dtoa_impl::write_shortest(w, seventeen ? ::nlohmann::detail::zmij::shortest_decimal{leading, exponent, last, last != 0} + : ::nlohmann::detail::zmij::shortest_decimal{digits, exponent - 1, 0, false}); + } + + /// the digits s of a token with the digits w and the last four digits + /// r4, as the batch keeps them: the new last four digits + 1 (only those + /// are written over the token's), or s marked if it differs from w beyond + /// them (0 stays for a float the batch does not write) + static std::uint64_t mark(std::uint64_t w, std::uint64_t s, std::uint64_t r4) noexcept + { + const std::uint64_t last = s - (w - r4); + return last < 10000 ? last + 1 : s | carried(); + } + + /*! + @brief a float token of 16 or 17 digits as dump() writes it, at w (64 bytes + of room): the token with its last four digits replaced by digits - 1 (see + mark(); the others are the token's), and without trailing zeros after the + point, but one + */ + char* write_token_digits(char* w, const char* src, const node& n, std::uint64_t digits) + { + const char* const token = src + n.off; + std::memcpy(w, token, 32); + char* const end = w + n.len; + const std::uint32_t last = four_digit_text(static_cast(digits - 1)); + std::memcpy(end - 4, &last, 4); + // (the token's text before the new digits, from the source: reading + // the output back would wait for the stores) + std::uint64_t before = 0; + std::memcpy(&before, token + n.len - 12, 8); + const std::uint32_t last_zeros = last ^ 0x30303030u; + const std::uint64_t before_zeros = before ^ 0x3030303030303030u; + if (NLOHMANN_VIEW_UNLIKELY(last_zeros == 0 && before_zeros == 0)) + { + return write_double_at(w, n); // (twelve or more trailing zeros) + } + const unsigned zeros = last_zeros != 0 ? (static_cast(count_leading_zeros(last_zeros)) / 8) - 4 + : 4 + (static_cast(count_leading_zeros(before_zeros)) / 8); + const unsigned frac_digits = n.extra >> 8u; + return end - (zeros < frac_digits ? zeros : frac_digits - 1); + } +#endif + /// a float node as dump() writes it void write_float_node(const node& n) { diff --git a/tests/src/unit-json_view_dump.cpp b/tests/src/unit-json_view_dump.cpp index 6470ab680..be1ec48ad 100644 --- a/tests/src/unit-json_view_dump.cpp +++ b/tests/src/unit-json_view_dump.cpp @@ -35,9 +35,9 @@ using json_view_test::has_duplicate_keys; #include #include -// These tests were split off unit-json_view.cpp, whose object file got too large -// for the MinGW linker (see json_view_test_helpers.hpp). Like that file, this one -// is also built with C++17, as it mentions JSON_HAS_CPP_17. +// These tests were split off unit-json_view.cpp, whose object file got too large for the MinGW +// linker (see json_view_test_helpers.hpp). Like that file, this one is also built +// with C++17, as it mentions JSON_HAS_CPP_17. TEST_CASE("json_view dump") { @@ -150,6 +150,88 @@ TEST_CASE("json_view dump") CHECK(many_doc.root().dump() == json::parse(many_tokens).dump()); } + // tokens of 16 and 17 digits without an exponent (like GeoJSON + // coordinates), which dump() converts in batches: doubles exactly + // halfway between two candidates for the shortest digits, random + // digits, and digits near a shorter decimal (...99999997, + // ...00000003), whose rounding carries; in pairs and among other + // values, so that the batches are interrupted; the last token near the + // end of the text + { + std::mt19937_64 tokens(5627); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + const auto draw = [&tokens](std::size_t n) + { + const std::uint64_t r = tokens() % n; + return static_cast(r); + }; + std::string coordinates = "[754570942.33984373,257769445.56835938,858106901.94921875,97944964.876953125," + "[0.5,1e-7],[1.5,-0.0,2.5],[1E2,12.5,0.1,3.25,4.5,5.75,6.125,7.0,8.5],[],[1.5],[1,2.5],[2.5,\"x\"],[[1.5],2.5],"; + for (int i = 0; i < 20000; ++i) + { + const std::size_t length = 16 + draw(2); + std::string digits(1, static_cast('1' + draw(9))); + const std::size_t kept = draw(3) == 0 ? length : 2 + draw(length - 3); + for (std::size_t k = 1; k < kept; ++k) + { + digits += static_cast('0' + draw(10)); + } + digits += std::string(length - kept, draw(2) == 0 ? '9' : '0'); + digits.back() = static_cast('0' + draw(10)); + const std::size_t point = 1 + draw(9); + const std::string token = (draw(2) == 0 ? "-" : "") + digits.substr(0, point) + "." + digits.substr(point); + if (i != 0) + { + coordinates += ','; + } + if (draw(8) == 0) + { + coordinates += R"({"x":)"; + coordinates += token; + coordinates += R"(,"s":"a"})"; + } + else + { + coordinates += '['; + coordinates += token; + coordinates += ','; + coordinates += token; + coordinates += ']'; + } + } + coordinates += ",-65.613616999999977]"; + const json_document parsed = json_document::parse(coordinates); + const ordered_json j = ordered_json::parse(coordinates); + CHECK(parsed.root().dump() == j.dump()); + CHECK(parsed.root().dump(1) == j.dump(1)); + CHECK(parsed.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); + CHECK(parsed.root().back().get() == j.back().get()); + CHECK(parsed.root().back().dump() == "-65.61361699999998"); + std::size_t i = 0; + for (const json_view v : parsed.root()) + { + const ordered_json& e = j[i++]; + CHECK(v.dump() == e.dump()); + if (v.is_array() && !v.empty() && v[0].is_number()) + { + CHECK(v[0].get() == e[0].get()); + } + } + + // an editable document: edits move some arrays, so that the walk + // leaves the order of the parsed nodes + ordered_json_editable_document editable = ordered_json_editable_document::parse(coordinates); + ordered_json expected = j; + for (std::size_t k = 0; k < 20000; k += 97) + { + if (expected[k].is_array()) + { + editable.push_back(editable.root()[k], ordered_json(1.5)); + expected[k].push_back(1.5); + } + } + CHECK(editable.root().dump() == expected.dump()); + } + // random doubles, written as parse() and dump() would std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) std::string many = "["; @@ -171,673 +253,4 @@ TEST_CASE("json_view dump") const nlohmann::basic_json_document float_document = nlohmann::basic_json_document::parse("[0.1, 1.5e10, 3.4028235e38]"); CHECK(float_document.root().dump() == json_float::parse("[0.1, 1.5e10, 3.4028235e38]").dump()); } - - SECTION("members in document order, all of them") - { - const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})"); - CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})"); - CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}"); - // dump() writes all members, though the lookup finds the first - CHECK(d.root()["b"].dump() == "1"); - } - - SECTION("deep nesting") - { - const std::string deep = std::string(100000, '[') + std::string(100000, ']'); - const json_document deep_document = json_document::parse(deep); - CHECK(deep_document.root().dump() == deep); - } - - SECTION("ensure_ascii: a character that needs an escape at each position of a string") - { - // string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text - for (const char* const special : - { - "\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\" - }) - { - for (std::size_t length = 0; length <= 40; ++length) - { - for (std::size_t position = 0; position <= length; ++position) - { - std::string content(length, 'a'); - content.insert(position, special); - const std::array texts = - { - { - "\"" + content + "\"", - "[\"" + content + "\"]", - "{\"" + content + "\":1}", - } - }; - for (const std::string& text : texts) - { - CAPTURE(text) - const ordered_json_document d = ordered_json_document::parse(text); - const ordered_json j = ordered_json::parse(text); - CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); - CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true)); - CHECK(d.root().dump() == j.dump()); - } - } - } - } - } - - SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond") - { - // (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF) - std::string text = "["; - std::size_t in_chunk = 0; - const auto append = [&](std::uint32_t cp) - { - if (cp < 0x80) - { - if (cp < 0x20 || cp == '"' || cp == '\\') - { - return; - } - text += static_cast(cp); - } - else if (cp < 0x800) - { - text += static_cast(0xC0 | (cp >> 6)); - text += static_cast(0x80 | (cp & 0x3F)); - } - else if (cp < 0x10000) - { - text += static_cast(0xE0 | (cp >> 12)); - text += static_cast(0x80 | ((cp >> 6) & 0x3F)); - text += static_cast(0x80 | (cp & 0x3F)); - } - else - { - text += static_cast(0xF0 | (cp >> 18)); - text += static_cast(0x80 | ((cp >> 12) & 0x3F)); - text += static_cast(0x80 | ((cp >> 6) & 0x3F)); - text += static_cast(0x80 | (cp & 0x3F)); - } - }; - for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp) - { - if (cp >= 0xD800 && cp < 0xE000) - { - continue; - } - if (in_chunk == 0) - { - text += text.size() > 1 ? ",\"" : "\""; - } - append(cp); - if (++in_chunk == 37) - { - text += "\""; - in_chunk = 0; - } - } - text += in_chunk != 0 ? "\"]" : "]"; - const ordered_json_document d = ordered_json_document::parse(text); - const ordered_json j = ordered_json::parse(text); - CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); - CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true)); - CHECK(d.root().dump() == j.dump()); - CHECK(d.root().dump(2) == j.dump(2)); - } - - SECTION("deep nesting, indented") - { - // indentations of up to and beyond the width of the writer's fixed-size copy - for (const int depth : - { - 1, 5, 20, 70, 300 - }) - { - std::string text; - for (int i = 0; i < depth; ++i) - { - text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)"; - } - text += "[]"; - for (int i = depth - 1; i >= 0; --i) - { - text += i % 2 == 0 ? "]" : "}"; - } - const ordered_json_document d = ordered_json_document::parse(text); - const ordered_json j = ordered_json::parse(text); - for (const int indent : - { - 0, 1, 2, 3, 8 - }) - { - CHECK(d.root().dump(indent) == j.dump(indent)); - CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t')); - } - } - } - - SECTION("the output buffer of a small value is small") - { - // an escaped key after the value: its node lies in the arena, so the - // source extent of the value cannot be read from the next node - const std::string big(100000, 'a'); - const std::string text = R"({"small":1,"list":[1,2,3],"k\n":")" + big + R"("})"; - const json_document d = json_document::parse(text); - const auto small = d.root()["small"].dump(); - CHECK(small == "1"); - CHECK(small.capacity() < 4096); - const auto list = d.root()["list"].dump(); - CHECK(list == "[1,2,3]"); - CHECK(list.capacity() < 4096); - CHECK(d.root()["list"].dump(2).capacity() < 4096); - - // the whole document and the large value are unaffected - CHECK(d.root().dump() == ordered_json::parse(text).dump()); - CHECK(d.root()["k\n"].dump() == "\"" + big + "\""); - } - - SECTION("output that outgrows the estimate") - { - // ensure_ascii writes six bytes for each two-byte character - std::string chars; - for (int i = 0; i < 5000; ++i) - { - chars += "\xC3\xA9"; - } - const json_document d = json_document::parse(R"({"a":")" + chars + R"(","k\n":1})"); - const json expected = json::parse("\"" + chars + "\""); - CHECK(d.root()["a"].dump(-1, ' ', true) == expected.dump(-1, ' ', true)); - CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6)); - } - - SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings") - { - // plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys; - // the second text has a DEL, which ensure_ascii writes as an escape - for (const char* const del : - {"", "\x7F" - }) - { - const std::string text = std::string("{\"plain\":\"abc\",\"caf\xC3\xA9\":\"\xC3\xA9\xE2\x82\xAC\xF0\x9F\x98\x80\",") - + "\"esc\\n\\\"\":\"a\\tb\\u00e4\\ud83d\\ude00\",\"del\":\"a" + del + "b\",\"\":\"\"," // NOLINT(modernize-raw-string-literal) - + "\"list\":[\"x\",\"\xC3\xBC\",\"\\\\\",\"long ASCII string with more than thirty-two bytes in it\"," - + "\"long string with more than thirty-two bytes and one non-ASCII byte \xC3\xB6\"]," - + "\"n\":[1,2.5,-0,true,null]}"; - const ordered_json j = ordered_json::parse(text); - const ordered_json_document d = ordered_json_document::parse(text); - CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); - CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true)); - CHECK(d.root().dump() == j.dump()); - CHECK(d.root()["list"].dump(-1, ' ', true) == j["list"].dump(-1, ' ', true)); - CHECK(d.root()["del"].dump(-1, ' ', true) == j["del"].dump(-1, ' ', true)); - - // strings that an edit wrote (the source strings that they replace were plain) - ordered_json_editable_document e = ordered_json_editable_document::parse(text); - ordered_json k = j; - e.set(e.root()["plain"], std::string("\xC3\xA4gypten")); - k["plain"] = "\xC3\xA4gypten"; - e.set(e.root()["list"][0], std::string("ascii after edit")); - k["list"][0] = "ascii after edit"; - e.set(e.root()["list"][1], std::string("\xC3\xA4")); - k["list"][1] = "\xC3\xA4"; - e.set(e.root()["del"], std::string("tab\there")); - k["del"] = "tab\there"; - CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true)); - CHECK(e.root().dump(2, ' ', true) == k.dump(2, ' ', true)); - CHECK(e.root().dump() == k.dump()); - - // strings that were edited from or to ASCII text - e.set(e.root()["list"][2], std::string("\xC3\xA4")); - k["list"][2] = "\xC3\xA4"; - e.set(e.root()["list"][4], std::string("plain again")); - k["list"][4] = "plain again"; - CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true)); - } - } - - SECTION("ensure_ascii with DEL or a non-ASCII character at every position") - { - // the check whether a string of the source has no byte >= 0x7F reads - // eight bytes at a time and then the rest one by one - for (std::size_t length = 1; length <= 20; ++length) - { - for (std::size_t pos = 0; pos < length; ++pos) - { - for (const char* const c : - { - "\x7F", "\x7E", "\xC3\xA4", "\xC3\xBF" // NOLINT(modernize-raw-string-literal) - }) - { - const std::string s = std::string(pos, 'a') + c + std::string(length - pos - 1, 'b'); - std::string text = "[\""; - text.append(s).append("\",{\"").append(s).append("\":1}]"); - const json_document d = json_document::parse(text); - const json j = json::parse(text); - CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); - CHECK(d.root().dump(1, ' ', true) == j.dump(1, ' ', true)); - } - } - } - } - - SECTION("streams and discarded views") - { - const json_document d = json_document::parse(R"({"a": [1, 2]})"); - std::ostringstream compact; - compact << d.root(); - CHECK(compact.str() == R"({"a":[1,2]})"); - std::ostringstream pretty; - pretty << std::setw(2) << std::setfill('.') << d.root() << d.root()["a"]; - CHECK(pretty.str() == "{\n..\"a\": [\n....1,\n....2\n..]\n}[1,2]"); - CHECK(json_view().dump() == json(json::value_t::discarded).dump()); - } -} - -TEST_CASE("json_view large objects") -{ - // objects with 128 members or more are looked up with a hash index - for (const std::size_t members : - { - 127u, 128u, 129u, 10000u - }) - { - CAPTURE(members) - std::string text = "{"; - for (std::size_t i = 0; i < members; ++i) - { - text += (i != 0 ? ",\"" : "\"") + std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\\n" : "") + "\":" + std::to_string(i); - } - text += R"(,"":"empty key","k1":"a duplicate of an earlier key"})"; - const json_document d = json_document::parse(text); - const json_view v = d.root(); - const json j = json::parse(text); - for (std::size_t i = 0; i < members; ++i) - { - const std::string key = std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\n" : ""); - CHECK(v[key].get() == i); - CHECK(v.contains(key)); - CHECK(v.find(key).key() == key); - CHECK(v.at(key).get() == i); - CHECK(!v.contains(key + "x")); - } - CHECK(v[""].get_string() == "empty key"); - CHECK(v["k1"].get() == 1); // the first of duplicate keys, as for small objects - CHECK(!v.contains("missing")); - CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&); - CHECK(v == j); - CHECK(v.materialize() == j); - } - - SECTION("duplicate keys: the first member wins, with and without a table") - { - // an object of `total` members: the keys "k0".."k" in order, then - // three keys repeated twice more (one copy in the middle, one at the - // end), and two keys repeated once; the value of a member is its - // position, so that the first member of a key can be told apart - struct member - { - std::string key; - std::size_t position; - }; - const auto make_members = [](std::size_t total) - { - std::vector keys; - for (std::size_t i = 0; i + 8 < total; ++i) - { - keys.push_back("k" + std::to_string(i)); - } - const std::size_t n = keys.size(); - const std::array triple = {{3, 17, n - 1}}; - const std::array twice = {{5, n / 2}}; - std::vector ordered = keys; - for (const std::size_t i : triple) - { - ordered.insert(ordered.begin() + static_cast(ordered.size() / 2), keys[i]); - } - for (const std::size_t i : twice) - { - ordered.insert(ordered.begin() + static_cast(ordered.size() / 3), keys[i]); - } - for (const std::size_t i : triple) - { - ordered.push_back(keys[i]); - } - std::vector result; - result.reserve(ordered.size()); - for (std::size_t i = 0; i < ordered.size(); ++i) - { - result.push_back({ordered[i], i}); - } - return result; - }; - - // 100 and 127 members: no table; 128 members and more: a table - for (const std::size_t total : - { - 100u, 127u, 128u, 200u, 5000u - }) - { - CAPTURE(total) - const std::vector members = make_members(total); - REQUIRE(members.size() >= total); - REQUIRE(members.size() >= 100); - std::string text = "{"; - std::map first; - std::map last; - std::map occurrences; - for (const member& m : members) - { - text += (text.size() > 1 ? ",\"" : "\"") + m.key + "\":" + std::to_string(m.position); - first.insert({m.key, m.position}); - last[m.key] = m.position; - ++occurrences[m.key]; - } - text += '}'; - REQUIRE(first.size() < members.size()); - - const json_document d = json_document::parse(text); - const json_view v = d.root(); - const json j = json::parse(text); - CHECK(v.size() == members.size()); // every occurrence is visited - const json m = v.materialize(); - for (const auto& entry : first) - { - CAPTURE(entry.first) - const std::size_t expected = entry.second; - CHECK(v[entry.first].get() == expected); - CHECK(v.at(entry.first).get() == expected); - CHECK(v.find(entry.first).value().get() == expected); - CHECK(v.value(entry.first, std::size_t{0}) == expected); - CHECK(v.contains(entry.first)); - CHECK(v.count(entry.first) == occurrences[entry.first]); - const std::string pointer = "/" + entry.first; - CHECK(v[json::json_pointer(pointer)].get() == expected); - CHECK(v.at(json::json_pointer(pointer)).get() == expected); - CHECK(v.value(json::json_pointer(pointer), std::size_t{0}) == expected); - CHECK(v.contains(json::json_pointer(pointer))); - // materialize() and parse() keep the last value instead - CHECK(j[entry.first].get() == last.at(entry.first)); - CHECK(m[entry.first].get() == last.at(entry.first)); - } - CHECK(!v.contains("k")); - CHECK(v["missing"].is_discarded()); - CHECK(v.materialize() == j); - CHECK(v == j); - } - } - - SECTION("colliding keys") - { - // the hash is not seeded, so keys that all land in one place must not - // make the table build quadratic: such an object gets no table and is - // searched linearly - constexpr std::size_t members = 300; - constexpr std::size_t table_size = 1024; // the table size for 300 members: the next power of two >= 600 - std::vector colliding; - std::vector spread; - for (std::uint64_t counter = 0; colliding.size() < members || spread.size() < members; ++counter) - { - std::string key(8, 'a'); - std::uint64_t x = counter; - for (std::size_t i = 0; i < 8; ++i, x /= 26) - { - key[i] = static_cast('a' + (x % 26)); - } - const bool lands_in_slot_zero = (nlohmann::detail::view::key_hash(key.data(), key.size()) & (table_size - 1)) == 0; - if (lands_in_slot_zero && colliding.size() < members) - { - colliding.push_back(key); - } - else if (!lands_in_slot_zero && spread.size() < members) - { - spread.push_back(key); - } - } - - const auto make_text = [](const std::vector& keys) - { - std::string text = "{"; - for (std::size_t i = 0; i < keys.size(); ++i) - { - text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i % 10); - } - return text + "}"; - }; - const std::string colliding_text = make_text(colliding); - const std::string spread_text = make_text(spread); - - json_document with_collisions = json_document::parse(colliding_text); - json_document without_collisions = json_document::parse(spread_text); - for (const auto* pair : - { - &colliding, &spread - }) - { - const json_view v = (pair == &colliding ? with_collisions : without_collisions).root(); - for (std::size_t i = 0; i < members; ++i) - { - CAPTURE(i) - CHECK(v[(*pair)[i]].get() == i % 10); - CHECK(v.at((*pair)[i]).get() == i % 10); - CHECK(v.find((*pair)[i]).key() == (*pair)[i]); - CHECK(!v.contains((*pair)[i] + "x")); - } - CHECK(!v.contains("missing")); - } - CHECK(with_collisions.root() == json::parse(colliding_text)); - - // only the object with the spread keys got a table (both texts have the - // same length, so the tables are the only difference) - with_collisions.shrink_to_fit(); - without_collisions.shrink_to_fit(); - CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t))); - } - - SECTION("keys that differ in their last bytes are spread over the table") - { - // The hash must spread the usual families of keys (numbered names, hex - // strings, dates, short names): a key farther than index_max_displacement - // from its home slot would leave the object without a table. The keys - // are placed with linear probing, as the table does. - const auto max_displacement = [](const std::vector& keys) - { - std::size_t slots = 16; - while (slots < 2 * keys.size()) - { - slots *= 2; - } - std::vector used(slots, false); - std::size_t farthest = 0; - for (const std::string& key : keys) - { - std::size_t i = nlohmann::detail::conditional_static_cast(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1); - std::size_t distance = 0; - while (used[i]) - { - i = (i + 1) & (slots - 1); - ++distance; - } - used[i] = true; - farthest = (std::max)(farthest, distance); - } - return farthest; - }; - // the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream - // (a format string that is not a literal is a Flawfinder error) - const auto numbered = [](const char* format, int count, int step) - { - const std::string pattern(format); - const std::size_t percent = pattern.find('%'); - std::size_t conversion = percent + 1; - std::size_t width = 0; - while (pattern[conversion] >= '0' && pattern[conversion] <= '9') - { - width = (width * 10) + static_cast(pattern[conversion] - '0'); - ++conversion; - } - const bool hex = pattern[conversion] == 'x'; - std::vector keys; - for (int i = 0; i < count; ++i) - { - std::ostringstream key; - key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast(width)) - << (hex ? std::hex : std::dec) << (i * step) << pattern.substr(conversion + 1); - keys.push_back(key.str()); - } - return keys; - }; - for (const char* format : - {"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d", - "configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z" - }) - { - CAPTURE(format) - CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); // NOLINT(abseil-string-find-str-contains) - CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement); - } - std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) - for (const std::size_t length : - { - std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33} - }) - { - CAPTURE(length) - std::set distinct; - // (there are 26 and 676 keys of one and two letters) - std::size_t wanted = 700; - if (length == 1) - { - wanted = 26; - } - else if (length == 2) - { - wanted = 500; - } - while (distinct.size() < wanted) - { - std::string key(length, 'a'); - for (char& c : key) - { - c = static_cast('a' + (rng() % 26)); - } - distinct.insert(key); - } - CHECK(max_displacement(std::vector(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement); - } - } - - SECTION("keys of every length that differ in one byte") - { - // In a large object, a key is found by comparing its bytes with those of the member the - // table points to: up to 16 bytes as two overlapping words, longer ones with memcmp. - // Keys of every length from 0 to 40, and keys that differ from them in one byte. - std::mt19937_64 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) - std::vector keys; - std::set distinct; - for (std::size_t length = 0; length <= 40; ++length) - { - for (int copy = 0; copy < 5; ++copy) - { - std::string key(length, 'a'); - for (char& c : key) - { - c = static_cast('a' + (rng() % 26)); - } - if (distinct.insert(key).second) - { - keys.push_back(key); - } - } - } - std::string text = "{"; - for (std::size_t i = 0; i < keys.size(); ++i) - { - text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i); - } - text += "}"; - REQUIRE(keys.size() >= 128); // (a table is built for 128 members) - const json_document d = json_document::parse(text); - const json_view v = d.root(); - for (std::size_t i = 0; i < keys.size(); ++i) - { - CAPTURE(keys[i]) - CHECK(v[keys[i]].get() == i); - CHECK(v.at(keys[i]).get() == i); - CHECK(v.contains(keys[i])); - for (std::size_t at = 0; at < keys[i].size(); ++at) - { - std::string other = keys[i]; - other[at] = other[at] == 'z' ? 'y' : 'z'; - CAPTURE(other) - CHECK(v.contains(other) == (distinct.count(other) != 0)); - } - CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0)); - if (!keys[i].empty()) - { - CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0)); - } - } - } - - SECTION("shrink_to_fit releases the tables' spare capacity") - { - const auto make_text = [](int objects, int members) - { - std::string text = "["; - for (int object = 0; object < objects; ++object) - { - text += object != 0 ? ",{" : "{"; - for (int i = 0, n = members + object; i < n; ++i) - { - text += (i != 0 ? ",\"" : "\"") + std::to_string(i) + "\":" + std::to_string(i); - } - text += '}'; - } - return text + "]"; - }; - const std::string small_text = make_text(5, 150); - const std::string big_text = make_text(40, 400); - - // reading a big text, and then a small one, leaves the spare capacity - // of the big one: shrink_to_fit() brings the document to the size of - // one parsed from the small text alone - json_document d = json_document::parse(big_text); - const std::size_t big = d.memory_usage(); - d.read(small_text); - CHECK(d.memory_usage() >= big); - d.shrink_to_fit(); - json_document fresh = json_document::parse(small_text); - fresh.shrink_to_fit(); - CHECK(d.memory_usage() == fresh.memory_usage()); - CHECK(d.memory_usage() < big / 2); - CHECK(d.root() == json::parse(small_text)); - for (int object = 0; object < 5; ++object) - { - const json_view v = d.root()[static_cast(object)]; - for (int i = 0, n = 150 + object; i < n; ++i) - { - CHECK(v[std::to_string(i)].get() == i); - } - } - } - - SECTION("nested, reused, and in arrays") - { - std::string inner = "{"; - for (int i = 0; i < 300; ++i) - { - inner += (i != 0 ? ",\"m" : "\"m") + std::to_string(i) + "\":" + std::to_string(i); - } - inner += '}'; - const std::string text = "[" + inner + ",{\"x\":" + inner + "}," + inner + "]"; - json_document d = json_document::parse(text); - CHECK(d.root()[0]["m299"].get() == 299); - CHECK(d.root()[1]["x"]["m150"].get() == 150); - CHECK(d.root()[2]["m0"].get() == 0); - const std::size_t with_index = d.memory_usage(); - d.read(std::string("{\"small\": 1}")); - CHECK(d.root()["small"].get() == 1); - d.read(text); - CHECK(d.root()[2]["m7"].get() == 7); - CHECK(d.memory_usage() >= with_index / 2); - } } diff --git a/tests/src/unit-json_view_dump_escape.cpp b/tests/src/unit-json_view_dump_escape.cpp new file mode 100644 index 000000000..3def62df6 --- /dev/null +++ b/tests/src/unit-json_view_dump_escape.cpp @@ -0,0 +1,298 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; +using nlohmann::ordered_json; +using nlohmann::json_document; +using nlohmann::ordered_json_editable_document; +using nlohmann::json_view; +using nlohmann::ordered_json_document; +using nlohmann::ordered_json_view; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// These tests were split off unit-json_view_dump.cpp, whose object file got too large for the MinGW +// linker (see json_view_test_helpers.hpp). Like that file, this one is also built +// with C++17, as it mentions JSON_HAS_CPP_17. + +TEST_CASE("json_view dump: escaping, nesting, and output buffers") +{ + SECTION("members in document order, all of them") + { + const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})"); + CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})"); + CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}"); + // dump() writes all members, though the lookup finds the first + CHECK(d.root()["b"].dump() == "1"); + } + + SECTION("deep nesting") + { + const std::string deep = std::string(100000, '[') + std::string(100000, ']'); + const json_document deep_document = json_document::parse(deep); + CHECK(deep_document.root().dump() == deep); + } + + SECTION("ensure_ascii: a character that needs an escape at each position of a string") + { + // string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text + for (const char* const special : + { + "\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\" + }) + { + for (std::size_t length = 0; length <= 40; ++length) + { + for (std::size_t position = 0; position <= length; ++position) + { + std::string content(length, 'a'); + content.insert(position, special); + const std::array texts = + { + { + "\"" + content + "\"", + "[\"" + content + "\"]", + "{\"" + content + "\":1}", + } + }; + for (const std::string& text : texts) + { + CAPTURE(text) + const ordered_json_document d = ordered_json_document::parse(text); + const ordered_json j = ordered_json::parse(text); + CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); + CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true)); + CHECK(d.root().dump() == j.dump()); + } + } + } + } + } + + SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond") + { + // (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF) + std::string text = "["; + std::size_t in_chunk = 0; + const auto append = [&](std::uint32_t cp) + { + if (cp < 0x80) + { + if (cp < 0x20 || cp == '"' || cp == '\\') + { + return; + } + text += static_cast(cp); + } + else if (cp < 0x800) + { + text += static_cast(0xC0 | (cp >> 6)); + text += static_cast(0x80 | (cp & 0x3F)); + } + else if (cp < 0x10000) + { + text += static_cast(0xE0 | (cp >> 12)); + text += static_cast(0x80 | ((cp >> 6) & 0x3F)); + text += static_cast(0x80 | (cp & 0x3F)); + } + else + { + text += static_cast(0xF0 | (cp >> 18)); + text += static_cast(0x80 | ((cp >> 12) & 0x3F)); + text += static_cast(0x80 | ((cp >> 6) & 0x3F)); + text += static_cast(0x80 | (cp & 0x3F)); + } + }; + for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp) + { + if (cp >= 0xD800 && cp < 0xE000) + { + continue; + } + if (in_chunk == 0) + { + text += text.size() > 1 ? ",\"" : "\""; + } + append(cp); + if (++in_chunk == 37) + { + text += "\""; + in_chunk = 0; + } + } + text += in_chunk != 0 ? "\"]" : "]"; + const ordered_json_document d = ordered_json_document::parse(text); + const ordered_json j = ordered_json::parse(text); + CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); + CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true)); + CHECK(d.root().dump() == j.dump()); + CHECK(d.root().dump(2) == j.dump(2)); + } + + SECTION("deep nesting, indented") + { + // indentations of up to and beyond the width of the writer's fixed-size copy + for (const int depth : + { + 1, 5, 20, 70, 300 + }) + { + std::string text; + for (int i = 0; i < depth; ++i) + { + text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)"; + } + text += "[]"; + for (int i = depth - 1; i >= 0; --i) + { + text += i % 2 == 0 ? "]" : "}"; + } + const ordered_json_document d = ordered_json_document::parse(text); + const ordered_json j = ordered_json::parse(text); + for (const int indent : + { + 0, 1, 2, 3, 8 + }) + { + CHECK(d.root().dump(indent) == j.dump(indent)); + CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t')); + } + } + } + + SECTION("the output buffer of a small value is small") + { + // an escaped key after the value: its node lies in the arena, so the + // source extent of the value cannot be read from the next node + const std::string big(100000, 'a'); + const std::string text = R"({"small":1,"list":[1,2,3],"k\n":")" + big + R"("})"; + const json_document d = json_document::parse(text); + const auto small = d.root()["small"].dump(); + CHECK(small == "1"); + CHECK(small.capacity() < 4096); + const auto list = d.root()["list"].dump(); + CHECK(list == "[1,2,3]"); + CHECK(list.capacity() < 4096); + CHECK(d.root()["list"].dump(2).capacity() < 4096); + + // the whole document and the large value are unaffected + CHECK(d.root().dump() == ordered_json::parse(text).dump()); + CHECK(d.root()["k\n"].dump() == "\"" + big + "\""); + } + + SECTION("output that outgrows the estimate") + { + // ensure_ascii writes six bytes for each two-byte character + std::string chars; + for (int i = 0; i < 5000; ++i) + { + chars += "\xC3\xA9"; + } + const json_document d = json_document::parse(R"({"a":")" + chars + R"(","k\n":1})"); + const json expected = json::parse("\"" + chars + "\""); + CHECK(d.root()["a"].dump(-1, ' ', true) == expected.dump(-1, ' ', true)); + CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6)); + } + + SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings") + { + // plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys; + // the second text has a DEL, which ensure_ascii writes as an escape + for (const char* const del : + {"", "\x7F" + }) + { + const std::string text = std::string("{\"plain\":\"abc\",\"caf\xC3\xA9\":\"\xC3\xA9\xE2\x82\xAC\xF0\x9F\x98\x80\",") + + "\"esc\\n\\\"\":\"a\\tb\\u00e4\\ud83d\\ude00\",\"del\":\"a" + del + "b\",\"\":\"\"," // NOLINT(modernize-raw-string-literal) + + "\"list\":[\"x\",\"\xC3\xBC\",\"\\\\\",\"long ASCII string with more than thirty-two bytes in it\"," + + "\"long string with more than thirty-two bytes and one non-ASCII byte \xC3\xB6\"]," + + "\"n\":[1,2.5,-0,true,null]}"; + const ordered_json j = ordered_json::parse(text); + const ordered_json_document d = ordered_json_document::parse(text); + CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); + CHECK(d.root().dump(4, ' ', true) == j.dump(4, ' ', true)); + CHECK(d.root().dump() == j.dump()); + CHECK(d.root()["list"].dump(-1, ' ', true) == j["list"].dump(-1, ' ', true)); + CHECK(d.root()["del"].dump(-1, ' ', true) == j["del"].dump(-1, ' ', true)); + + // strings that an edit wrote (the source strings that they replace were plain) + ordered_json_editable_document e = ordered_json_editable_document::parse(text); + ordered_json k = j; + e.set(e.root()["plain"], std::string("\xC3\xA4gypten")); + k["plain"] = "\xC3\xA4gypten"; + e.set(e.root()["list"][0], std::string("ascii after edit")); + k["list"][0] = "ascii after edit"; + e.set(e.root()["list"][1], std::string("\xC3\xA4")); + k["list"][1] = "\xC3\xA4"; + e.set(e.root()["del"], std::string("tab\there")); + k["del"] = "tab\there"; + CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true)); + CHECK(e.root().dump(2, ' ', true) == k.dump(2, ' ', true)); + CHECK(e.root().dump() == k.dump()); + + // strings that were edited from or to ASCII text + e.set(e.root()["list"][2], std::string("\xC3\xA4")); + k["list"][2] = "\xC3\xA4"; + e.set(e.root()["list"][4], std::string("plain again")); + k["list"][4] = "plain again"; + CHECK(e.root().dump(-1, ' ', true) == k.dump(-1, ' ', true)); + } + } + + SECTION("ensure_ascii with DEL or a non-ASCII character at every position") + { + // the check whether a string of the source has no byte >= 0x7F reads + // eight bytes at a time and then the rest one by one + for (std::size_t length = 1; length <= 20; ++length) + { + for (std::size_t pos = 0; pos < length; ++pos) + { + for (const char* const c : + { + "\x7F", "\x7E", "\xC3\xA4", "\xC3\xBF" // NOLINT(modernize-raw-string-literal) + }) + { + const std::string s = std::string(pos, 'a') + c + std::string(length - pos - 1, 'b'); + std::string text = "[\""; + text.append(s).append("\",{\"").append(s).append("\":1}]"); + const json_document d = json_document::parse(text); + const json j = json::parse(text); + CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true)); + CHECK(d.root().dump(1, ' ', true) == j.dump(1, ' ', true)); + } + } + } + } + + SECTION("streams and discarded views") + { + const json_document d = json_document::parse(R"({"a": [1, 2]})"); + std::ostringstream compact; + compact << d.root(); + CHECK(compact.str() == R"({"a":[1,2]})"); + std::ostringstream pretty; + pretty << std::setw(2) << std::setfill('.') << d.root() << d.root()["a"]; + CHECK(pretty.str() == "{\n..\"a\": [\n....1,\n....2\n..]\n}[1,2]"); + CHECK(json_view().dump() == json(json::value_t::discarded).dump()); + } +} diff --git a/tests/src/unit-json_view_large.cpp b/tests/src/unit-json_view_large.cpp new file mode 100644 index 000000000..89d7db326 --- /dev/null +++ b/tests/src/unit-json_view_large.cpp @@ -0,0 +1,544 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; +using nlohmann::ordered_json; +using nlohmann::json_document; +using nlohmann::ordered_json_editable_document; +using nlohmann::json_view; +using nlohmann::ordered_json_document; +using nlohmann::ordered_json_view; + +#include "json_view_test_helpers.hpp" +using json_view_test::generator; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// These tests were split off unit-json_view_dump.cpp, whose object file got too large for the MinGW +// linker (see json_view_test_helpers.hpp). Like that file, this one is also built +// with C++17, as it mentions JSON_HAS_CPP_17. + +TEST_CASE("json_view comparison") +{ + SECTION("equality of the values parse() produces") + { + generator g; + std::vector texts; + for (int i = 0; i < 600; ++i) + { + std::string text; + g.value(text, 0); + texts.push_back(text); + // the same value written differently: sorted keys, canonical numbers + texts.push_back(json::parse(text).dump(1)); + } + for (std::size_t i = 0; i + 2 < texts.size(); ++i) + { + for (std::size_t k = i; k < i + 3; ++k) + { + CAPTURE(texts[i]) + CAPTURE(texts[k]) + const json_document a = json_document::parse(texts[i]); + const json_document b = json_document::parse(texts[k]); + const json ja = json::parse(texts[i]); + const json jb = json::parse(texts[k]); + CHECK((a.root() == b.root()) == (ja == jb)); + CHECK((a.root() != b.root()) == (ja != jb)); + CHECK((a.root() == jb) == (ja == jb)); + CHECK((jb == a.root()) == (ja == jb)); + CHECK((a.root() != jb) == (ja != jb)); + CHECK((jb != a.root()) == (ja != jb)); + + // ordered_json compares members in order + const ordered_json_document oa = ordered_json_document::parse(texts[i]); + const ordered_json_document ob = ordered_json_document::parse(texts[k]); + const ordered_json oja = ordered_json::parse(texts[i]); + const ordered_json ojb = ordered_json::parse(texts[k]); + CHECK((oa.root() == ob.root()) == (oja == ojb)); + CHECK((oa.root() == ojb) == (oja == ojb)); + } + } + } + + SECTION("numbers, duplicate keys, member order") + { + const auto same = [](const char* x, const char* y) + { + const json_document dx = json_document::parse(x); + const json_document dy = json_document::parse(y); + return dx.root() == dy.root(); + }; + CHECK(same("1", "1.0")); + CHECK(same("[1, -1, 2.5]", "[1.0, -1.0, 25e-1]")); + CHECK(!same("1", "1.5")); + CHECK(same("18446744073709551615", "18446744073709551615")); + CHECK(same(R"({"a": 1, "a": 2})", R"({"a": 2})")); + CHECK(!same(R"({"a": 1, "a": 2})", R"({"a": 1})")); + CHECK(same(R"({"a": 1, "b": 2})", R"({"b": 2, "a": 1})")); + CHECK(!same(R"({"a": 1})", R"({"a": 1, "b": 2})")); + CHECK(!same("[1, 2]", "[2, 1]")); + CHECK(!same("\"a\"", "\"b\"")); + CHECK(same("\"\\u00e9\"", "\"\xc3\xa9\"")); + CHECK(!same("null", "false")); + CHECK(!same("[]", "{}")); + const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})"); + const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})"); + CHECK(dup.root() == last.root()); + // (== compares as parse() resolves duplicates, a lookup finds the first) + CHECK(dup.root()["a"].materialize() == 1); + CHECK(dup.root()["a"] != last.root()["a"]); + const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})"); + const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})"); + CHECK(ab.root() != ba.root()); + + // discarded values compare as basic_json's do + const json discarded(json::value_t::discarded); + CHECK((json_view() == json_view()) == (discarded == discarded)); // NOLINT(readability-container-size-empty): operator== is tested + CHECK((json_view() == discarded) == (discarded == discarded)); // NOLINT(readability-container-size-empty) + const json_document null_document = json_document::parse("null"); + CHECK(!(json_view() == null_document.root())); // NOLINT(readability-container-size-empty) + CHECK(!(null_document.root() == discarded)); + } + + SECTION("deep nesting") + { + const std::string deep = std::string(100000, '[') + std::string(100000, ']'); + const json_document a = json_document::parse(deep); + const json_document b = json_document::parse(deep); + CHECK(a.root() == b.root()); + CHECK(a.root() == json::parse(deep)); + const std::string other = std::string(100000, '[') + "1" + std::string(100000, ']'); + const json_document c = json_document::parse(other); + CHECK(a.root() != c.root()); + } +} + +TEST_CASE("json_view large objects") +{ + // objects with 128 members or more are looked up with a hash index + for (const std::size_t members : + { + 127u, 128u, 129u, 10000u + }) + { + CAPTURE(members) + std::string text = "{"; + for (std::size_t i = 0; i < members; ++i) + { + text += (i != 0 ? ",\"" : "\"") + std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\\n" : "") + "\":" + std::to_string(i); + } + text += R"(,"":"empty key","k1":"a duplicate of an earlier key"})"; + const json_document d = json_document::parse(text); + const json_view v = d.root(); + const json j = json::parse(text); + for (std::size_t i = 0; i < members; ++i) + { + const std::string key = std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\n" : ""); + CHECK(v[key].get() == i); + CHECK(v.contains(key)); + CHECK(v.find(key).key() == key); + CHECK(v.at(key).get() == i); + CHECK(!v.contains(key + "x")); + } + CHECK(v[""].get_string() == "empty key"); + CHECK(v["k1"].get() == 1); // the first of duplicate keys, as for small objects + CHECK(!v.contains("missing")); + CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&); + CHECK(v == j); + CHECK(v.materialize() == j); + } + + SECTION("duplicate keys: the first member wins, with and without a table") + { + // an object of `total` members: the keys "k0".."k" in order, then + // three keys repeated twice more (one copy in the middle, one at the + // end), and two keys repeated once; the value of a member is its + // position, so that the first member of a key can be told apart + struct member + { + std::string key; + std::size_t position; + }; + const auto make_members = [](std::size_t total) + { + std::vector keys; + for (std::size_t i = 0; i + 8 < total; ++i) + { + keys.push_back("k" + std::to_string(i)); + } + const std::size_t n = keys.size(); + const std::array triple = {{3, 17, n - 1}}; + const std::array twice = {{5, n / 2}}; + std::vector ordered = keys; + for (const std::size_t i : triple) + { + ordered.insert(ordered.begin() + static_cast(ordered.size() / 2), keys[i]); + } + for (const std::size_t i : twice) + { + ordered.insert(ordered.begin() + static_cast(ordered.size() / 3), keys[i]); + } + for (const std::size_t i : triple) + { + ordered.push_back(keys[i]); + } + std::vector result; + result.reserve(ordered.size()); + for (std::size_t i = 0; i < ordered.size(); ++i) + { + result.push_back({ordered[i], i}); + } + return result; + }; + + // 100 and 127 members: no table; 128 members and more: a table + for (const std::size_t total : + { + 100u, 127u, 128u, 200u, 5000u + }) + { + CAPTURE(total) + const std::vector members = make_members(total); + REQUIRE(members.size() >= total); + REQUIRE(members.size() >= 100); + std::string text = "{"; + std::map first; + std::map last; + std::map occurrences; + for (const member& m : members) + { + text += (text.size() > 1 ? ",\"" : "\"") + m.key + "\":" + std::to_string(m.position); + first.insert({m.key, m.position}); + last[m.key] = m.position; + ++occurrences[m.key]; + } + text += '}'; + REQUIRE(first.size() < members.size()); + + const json_document d = json_document::parse(text); + const json_view v = d.root(); + const json j = json::parse(text); + CHECK(v.size() == members.size()); // every occurrence is visited + const json m = v.materialize(); + for (const auto& entry : first) + { + CAPTURE(entry.first) + const std::size_t expected = entry.second; + CHECK(v[entry.first].get() == expected); + CHECK(v.at(entry.first).get() == expected); + CHECK(v.find(entry.first).value().get() == expected); + CHECK(v.value(entry.first, std::size_t{0}) == expected); + CHECK(v.contains(entry.first)); + CHECK(v.count(entry.first) == occurrences[entry.first]); + const std::string pointer = "/" + entry.first; + CHECK(v[json::json_pointer(pointer)].get() == expected); + CHECK(v.at(json::json_pointer(pointer)).get() == expected); + CHECK(v.value(json::json_pointer(pointer), std::size_t{0}) == expected); + CHECK(v.contains(json::json_pointer(pointer))); + // materialize() and parse() keep the last value instead + CHECK(j[entry.first].get() == last.at(entry.first)); + CHECK(m[entry.first].get() == last.at(entry.first)); + } + CHECK(!v.contains("k")); + CHECK(v["missing"].is_discarded()); + CHECK(v.materialize() == j); + CHECK(v == j); + } + } + + SECTION("colliding keys") + { + // the hash is not seeded, so keys that all land in one place must not + // make the table build quadratic: such an object gets no table and is + // searched linearly + constexpr std::size_t members = 300; + constexpr std::size_t table_size = 1024; // the table size for 300 members: the next power of two >= 600 + std::vector colliding; + std::vector spread; + for (std::uint64_t counter = 0; colliding.size() < members || spread.size() < members; ++counter) + { + std::string key(8, 'a'); + std::uint64_t x = counter; + for (std::size_t i = 0; i < 8; ++i, x /= 26) + { + key[i] = static_cast('a' + (x % 26)); + } + const bool lands_in_slot_zero = (nlohmann::detail::view::key_hash(key.data(), key.size()) & (table_size - 1)) == 0; + if (lands_in_slot_zero && colliding.size() < members) + { + colliding.push_back(key); + } + else if (!lands_in_slot_zero && spread.size() < members) + { + spread.push_back(key); + } + } + + const auto make_text = [](const std::vector& keys) + { + std::string text = "{"; + for (std::size_t i = 0; i < keys.size(); ++i) + { + text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i % 10); + } + return text + "}"; + }; + const std::string colliding_text = make_text(colliding); + const std::string spread_text = make_text(spread); + + json_document with_collisions = json_document::parse(colliding_text); + json_document without_collisions = json_document::parse(spread_text); + for (const auto* pair : + { + &colliding, &spread + }) + { + const json_view v = (pair == &colliding ? with_collisions : without_collisions).root(); + for (std::size_t i = 0; i < members; ++i) + { + CAPTURE(i) + CHECK(v[(*pair)[i]].get() == i % 10); + CHECK(v.at((*pair)[i]).get() == i % 10); + CHECK(v.find((*pair)[i]).key() == (*pair)[i]); + CHECK(!v.contains((*pair)[i] + "x")); + } + CHECK(!v.contains("missing")); + } + CHECK(with_collisions.root() == json::parse(colliding_text)); + + // only the object with the spread keys got a table (both texts have the + // same length, so the tables are the only difference) + with_collisions.shrink_to_fit(); + without_collisions.shrink_to_fit(); + CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t))); + } + + SECTION("keys that differ in their last bytes are spread over the table") + { + // The hash must spread the usual families of keys (numbered names, hex + // strings, dates, short names): a key farther than index_max_displacement + // from its home slot would leave the object without a table. The keys + // are placed with linear probing, as the table does. + const auto max_displacement = [](const std::vector& keys) + { + std::size_t slots = 16; + while (slots < 2 * keys.size()) + { + slots *= 2; + } + std::vector used(slots, false); + std::size_t farthest = 0; + for (const std::string& key : keys) + { + std::size_t i = nlohmann::detail::conditional_static_cast(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1); + std::size_t distance = 0; + while (used[i]) + { + i = (i + 1) & (slots - 1); + ++distance; + } + used[i] = true; + farthest = (std::max)(farthest, distance); + } + return farthest; + }; + // the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream + // (a format string that is not a literal is a Flawfinder error) + const auto numbered = [](const char* format, int count, int step) + { + const std::string pattern(format); + const std::size_t percent = pattern.find('%'); + std::size_t conversion = percent + 1; + std::size_t width = 0; + while (pattern[conversion] >= '0' && pattern[conversion] <= '9') + { + width = (width * 10) + static_cast(pattern[conversion] - '0'); + ++conversion; + } + const bool hex = pattern[conversion] == 'x'; + std::vector keys; + for (int i = 0; i < count; ++i) + { + std::ostringstream key; + key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast(width)) + << (hex ? std::hex : std::dec) << (i * step) << pattern.substr(conversion + 1); + keys.push_back(key.str()); + } + return keys; + }; + for (const char* format : + {"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d", + "configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z" + }) + { + CAPTURE(format) + CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); // NOLINT(abseil-string-find-str-contains) + CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement); + } + std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + for (const std::size_t length : + { + std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33} + }) + { + CAPTURE(length) + std::set distinct; + // (there are 26 and 676 keys of one and two letters) + std::size_t wanted = 700; + if (length == 1) + { + wanted = 26; + } + else if (length == 2) + { + wanted = 500; + } + while (distinct.size() < wanted) + { + std::string key(length, 'a'); + for (char& c : key) + { + c = static_cast('a' + (rng() % 26)); + } + distinct.insert(key); + } + CHECK(max_displacement(std::vector(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement); + } + } + + SECTION("keys of every length that differ in one byte") + { + // In a large object, a key is found by comparing its bytes with those of the member the + // table points to: up to 16 bytes as two overlapping words, longer ones with memcmp. + // Keys of every length from 0 to 40, and keys that differ from them in one byte. + std::mt19937_64 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::vector keys; + std::set distinct; + for (std::size_t length = 0; length <= 40; ++length) + { + for (int copy = 0; copy < 5; ++copy) + { + std::string key(length, 'a'); + for (char& c : key) + { + c = static_cast('a' + (rng() % 26)); + } + if (distinct.insert(key).second) + { + keys.push_back(key); + } + } + } + std::string text = "{"; + for (std::size_t i = 0; i < keys.size(); ++i) + { + text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i); + } + text += "}"; + REQUIRE(keys.size() >= 128); // (a table is built for 128 members) + const json_document d = json_document::parse(text); + const json_view v = d.root(); + for (std::size_t i = 0; i < keys.size(); ++i) + { + CAPTURE(keys[i]) + CHECK(v[keys[i]].get() == i); + CHECK(v.at(keys[i]).get() == i); + CHECK(v.contains(keys[i])); + for (std::size_t at = 0; at < keys[i].size(); ++at) + { + std::string other = keys[i]; + other[at] = other[at] == 'z' ? 'y' : 'z'; + CAPTURE(other) + CHECK(v.contains(other) == (distinct.count(other) != 0)); + } + CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0)); + if (!keys[i].empty()) + { + CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0)); + } + } + } + + SECTION("shrink_to_fit releases the tables' spare capacity") + { + const auto make_text = [](int objects, int members) + { + std::string text = "["; + for (int object = 0; object < objects; ++object) + { + text += object != 0 ? ",{" : "{"; + for (int i = 0, n = members + object; i < n; ++i) + { + text += (i != 0 ? ",\"" : "\"") + std::to_string(i) + "\":" + std::to_string(i); + } + text += '}'; + } + return text + "]"; + }; + const std::string small_text = make_text(5, 150); + const std::string big_text = make_text(40, 400); + + // reading a big text, and then a small one, leaves the spare capacity + // of the big one: shrink_to_fit() brings the document to the size of + // one parsed from the small text alone + json_document d = json_document::parse(big_text); + const std::size_t big = d.memory_usage(); + d.read(small_text); + CHECK(d.memory_usage() >= big); + d.shrink_to_fit(); + json_document fresh = json_document::parse(small_text); + fresh.shrink_to_fit(); + CHECK(d.memory_usage() == fresh.memory_usage()); + CHECK(d.memory_usage() < big / 2); + CHECK(d.root() == json::parse(small_text)); + for (int object = 0; object < 5; ++object) + { + const json_view v = d.root()[static_cast(object)]; + for (int i = 0, n = 150 + object; i < n; ++i) + { + CHECK(v[std::to_string(i)].get() == i); + } + } + } + + SECTION("nested, reused, and in arrays") + { + std::string inner = "{"; + for (int i = 0; i < 300; ++i) + { + inner += (i != 0 ? ",\"m" : "\"m") + std::to_string(i) + "\":" + std::to_string(i); + } + inner += '}'; + const std::string text = "[" + inner + ",{\"x\":" + inner + "}," + inner + "]"; + json_document d = json_document::parse(text); + CHECK(d.root()[0]["m299"].get() == 299); + CHECK(d.root()[1]["x"]["m150"].get() == 150); + CHECK(d.root()[2]["m0"].get() == 0); + const std::size_t with_index = d.memory_usage(); + d.read(std::string("{\"small\": 1}")); + CHECK(d.root()["small"].get() == 1); + d.read(text); + CHECK(d.root()[2]["m7"].get() == 7); + CHECK(d.memory_usage() >= with_index / 2); + } +} From ec6986c918e9ea510dacf5c3f884eb2119610aae Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:20:41 +0100 Subject: [PATCH 48/63] Add natvis visualizers for json_document and json_view (#5967) Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/home/debugging.md | 9 +- nlohmann_json.natvis | 11880 ++++++++++++++++ tools/generate_natvis/README.md | 7 +- tools/generate_natvis/generate_natvis.py | 2 +- tools/generate_natvis/nlohmann_json.natvis.j2 | 72 + 5 files changed, 11967 insertions(+), 3 deletions(-) diff --git a/docs/mkdocs/docs/home/debugging.md b/docs/mkdocs/docs/home/debugging.md index d4009dcd0..5722393aa 100644 --- a/docs/mkdocs/docs/home/debugging.md +++ b/docs/mkdocs/docs/home/debugging.md @@ -10,6 +10,13 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/ file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code. +The file also has visualizers for `json_document` and `json_view` (and their `ordered_json`/editable variants). They +show the value of the document or view, and its elements as an expandable list. Because a document is only a flat index +into the source text, the strings of the direct elements are shown, but the elements of nested arrays and objects are +shown as index nodes (their kind, size, and numbers; strings only by length). Arrays and objects that have been edited +in a `json_editable_document` are not expanded. The visualizers read the internal layout of the MSVC STL (`std::array`, +`std::unique_ptr`). + The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration: @@ -29,7 +36,7 @@ ABI tags are not covered and may show raw internal fields instead. To generate a 4. Use the generated file instead of `nlohmann_json.natvis`. -`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12 +`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 11 ABI tags, doubling with each further tag). See [`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options. diff --git a/nlohmann_json.natvis b/nlohmann_json.natvis index 1bb733717..33c0caa67 100644 --- a/nlohmann_json.natvis +++ b/nlohmann_json.natvis @@ -79,6 +79,94 @@
    + with a static fallback list inside (shown without JavaScript). Links marked with { data-nj-mc="KEY" } in the + fallback are reused for the reference links, so each embedding page resolves its own relative URLs. Keys: + intrusive, non_intrusive, derived, with_names, third_party, non_default. + The rules follow include/nlohmann/detail/macro_scope.hpp: WITH_NAMES takes "key", member pairs and no empty + member list; WITH_DEFAULT and the plain variants default-construct the type in from_json/get; ONLY_SERIALIZE + generates to_json only; a type that is not default constructible and converts both ways gets no macro but an + adl_serializer specialization whose from_json returns the value; DERIVED_TYPE calls the base type's to_json/from_json first; at most 63 members (31 with + WITH_NAMES). The generated programs were compiled and run with clang++ -std=c++11 against include/. */ +(function () { + "use strict"; + + var QUESTIONS = [ + { id: "edit", legend: "Can you edit the class?", options: [["yes", "Yes"], ["no", "No, it is third-party"]] }, + { id: "access", legend: "Member access", options: [["public", "All public"], ["private", "Some private"]] }, + { id: "base", legend: "Base class", options: [["no", "None"], ["yes", "Derived from a base"]] }, + { id: "dir", legend: "Direction", options: [["both", "To and from JSON"], ["to", "Only to JSON"]] }, + { id: "missing", legend: "Missing keys when parsing", options: [["throw", "Throw"], ["default", "Keep defaults"]] }, + { id: "keys", legend: "JSON keys", options: [["members", "Member names"], ["custom", "Custom names"]] }, + { id: "ctor", legend: "Default constructible?", options: [["yes", "Yes"], ["no", "No"]] } + ]; + + var KEYWORDS = ("alignas alignof and asm auto bool break case catch char class const constexpr continue decltype default " + + "delete do double else enum explicit export extern false float for friend goto if inline int long mutable namespace " + + "new noexcept not nullptr operator or private protected public register return short signed sizeof static " + + "struct switch template this throw true try typedef typeid typename union unsigned using virtual void volatile " + + "while").split(" "); + var HL_TYPES = ["int", "bool", "double", "float", "char", "void", "auto", "unsigned", "long", "short", "signed"]; + var HL_CONST = ["true", "false", "nullptr"]; + var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/; + var MEMBER_TYPE = /^[A-Za-z_][A-Za-z0-9_:<>, ]*$/; + + var counter = 0; + + function el(tag, attrs, text) { + var e = document.createElement(tag); + if (attrs) { + Object.keys(attrs).forEach(function (k) { e.setAttribute(k, attrs[k]); }); + } + if (text !== undefined) e.textContent = text; + return e; + } + + function esc(s) { + return s.replace(/&/g, "&").replace(//g, ">"); + } + + /* small C++ tokenizer emitting the Pygments classes the site already colours */ + function highlight(code) { + var out = ""; + var re = /(\/\/[^\n]*)|(#include)|(<[a-z_/.]+\.hpp>|<[a-z]+>)|("(?:[^"\\\n]|\\.)*")|(\b\d+\b)|([A-Za-z_][A-Za-z0-9_]*)|(::|<<|==|[=:<>&])|([{}()\[\];,.!])|(\s+)|(.)/g; + var m, prev = ""; + while ((m = re.exec(code)) !== null) { + var t = m[0]; + if (m[1]) out += '' + esc(t) + ""; + else if (m[2]) out += '' + esc(t) + ""; + else if (m[3] && prev === "#include") out += '' + esc(t) + ""; + else if (m[4]) out += '' + esc(t) + ""; + else if (m[5]) out += '' + esc(t) + ""; + else if (m[6]) { + var cls = "n"; + if (HL_CONST.indexOf(t) >= 0) cls = "kc"; + else if (HL_TYPES.indexOf(t) >= 0) cls = "kt"; + else if (KEYWORDS.indexOf(t) >= 0) cls = "k"; + else if (prev === "namespace") cls = "nn"; + else if (prev === "struct" || prev === "class") cls = "nc"; + out += '' + esc(t) + ""; + } else if (m[7]) out += '' + esc(t) + ""; + else if (m[8]) out += '' + esc(t) + ""; + else out += esc(t); + if (!m[9]) prev = (m[3] && prev === "#include") ? "" : t; + } + return out; + } + + function parsePreset(s) { + var r = {}; + (s || "").split(",").forEach(function (kv) { + var p = kv.split("="); + if (p.length === 2) r[p[0].trim()] = p[1].trim(); + }); + return r; + } + + /* ---- the decision logic, independent of the DOM ---- */ + function decide(st) { + var res = { errors: [], warnings: [], macro: null, reason: "", place: "", ref: null }; + var n = st.members.length; + var names = st.keys === "custom"; + + if (st.edit === "no" && st.access === "private") { + res.errors.push({ text: "No macro can reach private members from outside the class. Write to_json/from_json by hand using the public interface, for example in a specialization of adl_serializer.", link: "third_party", linkText: "How do I convert third-party types?" }); + } + var adl = st.dir === "both" && st.ctor === "no"; + if (!adl && names && n === 0) { + res.errors.push({ text: "The WITH_NAMES macros need at least one key and member pair. Without members there is nothing to rename: choose Member names." }); + } + var limit = names ? 31 : 63; + if (!adl && n > limit) { + res.errors.push({ text: "The macros support at most " + limit + " members" + (names ? " with custom names" : "") + ". Write to_json/from_json by hand for larger types." }); + } + + /* identifier checks */ + if (!IDENT.test(st.type) || KEYWORDS.indexOf(st.type) >= 0) res.errors.push({ text: "The type name must be a C++ identifier." }); + if (st.base === "yes" && (!IDENT.test(st.baseName) || KEYWORDS.indexOf(st.baseName) >= 0)) res.errors.push({ text: "The base class name must be a C++ identifier." }); + if (st.base === "yes" && st.baseName === st.type) res.errors.push({ text: "The base class needs a different name than the type." }); + var seenName = {}, seenKey = {}; + st.members.forEach(function (mb, i) { + var label = "Member " + (i + 1); + if (!IDENT.test(mb.name) || KEYWORDS.indexOf(mb.name) >= 0) res.errors.push({ text: label + ": the name must be a C++ identifier." }); + else if (seenName[mb.name]) res.errors.push({ text: label + ": the name " + mb.name + " is used twice." }); + else if (mb.name === st.type) res.errors.push({ text: label + ": a member cannot have the name of its class." }); + seenName[mb.name] = true; + if (!MEMBER_TYPE.test(mb.mtype.trim())) res.errors.push({ text: label + ": enter a type such as int, std::string or std::vector." }); + var key = names ? mb.key : mb.name; + if (seenKey[key]) res.warnings.push(label + ": the JSON key \"" + key + "\" is used twice; the later member overwrites the earlier one in to_json."); + seenKey[key] = true; + if (st.base === "yes" && key === "id") res.warnings.push(label + ": the key \"id\" is also written by the example base class; the derived member overwrites it."); + }); + + if (st.edit === "no" && st.access === "private") return res; + + if (adl) { + res.adl = true; + res.intrusive = st.access === "private"; + res.ref = "non_default"; + res.reason = "Every macro that generates from_json default-constructs the type, so none fits. A specialization of adl_serializer returns the value from from_json instead" + + (st.access === "private" ? "; the class befriends it to reach the private members" : "") + "."; + res.place = "Write the specialization in namespace nlohmann, after the class."; + return res; + } + + var intrusive = st.access === "private"; + var parts = ["NLOHMANN_DEFINE", st.base === "yes" ? "DERIVED_TYPE" : "TYPE", intrusive ? "INTRUSIVE" : "NON_INTRUSIVE"]; + if (st.dir === "to") parts.push("ONLY_SERIALIZE"); + else if (st.missing === "default") parts.push("WITH_DEFAULT"); + if (names) parts.push("WITH_NAMES"); + res.macro = parts.join("_"); + res.intrusive = intrusive; + res.ref = names ? "with_names" : (st.base === "yes" ? "derived" : (intrusive ? "intrusive" : "non_intrusive")); + + var why = []; + if (intrusive) why.push("Private members need the macro inside the class, where its friend functions can access them"); + else if (st.edit === "no") why.push("You cannot edit the class, so the macro goes next to it in its namespace, which works because all members are public"); + else why.push("All members are public, so the macro can stay outside the class, in its namespace"); + if (st.base === "yes") why.push("the conversion of " + (st.baseName || "the base") + " runs first"); + if (st.dir === "to") why.push("only to_json is generated"); + else if (st.missing === "default") why.push("missing keys keep the value of a default-constructed " + st.type); + else why.push("a missing key throws out_of_range.403"); + if (names) why.push("each member is preceded by its JSON key"); + res.reason = why.join("; ") + "."; + res.place = intrusive ? "Write the macro inside the class body." : "Write the macro after the class, in the same namespace."; + if (st.dir === "to" && st.ctor === "no") res.warnings.push("The type is not default constructible: fine for ONLY_SERIALIZE, but j.get<" + st.type + ">() will not compile."); + return res; + } + + /* C++ string literal; control characters as three-digit octal escapes, which cannot run into the next character */ + function cppString(s) { + return '"' + s.replace(/[\\"\u0000-\u001f\u007f]/g, function (c) { + if (c === "\\" || c === '"') return "\\" + c; + var o = c.charCodeAt(0).toString(8); + return "\\" + ("00" + o).slice(-3); + }) + '"'; + } + + function macroCall(name, head, pairs, indent) { + var one = name + "(" + head.concat(pairs).join(", ") + ")"; + if (one.length + indent.length <= 88) return indent + one; + var lines = [indent + name + "(" + head.join(", ") + (pairs.length ? "," : ")")]; + pairs.forEach(function (p, i) { + lines.push(indent + " " + p + (i === pairs.length - 1 ? ")" : ",")); + }); + return lines.join("\n"); + } + + function generate(st, res) { + var ns = "ns"; + var T = st.type, B = st.baseName; + var priv = res.intrusive; + var names = st.keys === "custom"; + var suffix = st.dir === "to" ? "_ONLY_SERIALIZE" : (st.missing === "default" ? "_WITH_DEFAULT" : ""); + var adl = !!res.adl; + var noCtor = st.ctor === "no" && (st.members.length > 0 || st.base === "yes"); + var L = []; + L.push("#include "); + L.push("#include "); + L.push(""); + L.push("using nlohmann::json;"); + L.push(""); + L.push("namespace " + ns); + L.push("{"); + if (st.base === "yes") { + L.push("// stand-in for your base class: it must be serializable itself"); + if (priv) { + L.push("class " + B); + L.push("{"); + L.push(" private:"); + L.push(" int id{};"); + L.push(""); + L.push(" public:"); + L.push(" NLOHMANN_DEFINE_TYPE_INTRUSIVE" + suffix + "(" + B + ", id)"); + L.push("};"); + } else { + L.push("struct " + B); + L.push("{"); + L.push(" int id{};"); + L.push("};"); + L.push("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE" + suffix + "(" + B + ", id)"); + } + L.push(""); + } + var kw = priv ? "class " : "struct "; + var inherit = st.base === "yes" ? (priv ? " : public " + B : " : " + B) : ""; + L.push(kw + T + inherit); + L.push("{"); + if (priv) L.push(" private:"); + st.members.forEach(function (mb) { + L.push(" " + mb.mtype.trim() + " " + mb.name + (noCtor ? ";" : "{};")); + }); + var head = [T]; + if (st.base === "yes") head.push(B); + var pairs = st.members.map(function (mb) { + return names ? cppString(mb.key) + ", " + mb.name : mb.name; + }); + if (noCtor || priv) { + if (st.members.length) L.push(""); + if (priv) L.push(" public:"); + } + if (noCtor) { + var plist = st.members.map(function (mb) { return mb.mtype.trim() + " " + mb.name + "_"; }); + var ilist = st.members.map(function (mb) { return mb.name + "(" + mb.name + "_)"; }); + if (st.base === "yes") { + plist.unshift(B + " base_"); + ilist.unshift(B + "(base_)"); + } + L.push(" " + T + "(" + plist.join(", ") + ")"); + L.push(" : " + ilist.join(", ")); + L.push(" {}"); + if (priv) L.push(""); + } + if (adl) { + if (priv) { + L.push(" friend struct nlohmann::adl_serializer<" + T + ">;"); + } + L.push("};"); + L.push("} // namespace " + ns); + L.push(""); + L.push("namespace nlohmann"); + L.push("{"); + L.push("template <>"); + L.push("struct adl_serializer<" + ns + "::" + T + ">"); + L.push("{"); + L.push(" // note: the return type is the value, not void, and there is no default construction"); + L.push(" static " + ns + "::" + T + " from_json(const json& j)"); + L.push(" {"); + var args = st.members.map(function (mb) { + var key = names ? cppString(mb.key) : cppString(mb.name); + var ty = mb.mtype.trim(); + return st.missing === "default" + ? "j.value(" + key + ", " + ty + "{})" + : "j.at(" + key + ").get<" + ty + ">()"; + }); + if (st.base === "yes") args.unshift("j.get<" + ns + "::" + B + ">()"); + L.push(" return " + ns + "::" + T + "{" + args.join(", ") + "};"); + L.push(" }"); + L.push(""); + L.push(" // you must provide to_json as well: the specialization replaces the default one"); + L.push(" static void to_json(json& j, const " + ns + "::" + T + "& t)"); + L.push(" {"); + if (st.base === "yes") L.push(" j = static_cast(t);"); + else L.push(" j = json::object();"); + st.members.forEach(function (mb) { + L.push(" j[" + (names ? cppString(mb.key) : cppString(mb.name)) + "] = t." + mb.name + ";"); + }); + L.push(" }"); + L.push("};"); + L.push("} // namespace nlohmann"); + } else if (priv) { + L.push(macroCall(res.macro, head, pairs, " ")); + L.push("};"); + } else { + L.push("};"); + L.push(""); + L.push(macroCall(res.macro, head, pairs, "")); + } + if (!adl) L.push("} // namespace " + ns); + L.push(""); + L.push("int main()"); + L.push("{"); + var Q = ns + "::" + T; + if (noCtor) { + var init = st.members.map(function (mb) { return mb.mtype.trim() + "{}"; }); + if (st.base === "yes") init.unshift(ns + "::" + B + "{}"); + L.push(" " + Q + " p{" + init.join(", ") + "};"); + } else { + L.push(" " + Q + " p{};"); + } + L.push(" json j = p; // to_json"); + L.push(" std::cout << j.dump() << '\\n';"); + if (st.dir === "both") { + L.push(""); + L.push(" auto p2 = j.get<" + Q + ">(); // from_json"); + L.push(" std::cout << (json(p2) == j) << '\\n'; // round trip: 1"); + if (st.missing === "default") { + L.push(""); + L.push(" // missing keys keep the defaults"); + L.push(" std::cout << json(json::object().get<" + Q + ">()).dump() << '\\n';"); + } else { + L.push(" // a missing key throws json::out_of_range (403)"); + } + } + L.push("}"); + return L.join("\n") + "\n"; + } + + function copyText(text, done) { + function fallback() { + var ta = el("textarea", { readonly: "", "aria-hidden": "true" }); + ta.value = text; + ta.style.position = "fixed"; + ta.style.opacity = "0"; + document.body.appendChild(ta); + ta.select(); + var ok = false; + try { ok = document.execCommand("copy"); } catch (e) { ok = false; } + document.body.removeChild(ta); + done(ok); + } + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text).then(function () { done(true); }, fallback); + } else { + fallback(); + } + } + + /* ---- DOM ---- */ + function setup(root) { + if (root.dataset.njMcReady) return; + root.dataset.njMcReady = "1"; + var uid = "nj-mc-" + (++counter); + var variant = root.getAttribute("data-variant") || "full"; + var preset = parsePreset(root.getAttribute("data-preset")); + + var links = {}; + Array.prototype.forEach.call(root.querySelectorAll("a[data-nj-mc]"), function (a) { + links[a.getAttribute("data-nj-mc")] = a.getAttribute("href"); + }); + + var st = { edit: "yes", access: "public", base: "no", dir: "both", missing: "throw", keys: "members", ctor: "yes", + type: "person", baseName: "entity", + members: [{ mtype: "std::string", name: "name", key: "name" }, { mtype: "int", name: "age", key: "age" }] }; + QUESTIONS.forEach(function (q) { + if (preset[q.id] && q.options.some(function (o) { return o[0] === preset[q.id]; })) st[q.id] = preset[q.id]; + }); + + var fallback = root.querySelector(".nj-mc-fallback"); + if (fallback) fallback.hidden = true; + + var app = el("div", { "class": "nj-mc-app" }); + var form = el("form", { "class": "nj-mc-form", "aria-label": "Macro chooser options" }); + form.addEventListener("submit", function (e) { e.preventDefault(); }); + + var qgrid = el("div", { "class": "nj-mc-questions" }); + var notes = {}; + var groups = {}; + QUESTIONS.forEach(function (q) { + var fs = el("fieldset", { "class": "nj-mc-q" }); + fs.appendChild(el("legend", null, q.legend)); + var seg = el("div", { "class": "nj-mc-seg" }); + q.options.forEach(function (o) { + var id = uid + "-" + q.id + "-" + o[0]; + var inp = el("input", { type: "radio", id: id, name: uid + "-" + q.id, value: o[0] }); + if (st[q.id] === o[0]) inp.checked = true; + inp.addEventListener("change", function () { st[q.id] = o[0]; update(); }); + seg.appendChild(inp); + seg.appendChild(el("label", { "for": id }, o[1])); + }); + fs.appendChild(seg); + var note = el("p", { "class": "nj-mc-qnote", id: uid + "-" + q.id + "-note" }); + note.hidden = true; + fs.appendChild(note); + fs.setAttribute("aria-describedby", note.id); + notes[q.id] = note; + groups[q.id] = fs; + qgrid.appendChild(fs); + }); + form.appendChild(qgrid); + + /* type editor */ + var editor = el("div", { "class": "nj-mc-editor" }); + var names = el("div", { "class": "nj-mc-names" }); + function textField(label, value, onInput, cls) { + var id = uid + "-f" + (++counter); + var w = el("div", { "class": "nj-mc-field" + (cls ? " " + cls : "") }); + w.appendChild(el("label", { "for": id }, label)); + var inp = el("input", { type: "text", id: id, spellcheck: "false", autocomplete: "off", autocapitalize: "off" }); + inp.value = value; + inp.addEventListener("input", function () { onInput(inp.value); update(); }); + w.appendChild(inp); + return w; + } + names.appendChild(textField("Type name", st.type, function (v) { st.type = v.trim(); })); + var baseField = textField("Base class", st.baseName, function (v) { st.baseName = v.trim(); }, "nj-mc-basefield"); + names.appendChild(baseField); + editor.appendChild(names); + + var mtable = el("div", { "class": "nj-mc-members", role: "group", "aria-label": "Member variables" }); + var mhead = el("div", { "class": "nj-mc-mrow nj-mc-mhead", "aria-hidden": "true" }); + mhead.appendChild(el("span", null, "Member type")); + mhead.appendChild(el("span", null, "Member name")); + var keyHead = el("span", { "class": "nj-mc-keycol" }, "JSON key"); + mhead.appendChild(keyHead); + mhead.appendChild(el("span")); + mtable.appendChild(mhead); + var mbody = el("div", { "class": "nj-mc-mbody" }); + mtable.appendChild(mbody); + var addBtn = el("button", { type: "button", "class": "nj-mc-add" }, "Add member"); + addBtn.addEventListener("click", function () { + var k = st.members.length + 1; + var nm = "member" + k; + while (st.members.some(function (mb) { return mb.name === nm; })) nm = "member" + (++k); + st.members.push({ mtype: "int", name: nm, key: nm }); + renderMembers(); + update(); + var rows = mbody.querySelectorAll(".nj-mc-mrow"); + var last = rows[rows.length - 1]; + if (last) last.querySelector("input").focus(); + }); + editor.appendChild(mtable); + editor.appendChild(addBtn); + if (variant === "full") form.appendChild(editor); + + function renderMembers() { + mbody.textContent = ""; + st.members.forEach(function (mb, i) { + var row = el("div", { "class": "nj-mc-mrow" }); + function cell(prop, label, cls) { + var inp = el("input", { type: "text", spellcheck: "false", autocomplete: "off", autocapitalize: "off", + "aria-label": label + " of member " + (i + 1), placeholder: label, "class": cls || "" }); + inp.value = mb[prop]; + inp.addEventListener("input", function () { + var keyFollows = prop === "name" && mb.key === mb.name; + mb[prop] = prop === "key" ? inp.value : inp.value.trim(); + if (keyFollows) { + mb.key = mb.name; + var k = row.querySelector(".nj-mc-keycol"); + if (k) k.value = mb.key; + } + update(); + }); + row.appendChild(inp); + } + cell("mtype", "Type"); + cell("name", "Name"); + cell("key", "JSON key", "nj-mc-keycol"); + var rm = el("button", { type: "button", "class": "nj-mc-rm", "aria-label": "Remove member " + (i + 1), title: "Remove member" }); + rm.appendChild(el("span", { "aria-hidden": "true" }, "\u00d7")); + rm.addEventListener("click", function () { + st.members.splice(i, 1); + renderMembers(); + update(); + var btns = mbody.querySelectorAll(".nj-mc-rm"); + (btns[Math.min(i, btns.length - 1)] || addBtn).focus(); + }); + row.appendChild(rm); + mbody.appendChild(row); + }); + } + renderMembers(); + app.appendChild(form); + + /* output */ + var out = el("div", { "class": "nj-mc-out" }); + var result = el("div", { "class": "nj-mc-result", role: "status", "aria-live": "polite" }); + out.appendChild(result); + var hints = el("ul", { "class": "nj-mc-hints" }); + out.appendChild(hints); + var codeWrap = el("div", { "class": "nj-mc-code" }); + var bar = el("div", { "class": "nj-mc-codebar" }); + bar.appendChild(el("span", { "class": "nj-mc-codetitle" }, "Generated code (C++11)")); + var copyBtn = el("button", { type: "button", "class": "nj-mc-copy", "aria-label": "Copy generated code" }, "Copy"); + var copyStatus = el("span", { "class": "nj-mc-copystatus", role: "status", "aria-live": "polite" }); + bar.appendChild(copyStatus); + bar.appendChild(copyBtn); + codeWrap.appendChild(bar); + var hl = el("div", { "class": "language-cpp highlight" }); + var pre = el("pre"); + var code = el("code", { tabindex: "0", "aria-label": "Generated C++ code" }); + pre.appendChild(code); + hl.appendChild(pre); + codeWrap.appendChild(hl); + if (variant === "full") out.appendChild(codeWrap); + app.appendChild(out); + root.appendChild(app); + + var currentCode = ""; + var statusTimer = null; + copyBtn.addEventListener("click", function () { + copyText(currentCode, function (ok) { + copyStatus.textContent = ok ? "Copied" : "Press Ctrl+C / Cmd+C to copy"; + if (!ok) { + var r = document.createRange(); + r.selectNodeContents(code); + var s = window.getSelection(); + s.removeAllRanges(); + s.addRange(r); + } + clearTimeout(statusTimer); + statusTimer = setTimeout(function () { copyStatus.textContent = ""; }, 2500); + }); + }); + + function setNote(id, text) { + notes[id].hidden = !text; + notes[id].textContent = text || ""; + } + + function addLink(parent, key, text) { + if (!key || !links[key]) return; + parent.appendChild(document.createTextNode(" ")); + parent.appendChild(el("a", { href: links[key] }, text)); + } + + function update() { + var dirTo = st.dir === "to"; + groups.missing.disabled = dirTo; + groups.missing.classList.toggle("nj-mc-off", dirTo); + setNote("missing", dirTo ? "Not applicable: ONLY_SERIALIZE generates no from_json." : ""); + setNote("access", st.edit === "no" && st.access === "private" ? "Private members of a class you cannot edit are out of reach for every macro." : ""); + setNote("ctor", st.ctor === "no" && !dirTo ? "The macros default-construct the type when parsing: this gives an adl_serializer specialization instead." : ""); + baseField.hidden = st.base !== "yes"; + mtable.classList.toggle("nj-mc-custom", st.keys === "custom"); + keyHead.hidden = st.keys !== "custom"; + + var res = decide(st); + result.textContent = ""; + hints.textContent = ""; + if ((res.macro || res.adl) && !res.errors.length) { + result.className = "nj-mc-result"; + result.appendChild(el("p", { "class": "nj-mc-label" }, res.adl ? "No macro fits, write a serializer" : "Recommended macro")); + var name = el("p", { "class": "nj-mc-macro" }); + name.appendChild(el("code", null, res.adl ? "adl_serializer<" + st.type + ">" : res.macro)); + result.appendChild(name); + var why = el("p", { "class": "nj-mc-why" }, res.reason + " " + res.place); + addLink(why, res.ref, res.adl ? "Details" : "Reference"); + result.appendChild(why); + } else { + result.className = "nj-mc-result nj-mc-bad"; + result.appendChild(el("p", { "class": "nj-mc-label" }, (res.macro || res.adl) ? "Cannot generate this code yet" : "No macro fits")); + res.errors.forEach(function (e) { + var p = el("p", { "class": "nj-mc-why" }, e.text); + addLink(p, e.link, e.linkText); + result.appendChild(p); + }); + } + res.warnings.forEach(function (w) { hints.appendChild(el("li", null, w)); }); + hints.hidden = !res.warnings.length; + + var ok = (res.macro || res.adl) && !res.errors.length; + codeWrap.hidden = !ok; + if (ok) { + currentCode = generate(st, res); + code.innerHTML = highlight(currentCode); + } + root.setAttribute("data-nj-mc-macro", ok ? (res.macro || "adl_serializer") : ""); + } + update(); + } + + function init() { + Array.prototype.forEach.call(document.querySelectorAll(".nj-macro-chooser"), setup); + } + + if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") { + document$.subscribe(init); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + /* exported for the verification scripts */ + window.njMacroChooser = { decide: decide, generate: generate }; +})(); diff --git a/docs/mkdocs/docs/js/fig-pointer.js b/docs/mkdocs/docs/js/fig-pointer.js new file mode 100644 index 000000000..1096c7991 --- /dev/null +++ b/docs/mkdocs/docs/js/fig-pointer.js @@ -0,0 +1,306 @@ +/* figures: pointer + * + * Two widgets that enhance static markup (the markup alone is a complete, readable figure): + * - [data-nj-pointer-walk] in features/json_pointer.md: a tree of the example document plus a pointer bar whose + * segments select a prefix of the pointer; the path down to that level is highlighted and its value is shown. + * - [data-nj-patch] in features/json_patch.md: a stepper through six JSON Patch operations; the static table of the + * operations is hidden once the stepper is built. + * Initialised through Material's document$ (navigation.instant), idempotent per element. + */ +(function () { + "use strict"; + + function el(tag, cls, html) { + var e = document.createElement(tag); + if (cls) e.className = cls; + if (html !== undefined) e.innerHTML = html; + return e; + } + function esc(s) { + return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + + /* ---------- JSON Pointer walker ---------- */ + + var PRESETS = ["", "/array", "/array/0", "/array/1", "/array/2", "/nested", "/nested/one", "/nested/two", + "/nested/three", "/nested/three/0", "/nested/three/1", "/"]; + + function tokens(ptr) { + if (ptr === "") return []; + return ptr.slice(1).split("/").map(function (t) { return t.replace(/~1/g, "/").replace(/~0/g, "~"); }); + } + function joinTokens(toks) { + return toks.map(function (t) { return "/" + t.replace(/~/g, "~0").replace(/\//g, "~1"); }).join(""); + } + // RFC 6901 resolution as json::at() does it; returns {ok, value, depth, error} + function resolve(doc, toks) { + var v = doc; + for (var i = 0; i < toks.length; ++i) { + var t = toks[i]; + if (Array.isArray(v)) { + if (!/^(0|[1-9][0-9]*)$/.test(t) || Number(t) >= v.length) { + return { ok: false, depth: i, error: "at() throws out_of_range: array index '" + t + "' is out of range" }; + } + v = v[Number(t)]; + } else if (v !== null && typeof v === "object") { + if (!Object.prototype.hasOwnProperty.call(v, t)) { + return { ok: false, depth: i, error: "at() throws out_of_range.403: key '" + t + "' not found" }; + } + v = v[t]; + } else { + return { ok: false, depth: i, error: "a primitive value has no children" }; + } + } + return { ok: true, value: v, depth: toks.length }; + } + function ptrLabel(p) { return p === "" ? '""' : p; } + + function initWalk(root) { + if (root.dataset.njReady) return; + var doc; + try { doc = JSON.parse(root.getAttribute("data-doc")); } catch (e) { return; } + root.dataset.njReady = "1"; + root.classList.add("nj-pointer-walk--js"); + + var tree = root.querySelector(".nj-pointer-tree"); + var items = Array.prototype.slice.call(tree.querySelectorAll("li[data-p]")); + var state = { ptr: "/nested/three/1", level: 3 }; + + // tree labels become buttons that select their own pointer + items.forEach(function (li) { + var span = li.querySelector(":scope > .nj-pointer-n"); + var b = el("button", "nj-pointer-n", span.innerHTML); + b.type = "button"; + var p = li.getAttribute("data-p"); + b.setAttribute("aria-label", "Select the pointer " + ptrLabel(p)); + b.addEventListener("click", function () { set(p, tokens(p).length); }); + span.replaceWith(b); + }); + + var panel = el("div", "nj-pointer-panel"); + panel.appendChild(el("div", "nj-pointer-lbl", "Pointer (select a segment)")); + var bar = el("div", "nj-pointer-bar"); + bar.setAttribute("role", "group"); + bar.setAttribute("aria-label", "Pointer segments"); + panel.appendChild(bar); + var out = el("div", "nj-pointer-out"); + out.setAttribute("aria-live", "polite"); + panel.appendChild(out); + panel.appendChild(el("div", "nj-pointer-lbl", "Examples")); + var presets = el("div", "nj-pointer-presets"); + presets.setAttribute("role", "group"); + presets.setAttribute("aria-label", "Example pointers"); + PRESETS.forEach(function (p) { + var b = el("button", "nj-pointer-chip", "" + esc(ptrLabel(p)) + ""); + b.type = "button"; + b.setAttribute("data-p", p); + b.setAttribute("aria-label", p === "" ? "Pointer \"\" (the whole document)" : "Pointer " + p); + b.addEventListener("click", function () { set(p, tokens(p).length); }); + presets.appendChild(b); + }); + panel.appendChild(presets); + var stat = root.querySelector(".nj-pointer-static"); + if (stat) stat.hidden = true; + root.insertBefore(panel, root.firstChild); + + function liFor(p) { + for (var i = 0; i < items.length; ++i) if (items[i].getAttribute("data-p") === p) return items[i]; + return null; + } + + function render() { + var toks = tokens(state.ptr); + var sel = toks.slice(0, state.level); + var r = resolve(doc, sel); + + // bar: root chip plus one button per reference token + bar.innerHTML = ""; + var parts = [{ label: '""', text: "root", n: 0 }]; + toks.forEach(function (t, i) { parts.push({ label: joinTokens([t]), n: i + 1 }); }); + parts.forEach(function (part) { + var b = el("button", "nj-pointer-seg" + (part.n === 0 ? " nj-pointer-seg--root" : ""), + part.n === 0 ? "\"\"" : "" + esc(part.label) + ""); + b.type = "button"; + b.setAttribute("aria-pressed", part.n === state.level ? "true" : "false"); + if (part.n > state.level) b.classList.add("is-after"); + var prefix = joinTokens(toks.slice(0, part.n)); + b.setAttribute("aria-label", "Go to level " + part.n + ": " + ptrLabel(prefix) + (part.n === 0 ? " (the whole document)" : "")); + b.addEventListener("click", function () { state.level = part.n; render(); focusSeg(part.n); }); + bar.appendChild(b); + }); + + // tree highlight + items.forEach(function (li) { li.classList.remove("is-on", "is-target", "is-pass", "is-miss"); }); + var depth = r.ok ? sel.length : r.depth; + for (var d = 0; d <= depth; ++d) { + var li = liFor(joinTokens(sel.slice(0, d))); + if (!li) break; + li.classList.add("is-on"); + if (d > 0) { + for (var s = li.previousElementSibling; s; s = s.previousElementSibling) s.classList.add("is-pass"); + } + if (r.ok && d === depth) li.classList.add("is-target"); + } + if (!r.ok) { + var miss = liFor(joinTokens(sel.slice(0, r.depth))); + if (miss) miss.classList.add("is-miss"); + } + + var shown = joinTokens(sel); + out.innerHTML = r.ok + ? "" + esc(ptrLabel(shown)) + " resolves to " + esc(JSON.stringify(r.value)) + "" + + (shown === "" ? " (the whole document)" : "") + : "" + esc(ptrLabel(shown)) + " identifies no value: " + esc(r.error) + + ". The token after / is the empty key, and the document has none."; + + Array.prototype.forEach.call(presets.children, function (b) { + b.setAttribute("aria-pressed", b.getAttribute("data-p") === shown && state.level === tokens(state.ptr).length ? "true" : "false"); + }); + } + function focusSeg(n) { + var b = bar.children[n]; + if (b) b.focus(); + } + function set(p, level) { + state.ptr = p; + state.level = level; + render(); + } + render(); + } + + /* ---------- JSON Patch stepper ---------- */ + + function k(s) { return '"' + s + '"'; } + function s(v) { return '"' + v + '"'; } + function ins(x) { return '' + x + ""; } + function src(x) { return '' + x + ""; } + var I = " "; + + var OPS = [ + { op: "replace", path: "/baz", extra: ', "value": "boo"' }, + { op: "add", path: "/hello", extra: ', "value": ["world"]' }, + { op: "remove", path: "/foo", extra: "" }, + { op: "move", from: "/baz", path: "/hello/1", extra: "" }, + { op: "copy", from: "/hello/0", path: "/greeting", extra: "" }, + { op: "test", path: "/greeting", extra: ', "value": "world"' } + ]; + // each frame: document lines [html, line class, note], optional connector {from, to}, status text + var FRAMES = [ + { lines: [["{"], [I + k("baz") + ": " + s("qux") + ","], [I + k("foo") + ": " + s("bar")], ["}"]], + status: "Start: the original document of the example below. Press Next to apply the first operation." }, + { lines: [["{"], [I + k("baz") + ": " + s("qux") + ",", "del"], [I + k("baz") + ": " + s("boo") + ",", "add", "replaced"], + [I + k("foo") + ": " + s("bar")], ["}"]], + status: "replace /baz: the value \"qux\" is replaced by \"boo\"." }, + { lines: [["{"], [I + k("baz") + ": " + s("boo") + ","], [I + k("foo") + ": " + s("bar") + ","], + [I + k("hello") + ": [" + s("world") + "]", "add", "added"], ["}"]], + status: "add /hello: the new member \"hello\" with the value [\"world\"] is added." }, + { lines: [["{"], [I + k("baz") + ": " + s("boo") + ","], [I + k("foo") + ": " + s("bar") + ",", "del", "removed"], + [I + k("hello") + ": [" + s("world") + "]"], ["}"]], + status: "remove /foo: the member \"foo\" is removed. This is the result of the example below." }, + { lines: [["{"], [I + k("baz") + ": " + s("boo") + ",", "del", "moved"], + [I + k("hello") + ": [" + s("world") + ", " + ins(s("boo")) + "]", "mod", "from /baz"], ["}"]], + arc: { from: 1, to: 2 }, + status: "move from /baz to /hello/1: \"boo\" is removed from /baz and inserted into the array at index 1." }, + { lines: [["{"], [I + k("greeting") + ": " + s("world") + ",", "add", "copy of /hello/0"], + [I + k("hello") + ": [" + src(s("world")) + ", " + s("boo") + "]"], ["}"]], + arc: { from: 2, to: 1 }, + status: "copy from /hello/0 to /greeting: the value \"world\" is copied to the new member \"greeting\"." }, + { lines: [["{"], [I + k("greeting") + ": " + s("world") + ",", "test", "test passed"], + [I + k("hello") + ": [" + s("world") + ", " + s("boo") + "]"], ["}"]], + status: "test /greeting: the value equals \"world\", so the document is unchanged. A failed test would make patch() throw other_error 501." } + ]; + + function opHtml(o) { + var h = '{"op": "' + o.op + '"'; + if (o.from) h += ', "from": "' + o.from + '"'; + h += ', "path": "' + o.path + '"' + esc(o.extra) + "}"; + return h; + } + + function initPatch(root) { + if (root.dataset.njReady) return; + root.dataset.njReady = "1"; + root.classList.add("nj-patch--js"); + var step = 0; + + var stage = el("div", "nj-patch-stage"); + var cols = el("div", "nj-patch-cols"); + var left = el("div", "nj-patch-col"); + left.appendChild(el("div", "nj-patch-h", "Document")); + var docBox = el("div", "nj-patch-doc"); + left.appendChild(docBox); + var right = el("div", "nj-patch-col"); + right.appendChild(el("div", "nj-patch-h", "Patch")); + var list = el("ol", "nj-patch-ops"); + OPS.forEach(function (o) { list.appendChild(el("li", "", "" + opHtml(o) + "")); }); + right.appendChild(list); + cols.appendChild(left); + cols.appendChild(right); + stage.appendChild(cols); + // controls and status sit under the document, so on a phone both stay in view together + + var ctrl = el("div", "nj-patch-ctrl"); + function btn(label, aria, fn) { + var b = el("button", "nj-patch-btn", label); + b.type = "button"; + b.setAttribute("aria-label", aria); + b.addEventListener("click", fn); + ctrl.appendChild(b); + return b; + } + var bReset = btn("Reset", "Back to the original document", function () { step = 0; render(true); }); + var bPrev = btn("Back", "Previous operation", function () { if (step > 0) { step--; render(true); } }); + var bNext = btn("Next", "Apply the next operation", function () { if (step < OPS.length) { step++; render(true); } }); + bNext.classList.add("nj-patch-btn--primary"); + var count = el("span", "nj-patch-count"); + ctrl.appendChild(count); + left.appendChild(ctrl); + var status = el("p", "nj-patch-status"); + status.setAttribute("aria-live", "polite"); + left.appendChild(status); + root.insertBefore(stage, root.firstChild); + + function render(animate) { + var f = FRAMES[step]; + docBox.innerHTML = ""; + f.lines.forEach(function (ln) { + var row = el("div", "nj-patch-line" + (ln[1] ? " nj-patch-line--" + ln[1] : "")); + row.appendChild(el("span", "nj-patch-code", ln[0])); + if (ln[2]) row.appendChild(el("span", "nj-patch-note", esc(ln[2]))); + docBox.appendChild(row); + }); + if (f.arc) { + var a = el("div", "nj-patch-arc " + (f.arc.to > f.arc.from ? "nj-patch-arc--down" : "nj-patch-arc--up")); + var top = Math.min(f.arc.from, f.arc.to), n = Math.abs(f.arc.to - f.arc.from); + a.style.top = "calc(var(--nj-patch-pad) + " + (top + 0.5) + " * var(--nj-patch-lh))"; + a.style.height = "calc(" + n + " * var(--nj-patch-lh))"; + a.setAttribute("aria-hidden", "true"); + docBox.appendChild(a); + } + docBox.classList.toggle("nj-patch-doc--anim", !!animate); + Array.prototype.forEach.call(list.children, function (li, i) { + li.className = i + 1 === step ? "is-cur" : (i + 1 < step ? "is-done" : ""); + if (i + 1 === step) li.setAttribute("aria-current", "step"); else li.removeAttribute("aria-current"); + }); + bPrev.disabled = step === 0; + bReset.disabled = step === 0; + bNext.disabled = step === OPS.length; + count.textContent = "Step " + step + " of " + OPS.length; + status.textContent = f.status; + } + render(false); + } + + function init() { + Array.prototype.forEach.call(document.querySelectorAll("[data-nj-pointer-walk]"), initWalk); + Array.prototype.forEach.call(document.querySelectorAll("[data-nj-patch]"), initPatch); + } + if (typeof window.document$ !== "undefined" && window.document$.subscribe) { + window.document$.subscribe(init); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/docs/mkdocs/docs/js/fig-sax.js b/docs/mkdocs/docs/js/fig-sax.js new file mode 100644 index 000000000..55e271c4e --- /dev/null +++ b/docs/mkdocs/docs/js/fig-sax.js @@ -0,0 +1,248 @@ +/* + * figures: sax + * + * SAX event stepper on features/parsing/sax_interface.md. The page contains the final state as static HTML (input, + * all nine events, the finished tree); this script adds the controls and redraws the three lanes for each step: + * the consumed part of the input, the events called so far, and the state of one of two handlers (a DOM builder + * that keeps a stack of open containers, or a handler that keeps only the value at /b). "return false at this + * event" stops the parse at the current event: the later events are not called and the rest of the input is not read. + */ +(function () { + "use strict"; + + var SRC = '{"a":[1,true],"b":"x"}'; + // tok: [start, end) of the token that makes the parser call the event + var EVENTS = [ + { call: "start_object(-1)", cls: "os", tok: [0, 1], op: "so" }, + { call: 'key("a")', cls: "k", tok: [1, 4], op: "key", v: "a" }, + { call: "start_array(-1)", cls: "as", tok: [5, 6], op: "sa" }, + { call: "number_unsigned(1)", cls: "v", tok: [6, 7], op: "val", v: { t: "n", v: "1" } }, + { call: "boolean(true)", cls: "v", tok: [8, 12], op: "val", v: { t: "b", v: "true" } }, + { call: "end_array()", cls: "ae", tok: [12, 13], op: "end" }, + { call: 'key("b")', cls: "k", tok: [14, 17], op: "key", v: "b" }, + { call: 'string("x")', cls: "v", tok: [18, 21], op: "val", v: { t: "s", v: '"x"' } }, + { call: "end_object()", cls: "oe", tok: [21, 22], op: "end" } + ]; + var N = EVENTS.length; + + function esc(s) { + return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + + // ---- handler A: DOM builder (like json_sax_dom_parser) ---- + function domState(steps) { + var root = null, stack = [], last = null; + function place(v) { + if (!root) { root = v; return; } + var top = stack[stack.length - 1]; + if (top.t === "a") { top.e.push(v); } else { top.m[top.m.length - 1][1] = v; } + } + for (var i = 0; i < steps; ++i) { + var ev = EVENTS[i], v; + if (ev.op === "so" || ev.op === "sa") { + v = ev.op === "so" ? { t: "o", m: [] } : { t: "a", e: [] }; + var top = stack[stack.length - 1]; + v.ptr = !top ? "" : top.ptr + "/" + (top.t === "a" ? top.e.length : top.m[top.m.length - 1][0]); + place(v); stack.push(v); last = v; + } else if (ev.op === "key") { + v = { t: "z", v: "null" }; // the DOM parser adds null at the key and fills it in later + stack[stack.length - 1].m.push([ev.v, v]); last = v; + } else if (ev.op === "val") { + v = { t: ev.v.t, v: ev.v.v }; place(v); last = v; + } else { + last = stack.pop(); + } + } + return { root: root, stack: stack, last: last }; + } + + function pretty(v, ind, last, out, prefix, suffix) { + var pad = new Array(ind + 1).join(" "); + var mark = function (s) { return v === last ? '' + s + "" : s; }; + if (v.t === "o" || v.t === "a") { + var items = v.t === "o" ? v.m : v.e, open = v.t === "o" ? "{" : "[", close = v.t === "o" ? "}" : "]"; + if (!items.length) { out.push(pad + prefix + mark(open + close) + suffix); return; } + out.push(pad + prefix + mark(open)); + for (var i = 0; i < items.length; ++i) { + var comma = i < items.length - 1 ? "," : ""; + if (v.t === "o") { + pretty(items[i][1], ind + 1, last, out, esc('"' + items[i][0] + '": '), comma); + } else { + pretty(items[i], ind + 1, last, out, "", comma); + } + } + out.push(pad + mark(close) + suffix); + } else { + out.push(pad + prefix + mark(esc(v.v)) + suffix); + } + } + + function renderDom(steps, stopped) { + var st = domState(steps), lines = []; + var tree = st.root ? (pretty(st.root, 0, st.last, lines, "", ""), lines.join("\n")) : '(nothing built yet)'; + var chips = st.stack.length ? st.stack.map(function (c) { + return '
  • ' + (c.t === "o" ? "{}" : "[]") + " " + esc(c.ptr === "" ? "root" : c.ptr) + "
  • "; + }).join("") : '
  • empty
  • '; + var note; + if (steps === 0) { note = "The handler starts with an empty stack."; } + else if (stopped) { note = "The handler keeps what it built so far; the containers on the stack stay unfinished."; } + else if (steps === N) { note = "The tree is complete and no container is open. The number of elements is passed as -1: the parser does not know it in advance."; } + else { note = EVENTS[steps - 1].op === "key" ? "A key adds the member with a null value, which the next value replaces." : "Values go into the container on top of the stack."; } + return '
    ' + tree + "
    " + + '

    stack of open containers

      ' + chips + "
    " + + '

    ' + note + "

    "; + } + + // ---- handler B: keep only the value at /b ---- + function renderKeep(steps, stopped) { + var frames = [], ptr = "", kept = null, keptAt = 0, action = ""; + var where = function () { + return frames.map(function (f) { return "/" + (f.t === "a" ? f.i : f.k); }).join(""); + }; + for (var i = 0; i < steps; ++i) { + var ev = EVENTS[i], top = frames[frames.length - 1]; + if (ev.op === "key") { top.k = ev.v; ptr = where(); action = "remembers the key"; continue; } + if (ev.op === "end") { ptr = frames.pop().loc; action = "leaves the container"; continue; } + ptr = where(); + action = ptr === "/b" ? "keeps it" : "ignores it"; + if (top && top.t === "a") { top.i++; } + if (ev.op === "so") { frames.push({ t: "o", k: "", loc: ptr }); } + else if (ev.op === "sa") { frames.push({ t: "a", i: 0, loc: ptr }); } + else if (ptr === "/b") { kept = ev.v.v; keptAt = i + 1; } + } + var note; + if (steps === 0) { note = "The handler keeps only the current position and, once it is found, one value."; } + else if (stopped) { note = kept !== null ? "The value is found, so stopping here saves reading the rest of the input." : "Stopped before /b was read: nothing is kept."; } + else { note = "At " + (ptr === "" ? "the root" : ptr) + " the handler " + action + "."; } + if (!stopped && steps === keptAt && kept !== null) { + note += " A handler that needs nothing else could return false here."; + } + return '
    ' + + "
    position
    " + (steps ? esc(ptr === "" ? "(root)" : ptr) : "-") + "
    " + + "
    kept value
    " + (kept !== null ? '' + esc(kept) + "" : 'none') + "
    " + + "
    " + + '

    ' + note + " No tree is built.

    "; + } + + function init(root) { + if (root.getAttribute("data-nj-sax-ready")) { return; } + root.setAttribute("data-nj-sax-ready", "1"); + var src = root.querySelector(".nj-sax__src"); + var items = root.querySelectorAll(".nj-sax__events li"); + var head = root.querySelector(".nj-sax__lane--state .nj-sax__h"); + var state = root.querySelector(".nj-sax__state"); + var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; + var step = 0, mode = "dom", stopAt = 0, timer = null; + + // controls + var bar = document.createElement("div"); + bar.className = "nj-sax__controls"; + bar.innerHTML = + '
    ' + + '' + + '' + + '' + + '' + + "
    " + + '' + + '
    ' + + '' + + '' + + "
    "; + root.insertBefore(bar, root.firstChild); + var status = document.createElement("p"); + status.className = "nj-sax__status"; + status.setAttribute("aria-live", "polite"); + root.appendChild(status); + var btn = function (a) { return bar.querySelector('[data-a="' + a + '"]'); }; + var stopBox = btn("stop"); + + function stopped() { return stopAt > 0 && step === stopAt; } + + function render() { + var end = step ? EVENTS[step - 1].tok[1] : 0, cur = step ? EVENTS[step - 1] : null; + var html = ""; + if (cur) { + html += '' + esc(SRC.slice(0, cur.tok[0])) + ""; + html += '' + esc(SRC.slice(cur.tok[0], end)) + ""; + } + html += '' + esc(SRC.slice(end)) + ""; + src.innerHTML = html; + for (var i = 0; i < N; ++i) { + var li = items[i]; + li.classList.toggle("nj-sax-ev--done", i < step); + li.classList.toggle("nj-sax-ev--cur", i === step - 1); + li.classList.toggle("nj-sax-ev--todo", i >= step && !stopped()); + li.classList.toggle("nj-sax-ev--skip", i >= step && stopped()); + li.classList.toggle("nj-sax-ev--false", stopped() && i === step - 1); + if (i === step - 1) { li.setAttribute("aria-current", "step"); } else { li.removeAttribute("aria-current"); } + } + head.innerHTML = mode === "dom" ? "handler: build a value tree, as parse does" : "handler: keep only the value at /b"; + state.innerHTML = mode === "dom" ? renderDom(step, stopped()) : renderKeep(step, stopped()); + bar.querySelectorAll("[data-m]").forEach(function (b) { b.setAttribute("aria-pressed", String(b.getAttribute("data-m") === mode)); }); + btn("prev").disabled = step === 0; + btn("reset").disabled = step === 0; + btn("next").disabled = step === N || stopped(); + stopBox.disabled = step === 0; + stopBox.checked = stopped(); + var playing = timer !== null; + btn("play").textContent = playing ? "Pause" : "Play"; + btn("play").setAttribute("aria-label", playing ? "Pause" : "Play all events"); + var msg; + if (step === 0) { msg = "Before the first event: nothing is read yet."; } + else if (stopped()) { msg = "Event " + step + " of " + N + ": " + cur.call + " returned false. Parsing stops, sax_parse returns false, and the rest of the input is not read."; } + else if (step === N) { msg = "Event " + N + " of " + N + ": " + cur.call + ". The input is read completely and sax_parse returns true."; } + else { msg = "Event " + step + " of " + N + ": the parser read " + SRC.slice(cur.tok[0], cur.tok[1]) + " and called " + cur.call + "."; } + status.textContent = msg; + } + + function stop() { if (timer !== null) { clearInterval(timer); timer = null; } } + function go(s) { + step = Math.max(0, Math.min(N, s)); + if (stopAt && step < stopAt) { stopAt = 0; } + render(); + } + + bar.addEventListener("click", function (e) { + var b = e.target.closest("button"); + if (!b) { return; } + var m = b.getAttribute("data-m"); + if (m) { mode = m; render(); return; } + var a = b.getAttribute("data-a"); + if (a === "play") { + if (timer !== null) { stop(); render(); return; } + if (step === N || stopped()) { stopAt = 0; step = 0; } + timer = setInterval(function () { + if (step >= N || stopped()) { stop(); render(); return; } + go(step + 1); + if (step >= N) { stop(); render(); } + }, reduce ? 1400 : 900); + go(step + 1); + return; + } + stop(); + if (a === "reset") { stopAt = 0; go(0); } + else if (a === "prev") { go(step - 1); } + else if (a === "next") { go(step + 1); } + }); + stopBox.addEventListener("change", function () { + stop(); + stopAt = stopBox.checked ? step : 0; + render(); + }); + root.classList.add("nj-sax--live"); + render(); + } + + function setup() { + document.querySelectorAll("[data-nj-sax]").forEach(init); + } + + if (typeof window.document$ !== "undefined" && window.document$ && typeof window.document$.subscribe === "function") { + window.document$.subscribe(setup); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", setup); + } else { + setup(); + } +})(); diff --git a/docs/mkdocs/docs/js/mermaid-brand.js b/docs/mkdocs/docs/js/mermaid-brand.js new file mode 100644 index 000000000..2605db4c5 --- /dev/null +++ b/docs/mkdocs/docs/js/mermaid-brand.js @@ -0,0 +1,100 @@ +/* + * Brand look for Mermaid diagrams. + * + * Material for MkDocs loads Mermaid on demand and calls mermaid.initialize() with its own theme CSS. This script + * catches Mermaid when it arrives on window.mermaid and wraps two calls: + * - initialize(): adds the brand's font, spacing and shape rules below to Material's options; + * - render(): marks hexagons ({{...}} in a flowchart) with the class "nj-decision", since in the SVG a hexagon and + * a parallelogram are both a that CSS cannot tell apart; and keeps a wide diagram at 80 % of its size + * or more, so its text stays readable and the diagram scrolls sideways instead (see .mermaid in brand.css). + * The colours come from the --md-mermaid-* variables in brand.css, so the diagrams follow the light/dark toggle. + */ +(function () { + "use strict"; + + var css = [ + // text: Inter everywhere, labels on boxes semibold + ".label, .nodeLabel, .nodeLabel p, .edgeLabel, .edgeLabel p, .cluster span, text { font-family: var(--md-mermaid-font-family) !important; }", + ".nodeLabel, .nodeLabel p { font-weight: 600; }", + ".edgeLabel, .edgeLabel p { color: var(--md-default-fg-color--light) !important; font-size: 13px; font-weight: 500; }", + + // boxes: 1.5 px outline, 6 px corners; "(...)" nodes stay rounder than "[...]" nodes + ".node rect, .node path, .node polygon, .node circle, .node ellipse { stroke-width: 1.5px !important; }", + ".node rect:not([rx]) { rx: 6px; ry: 6px; }", + ".node rect[rx] { rx: 14px; ry: 14px; }", + + // decisions: solid blue hexagons with white text + ".node.nj-decision polygon { fill: var(--nj-band) !important; stroke: var(--nj-band) !important; stroke-linejoin: round; }", + ".node.nj-decision .nodeLabel, .node.nj-decision .nodeLabel p { color: #fff !important; }", + + // lines: 1.5 px, solid unless written as -.- + ".edgePath .path, .flowchart-link { stroke-width: 1.5px !important; }", + "marker path { stroke: none !important; }", + + // class diagrams; a node with its own fill ("style X fill:#CCCCFF" on the exception pages) is the highlighted + // one and gets soft blue, which also works in dark mode + "g.classGroup rect, .classGroup line, g.node.default > g > path { stroke-width: 1.5px !important; }", + ".node path[style*='fill'], .node rect[style*='fill'] { fill: var(--nj-blue-soft) !important; }", + ".relation { stroke-width: 1.5px !important; }", + + // sequence diagrams + ".actor { stroke-width: 1.5px !important; rx: 6px; ry: 6px; }", + ".actor-line { stroke-width: 1px; }", + ".messageLine0, .messageLine1 { stroke-width: 1.5px !important; }", + ".note { rx: 6px; ry: 6px; }" + ].join("\n"); + + var options = { + theme: "base", + fontFamily: "Inter, sans-serif", + themeVariables: { fontFamily: "Inter, sans-serif", fontSize: "15px" }, + flowchart: { curve: "basis", padding: 12, nodeSpacing: 36, rankSpacing: 48, wrappingWidth: 240 } + }; + + var minScale = 0.8; + + function postProcess(svg) { + // shrink to fit the column, but not below minScale + svg = svg.replace(/^(]*style="[^"]*)max-width:\s*([\d.]+)px;/, function (all, head, width) { + return head + "max-width: " + width + "px; min-width: " + Math.round(width * minScale) + "px;"; + }); + // add class "nj-decision" to every flowchart node drawn as a hexagon (a polygon with six corners) + return svg.replace(/]*)> +

    // error 404

    +

    This page does not exist

    +

    The page may have moved or been renamed. Try the search in the header, or start from one of these pages.

    +

    + Home + Features + API documentation +

    +
    + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -153,6 +241,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -227,6 +403,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -301,6 +565,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -375,6 +727,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -449,6 +889,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -523,6 +1051,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -597,6 +1213,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -671,6 +1375,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -745,6 +1537,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -819,6 +1699,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -893,6 +1861,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -967,6 +2023,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1041,6 +2185,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1115,6 +2347,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1189,6 +2509,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1263,6 +2671,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1337,6 +2833,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1411,6 +2995,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1485,6 +3157,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1559,6 +3319,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1633,6 +3481,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1707,6 +3643,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1781,6 +3805,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1855,6 +3967,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -1929,6 +4129,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2003,6 +4291,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2077,6 +4453,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2151,6 +4615,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2225,6 +4777,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2299,6 +4939,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2373,6 +5101,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2447,6 +5263,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2521,6 +5425,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2595,6 +5587,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2669,6 +5749,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2743,6 +5911,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2817,6 +6073,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2891,6 +6235,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -2965,6 +6397,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3039,6 +6559,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3113,6 +6721,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3187,6 +6883,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3261,6 +7045,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3335,6 +7207,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3409,6 +7369,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3483,6 +7531,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3557,6 +7693,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3631,6 +7855,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3705,6 +8017,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3779,6 +8179,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3853,6 +8341,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -3927,6 +8503,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4001,6 +8665,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4075,6 +8827,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4149,6 +8989,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4223,6 +9151,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4297,6 +9313,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4371,6 +9475,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4445,6 +9637,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4519,6 +9799,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4593,6 +9961,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4667,6 +10123,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4741,6 +10285,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4815,6 +10447,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4889,6 +10609,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -4963,6 +10771,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5037,6 +10933,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5111,6 +11095,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5185,6 +11257,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5259,6 +11419,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5333,6 +11581,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5407,6 +11743,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5481,6 +11905,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5555,6 +12067,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5629,6 +12229,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5703,6 +12391,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5777,6 +12553,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5851,6 +12715,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5925,6 +12877,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -5999,6 +13039,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6073,6 +13201,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6147,6 +13363,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6221,6 +13525,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6295,6 +13687,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6369,6 +13849,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6443,6 +14011,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6517,6 +14173,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6591,6 +14335,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6665,6 +14497,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6739,6 +14659,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6813,6 +14821,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6887,6 +14983,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -6961,6 +15145,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7035,6 +15307,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7109,6 +15469,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7183,6 +15631,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7257,6 +15793,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7331,6 +15955,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7405,6 +16117,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7479,6 +16279,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7553,6 +16441,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7627,6 +16603,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7701,6 +16765,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7775,6 +16927,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7849,6 +17089,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7923,6 +17251,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -7997,6 +17413,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8071,6 +17575,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8145,6 +17737,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8219,6 +17899,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8293,6 +18061,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8367,6 +18223,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8441,6 +18385,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8515,6 +18547,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8589,6 +18709,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8663,6 +18871,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8737,6 +19033,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8811,6 +19195,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8885,6 +19357,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -8959,6 +19519,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9033,6 +19681,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9107,6 +19843,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9181,6 +20005,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9255,6 +20167,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9329,6 +20329,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9403,6 +20491,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9477,6 +20653,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9551,6 +20815,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9625,6 +20977,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9699,6 +21139,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9773,6 +21301,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9847,6 +21463,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9921,6 +21625,94 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + null @@ -9995,4 +21787,92 @@ + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + + + + + + + + obj ? 2 * n : n + + *p + p = p + 1 + *p + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + + + + + + + + obj ? 2 * n : n + + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_doc->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count + + + + + + + obj ? 2 * n : n + + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + p = p + 1 + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + m_data._Mypair._Myval2->base._Elems[p->flags & 3] + p->off,[p->len]s8 + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + + + diff --git a/tools/generate_natvis/README.md b/tools/generate_natvis/README.md index cc4fc4a8f..712024c57 100644 --- a/tools/generate_natvis/README.md +++ b/tools/generate_natvis/README.md @@ -6,6 +6,11 @@ The checked-in `nlohmann_json.natvis` covers the default namespace (no ABI tags) of ABI tags, with both versioned and unversioned namespace variants. Configurations that enable three or more ABI tags are not covered by this reduced file; use `--abi-tags` or `--all` (see below) to generate a file for them. +The visualizers of `basic_json_document` and `basic_json_view` live in the same inline namespaces as `basic_json`, so the +ABI tags apply to them in the same way, and the `view_mode` template argument is covered by a wildcard: it does not +multiply the namespaces. Each namespace gets three more types (`basic_json_view<*,*>`, `basic_json_document<*,*>`, and +`detail::view::node`), which roughly doubles the size of the file. + The ABI tag list and the library version are parsed from `include/nlohmann/detail/abi_macros.hpp`, so this script must be re-run (via `make natvis`) whenever an `NLOHMANN_JSON_ABI_TAG_*` macro is added to that @@ -35,7 +40,7 @@ version other than the one in `abi_macros.hpp`. |-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `--list-tags` | Print every ABI tag (in namespace order) with the macro that enables it and the `NLOHMANN_JSON_ABI_TAG_*` identifier, then exit. | | `--abi-tags TAGS` | Add the namespaces of one configuration, given as a comma-separated list of ABI tags (`_diag,_dp,_ns`; the leading underscore is optional, and the order does not matter). Can be repeated for several configurations. Unknown tags are an error. | -| `--all` | Generate the namespaces for every combination of ABI tags. The file size doubles with each tag (about 60 MB for 12 tags). Cannot be combined with `--abi-tags`. | +| `--all` | Generate the namespaces for every combination of ABI tags. The file size doubles with each tag (about 60 MB for 11 tags). Cannot be combined with `--abi-tags`. | | `--output FILE` | Write the Natvis file to `FILE` instead of `nlohmann_json.natvis` in the repository root. | Each configuration gets both the versioned (`json_abi_diag_dp_ns_v3_12_0`) and the unversioned (`json_abi_diag_dp_ns`, diff --git a/tools/generate_natvis/generate_natvis.py b/tools/generate_natvis/generate_natvis.py index 8452d98e0..5a7d1630b 100755 --- a/tools/generate_natvis/generate_natvis.py +++ b/tools/generate_natvis/generate_natvis.py @@ -95,7 +95,7 @@ if __name__ == '__main__': 'optional) of one configuration to add to the default namespaces; can be repeated') selection.add_argument('--all', action='store_true', help='Generate the namespaces for every combination of ABI tags; the file size doubles ' - 'with each ABI tag (about 60 MB for 12 tags)') + 'with each ABI tag (about 60 MB for 11 tags)') parser.add_argument('--list-tags', action='store_true', help='List the ABI tags with the macros that enable them, and exit') parser.add_argument('--output', metavar='FILE', diff --git a/tools/generate_natvis/nlohmann_json.natvis.j2 b/tools/generate_natvis/nlohmann_json.natvis.j2 index bc9e56fd1..e5e7fe03e 100644 --- a/tools/generate_natvis/nlohmann_json.natvis.j2 +++ b/tools/generate_natvis/nlohmann_json.natvis.j2 @@ -5,6 +5,39 @@ +{# The children of the container node `node` (a pointer expression) of the document `doc` (a document_data pointer + expression). A node is only an index into the source text: strings are read from `doc`, so the strings of the + direct children are readable here, but the children of a nested container are shown as nodes (see the node + visualizer below). Containers with edits (flag 8, "moved") are not expanded. The node kinds are the numbers of + node_kind: 1 object, 2 array, 3 string; the float token is shown only by the node visualizer. #} +{% macro children(node, doc=none) %} + + + + + + + obj ? 2 * n : n + +{% if doc %} + {{ doc }}->base._Elems[p->flags & 3] + p->off,[p->len]s8 +{% else %} + *p +{% endif %} + p = p + 1 +{% if doc %} + {{ doc }}->base._Elems[p->flags & 3] + p->off,[p->len]s8 +{% endif %} + *p +{% if doc %} + {{ doc }}->base._Elems[p->flags & 3] + p->off,[p->len]s8 +{% endif %} + *p + p = p + (((int)p->kind == 1 || (int)p->kind == 2) ? p->next : 1) + i = i + 1 + + +{%- endmacro %} {% for ns in namespaces %} @@ -62,5 +95,44 @@ {% endfor %} + + + + null + object, size={len} + array, size={len} + string, length={len} + boolean: {(flags & 4) != 0} + int64: {(__int64)((unsigned __int64)len | ((unsigned __int64)next << 32))} + uint64: {(unsigned __int64)len | ((unsigned __int64)next << 32)} + float, length={len} + link + +{{ children('this') }} + + + + + discarded + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8} + {m_doc->base._Elems[m_node->flags & 3] + m_node->off,[m_node->len]s8b} + {*m_node} + +{{ children('m_node', 'm_doc') }} + + + + + discarded + {*m_data._Mypair._Myval2->nodes} + + m_data._Mypair._Myval2->src,[m_data._Mypair._Myval2->size]s8b + m_data._Mypair._Myval2->node_count +{{ children('m_data._Mypair._Myval2->nodes', 'm_data._Mypair._Myval2') }} + + {% endfor %} From 76001c6b4cef42b431ee4f72d50a1b55cb260284 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:20:54 +0100 Subject: [PATCH 49/63] BSON reader: find C-string terminators 8 bytes at a time, skip array element names (#5968) Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 40 ++++++----- include/nlohmann/detail/input/string_scan.hpp | 28 ++++++++ single_include/nlohmann/json.hpp | 68 ++++++++++++++----- tests/src/unit-bson.cpp | 37 +++++++++- 4 files changed, 135 insertions(+), 38 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 9ed0e4d1b..b671fd84e 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -465,15 +465,17 @@ class binary_reader } const std::size_t element_type_parse_position = chars_read; + // an array's elements are named "0", "1", ... in the wire format, + // and those names are not passed on: they are skipped unless + // error_handler_t::strict must reject ill-formed UTF-8 in them + const bool is_object = container_stack.back().is_object; key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) + if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict))) { return false; } - // an array's elements are named "0", "1", ... in the wire format, - // and those names are not passed on - if (container_stack.back().is_object && !sax->key(key)) + if (is_object && !sax->key(key)) { return false; } @@ -489,14 +491,18 @@ class binary_reader @brief Parses a C-style string from the BSON input. @param[in,out] result A reference to the string variable where the read string is to be stored. + @param[in] store whether the characters are needed; if not (the names of + an array's elements), they are skipped and @a result may + be left unchanged, and they are not checked against + @ref error_handler @return `true` if the \\x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ - bool get_bson_cstr(string_t& result) + bool get_bson_cstr(string_t& result, const bool store = true) { - if (get_bson_cstr_bulk(result, std::integral_constant {})) + if (get_bson_cstr_bulk(result, store, std::integral_constant {})) { - return check_string_utf8(result, "key"); + return !store || check_string_utf8(result, "key"); } auto out = std::back_inserter(result); @@ -509,7 +515,7 @@ class binary_reader } if (current == 0x00) { - return check_string_utf8(result, "key"); + return !store || check_string_utf8(result, "key"); } *out++ = static_cast(current); } @@ -519,10 +525,11 @@ class binary_reader @brief read a C-style string from contiguous input in one step @param[in,out] result the string to append to + @param[in] store whether to append the characters to @a result @return whether the string was read; if the input has no \\x00-byte, nothing is read, and @ref get_bson_cstr reports the end of the input */ - bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/) + bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/) { const std::size_t remaining = ia.bulk_remaining(); if (remaining == 0) @@ -530,18 +537,15 @@ class binary_reader return false; } const auto* const data = reinterpret_cast(ia.bulk_data()); - // a plain loop rather than std::memchr: most keys are short (array - // indices are keys, too), and the call would cost more than it saves - std::size_t length = 0; - while (length < remaining && data[length] != 0x00) - { - ++length; - } + const std::size_t length = find_zero_byte(data, remaining); if (length == remaining) { return false; } - result.append(reinterpret_cast(data), length); + if (store) + { + result.append(reinterpret_cast(data), length); + } // consume the string and its \x00-byte, as the byte-wise path does ia.bulk_skip(length + 1); chars_read += length + 1; @@ -550,7 +554,7 @@ class binary_reader } /// input that is not contiguous: C-style strings are read byte by byte - bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept + bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept { return false; } diff --git a/include/nlohmann/detail/input/string_scan.hpp b/include/nlohmann/detail/input/string_scan.hpp index d01a2f1a4..daa9697e1 100644 --- a/include/nlohmann/detail/input/string_scan.hpp +++ b/include/nlohmann/detail/input/string_scan.hpp @@ -90,6 +90,34 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d return n; } +// return the index of the first 0x00 byte in [data, data+n), or n if there is +// none; scans 8 bytes at a time (BSON's C-style strings) +JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept +{ + constexpr std::uint64_t ones = 0x0101010101010101ull; + constexpr std::uint64_t high = 0x8080808080808080ull; + std::size_t i = 0; + for (; i + 8 <= n; i += 8) + { + const std::uint64_t v = read_eight_bytes(data + i); + const std::uint64_t zero = (v - ones) & ~v & high; + if (zero != 0) + { + // the lowest flagged byte is the first zero byte (see + // find_string_special()) + return i + (static_cast(count_trailing_zeros(zero)) / 8); + } + } + for (; i < n; ++i) + { + if (data[i] == 0x00) + { + return i; + } + } + return n; +} + // classify a byte as one the serializer must NOT copy verbatim when // ensure_ascii is requested: the closing quote, an escape, a control character // (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else - diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 83a7090cc..e2ead9c41 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -12458,6 +12458,34 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d return n; } +// return the index of the first 0x00 byte in [data, data+n), or n if there is +// none; scans 8 bytes at a time (BSON's C-style strings) +JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept +{ + constexpr std::uint64_t ones = 0x0101010101010101ull; + constexpr std::uint64_t high = 0x8080808080808080ull; + std::size_t i = 0; + for (; i + 8 <= n; i += 8) + { + const std::uint64_t v = read_eight_bytes(data + i); + const std::uint64_t zero = (v - ones) & ~v & high; + if (zero != 0) + { + // the lowest flagged byte is the first zero byte (see + // find_string_special()) + return i + (static_cast(count_trailing_zeros(zero)) / 8); + } + } + for (; i < n; ++i) + { + if (data[i] == 0x00) + { + return i; + } + } + return n; +} + // classify a byte as one the serializer must NOT copy verbatim when // ensure_ascii is requested: the closing quote, an escape, a control character // (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else - @@ -18241,15 +18269,17 @@ class binary_reader } const std::size_t element_type_parse_position = chars_read; + // an array's elements are named "0", "1", ... in the wire format, + // and those names are not passed on: they are skipped unless + // error_handler_t::strict must reject ill-formed UTF-8 in them + const bool is_object = container_stack.back().is_object; key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) + if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict))) { return false; } - // an array's elements are named "0", "1", ... in the wire format, - // and those names are not passed on - if (container_stack.back().is_object && !sax->key(key)) + if (is_object && !sax->key(key)) { return false; } @@ -18265,14 +18295,18 @@ class binary_reader @brief Parses a C-style string from the BSON input. @param[in,out] result A reference to the string variable where the read string is to be stored. + @param[in] store whether the characters are needed; if not (the names of + an array's elements), they are skipped and @a result may + be left unchanged, and they are not checked against + @ref error_handler @return `true` if the \\x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ - bool get_bson_cstr(string_t& result) + bool get_bson_cstr(string_t& result, const bool store = true) { - if (get_bson_cstr_bulk(result, std::integral_constant {})) + if (get_bson_cstr_bulk(result, store, std::integral_constant {})) { - return check_string_utf8(result, "key"); + return !store || check_string_utf8(result, "key"); } auto out = std::back_inserter(result); @@ -18285,7 +18319,7 @@ class binary_reader } if (current == 0x00) { - return check_string_utf8(result, "key"); + return !store || check_string_utf8(result, "key"); } *out++ = static_cast(current); } @@ -18295,10 +18329,11 @@ class binary_reader @brief read a C-style string from contiguous input in one step @param[in,out] result the string to append to + @param[in] store whether to append the characters to @a result @return whether the string was read; if the input has no \\x00-byte, nothing is read, and @ref get_bson_cstr reports the end of the input */ - bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/) + bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/) { const std::size_t remaining = ia.bulk_remaining(); if (remaining == 0) @@ -18306,18 +18341,15 @@ class binary_reader return false; } const auto* const data = reinterpret_cast(ia.bulk_data()); - // a plain loop rather than std::memchr: most keys are short (array - // indices are keys, too), and the call would cost more than it saves - std::size_t length = 0; - while (length < remaining && data[length] != 0x00) - { - ++length; - } + const std::size_t length = find_zero_byte(data, remaining); if (length == remaining) { return false; } - result.append(reinterpret_cast(data), length); + if (store) + { + result.append(reinterpret_cast(data), length); + } // consume the string and its \x00-byte, as the byte-wise path does ia.bulk_skip(length + 1); chars_read += length + 1; @@ -18326,7 +18358,7 @@ class binary_reader } /// input that is not contiguous: C-style strings are read byte by byte - bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept + bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept { return false; } diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index 8e2d00cbf..f93e0a71b 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1164,7 +1164,13 @@ TEST_CASE("BSON keys from contiguous and stream input") // contiguous input reads a key up to its \x00-byte in one step, a stream // reads it byte by byte; both must give the same value or error for the // complete document and for every truncation of it - const json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}}; + json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}}; + // keys and array element names ("0" to "11") that end at every position of + // an 8-byte word, which contiguous input scans at a time + for (std::size_t length = 1; length <= 17; ++length) + { + j[std::string(length, static_cast('a' + length))] = json::array({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, {11}}); + } const std::vector bson = json::to_bson(j); CHECK(json::from_bson(bson) == j); @@ -1184,7 +1190,7 @@ TEST_CASE("BSON keys from contiguous and stream input") } try { - std::istringstream stream(std::string(input.begin(), input.end())); + std::istringstream stream(std::string(reinterpret_cast(input.data()), input.size())); from_stream = json::from_bson(stream).dump(); } catch (const json::parse_error& e) @@ -1194,6 +1200,33 @@ TEST_CASE("BSON keys from contiguous and stream input") CHECK(from_vector == from_stream); } } + +TEST_CASE("BSON array element names") +{ + // an array's element names are not part of the value, so they are not + // passed on; error_handler_t::strict still rejects ill-formed UTF-8 in them + const std::vector bson = + { + 0x1D, 0x00, 0x00, 0x00, // size (little endian) + 0x04, 'a', 0x00, // entry: array "a" + 0x15, 0x00, 0x00, 0x00, // size (little endian) + 0x08, 0xFF, 0x00, 0x01, // entry: boolean named "\xFF", true + 0x08, '1', '2', '3', '4', '5', '6', '7', '8', '9', 0x00, 0x00, // entry: boolean named "123456789", false + 0x00, // end of the array + 0x00 // end of the document + }; + const json expected = {{"a", {true, false}}}; + + CHECK(json::from_bson(bson) == expected); + CHECK(json::from_bson(bson, true, true, json::error_handler_t::replace) == expected); + CHECK(json::from_bson(bson, true, true, json::error_handler_t::ignore) == expected); + + json _; + CHECK_THROWS_WITH_AS(_ = json::from_bson(bson, true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&); + + std::istringstream stream(std::string(reinterpret_cast(bson.data()), bson.size())); + CHECK(json::from_bson(stream) == expected); +} #endif TEST_CASE("Negative size of binary value") From 1a018577cccde18ae47a43195fadccce011257ff Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:21:06 +0100 Subject: [PATCH 50/63] json_view: accept() checks the text without writing an index (#5970) Signed-off-by: Niels Lohmann --- .../docs/api/basic_json_document/accept.md | 12 +- include/nlohmann/detail/view/builder.hpp | 138 ++++++++++-- include/nlohmann/detail/view/macro_scope.hpp | 9 + .../nlohmann/detail/view/macro_unscope.hpp | 1 + include/nlohmann/detail/view/simd.hpp | 7 +- include/nlohmann/json_view.hpp | 56 +++-- single_include/nlohmann/json_view.hpp | 211 ++++++++++++++---- tests/src/unit-json_view_builder.cpp | 147 +++++++++++- tests/src/unit-json_view_macros.cpp | 2 +- 9 files changed, 500 insertions(+), 83 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_document/accept.md b/docs/mkdocs/docs/api/basic_json_document/accept.md index 1f1070d51..766fd20fa 100644 --- a/docs/mkdocs/docs/api/basic_json_document/accept.md +++ b/docs/mkdocs/docs/api/basic_json_document/accept.md @@ -13,9 +13,15 @@ function never throws an exception for invalid input, and the returned `#!cpp bo is returned. The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This -function runs the document's index builder (and discards the index), while `BasicJsonType::accept()` runs the SAX -parser with a handler that ignores every event. Use this one if you go on to parse the same input with -[`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain yes/no answer. +function runs the document's builder, which checks the text as [`parse()`](parse.md) does, while +`BasicJsonType::accept()` runs the SAX parser with a handler that ignores every event. Use this one if you go on to +parse the same input with [`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain +yes/no answer. + +When both `ignore_comments` and `ignore_trailing_commas` are `#!cpp false` (the default), the builder writes no index. +With either option set, it builds the index as [`parse()`](parse.md) does and discards it: the result is the same, only +the time differs. (Neither option needs the index; the variant that skips it exists only for the default options to +keep the code size down.) ## Template parameters diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index daf0d59bb..4b729ce39 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -69,7 +69,8 @@ struct parse_failure }; /// FloatType: the number_float_t of the document, whose overflow parse() rejects -template +/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written +template class builder { public: @@ -245,11 +246,87 @@ class builder return p; } + /// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not + /// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of + /// slow_string(), for everything up to the closing quote + NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p) + { + const unsigned char* const end = m_end; + for (;;) + { + if (p == end) + { + return failed(error_code::string_missing_quote, p); + } + const unsigned char c = *p; + if (c == '"') + { + ++p; + return decoded{p, 0, 0}; + } + if (c != '\\') + { + return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p); + } + ++p; + if (p == end) + { + return failed(error_code::string_missing_quote, p); + } + const unsigned char d = *p++; + if (d == 'u') + { + const int cp = hex4(p); // (moves p past the four digits) + if (NLOHMANN_VIEW_UNLIKELY(cp < 0)) + { + return failed(error_code::string_unicode_hex, p); + } + if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate + { + if (cp >= 0xDC00) + { + return failed(error_code::string_surrogate_low, p); + } + if (end - p < 2 || p[0] != '\\' || p[1] != 'u') + { + return failed(error_code::string_surrogate_high, p); + } + p += 2; + const int lo = hex4(p); // (moves p past the four digits) + if (lo < 0) + { + return failed(error_code::string_unicode_hex, p); + } + if (lo < 0xDC00 || lo > 0xDFFF) + { + return failed(error_code::string_surrogate_high, p); + } + } + } + else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't')) + { + --p; + return failed(error_code::string_escape, p); + } + if (p != end && *p == '\\') + { + continue; // (escapes in a row: no run of text between them) + } + p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8 + } + } + /// s points to the start of the string, p to a backslash, a control /// character, an ill-formed UTF-8 byte, or the end: decode the string into /// the arena NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p) { + if (!enabled(Store)) + { + // (accept(): the string is checked, not decoded) + static_cast(s); + return check_string(p); + } // single-character escapes; 0: invalid (and 'u', handled separately) static const std::array simple_escape = { @@ -625,10 +702,13 @@ run_found: NLOHMANN_VIEW_ALWAYS_INLINE bool run() { - cold.m_doc.reserve(estimate_nodes(reinterpret_cast(b), static_cast(e - b))); - base = cold.m_doc.nodes; - out = base; - cap = base + cold.m_doc.node_capacity; + if (enabled(Store)) + { + cold.m_doc.reserve(estimate_nodes(reinterpret_cast(b), static_cast(e - b))); + base = cold.m_doc.nodes; + out = base; + cap = base + cold.m_doc.node_capacity; + } if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF) { @@ -812,7 +892,10 @@ close_object: // an array pays nothing for this) if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members)) { - cold.note_large_object(cur_idx); + if (enabled(Store)) + { + cold.note_large_object(cur_idx); + } } close_container: @@ -972,6 +1055,12 @@ indent_done: /// an integer's value); two stores on little-endian targets NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) { + if (!enabled(Store)) + { + // accept(): nothing is stored, so what computes the arguments is not run either. base and out stay + // null: their difference, which open() takes, is 0. + return out; + } if (NLOHMANN_VIEW_UNLIKELY(out == cap)) { const auto n = static_cast(out - base); @@ -1030,9 +1119,12 @@ indent_done: NLOHMANN_VIEW_ALWAYS_INLINE void close() { - node& n = base[cur_idx]; - n.len = cur_count; - n.next = static_cast(out - base) - cur_idx; + if (enabled(Store)) + { + node& n = base[cur_idx]; + n.len = cur_count; + n.next = static_cast(out - base) - cur_idx; + } if (--depth != 0) { if (NLOHMANN_VIEW_LIKELY(depth <= 64)) @@ -1207,34 +1299,44 @@ indent_done: }; /// run the builder with compile-time options -template +template inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure) { if (sentinel) { - builder bld(d, src, size); + builder bld(d, src, size); const bool ok = bld.run(); failure = bld.failure(); return ok; } - builder bld(d, src, size); + builder bld(d, src, size); const bool ok = bld.run(); failure = bld.failure(); return ok; } /// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the -/// number_float_t of the document +/// number_float_t of the document; validate_only: accept(), only the result is +/// used template -inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure) +inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only, + parse_failure& failure) { + if (validate_only && !comments && !trailing_commas) + { + // accept() of plain JSON: nothing is stored. The other option sets + // could skip the stores just as well; they keep the builder that + // stores only so that there are two more instantiations of the parse + // loop, not eight. + return build_with(d, src, size, sentinel, failure); + } if (comments) { - return trailing_commas ? build_with(d, src, size, sentinel, failure) - : build_with(d, src, size, sentinel, failure); + return trailing_commas ? build_with(d, src, size, sentinel, failure) + : build_with(d, src, size, sentinel, failure); } - return trailing_commas ? build_with(d, src, size, sentinel, failure) - : build_with(d, src, size, sentinel, failure); + return trailing_commas ? build_with(d, src, size, sentinel, failure) + : build_with(d, src, size, sentinel, failure); } } // namespace view diff --git a/include/nlohmann/detail/view/macro_scope.hpp b/include/nlohmann/detail/view/macro_scope.hpp index bbe1a240b..f19492648 100644 --- a/include/nlohmann/detail/view/macro_scope.hpp +++ b/include/nlohmann/detail/view/macro_scope.hpp @@ -40,6 +40,15 @@ #define NLOHMANN_VIEW_NOINLINE #endif +// GCC: a function compiled as written, without the clones in which interprocedural +// constant propagation drops an argument that every caller passes as the same constant +// (calling such a clone changes the code GCC generates for the parse loop that calls it) +#if defined(__GNUC__) && !defined(__clang__) + #define NLOHMANN_VIEW_NOCLONE __attribute__((noclone)) +#else + #define NLOHMANN_VIEW_NOCLONE +#endif + #if defined(__GNUC__) || defined(__clang__) #define NLOHMANN_VIEW_NODISCARD __attribute__((warn_unused_result)) #elif defined(_MSC_VER) diff --git a/include/nlohmann/detail/view/macro_unscope.hpp b/include/nlohmann/detail/view/macro_unscope.hpp index a9aec867b..0c4fab4d2 100644 --- a/include/nlohmann/detail/view/macro_unscope.hpp +++ b/include/nlohmann/detail/view/macro_unscope.hpp @@ -15,6 +15,7 @@ #undef NLOHMANN_VIEW_UNLIKELY #undef NLOHMANN_VIEW_ALWAYS_INLINE #undef NLOHMANN_VIEW_NOINLINE +#undef NLOHMANN_VIEW_NOCLONE #undef NLOHMANN_VIEW_NODISCARD #undef NLOHMANN_VIEW_THROW #undef NLOHMANN_VIEW_LITTLE_ENDIAN diff --git a/include/nlohmann/detail/view/simd.hpp b/include/nlohmann/detail/view/simd.hpp index 9dede9b09..f4745f6b8 100644 --- a/include/nlohmann/detail/view/simd.hpp +++ b/include/nlohmann/detail/view/simd.hpp @@ -289,9 +289,12 @@ compares, and the UTF-8 check covers the bytes up to it. Returns where the string scan stops, like scan_string_run: before ill-formed UTF-8 and for the last bytes of the input, the bytes are checked one sequence at a time. Out of line, so that no constants of the check occupy registers in the parse loop. -On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()). +On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()). Not cloned: with +the calls of accept()'s builder, GCC 16 would clone it without plain (always +string_plain()), and calling the clone made json_document::parse() run 0.7-1.2 % +more instructions. */ -NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept +NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE NLOHMANN_VIEW_NOCLONE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept { using lookup = utf8_lookup4<>; const unsigned char* block = p; diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 9d26d12ea..097e489a4 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -1060,7 +1060,7 @@ class basic_json_document const bool ignore_trailing_commas = false) { basic_json_document d; - d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas); + d.read_range(first, last, error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); return d; } @@ -1075,7 +1075,7 @@ class basic_json_document basic_json_document d; std::string buf; append_input(buf, std::forward(input)); - d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas); + d.build_owned(std::move(buf), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); return d; } @@ -1088,7 +1088,8 @@ class basic_json_document static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) { basic_json_document d; - d.read(std::forward(input), false, ignore_comments, ignore_trailing_commas); + d.read_kind(std::forward(input), on_error::validate, ignore_comments, ignore_trailing_commas, + std::integral_constant::value> {}); return !d.is_discarded(); } @@ -1104,7 +1105,7 @@ class basic_json_document const bool ignore_comments = false, const bool ignore_trailing_commas = false) { - read_kind(std::forward(input), allow_exceptions, ignore_comments, ignore_trailing_commas, + read_kind(std::forward(input), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas, std::integral_constant::value> {}); } @@ -1463,6 +1464,22 @@ class basic_json_document return idx; } + /// what a failed parse does + enum class on_error : std::uint8_t + { + discard, ///< the document is discarded + raise, ///< parse_error is thrown + validate ///< accept(): the document is discarded, and no node is stored while parsing + }; + + static on_error error_mode(bool allow_exceptions) noexcept + { + return allow_exceptions ? on_error::raise : on_error::discard; + } + + /// NulIsEnd of the builder: a null byte ends the input unless strict_nul_handling + static constexpr bool nul_is_end = !detail::abi_config::strict_nul_handling; + /// create the storage (sized for the input) on first use void ensure_data(const char* src, std::size_t size) { @@ -1474,7 +1491,7 @@ class basic_json_document /// parse a buffer the document takes ownership of (it replaces the /// previous owned copy) - void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas) + void build_owned(std::string&& buf, on_error allow_exceptions, bool comments, bool trailing_commas) { ensure_data(buf.data(), buf.size()); m_data->owned = std::move(buf); @@ -1484,7 +1501,7 @@ class basic_json_document /// parse a copy of the input; the copy is appended to the cleared owned /// buffer, so that its capacity is reused template - void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas) + void build_copy(T&& input, on_error allow_exceptions, bool comments, bool trailing_commas) { if (!m_data) { @@ -1505,7 +1522,7 @@ class basic_json_document } /// sentinel: src[size] is readable and 0 (std::string, C strings) - void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel) + void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel) { ensure_data(src, size); document_data& d = *m_data; @@ -1532,7 +1549,8 @@ class basic_json_document } else { - ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure); + ok = detail::view::build(d, src, size, comments, trailing_commas, sentinel, + allow_exceptions == on_error::validate, failure); } if (NLOHMANN_VIEW_LIKELY(ok)) { @@ -1545,7 +1563,7 @@ class basic_json_document return; } std::vector().swap(d.large_objects); - if (allow_exceptions) + if (allow_exceptions == on_error::raise) { detail::view::throw_parse_failure(failure, src, size, comments, trailing_commas); } @@ -1553,13 +1571,13 @@ class basic_json_document // --- input dispatch (see detail::view::input_kind) --- - void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(std::string&& s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { build_owned(std::move(s), ae, c, tc); } template - void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { if (s == nullptr) { @@ -1573,7 +1591,7 @@ class basic_json_document } template - void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(Array& a, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { using CharT = typename std::remove_cv::type>::type; const std::size_t n = std::extent::value; @@ -1584,7 +1602,7 @@ class basic_json_document } template - void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { // std::basic_string guarantees data()[size()] == 0: use it as sentinel const auto size = static_cast(s.size()); @@ -1593,13 +1611,13 @@ class basic_json_document } template - void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { build_copy(s, ae, c, tc); } template - void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { if (s == nullptr) { @@ -1611,13 +1629,13 @@ class basic_json_document } template - void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { build_copy(std::forward(input), ae, c, tc); } template - void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc) + void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc) { using value_type = typename std::remove_cv::value_type>::type; // borrow the range where the library's input adapter scans it as one @@ -1628,14 +1646,14 @@ class basic_json_document } template - void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/) + void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::true_type /*contiguous bytes*/) { const auto size = static_cast(std::distance(first, last)); build(size == 0 ? "" : reinterpret_cast(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } template - void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/) + void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::false_type /*other*/) { build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc); } diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 7e20544ba..f446b273d 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -157,6 +157,15 @@ #define NLOHMANN_VIEW_NOINLINE #endif +// GCC: a function compiled as written, without the clones in which interprocedural +// constant propagation drops an argument that every caller passes as the same constant +// (calling such a clone changes the code GCC generates for the parse loop that calls it) +#if defined(__GNUC__) && !defined(__clang__) + #define NLOHMANN_VIEW_NOCLONE __attribute__((noclone)) +#else + #define NLOHMANN_VIEW_NOCLONE +#endif + #if defined(__GNUC__) || defined(__clang__) #define NLOHMANN_VIEW_NODISCARD __attribute__((warn_unused_result)) #elif defined(_MSC_VER) @@ -948,9 +957,12 @@ compares, and the UTF-8 check covers the bytes up to it. Returns where the string scan stops, like scan_string_run: before ill-formed UTF-8 and for the last bytes of the input, the bytes are checked one sequence at a time. Out of line, so that no constants of the check occupy registers in the parse loop. -On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()). +On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()). Not cloned: with +the calls of accept()'s builder, GCC 16 would clone it without plain (always +string_plain()), and calling the clone made json_document::parse() run 0.7-1.2 % +more instructions. */ -NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept +NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE NLOHMANN_VIEW_NOCLONE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept { using lookup = utf8_lookup4<>; const unsigned char* block = p; @@ -1311,7 +1323,8 @@ struct parse_failure }; /// FloatType: the number_float_t of the document, whose overflow parse() rejects -template +/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written +template class builder { public: @@ -1487,11 +1500,87 @@ class builder return p; } + /// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not + /// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of + /// slow_string(), for everything up to the closing quote + NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p) + { + const unsigned char* const end = m_end; + for (;;) + { + if (p == end) + { + return failed(error_code::string_missing_quote, p); + } + const unsigned char c = *p; + if (c == '"') + { + ++p; + return decoded{p, 0, 0}; + } + if (c != '\\') + { + return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p); + } + ++p; + if (p == end) + { + return failed(error_code::string_missing_quote, p); + } + const unsigned char d = *p++; + if (d == 'u') + { + const int cp = hex4(p); // (moves p past the four digits) + if (NLOHMANN_VIEW_UNLIKELY(cp < 0)) + { + return failed(error_code::string_unicode_hex, p); + } + if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate + { + if (cp >= 0xDC00) + { + return failed(error_code::string_surrogate_low, p); + } + if (end - p < 2 || p[0] != '\\' || p[1] != 'u') + { + return failed(error_code::string_surrogate_high, p); + } + p += 2; + const int lo = hex4(p); // (moves p past the four digits) + if (lo < 0) + { + return failed(error_code::string_unicode_hex, p); + } + if (lo < 0xDC00 || lo > 0xDFFF) + { + return failed(error_code::string_surrogate_high, p); + } + } + } + else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't')) + { + --p; + return failed(error_code::string_escape, p); + } + if (p != end && *p == '\\') + { + continue; // (escapes in a row: no run of text between them) + } + p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8 + } + } + /// s points to the start of the string, p to a backslash, a control /// character, an ill-formed UTF-8 byte, or the end: decode the string into /// the arena NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p) { + if (!enabled(Store)) + { + // (accept(): the string is checked, not decoded) + static_cast(s); + return check_string(p); + } // single-character escapes; 0: invalid (and 'u', handled separately) static const std::array simple_escape = { @@ -1867,10 +1956,13 @@ run_found: NLOHMANN_VIEW_ALWAYS_INLINE bool run() { - cold.m_doc.reserve(estimate_nodes(reinterpret_cast(b), static_cast(e - b))); - base = cold.m_doc.nodes; - out = base; - cap = base + cold.m_doc.node_capacity; + if (enabled(Store)) + { + cold.m_doc.reserve(estimate_nodes(reinterpret_cast(b), static_cast(e - b))); + base = cold.m_doc.nodes; + out = base; + cap = base + cold.m_doc.node_capacity; + } if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF) { @@ -2054,7 +2146,10 @@ close_object: // an array pays nothing for this) if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members)) { - cold.note_large_object(cur_idx); + if (enabled(Store)) + { + cold.note_large_object(cur_idx); + } } close_container: @@ -2214,6 +2309,12 @@ indent_done: /// an integer's value); two stores on little-endian targets NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) { + if (!enabled(Store)) + { + // accept(): nothing is stored, so what computes the arguments is not run either. base and out stay + // null: their difference, which open() takes, is 0. + return out; + } if (NLOHMANN_VIEW_UNLIKELY(out == cap)) { const auto n = static_cast(out - base); @@ -2272,9 +2373,12 @@ indent_done: NLOHMANN_VIEW_ALWAYS_INLINE void close() { - node& n = base[cur_idx]; - n.len = cur_count; - n.next = static_cast(out - base) - cur_idx; + if (enabled(Store)) + { + node& n = base[cur_idx]; + n.len = cur_count; + n.next = static_cast(out - base) - cur_idx; + } if (--depth != 0) { if (NLOHMANN_VIEW_LIKELY(depth <= 64)) @@ -2449,34 +2553,44 @@ indent_done: }; /// run the builder with compile-time options -template +template inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure) { if (sentinel) { - builder bld(d, src, size); + builder bld(d, src, size); const bool ok = bld.run(); failure = bld.failure(); return ok; } - builder bld(d, src, size); + builder bld(d, src, size); const bool ok = bld.run(); failure = bld.failure(); return ok; } /// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the -/// number_float_t of the document +/// number_float_t of the document; validate_only: accept(), only the result is +/// used template -inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure) +inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only, + parse_failure& failure) { + if (validate_only && !comments && !trailing_commas) + { + // accept() of plain JSON: nothing is stored. The other option sets + // could skip the stores just as well; they keep the builder that + // stores only so that there are two more instantiations of the parse + // loop, not eight. + return build_with(d, src, size, sentinel, failure); + } if (comments) { - return trailing_commas ? build_with(d, src, size, sentinel, failure) - : build_with(d, src, size, sentinel, failure); + return trailing_commas ? build_with(d, src, size, sentinel, failure) + : build_with(d, src, size, sentinel, failure); } - return trailing_commas ? build_with(d, src, size, sentinel, failure) - : build_with(d, src, size, sentinel, failure); + return trailing_commas ? build_with(d, src, size, sentinel, failure) + : build_with(d, src, size, sentinel, failure); } } // namespace view @@ -10674,7 +10788,7 @@ class basic_json_document const bool ignore_trailing_commas = false) { basic_json_document d; - d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas); + d.read_range(first, last, error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); return d; } @@ -10689,7 +10803,7 @@ class basic_json_document basic_json_document d; std::string buf; append_input(buf, std::forward(input)); - d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas); + d.build_owned(std::move(buf), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas); return d; } @@ -10702,7 +10816,8 @@ class basic_json_document static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) { basic_json_document d; - d.read(std::forward(input), false, ignore_comments, ignore_trailing_commas); + d.read_kind(std::forward(input), on_error::validate, ignore_comments, ignore_trailing_commas, + std::integral_constant::value> {}); return !d.is_discarded(); } @@ -10718,7 +10833,7 @@ class basic_json_document const bool ignore_comments = false, const bool ignore_trailing_commas = false) { - read_kind(std::forward(input), allow_exceptions, ignore_comments, ignore_trailing_commas, + read_kind(std::forward(input), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas, std::integral_constant::value> {}); } @@ -11077,6 +11192,22 @@ class basic_json_document return idx; } + /// what a failed parse does + enum class on_error : std::uint8_t + { + discard, ///< the document is discarded + raise, ///< parse_error is thrown + validate ///< accept(): the document is discarded, and no node is stored while parsing + }; + + static on_error error_mode(bool allow_exceptions) noexcept + { + return allow_exceptions ? on_error::raise : on_error::discard; + } + + /// NulIsEnd of the builder: a null byte ends the input unless strict_nul_handling + static constexpr bool nul_is_end = !detail::abi_config::strict_nul_handling; + /// create the storage (sized for the input) on first use void ensure_data(const char* src, std::size_t size) { @@ -11088,7 +11219,7 @@ class basic_json_document /// parse a buffer the document takes ownership of (it replaces the /// previous owned copy) - void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas) + void build_owned(std::string&& buf, on_error allow_exceptions, bool comments, bool trailing_commas) { ensure_data(buf.data(), buf.size()); m_data->owned = std::move(buf); @@ -11098,7 +11229,7 @@ class basic_json_document /// parse a copy of the input; the copy is appended to the cleared owned /// buffer, so that its capacity is reused template - void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas) + void build_copy(T&& input, on_error allow_exceptions, bool comments, bool trailing_commas) { if (!m_data) { @@ -11119,7 +11250,7 @@ class basic_json_document } /// sentinel: src[size] is readable and 0 (std::string, C strings) - void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel) + void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel) { ensure_data(src, size); document_data& d = *m_data; @@ -11146,7 +11277,8 @@ class basic_json_document } else { - ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure); + ok = detail::view::build(d, src, size, comments, trailing_commas, sentinel, + allow_exceptions == on_error::validate, failure); } if (NLOHMANN_VIEW_LIKELY(ok)) { @@ -11159,7 +11291,7 @@ class basic_json_document return; } std::vector().swap(d.large_objects); - if (allow_exceptions) + if (allow_exceptions == on_error::raise) { detail::view::throw_parse_failure(failure, src, size, comments, trailing_commas); } @@ -11167,13 +11299,13 @@ class basic_json_document // --- input dispatch (see detail::view::input_kind) --- - void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(std::string&& s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { build_owned(std::move(s), ae, c, tc); } template - void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { if (s == nullptr) { @@ -11187,7 +11319,7 @@ class basic_json_document } template - void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(Array& a, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { using CharT = typename std::remove_cv::type>::type; const std::size_t n = std::extent::value; @@ -11198,7 +11330,7 @@ class basic_json_document } template - void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { // std::basic_string guarantees data()[size()] == 0: use it as sentinel const auto size = static_cast(s.size()); @@ -11207,13 +11339,13 @@ class basic_json_document } template - void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { build_copy(s, ae, c, tc); } template - void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { if (s == nullptr) { @@ -11225,13 +11357,13 @@ class basic_json_document } template - void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant /*unused*/) + void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant /*unused*/) { build_copy(std::forward(input), ae, c, tc); } template - void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc) + void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc) { using value_type = typename std::remove_cv::value_type>::type; // borrow the range where the library's input adapter scans it as one @@ -11242,14 +11374,14 @@ class basic_json_document } template - void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/) + void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::true_type /*contiguous bytes*/) { const auto size = static_cast(std::distance(first, last)); build(size == 0 ? "" : reinterpret_cast(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) } template - void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/) + void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::false_type /*other*/) { build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc); } @@ -11380,6 +11512,7 @@ class tuple_element> // NOLINT(cert #undef NLOHMANN_VIEW_UNLIKELY #undef NLOHMANN_VIEW_ALWAYS_INLINE #undef NLOHMANN_VIEW_NOINLINE +#undef NLOHMANN_VIEW_NOCLONE #undef NLOHMANN_VIEW_NODISCARD #undef NLOHMANN_VIEW_THROW #undef NLOHMANN_VIEW_LITTLE_ENDIAN diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 7ac7a7d40..418f219da 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -13,6 +13,7 @@ #include #include #include + #include // json_document::accept #else #include // the single header contains the internal headers #endif @@ -59,7 +60,8 @@ built build(const std::string& text, bool comments, bool trailing_commas, bool s r.copy.assign(text.begin(), text.end()); src = r.copy.data(); } - r.ok = nlohmann::detail::view::build < FloatType, !nlohmann::detail::abi_config::strict_nul_handling > (*r.data, src, text.size(), comments, trailing_commas, sentinel, r.failure); + constexpr bool nul_is_end = !nlohmann::detail::abi_config::strict_nul_handling; + r.ok = nlohmann::detail::view::build(*r.data, src, text.size(), comments, trailing_commas, sentinel, false, r.failure); r.data->src = src; r.data->base[0] = src; r.data->base[1] = r.data->arena.data(); @@ -172,6 +174,22 @@ void check_string(const std::string& content) } } +// accept() stores no nodes: it must accept and reject what json::accept does, for all options and for inputs with +// and without a terminating NUL +void check_accept(const std::string& text) +{ + CAPTURE(text) + const std::vector bytes(text.begin(), text.end()); + for (int options = 0; options < 4; ++options) + { + const bool comments = (options & 1) != 0; + const bool trailing_commas = (options & 2) != 0; + const bool accepted = json::accept(text, comments, trailing_commas); + CHECK(nlohmann::json_document::accept(text, comments, trailing_commas) == accepted); + CHECK(nlohmann::json_document::accept(bytes, comments, trailing_commas) == accepted); + } +} + // a small deterministic generator of documents struct generator { @@ -423,6 +441,133 @@ TEST_CASE("json_view builder") } } + SECTION("accept() without an index") + { + for (const char* text : + { + "", " ", "\n", "null", "nul", "true", "[", "]", "[]", "{}", "[1,2,3]", "[1,2,3", "[1,2,3,]", "{\"a\":1,}", "{\"a\"}", "{\"a\":}", + "[1] x", "[1][2]", "\xEF\xBB\xBF[1]", "[\"a\\", R"(["\u12"])", R"(["\ud800"])", "[\"\x01\"]", "[\"\xff\"]", "[01]", "[1.]", + "[.5]", "[-]", "[1e]", "// c\n[1]", "/* c */ {\"a\" /* x */ : 1}", "/* c", "[1] // c", "1e400", "-1e400", "123456789012345678901234567890", + R"({"a":1,"a":2})", "[[[]]]", R"("\u00e4\n\ud83d\ude00")" + }) + { + check_accept(text); + } + // a null byte inside the text, and after it + check_accept(std::string("[1,\0]", 5)); + check_accept(std::string("[1]\0", 4)); + check_accept(std::string("\0", 1)); + + // generated documents and damaged copies, as for the builder above + generator g; + for (int i = 0; i < 2000; ++i) + { + std::string text; + g.value(text, 0); + check_accept(text); + std::string damaged = text; + const auto at = static_cast(g.r(static_cast(damaged.size()))); + static const char replacements[] = {'x', '"', '\\', ',', ':', ']', '}', '[', '{', '1', '-', '.', 'e', '\0', '\n', '/'}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + damaged[at] = replacements[g.r(16)]; + check_accept(damaged); + check_accept(text.substr(0, at)); + } + + // nesting beyond the 64 levels that are kept on the builder's own stack, closed and not closed + for (const std::size_t depth : + { + 63u, 64u, 65u, 66u, 200u, 1000u + }) + { + check_accept(std::string(depth, '[') + std::string(depth, ']')); + check_accept(std::string(depth, '[') + std::string(depth - 1, ']')); + std::string objects; + for (std::size_t i = 0; i < depth; ++i) + { + objects += "{\"k\":"; + } + objects += "1" + std::string(depth, '}'); + check_accept(objects); + check_accept(objects.substr(0, objects.size() - 1)); + } + + // objects with enough members to get a hash index when they are indexed (and duplicate keys) + std::string large = "{"; + for (int i = 0; i < 300; ++i) + { + large += (i == 0 ? "\"k" : ",\"k") + std::to_string(i % 200) + "\":" + std::to_string(i); + } + check_accept(large + "}"); + check_accept(large); + check_accept("[" + large + "},{" + large.substr(1) + "}]"); + + // the overflow check uses the floating-point type of the document + using float_json = nlohmann::basic_json; + for (const char* text : + { + "1e39", "-1e39", "3.4028235e38", "3.4028236e38", "[1.5e38, 3.5e38]", R"({"a": 1e-50, "b": 1e39})", "340282356779733661637539395458142568448" + }) + { + CAPTURE(text) + CHECK(nlohmann::basic_json_document::accept(text) == float_json::accept(text)); + } + + // strings are checked and not decoded: runs of text of every length from 0 to 40 between valid and invalid + // escapes, UTF-8 sequences and control characters, in values and in keys, cut near the end of the text + const std::array valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}}; + const std::array invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\ud83d\\ud83d", "\\", "\x01", "\x1f", "\x7f"}}; + const std::array utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}}; + std::mt19937 rng(2029); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible + for (std::size_t run = 0; run <= 40; ++run) + { + for (int i = 0; i < 30; ++i) + { + std::string content = std::string(rng() % 20, 'a'); + const auto pieces = 1 + (rng() % 6); + for (unsigned k = 0; k < pieces; ++k) + { + content += valid[rng() % valid.size()]; + content += std::string(k == 0 ? run : rng() % 41, static_cast('b' + (rng() % 20))); + switch (rng() % 8) + { + case 0: + content += utf8[rng() % 3]; + break; + case 1: + content += utf8[rng() % utf8.size()]; + break; + case 2: + content += invalid[rng() % invalid.size()]; + break; + default: + break; + } + } + for (const std::string& text : + { + "[\"" + content + "\"]", "{\"" + content + "\":1}", "[\"" + content, "[\"" + content.substr(0, content.size() - (rng() % 4)) + "\"]" + }) + { + check_accept(text); + } + } + } + } + + SECTION("escapes in a row") + { + for (const char* content : + { + "\\n", R"(\n\t\\)", R"(\u00e9\u00e9\u4e2d)", "\\u00e9\\u00e", "\\u00e9\\", "\\n\\q", R"(\n\ud83d\ude00\n)", + R"(\ud83d\ude00\ud83d)", R"(\n\n\n abc \n\n)", "\\n\x01", "\\n\xC3\x28", "\\n\\" + }) + { + check_accept(std::string("[\"") + content + "\"]"); + check_accept(std::string("[\"") + content); + check_accept(std::string("{\"") + content + "\":1}"); + } + } + SECTION("test files") { for (const char* name : diff --git a/tests/src/unit-json_view_macros.cpp b/tests/src/unit-json_view_macros.cpp index 2cc8b7744..d2c9e197f 100644 --- a/tests/src/unit-json_view_macros.cpp +++ b/tests/src/unit-json_view_macros.cpp @@ -25,7 +25,7 @@ #endif // the view's own macros do not leak -#if defined(NLOHMANN_VIEW_LIKELY) || defined(NLOHMANN_VIEW_UNLIKELY) || defined(NLOHMANN_VIEW_ALWAYS_INLINE) || defined(NLOHMANN_VIEW_NOINLINE) \ +#if defined(NLOHMANN_VIEW_LIKELY) || defined(NLOHMANN_VIEW_UNLIKELY) || defined(NLOHMANN_VIEW_ALWAYS_INLINE) || defined(NLOHMANN_VIEW_NOINLINE) || defined(NLOHMANN_VIEW_NOCLONE) \ || defined(NLOHMANN_VIEW_NODISCARD) || defined(NLOHMANN_VIEW_THROW) || defined(NLOHMANN_VIEW_HAS_CPP_17) || defined(NLOHMANN_VIEW_LITTLE_ENDIAN) \ || defined(NLOHMANN_VIEW_REPEAT16) #error "json_view.hpp leaks a macro" From 162eda2574bb46a6d31c92a0037d9939f1009f84 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:21:19 +0100 Subject: [PATCH 51/63] json_view: skip the digits of a fraction 16 bytes at a time (SSE2) (#5971) Signed-off-by: Niels Lohmann --- include/nlohmann/detail/view/builder.hpp | 2 +- include/nlohmann/detail/view/scan.hpp | 51 +++++++++++++++++++++++ single_include/nlohmann/json_view.hpp | 53 +++++++++++++++++++++++- tests/src/unit-json_view_builder.cpp | 48 +++++++++++++++++++++ 4 files changed, 152 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 4b729ce39..938d6d73b 100644 --- a/include/nlohmann/detail/view/builder.hpp +++ b/include/nlohmann/detail/view/builder.hpp @@ -1189,7 +1189,7 @@ indent_done: { ++p; const unsigned char* const f0 = p; - p = skip_digits(p, e); + p = skip_fraction_digits(p, e); if (NLOHMANN_VIEW_UNLIKELY(p == f0)) { return fail(error_code::number_after_dot); diff --git a/include/nlohmann/detail/view/scan.hpp b/include/nlohmann/detail/view/scan.hpp index b8d862203..9cea6b05c 100644 --- a/include/nlohmann/detail/view/scan.hpp +++ b/include/nlohmann/detail/view/scan.hpp @@ -198,6 +198,57 @@ inline const unsigned char* find_block_comment_end(const unsigned char* p, const return p; } +/// The digits of a fraction, whose number varies from one number to the next: skip_digits() with 16 bytes compared at +/// once and the end of the run taken from a bit mask (SSE2), instead of a compare and a branch for each digit. That +/// is about a third of the instructions for a fraction of 12 digits, and no branch that is mispredicted when the +/// lengths vary. Where there is no SSE2, skip_digits() is used. +#if NLOHMANN_VIEW_SSE2 +// The last bytes of the input, one at a time. GCC unrolls this loop in every inlined copy of the function below, which +// makes the parse loop 4 KiB larger and slows down documents that have hardly any fractions; out of line, it does +// not. Clang does better with the loop inline (a call in the parse loop costs it more than the unrolling). +#if defined(__GNUC__) && !defined(__clang__) +inline NLOHMANN_VIEW_NOINLINE const unsigned char* skip_digits_tail(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && is_digit(*p)) + { + ++p; + } + return p; +} +#else +NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits_tail(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && is_digit(*p)) + { + ++p; + } + return p; +} +#endif + +NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_fraction_digits(const unsigned char* p, const unsigned char* e) noexcept +{ + while (e - p >= 16) + { + const __m128i in = _mm_loadu_si128(static_cast(static_cast(p))); // NOLINT(bugprone-casting-through-void) + // a digit is above '/' and below ':' (signed compares: a byte with the top bit set is negative, and neither) + const __m128i digits = _mm_and_si128(_mm_cmpgt_epi8(in, _mm_set1_epi8('/')), _mm_cmplt_epi8(in, _mm_set1_epi8(':'))); + const auto not_digit = static_cast(~static_cast(_mm_movemask_epi8(digits)) & 0xFFFFu); + if (not_digit != 0) + { + return p + count_trailing_zeros(not_digit); + } + p += 16; + } + return skip_digits_tail(p, e); +} +#else +NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_fraction_digits(const unsigned char* p, const unsigned char* e) noexcept +{ + return skip_digits(p, e); +} +#endif + /// powers of ten up to 10^19 as integers inline std::uint64_t int_pow10(unsigned k) noexcept { diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index f446b273d..408d845d4 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -1230,6 +1230,57 @@ inline const unsigned char* find_block_comment_end(const unsigned char* p, const return p; } +/// The digits of a fraction, whose number varies from one number to the next: skip_digits() with 16 bytes compared at +/// once and the end of the run taken from a bit mask (SSE2), instead of a compare and a branch for each digit. That +/// is about a third of the instructions for a fraction of 12 digits, and no branch that is mispredicted when the +/// lengths vary. Where there is no SSE2, skip_digits() is used. +#if NLOHMANN_VIEW_SSE2 +// The last bytes of the input, one at a time. GCC unrolls this loop in every inlined copy of the function below, which +// makes the parse loop 4 KiB larger and slows down documents that have hardly any fractions; out of line, it does +// not. Clang does better with the loop inline (a call in the parse loop costs it more than the unrolling). +#if defined(__GNUC__) && !defined(__clang__) +inline NLOHMANN_VIEW_NOINLINE const unsigned char* skip_digits_tail(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && is_digit(*p)) + { + ++p; + } + return p; +} +#else +NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits_tail(const unsigned char* p, const unsigned char* e) noexcept +{ + while (p != e && is_digit(*p)) + { + ++p; + } + return p; +} +#endif + +NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_fraction_digits(const unsigned char* p, const unsigned char* e) noexcept +{ + while (e - p >= 16) + { + const __m128i in = _mm_loadu_si128(static_cast(static_cast(p))); // NOLINT(bugprone-casting-through-void) + // a digit is above '/' and below ':' (signed compares: a byte with the top bit set is negative, and neither) + const __m128i digits = _mm_and_si128(_mm_cmpgt_epi8(in, _mm_set1_epi8('/')), _mm_cmplt_epi8(in, _mm_set1_epi8(':'))); + const auto not_digit = static_cast(~static_cast(_mm_movemask_epi8(digits)) & 0xFFFFu); + if (not_digit != 0) + { + return p + count_trailing_zeros(not_digit); + } + p += 16; + } + return skip_digits_tail(p, e); +} +#else +NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_fraction_digits(const unsigned char* p, const unsigned char* e) noexcept +{ + return skip_digits(p, e); +} +#endif + /// powers of ten up to 10^19 as integers inline std::uint64_t int_pow10(unsigned k) noexcept { @@ -2443,7 +2494,7 @@ indent_done: { ++p; const unsigned char* const f0 = p; - p = skip_digits(p, e); + p = skip_fraction_digits(p, e); if (NLOHMANN_VIEW_UNLIKELY(p == f0)) { return fail(error_code::number_after_dot); diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 418f219da..06059aba8 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -668,6 +668,54 @@ TEST_CASE("json_view builder: strings across vector blocks") } } +TEST_CASE("json_view builder: fraction digits") +{ + // The digits of a fraction are skipped 16 bytes at a time where there are + // that many left (SSE2), and one at a time after that. Fractions of every + // length from 0 to 40 digits, followed by every kind of byte that ends a + // number or lies next to the digits in ASCII ('/' and ':'), as values, as + // keys' neighbours, and at the very end of the text (a vector without a + // terminating NUL). The length of the number is part of the value that + // the test compares with json::parse(). + const std::string followers = ",]}/:.eE+-x \n\t\"09"; + std::mt19937 rng(2028); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible + for (std::size_t digits = 0; digits <= 40; ++digits) + { + for (int i = 0; i < 3; ++i) + { + std::string fraction; + for (std::size_t k = 0; k < digits; ++k) + { + fraction += static_cast('0' + (rng() % 10)); + } + const std::string number = std::string(rng() % 2 == 0 ? "-" : "") + std::to_string(rng() % 100000) + "." + fraction; + check_same(number); + check_same("[" + number + "]"); + std::string pair; + pair.reserve(number.size() * 2 + 3); + pair += '['; + pair += number; + pair += ','; + pair += number; + pair += ']'; + check_same(pair); + check_same("{\"a\":" + number + "}"); + for (const char follower : followers) + { + check_same(number + follower); + check_same("[" + number + follower + "]"); + check_same("[" + number + follower + std::string(rng() % 20, '1')); + } + // everything near the end of the text: the number cut in every place + for (std::size_t cut = 1; cut <= 18 && cut <= number.size(); ++cut) + { + check_same(number.substr(0, number.size() - cut)); + check_same("[" + number.substr(0, number.size() - cut)); + } + } + } +} + TEST_CASE("json_view builder: escapes between runs of text") { // The text between escapes is copied 16 bytes at a time where there are From 5337d841d43bbc2fd9e54ea3367610ca49ad22e8 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:21:31 +0100 Subject: [PATCH 52/63] Document and test the lifetime of string_view members decoded from a json_view (#5973) Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/features/json_view.md | 28 +++++++++++ tests/src/unit-json_view_from_json.cpp | 66 ++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 09779dba2..08529fd7d 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -253,6 +253,34 @@ Two conversions never copy at all: Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is. +### `string_view` members + +A struct whose member is a `#!cpp std::string_view` (C++17) can be decoded from a view with the +`NLOHMANN_DEFINE_TYPE_*` macros, and the member is not copied either: for a string without escape sequences it points +into the document's source text, and for a string with escape sequences into the document's buffer of decoded strings. +The member is valid as long as the document is alive and, for a document that +[borrows its source text](#ownership-and-lifetime) (as [`parse()`](../api/basic_json_document/parse.md) does for +an lvalue `#!cpp std::string`, a `#!cpp std::string_view`, or a C string), as long as that text is. + +```cpp +struct record +{ + std::string_view name; + int count; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(record, name, count) +}; + +const std::string text = R"({"name": "widget", "count": 3})"; +const json_document doc = json_document::parse(text); // borrows text +const record r = doc.root().get(); // r.name points into text, valid while text and doc are +``` + +!!! warning "Decoding into a `string_view` from a `json` value dangles at once" + + The same struct also compiles with `#!cpp json::parse(text).get()`, but there `name` points into the + temporary `json` value, which is destroyed at the end of the statement. Use a named + [`json_document`](../api/json_document.md), as above, to decode such a struct. + ## Writing generic code A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md): diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index a1d94d586..fa01b639d 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -39,6 +39,7 @@ using nlohmann::ordered_json_document; #ifdef JSON_HAS_CPP_17 #include + #include #endif // NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair; same() compares floating-point values on purpose @@ -493,5 +494,70 @@ TEST_CASE("json_view from_json") #endif } +#ifdef JSON_HAS_CPP_17 +namespace view_from_json +{ +// a string_view member is not copied: it points into the source text or, for a +// string with escape sequences, into the buffer of decoded strings of the document +struct named_view +{ + // initialized for GCC -Weffc++ + // NOLINTNEXTLINE(readability-redundant-member-init) + std::string_view name{}; + int n = 0; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(named_view, name, n) +}; +} // namespace view_from_json + +TEST_CASE("json_view from_json: string_view members borrow from the document") +{ + using view_from_json::named_view; + + // whether all of s is inside the bytes of text + const auto within = [](std::string_view s, std::string_view text) + { + const auto first = reinterpret_cast(text.data()); + const auto begin = reinterpret_cast(s.data()); + return begin >= first && begin + s.size() <= first + text.size(); + }; + + SECTION("a string without escapes points into the source text") + { + const std::string text = R"({"name": "hello", "n": 1})"; + const json_document d = json_document::parse(text); + REQUIRE_FALSE(d.owns_source()); + const auto r = d.root().get(); + CHECK(r.name == "hello"); + CHECK(r.n == 1); + CHECK(within(r.name, text)); + CHECK(within(r.name, d.source())); + } + + SECTION("a string with escapes points into the document, not into the source text") + { + const std::string text = R"({"name": "a\nb", "n": 2})"; + json_document d = json_document::parse(text); + REQUIRE_FALSE(d.owns_source()); + const auto r = d.root().get(); + CHECK(r.name == "a\nb"); + CHECK(r.n == 2); + CHECK_FALSE(within(r.name, text)); + // valid as long as the document is alive, even if the document is moved + const json_document moved = std::move(d); + CHECK(r.name == "a\nb"); + CHECK(moved.root().get().name == r.name); + } + + SECTION("a document that owns its text keeps the members readable while it is alive") + { + const json_document d = json_document::parse(std::string(R"({"name": "owned", "n": 3})")); + REQUIRE(d.owns_source()); + const auto r = d.root().get(); + CHECK(r.name == "owned"); + CHECK(within(r.name, d.source())); + } +} +#endif + DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP From 6c3ab966d3b086657301ca14c54d5839b38c3e8d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:22:31 +0100 Subject: [PATCH 53/63] CBOR reader: read runs of scalar array elements in one loop (#5975) * CBOR reader: read runs of scalar array elements in one loop When a definite-length CBOR array opens and the input is contiguous, read the integers, floats, booleans, and nulls that follow in a tight loop instead of going through parse_cbor_internal's container logic and parse_cbor_value's dispatch for every element. The loop stops at any other kind of value and when fewer than 9 bytes are left; those values are read by the general path exactly as before. Non-contiguous inputs are not affected (tag dispatch on the bulk-scan capability). Also pass a member empty string as the number source to number_float instead of constructing a std::string for every float. Signed-off-by: Niels Lohmann * CBOR reader: address review of the scalar-run reader - parse_cbor_scalar_run: set the size explicitly in every case that reads a value, instead of relying on a default that every other case overwrites. - Remove cbor_scalar_follows(): parse_cbor_scalar_run already returns on the same conditions, and without the pre-check SAX canada needs another 8.5 % fewer instructions with GCC 13 (0.915 against the previous version), with no row more than 0.5 % worse (Mac and GCC). - Restore the comment on why parse_cbor_internal copies the container frame. Signed-off-by: Niels Lohmann * CBOR reader: enter the scalar run only when a scalar follows An array of containers or strings called the out-of-line run start once per array only to leave it at once; on nested data (arrays of objects) that call made reading slower than before the run reader. enter_cbor_array now tests the first byte and the remaining length inline and calls the run only for a scalar, so the run's loop condition moves to the end (do-while). Signed-off-by: Niels Lohmann * CBOR reader: mark the initial accepted in the scalar run as a false-positive dead store ci_infer reports DEAD_STORE for 'bool accepted = true;' in parse_cbor_scalar_run because every case assigns accepted before it is read. Signed-off-by: Niels Lohmann * CBOR reader: drop the scalar pre-check before the run enter_cbor_array calls the run directly again, and parse_cbor_scalar_run tests the remaining count and length at the top of a while loop; its own switch already leaves on any value that is not a scalar. The MessagePack reader is built the same way. Instructions per repetition, new/old: clang SAX canada 0.983, read canada 0.993; GCC 13 read canada 0.997, the other rows at most 1.004. Signed-off-by: Niels Lohmann * CBOR reader: say why the initial accepted in the scalar run stays Infer's DEAD_STORE report is right, not a false positive: every case that reaches the read assigns accepted. The initializer is kept for clang-tidy's cppcoreguidelines-init-variables. Signed-off-by: Niels Lohmann * CBOR reader: give the scalar run the bulk tag instead of a wrapper parse_cbor_scalar_run takes the bulk-scan tag itself and is the function that is kept out of line, so the one-line parse_cbor_array_start wrapper goes, as the UBJSON typed run already does. Signed-off-by: Niels Lohmann * CBOR reader: start the scalar run's accepted at false Infer ignores dead stores of 0 (--liveness-ignored-constant defaults to "0"), and false is 0 to it, so the suppression and its comment go. Signed-off-by: Niels Lohmann * CBOR reader: let the scalar run find its array itself parse_cbor_scalar_run reads container_stack.back() in its bulk version, so the caller no longer evaluates it for input that is not contiguous. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 184 ++++++++++++++++-- single_include/nlohmann/json.hpp | 184 ++++++++++++++++-- tests/src/unit-cbor.cpp | 74 +++++++ 3 files changed, 414 insertions(+), 28 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index b671fd84e..40df891bc 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -745,11 +745,13 @@ class binary_reader bool get_cbor_negative_integer() { NumberType number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(number))) - { - return false; - } + return get_number(number) && emit_cbor_negative_integer(number); + } + /// pass the CBOR negative integer -1 - @a number to the SAX parser + template + bool emit_cbor_negative_integer(const NumberType number) + { // the value is -1 - number, which fits into number_integer_t // whenever number does; the outer cast undoes the integral promotion // for number_integer_t types narrower than int @@ -974,7 +976,7 @@ class binary_reader case 0x95: case 0x96: case 0x97: - return enter_array(conditional_static_cast(static_cast(current) & 0x1Fu)); + return enter_cbor_array(conditional_static_cast(static_cast(current) & 0x1Fu)); case 0x98: // array (one-byte uint8_t for n follows) case 0x99: // array (two-byte uint16_t for n follow) @@ -983,7 +985,7 @@ class binary_reader { std::uint64_t len{}; std::size_t size{}; - return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size); + return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size); } case 0x9F: // array (indefinite length) @@ -1680,6 +1682,155 @@ class binary_reader return true; } + /*! + @brief read a big-endian number of type @a NumberType at @a data + */ + template + NumberType load_big_endian(const unsigned char* data) const noexcept + { + NumberType result{}; + std::memcpy(&result, data, sizeof(NumberType)); + if (is_little_endian) + { + byte_swap(result); + } + return result; + } + + /*! + @brief read the integers, floats, booleans, and nulls that follow in a + definite-length array, without the per-element bookkeeping of + @ref parse_cbor_internal + + Stops at the end of the array, at any other kind of value, and when fewer + than 9 bytes (the longest of these values) are left in the input, leaving + those to @ref parse_cbor_value, which reads them the same way. + + Out of line, so that @ref parse_cbor_value stays small enough to be inlined. + + The array is the one on top of @ref container_stack; its count of + elements not yet read is updated. + + @return whether the SAX parser accepted the values + */ + JSON_HEDLEY_NEVER_INLINE + bool parse_cbor_scalar_run(std::true_type /*bulk*/) + { + // nothing pushes onto the stack while the run reads scalars + std::size_t& remaining = container_stack.back().remaining; + while (remaining != 0 && ia.bulk_remaining() >= 9) + { + const auto* const data = reinterpret_cast(ia.bulk_data()); + const unsigned char head = data[0]; + std::size_t size = 0; + bool accepted = false; + switch (head) + { + case 0x18: + size = 2; + accepted = emit_unsigned(data[1]); + break; + case 0x19: + size = 3; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0x1A: + size = 5; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0x1B: + size = 9; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0x38: + size = 2; + accepted = emit_cbor_negative_integer(data[1]); + break; + case 0x39: + size = 3; + accepted = emit_cbor_negative_integer(load_big_endian(data + 1)); + break; + case 0x3A: + size = 5; + accepted = emit_cbor_negative_integer(load_big_endian(data + 1)); + break; + case 0x3B: + size = 9; + accepted = emit_cbor_negative_integer(load_big_endian(data + 1)); + break; + case 0xF4: + size = 1; + accepted = sax->boolean(false); + break; + case 0xF5: + size = 1; + accepted = sax->boolean(true); + break; + case 0xF6: + size = 1; + accepted = sax->null(); + break; + case 0xFA: + size = 5; + accepted = emit_float(load_big_endian(data + 1)); + break; + case 0xFB: + size = 9; + accepted = emit_float(load_big_endian(data + 1)); + break; + default: + if (head <= 0x17) + { + size = 1; + accepted = sax->number_unsigned(static_cast(head)); + } + else if (head >= 0x20 && head <= 0x37) + { + size = 1; + accepted = sax->number_integer(static_cast(0x20 - 1 - head)); + } + else + { + // any other value is read by parse_cbor_value + return true; + } + break; + } + // as if read by get() and get_number(): current is the head byte + ia.bulk_skip(size); + chars_read += size; + current = head; + --remaining; + if (JSON_HEDLEY_UNLIKELY(!accepted)) + { + return false; + } + } + return true; + } + + /*! + @brief open a definite-length array and read the scalars it begins with + (see @ref parse_cbor_scalar_run) + + Only the start of an array is tried, which covers arrays of numbers + (e.g. coordinates) but costs arrays of other values only one call, not one + per element. + + @param[in] len the number of elements + @return whether the SAX parser accepted the array and the values read + */ + bool enter_cbor_array(const std::size_t len) + { + return enter_array(len) && (len == 0 || parse_cbor_scalar_run(std::integral_constant {})); + } + + /// input that is not contiguous: every value is read by parse_cbor_value + bool parse_cbor_scalar_run(std::false_type /*bulk*/) const noexcept + { + return true; + } + /*! @brief read a CBOR value and everything nested inside it @@ -1709,6 +1860,7 @@ class binary_reader // pop_back() below, which destroys the container_stack element // it would otherwise alias const container_frame top = container_stack.back(); + const bool is_object = top.is_object; bool at_end = false; if (top.remaining != npos) @@ -1720,7 +1872,7 @@ class binary_reader { // claim the element about to be read --container_stack.back().remaining; - if (top.is_object) + if (is_object) { get(); } @@ -1733,7 +1885,7 @@ class binary_reader // Testing for it consumes a byte, which is the first byte // of the next element when it is not one. at_end = (get() == 0xFF); - fetch = top.is_object; + fetch = is_object; } if (at_end) @@ -1750,7 +1902,7 @@ class binary_reader continue; } - if (top.is_object) + if (is_object) { if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler))) { @@ -3695,13 +3847,13 @@ class binary_reader return sax->null(); case 0xFB: - return sax->number_float(static_cast(-1.0), ""); + return sax->number_float(static_cast(-1.0), no_source); case 0xFC: - return sax->number_float(static_cast(0.0), ""); + return sax->number_float(static_cast(0.0), no_source); case 0xFD: - return sax->number_float(static_cast(1.0), ""); + return sax->number_float(static_cast(1.0), no_source); case 0xFF: // empty string { @@ -4205,7 +4357,7 @@ class binary_reader { return out_of_range_error(exception_id::number_overflow, "number overflow", "value"); } - return sax->number_float(result, ""); + return sax->number_float(result, no_source); } /*! @@ -4265,7 +4417,7 @@ class binary_reader }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) - : static_cast(val), ""); + : static_cast(val), no_source); } /*! @@ -4590,6 +4742,10 @@ class binary_reader /// the SAX parser json_sax_t* sax = nullptr; + /// the (empty) source text of every number_float event: binary formats + /// have none, and a string_t built for each float would cost more + const string_t no_source{}; + /// the containers that have been opened and not closed yet; see @ref container_frame std::vector container_stack{}; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e2ead9c41..d16cbb3d4 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18549,11 +18549,13 @@ class binary_reader bool get_cbor_negative_integer() { NumberType number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(number))) - { - return false; - } + return get_number(number) && emit_cbor_negative_integer(number); + } + /// pass the CBOR negative integer -1 - @a number to the SAX parser + template + bool emit_cbor_negative_integer(const NumberType number) + { // the value is -1 - number, which fits into number_integer_t // whenever number does; the outer cast undoes the integral promotion // for number_integer_t types narrower than int @@ -18778,7 +18780,7 @@ class binary_reader case 0x95: case 0x96: case 0x97: - return enter_array(conditional_static_cast(static_cast(current) & 0x1Fu)); + return enter_cbor_array(conditional_static_cast(static_cast(current) & 0x1Fu)); case 0x98: // array (one-byte uint8_t for n follows) case 0x99: // array (two-byte uint16_t for n follow) @@ -18787,7 +18789,7 @@ class binary_reader { std::uint64_t len{}; std::size_t size{}; - return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size); + return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size); } case 0x9F: // array (indefinite length) @@ -19484,6 +19486,155 @@ class binary_reader return true; } + /*! + @brief read a big-endian number of type @a NumberType at @a data + */ + template + NumberType load_big_endian(const unsigned char* data) const noexcept + { + NumberType result{}; + std::memcpy(&result, data, sizeof(NumberType)); + if (is_little_endian) + { + byte_swap(result); + } + return result; + } + + /*! + @brief read the integers, floats, booleans, and nulls that follow in a + definite-length array, without the per-element bookkeeping of + @ref parse_cbor_internal + + Stops at the end of the array, at any other kind of value, and when fewer + than 9 bytes (the longest of these values) are left in the input, leaving + those to @ref parse_cbor_value, which reads them the same way. + + Out of line, so that @ref parse_cbor_value stays small enough to be inlined. + + The array is the one on top of @ref container_stack; its count of + elements not yet read is updated. + + @return whether the SAX parser accepted the values + */ + JSON_HEDLEY_NEVER_INLINE + bool parse_cbor_scalar_run(std::true_type /*bulk*/) + { + // nothing pushes onto the stack while the run reads scalars + std::size_t& remaining = container_stack.back().remaining; + while (remaining != 0 && ia.bulk_remaining() >= 9) + { + const auto* const data = reinterpret_cast(ia.bulk_data()); + const unsigned char head = data[0]; + std::size_t size = 0; + bool accepted = false; + switch (head) + { + case 0x18: + size = 2; + accepted = emit_unsigned(data[1]); + break; + case 0x19: + size = 3; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0x1A: + size = 5; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0x1B: + size = 9; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0x38: + size = 2; + accepted = emit_cbor_negative_integer(data[1]); + break; + case 0x39: + size = 3; + accepted = emit_cbor_negative_integer(load_big_endian(data + 1)); + break; + case 0x3A: + size = 5; + accepted = emit_cbor_negative_integer(load_big_endian(data + 1)); + break; + case 0x3B: + size = 9; + accepted = emit_cbor_negative_integer(load_big_endian(data + 1)); + break; + case 0xF4: + size = 1; + accepted = sax->boolean(false); + break; + case 0xF5: + size = 1; + accepted = sax->boolean(true); + break; + case 0xF6: + size = 1; + accepted = sax->null(); + break; + case 0xFA: + size = 5; + accepted = emit_float(load_big_endian(data + 1)); + break; + case 0xFB: + size = 9; + accepted = emit_float(load_big_endian(data + 1)); + break; + default: + if (head <= 0x17) + { + size = 1; + accepted = sax->number_unsigned(static_cast(head)); + } + else if (head >= 0x20 && head <= 0x37) + { + size = 1; + accepted = sax->number_integer(static_cast(0x20 - 1 - head)); + } + else + { + // any other value is read by parse_cbor_value + return true; + } + break; + } + // as if read by get() and get_number(): current is the head byte + ia.bulk_skip(size); + chars_read += size; + current = head; + --remaining; + if (JSON_HEDLEY_UNLIKELY(!accepted)) + { + return false; + } + } + return true; + } + + /*! + @brief open a definite-length array and read the scalars it begins with + (see @ref parse_cbor_scalar_run) + + Only the start of an array is tried, which covers arrays of numbers + (e.g. coordinates) but costs arrays of other values only one call, not one + per element. + + @param[in] len the number of elements + @return whether the SAX parser accepted the array and the values read + */ + bool enter_cbor_array(const std::size_t len) + { + return enter_array(len) && (len == 0 || parse_cbor_scalar_run(std::integral_constant {})); + } + + /// input that is not contiguous: every value is read by parse_cbor_value + bool parse_cbor_scalar_run(std::false_type /*bulk*/) const noexcept + { + return true; + } + /*! @brief read a CBOR value and everything nested inside it @@ -19513,6 +19664,7 @@ class binary_reader // pop_back() below, which destroys the container_stack element // it would otherwise alias const container_frame top = container_stack.back(); + const bool is_object = top.is_object; bool at_end = false; if (top.remaining != npos) @@ -19524,7 +19676,7 @@ class binary_reader { // claim the element about to be read --container_stack.back().remaining; - if (top.is_object) + if (is_object) { get(); } @@ -19537,7 +19689,7 @@ class binary_reader // Testing for it consumes a byte, which is the first byte // of the next element when it is not one. at_end = (get() == 0xFF); - fetch = top.is_object; + fetch = is_object; } if (at_end) @@ -19554,7 +19706,7 @@ class binary_reader continue; } - if (top.is_object) + if (is_object) { if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler))) { @@ -21499,13 +21651,13 @@ class binary_reader return sax->null(); case 0xFB: - return sax->number_float(static_cast(-1.0), ""); + return sax->number_float(static_cast(-1.0), no_source); case 0xFC: - return sax->number_float(static_cast(0.0), ""); + return sax->number_float(static_cast(0.0), no_source); case 0xFD: - return sax->number_float(static_cast(1.0), ""); + return sax->number_float(static_cast(1.0), no_source); case 0xFF: // empty string { @@ -22009,7 +22161,7 @@ class binary_reader { return out_of_range_error(exception_id::number_overflow, "number overflow", "value"); } - return sax->number_float(result, ""); + return sax->number_float(result, no_source); } /*! @@ -22069,7 +22221,7 @@ class binary_reader }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) - : static_cast(val), ""); + : static_cast(val), no_source); } /*! @@ -22394,6 +22546,10 @@ class binary_reader /// the SAX parser json_sax_t* sax = nullptr; + /// the (empty) source text of every number_float event: binary formats + /// have none, and a string_t built for each float would cost more + const string_t no_source{}; + /// the containers that have been opened and not closed yet; see @ref container_frame std::vector container_stack{}; diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 44cfa7723..930cbe7f5 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -29,7 +29,9 @@ using nlohmann::json; #include "round_trip_corpus.hpp" #include "test_utils.hpp" #include "sax_countdown.hpp" +#include "sax_event_loggers.hpp" using utils::SaxCountdown; +using utils::SaxEventLogger; TEST_CASE("CBOR") @@ -2446,6 +2448,78 @@ TEST_CASE("CBOR SAX parsing stops at every event") CHECK(count_events(std::vector({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6); } +TEST_CASE("CBOR arrays of scalars from contiguous and non-contiguous input") +{ + // contiguous input reads the integers, floats, booleans, and nulls an + // array begins with in one loop, other input value by value; both must + // give the same events, value, or error for the complete document and for + // every truncation of it, and stop at every event a SAX handler rejects + const std::vector cbor = + { + 0xA3, // map(3) + 0x61, 'a', // "a" + 0x98, 0x16, // array(22) + 0x00, 0x17, 0x18, 0xFF, 0x19, 0x01, 0x00, // 0, 23, 255, 256 + 0x1A, 0x00, 0x01, 0x00, 0x00, // 65536 + 0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615 + 0x20, 0x37, 0x38, 0xFF, 0x39, 0x01, 0x00, // -1, -24, -256, -257 + 0x3A, 0x00, 0x01, 0x00, 0x00, // -65537 + 0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -18446744073709551616 (a float) + 0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -9223372036854775808 + 0xF4, 0xF5, 0xF6, // false, true, null + 0xFA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float32) + 0xFB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1 + 0xF9, 0x3C, 0x00, // 1.0 (float16, ends the loop) + 0x01, 0x61, 'a', // 1, "a" + 0xFB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5 + 0x61, 'b', // "b" + 0x83, // array(3) + 0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25, + 0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75] + 0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25, + 0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75] + 0x80, // [] + 0x61, 'c', // "c" + 0x84, 0xA0, 0x01, 0xC1, 0x02, 0x81, 0x03 // [{}, 1, tag(1) 2, [3]] + }; + const auto tags = json::cbor_tag_handler_t::ignore; + + const json j = json::from_cbor(cbor, true, true, tags); + CHECK(j["a"].size() == 22); + CHECK(j["a"][5] == 18446744073709551615u); + CHECK(j["a"][11].is_number_float()); + CHECK(j["a"][11] == -18446744073709551616.0); + CHECK(j["a"][12] == (std::numeric_limits::min)()); + CHECK(j["a"][17] == -0.1); + CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]")); + CHECK(j["c"] == json::parse("[{}, 1, 2, [3]]")); + + for (std::size_t length = 0; length <= cbor.size(); ++length) + { + CAPTURE(length) + const std::vector contiguous(cbor.begin(), cbor.begin() + static_cast(length)); + const std::list bytewise(contiguous.begin(), contiguous.end()); + + CHECK(json::from_cbor(contiguous, true, false, tags).dump() == json::from_cbor(bytewise.begin(), bytewise.end(), true, false, tags).dump()); + + SaxEventLogger from_contiguous; + SaxEventLogger from_bytewise; + CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::cbor, true, false, false, tags) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags)); + CHECK(from_contiguous.events == from_bytewise.events); + } + + for (int events = 0; events <= 60; ++events) + { + CAPTURE(events) + const std::list bytewise(cbor.begin(), cbor.end()); + SaxCountdown from_contiguous(events); + SaxCountdown from_bytewise(events); + CHECK(json::sax_parse(cbor, &from_contiguous, json::input_format_t::cbor, true, false, false, tags) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags)); + } +} + TEST_CASE("CBOR indefinite-length strings do not recurse per chunk") { // Reading an indefinite-length string or byte array used to call itself From aecbef39490cfea51203f9663ce06e00a3b3796f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:22:32 +0100 Subject: [PATCH 54/63] Binary readers: swap the bytes of floats with a single byteswap (#5976) * Binary readers: swap the bytes of floats with a single byteswap byte_swap reversed float and double values byte by byte. GCC does not merge that loop into one bswap, and C++23's std::byteswap accepts only integers. Overloads for float and double now copy the bits into a uint32_t/uint64_t, call detail::byteswap, and copy them back. This applies to every binary reader that uses get_number. In the CBOR reader, the single- and double-precision float cases call a non-inlined helper. Without it, GCC stopped inlining the stream adapter's get_character() into parse_cbor_internal, which made reading from a std::istream about 0.5 % slower. Signed-off-by: Niels Lohmann * CBOR reader: inline get_cbor_float() and always inline get() instead The never-inlined get_cbor_float() costs a call for every float read from input without bulk access: reading canada.json from CBOR through a FILE* became 5.6 % slower on Apple clang in the benchmark suite. The helper existed only because GCC 13 stopped inlining the stream adapter's get_character() into parse_cbor_internal. Always inlining binary_reader::get() fixes that as well. callgrind, all binary formats on buffer, std::istringstream and FILE* input (canada and citm_catalog): GCC 13 0.986-1.003, GCC 16 0.994-1.006, Clang 23 identical. Apple clang suite: FromBinaryFile/cbor/canada 0.939, geomean of the 53 binary reader rows 0.998. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 45 +- single_include/nlohmann/json.hpp | 496 ++++++++++-------- 2 files changed, 300 insertions(+), 241 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 40df891bc..686b265a6 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -22,6 +22,7 @@ #include //byteswap #endif +#include #include #include #include @@ -1139,16 +1140,10 @@ class binary_reader return get_half_float(false); case 0xFA: // Single-Precision Float (four-byte IEEE 754) - { - float number{}; - return get_number(number) && emit_float(number); - } + return get_cbor_float(); case 0xFB: // Double-Precision Float (eight-byte IEEE 754) - { - double number{}; - return get_number(number) && emit_float(number); - } + return get_cbor_float(); default: // anything else (0xFF is handled inside the other types) { @@ -1157,6 +1152,14 @@ class binary_reader } } + /// read a single- or double-precision float + template + bool get_cbor_float() + { + NumberType number{}; + return get_number(number) && emit_float(number); + } + /*! @brief reports a nested indefinite-length CBOR string or byte array @param[in] type_name name of the rejected string type @@ -4176,7 +4179,12 @@ class binary_reader `char_traits::eof()` in that case. @return character read from the input + + @note always inlined: GCC otherwise stops inlining the stream adapter's + get_character() into parse_cbor_internal once that function grows + (+0.45 % instructions for CBOR from a std::istringstream with GCC 13) */ + JSON_HEDLEY_ALWAYS_INLINE char_int_type get() { ++chars_read; @@ -4222,6 +4230,27 @@ class binary_reader return current; } + /// reverse the byte order of a double with a single byteswap(): GCC does + /// not merge the byte-wise swap of the generic version into one + static void byte_swap(double& number) noexcept + { + static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits"); + std::uint64_t bits{}; + std::memcpy(&bits, &number, sizeof(bits)); + bits = byteswap(bits); + std::memcpy(&number, &bits, sizeof(bits)); + } + + /// reverse the byte order of a float with a single byteswap() + static void byte_swap(float& number) noexcept + { + static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits"); + std::uint32_t bits{}; + std::memcpy(&bits, &number, sizeof(bits)); + bits = byteswap(bits); + std::memcpy(&number, &bits, sizeof(bits)); + } + template static void byte_swap(NumberType& number) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index d16cbb3d4..fe5bf782b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -8852,6 +8852,233 @@ NLOHMANN_JSON_NAMESPACE_END #include //byteswap #endif +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // uint64_t +#include // memcpy +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) && (!defined(__SIZEOF_INT128__) || (!defined(__GNUC__) && !defined(__clang__))) + #include // __umulh, _umul128, _BitScanForward64, _BitScanReverse64 +#endif + +#ifdef _MSC_VER + #include // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 +#endif +#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM)) + #include // _BitScanForward, _BitScanReverse +#endif + +// #include +// JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN + +#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap) + #include // bit_cast, byteswap, countl_zero, countr_zero +#endif + +// Portable bit-level helpers for the number and string scanners. They use +// compiler builtins or platform-specific intrinsics where available, the +// C++20 functions next, and plain C++ otherwise, so they work regardless +// of byte order. (The builtins and intrinsics come first: MSVC's +// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.) + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// number of leading zero bits of x (x != 0) +inline int count_leading_zeros(std::uint64_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_clzll(x); +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes + _BitScanReverse64(&index, x); + return 63 - static_cast(index); +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes + if (_BitScanReverse(&index, static_cast(x >> 32u))) // NOLINT(runtime/int) + { + return 31 - static_cast(index); + } + _BitScanReverse(&index, static_cast(x)); // NOLINT(runtime/int) + return 63 - static_cast(index); +#elif defined(__cpp_lib_bitops) + return std::countl_zero(x); +#else + int n = 0; + for (int shift = 32; shift != 0; shift >>= 1) + { + if ((x >> (64 - shift)) == 0) + { + n += shift; + x <<= shift; + } + } + return n; +#endif +} + +/// number of trailing zero bits of x (x != 0) +inline int count_trailing_zeros(std::uint64_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_ctzll(x); +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes + _BitScanForward64(&index, x); + return static_cast(index); +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) + unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes + if (_BitScanForward(&index, static_cast(x))) // NOLINT(runtime/int) + { + return static_cast(index); + } + _BitScanForward(&index, static_cast(x >> 32u)); // NOLINT(runtime/int) + return 32 + static_cast(index); +#elif defined(__cpp_lib_bitops) + return std::countr_zero(x); +#else + int n = 0; + for (int shift = 32; shift != 0; shift >>= 1) + { + if ((x << (64 - shift)) == 0) + { + n += shift; + x >>= shift; + } + } + return n; +#endif +} + +/// the object representation of @a from as a @a To (std::bit_cast in C++20) +template +JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept +{ + static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size"); +#if defined(__cpp_lib_bit_cast) + return std::bit_cast(from); +#else + To to {}; + std::memcpy(&to, &from, sizeof(To)); + return to; +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower +/// it to a single bswap/rev/movbe +inline std::uint16_t byteswap(std::uint16_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(x); +#elif defined(_MSC_VER) + return _byteswap_ushort(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + return static_cast((x >> 8u) | (x << 8u)); +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23) +inline std::uint32_t byteswap(std::uint32_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#elif defined(_MSC_VER) + return _byteswap_ulong(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u) + | ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u); +#endif +} + +/// x with its bytes in reverse order (std::byteswap in C++23) +inline std::uint64_t byteswap(std::uint64_t x) noexcept +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(x); +#elif defined(_MSC_VER) + return _byteswap_uint64(x); +#elif defined(__cpp_lib_byteswap) + return std::byteswap(x); +#else + x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u); + x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u); + x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u); + return x; +#endif +} + +/// the 128-bit product of two 64-bit numbers +struct uint128_parts +{ + std::uint64_t low; + std::uint64_t high; +}; + +inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexcept +{ +#if defined(__SIZEOF_INT128__) + __extension__ using uint128 = unsigned __int128; + const uint128 r = static_cast(a) * b; + return {static_cast(r), static_cast(r >> 64u)}; +#elif defined(_MSC_VER) && defined(_M_X64) + std::uint64_t high = 0; + const std::uint64_t low = _umul128(a, b, &high); + return {low, high}; +#elif defined(_MSC_VER) && defined(_M_ARM64) + return {a * b, __umulh(a, b)}; +#else + const std::uint64_t a_lo = a & 0xFFFFFFFFu; + const std::uint64_t a_hi = a >> 32u; + const std::uint64_t b_lo = b & 0xFFFFFFFFu; + const std::uint64_t b_hi = b >> 32u; + const std::uint64_t lo_lo = a_lo * b_lo; + const std::uint64_t hi_lo = a_hi * b_lo; + const std::uint64_t lo_hi = a_lo * b_hi; + const std::uint64_t hi_hi = a_hi * b_hi; + const std::uint64_t cross = (lo_lo >> 32u) + (hi_lo & 0xFFFFFFFFu) + lo_hi; + return {(cross << 32u) | (lo_lo & 0xFFFFFFFFu), (hi_lo >> 32u) + (cross >> 32u) + hi_hi}; +#endif +} + +/// eight bytes as a little-endian word (a single load on little-endian +/// targets; always inlined, as GCC otherwise calls it in the number loops) +JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept +{ +#if JSON_LITTLE_ENDIAN_TARGET + // the byte order already matches + std::uint64_t result = 0; + std::memcpy(&result, b, sizeof(result)); + return result; +#else + return static_cast(b[0]) | (static_cast(b[1]) << 8u) + | (static_cast(b[2]) << 16u) | (static_cast(b[3]) << 24u) + | (static_cast(b[4]) << 32u) | (static_cast(b[5]) << 40u) + | (static_cast(b[6]) << 48u) | (static_cast(b[7]) << 56u); +#endif +} + +/// eight bytes as a little-endian word +JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const char* p) noexcept +{ + return read_eight_bytes(reinterpret_cast(p)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + // #include // #include @@ -10316,231 +10543,6 @@ NLOHMANN_JSON_NAMESPACE_END #include // vector // #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // uint64_t -#include // memcpy -#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) && (!defined(__SIZEOF_INT128__) || (!defined(__GNUC__) && !defined(__clang__))) - #include // __umulh, _umul128, _BitScanForward64, _BitScanReverse64 -#endif - -#ifdef _MSC_VER - #include // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 -#endif -#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM)) - #include // _BitScanForward, _BitScanReverse -#endif - -// #include -// JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN - -#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap) - #include // bit_cast, byteswap, countl_zero, countr_zero -#endif - -// Portable bit-level helpers for the number and string scanners. They use -// compiler builtins or platform-specific intrinsics where available, the -// C++20 functions next, and plain C++ otherwise, so they work regardless -// of byte order. (The builtins and intrinsics come first: MSVC's -// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.) - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/// number of leading zero bits of x (x != 0) -inline int count_leading_zeros(std::uint64_t x) noexcept -{ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_clzll(x); -#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) - unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes - _BitScanReverse64(&index, x); - return 63 - static_cast(index); -#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) - unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes - if (_BitScanReverse(&index, static_cast(x >> 32u))) // NOLINT(runtime/int) - { - return 31 - static_cast(index); - } - _BitScanReverse(&index, static_cast(x)); // NOLINT(runtime/int) - return 63 - static_cast(index); -#elif defined(__cpp_lib_bitops) - return std::countl_zero(x); -#else - int n = 0; - for (int shift = 32; shift != 0; shift >>= 1) - { - if ((x >> (64 - shift)) == 0) - { - n += shift; - x <<= shift; - } - } - return n; -#endif -} - -/// number of trailing zero bits of x (x != 0) -inline int count_trailing_zeros(std::uint64_t x) noexcept -{ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_ctzll(x); -#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) - unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes - _BitScanForward64(&index, x); - return static_cast(index); -#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM)) - unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes - if (_BitScanForward(&index, static_cast(x))) // NOLINT(runtime/int) - { - return static_cast(index); - } - _BitScanForward(&index, static_cast(x >> 32u)); // NOLINT(runtime/int) - return 32 + static_cast(index); -#elif defined(__cpp_lib_bitops) - return std::countr_zero(x); -#else - int n = 0; - for (int shift = 32; shift != 0; shift >>= 1) - { - if ((x << (64 - shift)) == 0) - { - n += shift; - x >>= shift; - } - } - return n; -#endif -} - -/// the object representation of @a from as a @a To (std::bit_cast in C++20) -template -JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept -{ - static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size"); -#if defined(__cpp_lib_bit_cast) - return std::bit_cast(from); -#else - To to {}; - std::memcpy(&to, &from, sizeof(To)); - return to; -#endif -} - -/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower -/// it to a single bswap/rev/movbe -inline std::uint16_t byteswap(std::uint16_t x) noexcept -{ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap16(x); -#elif defined(_MSC_VER) - return _byteswap_ushort(x); -#elif defined(__cpp_lib_byteswap) - return std::byteswap(x); -#else - return static_cast((x >> 8u) | (x << 8u)); -#endif -} - -/// x with its bytes in reverse order (std::byteswap in C++23) -inline std::uint32_t byteswap(std::uint32_t x) noexcept -{ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap32(x); -#elif defined(_MSC_VER) - return _byteswap_ulong(x); -#elif defined(__cpp_lib_byteswap) - return std::byteswap(x); -#else - return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u) - | ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u); -#endif -} - -/// x with its bytes in reverse order (std::byteswap in C++23) -inline std::uint64_t byteswap(std::uint64_t x) noexcept -{ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap64(x); -#elif defined(_MSC_VER) - return _byteswap_uint64(x); -#elif defined(__cpp_lib_byteswap) - return std::byteswap(x); -#else - x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u); - x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u); - x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u); - return x; -#endif -} - -/// the 128-bit product of two 64-bit numbers -struct uint128_parts -{ - std::uint64_t low; - std::uint64_t high; -}; - -inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexcept -{ -#if defined(__SIZEOF_INT128__) - __extension__ using uint128 = unsigned __int128; - const uint128 r = static_cast(a) * b; - return {static_cast(r), static_cast(r >> 64u)}; -#elif defined(_MSC_VER) && defined(_M_X64) - std::uint64_t high = 0; - const std::uint64_t low = _umul128(a, b, &high); - return {low, high}; -#elif defined(_MSC_VER) && defined(_M_ARM64) - return {a * b, __umulh(a, b)}; -#else - const std::uint64_t a_lo = a & 0xFFFFFFFFu; - const std::uint64_t a_hi = a >> 32u; - const std::uint64_t b_lo = b & 0xFFFFFFFFu; - const std::uint64_t b_hi = b >> 32u; - const std::uint64_t lo_lo = a_lo * b_lo; - const std::uint64_t hi_lo = a_hi * b_lo; - const std::uint64_t lo_hi = a_lo * b_hi; - const std::uint64_t hi_hi = a_hi * b_hi; - const std::uint64_t cross = (lo_lo >> 32u) + (hi_lo & 0xFFFFFFFFu) + lo_hi; - return {(cross << 32u) | (lo_lo & 0xFFFFFFFFu), (hi_lo >> 32u) + (cross >> 32u) + hi_hi}; -#endif -} - -/// eight bytes as a little-endian word (a single load on little-endian -/// targets; always inlined, as GCC otherwise calls it in the number loops) -JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept -{ -#if JSON_LITTLE_ENDIAN_TARGET - // the byte order already matches - std::uint64_t result = 0; - std::memcpy(&result, b, sizeof(result)); - return result; -#else - return static_cast(b[0]) | (static_cast(b[1]) << 8u) - | (static_cast(b[2]) << 16u) | (static_cast(b[3]) << 24u) - | (static_cast(b[4]) << 32u) | (static_cast(b[5]) << 40u) - | (static_cast(b[6]) << 48u) | (static_cast(b[7]) << 56u); -#endif -} - -/// eight bytes as a little-endian word -JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const char* p) noexcept -{ - return read_eight_bytes(reinterpret_cast(p)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END // #include @@ -18943,16 +18945,10 @@ class binary_reader return get_half_float(false); case 0xFA: // Single-Precision Float (four-byte IEEE 754) - { - float number{}; - return get_number(number) && emit_float(number); - } + return get_cbor_float(); case 0xFB: // Double-Precision Float (eight-byte IEEE 754) - { - double number{}; - return get_number(number) && emit_float(number); - } + return get_cbor_float(); default: // anything else (0xFF is handled inside the other types) { @@ -18961,6 +18957,14 @@ class binary_reader } } + /// read a single- or double-precision float + template + bool get_cbor_float() + { + NumberType number{}; + return get_number(number) && emit_float(number); + } + /*! @brief reports a nested indefinite-length CBOR string or byte array @param[in] type_name name of the rejected string type @@ -21980,7 +21984,12 @@ class binary_reader `char_traits::eof()` in that case. @return character read from the input + + @note always inlined: GCC otherwise stops inlining the stream adapter's + get_character() into parse_cbor_internal once that function grows + (+0.45 % instructions for CBOR from a std::istringstream with GCC 13) */ + JSON_HEDLEY_ALWAYS_INLINE char_int_type get() { ++chars_read; @@ -22026,6 +22035,27 @@ class binary_reader return current; } + /// reverse the byte order of a double with a single byteswap(): GCC does + /// not merge the byte-wise swap of the generic version into one + static void byte_swap(double& number) noexcept + { + static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits"); + std::uint64_t bits{}; + std::memcpy(&bits, &number, sizeof(bits)); + bits = byteswap(bits); + std::memcpy(&number, &bits, sizeof(bits)); + } + + /// reverse the byte order of a float with a single byteswap() + static void byte_swap(float& number) noexcept + { + static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits"); + std::uint32_t bits{}; + std::memcpy(&bits, &number, sizeof(bits)); + bits = byteswap(bits); + std::memcpy(&number, &bits, sizeof(bits)); + } + template static void byte_swap(NumberType& number) { From edf8d1760129d82162d0d1ca7e8fb2d0c08db638 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:22:32 +0100 Subject: [PATCH 55/63] MessagePack reader: read runs of scalar array elements in one loop (#5980) * MessagePack reader: read runs of scalar array elements in one loop As for CBOR: when an array opens and the input is contiguous, read the integers, floats, booleans, and nils that follow in a tight loop instead of going through parse_msgpack_internal and parse_msgpack_value for every element. The loop stops at any other kind of value and when fewer than 9 bytes are left; those values are read by the general path as before. Non-contiguous inputs are not affected. Signed-off-by: Niels Lohmann * MessagePack reader: enter the scalar run only when a scalar follows As for CBOR: enter_msgpack_array tests the first byte and the remaining length inline and calls the out-of-line run only for a scalar, so arrays of maps or strings no longer pay for a call that reads nothing; the run's loop condition moves to the end (do-while). Signed-off-by: Niels Lohmann * MessagePack reader: mark the initial accepted in the scalar run as a false-positive dead store ci_infer reports DEAD_STORE for 'bool accepted = true;' in the MessagePack scalar run, as in the CBOR one, because every case assigns accepted before it is read. Signed-off-by: Niels Lohmann * MessagePack tests: assign the parsed value instead of casting it to void GCC (ci_test_gcc) reports -Werror=unused-result for static_cast(json::from_msgpack(...)): a cast to void does not silence warn_unused_result on GCC. Assign to a json, as the other tests in the file do. Signed-off-by: Niels Lohmann * MessagePack reader: drop the scalar pre-check before the run enter_msgpack_array now calls the run directly, and parse_msgpack_scalar_run tests the remaining count and length at the top of a while loop instead of relying on msgpack_scalar_follows(); its own switch already leaves on any value that is not a scalar. Instructions per repetition, new/old: SAX canada 0.946 (clang) and 0.973 (GCC 13), DOM read canada 0.991 / 0.992; no row more than 0.3 % higher. Signed-off-by: Niels Lohmann * MessagePack reader: say why the initial accepted in the scalar run stays As for CBOR: Infer's DEAD_STORE report is right, not a false positive. The initializer is kept for clang-tidy's cppcoreguidelines-init-variables. Signed-off-by: Niels Lohmann * MessagePack reader: give the scalar run the bulk tag instead of a wrapper As for CBOR: parse_msgpack_scalar_run takes the bulk-scan tag itself and is the function kept out of line, so parse_msgpack_array_start goes. Signed-off-by: Niels Lohmann * MessagePack reader: start the scalar run's accepted at false As for CBOR: Infer ignores dead stores of 0, so the suppression goes. Signed-off-by: Niels Lohmann * MessagePack reader: let the scalar run find its array itself As for CBOR: parse_msgpack_scalar_run reads container_stack.back() in its bulk version, so the caller no longer evaluates it for input that is not contiguous. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 137 +++++++++++++++++- single_include/nlohmann/json.hpp | 137 +++++++++++++++++- tests/src/unit-msgpack.cpp | 99 +++++++++++++ 3 files changed, 367 insertions(+), 6 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 686b265a6..0c345fea0 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -2130,7 +2130,7 @@ class binary_reader case 0x9D: case 0x9E: case 0x9F: - return enter_array(conditional_static_cast(static_cast(current) & 0x0Fu)); + return enter_msgpack_array(conditional_static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: @@ -2261,13 +2261,13 @@ class binary_reader case 0xDC: // array 16 { std::uint16_t len{}; - return get_number(len) && enter_array(static_cast(len)); + return get_number(len) && enter_msgpack_array(static_cast(len)); } case 0xDD: // array 32 { std::uint32_t len{}; - return get_number(len) && enter_array(conditional_static_cast(len)); + return get_number(len) && enter_msgpack_array(conditional_static_cast(len)); } case 0xDE: // map 16 @@ -2609,6 +2609,137 @@ class binary_reader } } + /*! + @brief read the integers, floats, booleans, and nils that follow in a + MessagePack array, without the per-element bookkeeping of + @ref parse_msgpack_internal + + Stops at the end of the array, at any other kind of value, and when fewer + than 9 bytes (the longest of these values) are left in the input, leaving + those to @ref parse_msgpack_value, which reads them the same way. + + Out of line, so that @ref parse_msgpack_value stays small enough to be inlined. + + The array is the one on top of @ref container_stack; its count of + elements not yet read is updated. + + @return whether the SAX parser accepted the values + */ + JSON_HEDLEY_NEVER_INLINE + bool parse_msgpack_scalar_run(std::true_type /*bulk*/) + { + // nothing pushes onto the stack while the run reads scalars + std::size_t& remaining = container_stack.back().remaining; + while (remaining != 0 && ia.bulk_remaining() >= 9) + { + const auto* const data = reinterpret_cast(ia.bulk_data()); + const unsigned char head = data[0]; + std::size_t size = 0; + bool accepted = false; + switch (head) + { + case 0xC0: // nil + size = 1; + accepted = sax->null(); + break; + case 0xC2: // false + size = 1; + accepted = sax->boolean(false); + break; + case 0xC3: // true + size = 1; + accepted = sax->boolean(true); + break; + case 0xCA: // float 32 + size = 5; + accepted = emit_float(load_big_endian(data + 1)); + break; + case 0xCB: // float 64 + size = 9; + accepted = emit_float(load_big_endian(data + 1)); + break; + case 0xCC: // uint 8 + size = 2; + accepted = emit_unsigned(data[1]); + break; + case 0xCD: // uint 16 + size = 3; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0xCE: // uint 32 + size = 5; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0xCF: // uint 64 + size = 9; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0xD0: // int 8 + size = 2; + accepted = emit_signed(static_cast(data[1])); + break; + case 0xD1: // int 16 + size = 3; + accepted = emit_signed(load_big_endian(data + 1)); + break; + case 0xD2: // int 32 + size = 5; + accepted = emit_signed(load_big_endian(data + 1)); + break; + case 0xD3: // int 64 + size = 9; + accepted = emit_signed(load_big_endian(data + 1)); + break; + default: + if (head <= 0x7F) + { + // positive fixint + size = 1; + accepted = sax->number_unsigned(static_cast(head)); + } + else if (head >= 0xE0) + { + // negative fixint + size = 1; + accepted = sax->number_integer(static_cast(head)); + } + else + { + // any other value is read by parse_msgpack_value + return true; + } + break; + } + // as if read by get() and get_number(): current is the last byte + ia.bulk_skip(size); + chars_read += size; + current = data[size - 1]; + --remaining; + if (JSON_HEDLEY_UNLIKELY(!accepted)) + { + return false; + } + } + return true; + } + + /*! + @brief open a MessagePack array and read the scalars it begins with (see + @ref parse_msgpack_scalar_run) + @param[in] len the number of elements + @return whether the SAX parser accepted the array and the values read + */ + bool enter_msgpack_array(const std::size_t len) + { + return enter_array(len) && (len == 0 || parse_msgpack_scalar_run(std::integral_constant {})); + } + + /// input that is not contiguous: every value is read by parse_msgpack_value + bool parse_msgpack_scalar_run(std::false_type /*bulk*/) const noexcept + { + return true; + } + /*! @brief read a MessagePack value and everything nested inside it diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index fe5bf782b..d440efd8f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -19935,7 +19935,7 @@ class binary_reader case 0x9D: case 0x9E: case 0x9F: - return enter_array(conditional_static_cast(static_cast(current) & 0x0Fu)); + return enter_msgpack_array(conditional_static_cast(static_cast(current) & 0x0Fu)); // fixstr case 0xA0: @@ -20066,13 +20066,13 @@ class binary_reader case 0xDC: // array 16 { std::uint16_t len{}; - return get_number(len) && enter_array(static_cast(len)); + return get_number(len) && enter_msgpack_array(static_cast(len)); } case 0xDD: // array 32 { std::uint32_t len{}; - return get_number(len) && enter_array(conditional_static_cast(len)); + return get_number(len) && enter_msgpack_array(conditional_static_cast(len)); } case 0xDE: // map 16 @@ -20414,6 +20414,137 @@ class binary_reader } } + /*! + @brief read the integers, floats, booleans, and nils that follow in a + MessagePack array, without the per-element bookkeeping of + @ref parse_msgpack_internal + + Stops at the end of the array, at any other kind of value, and when fewer + than 9 bytes (the longest of these values) are left in the input, leaving + those to @ref parse_msgpack_value, which reads them the same way. + + Out of line, so that @ref parse_msgpack_value stays small enough to be inlined. + + The array is the one on top of @ref container_stack; its count of + elements not yet read is updated. + + @return whether the SAX parser accepted the values + */ + JSON_HEDLEY_NEVER_INLINE + bool parse_msgpack_scalar_run(std::true_type /*bulk*/) + { + // nothing pushes onto the stack while the run reads scalars + std::size_t& remaining = container_stack.back().remaining; + while (remaining != 0 && ia.bulk_remaining() >= 9) + { + const auto* const data = reinterpret_cast(ia.bulk_data()); + const unsigned char head = data[0]; + std::size_t size = 0; + bool accepted = false; + switch (head) + { + case 0xC0: // nil + size = 1; + accepted = sax->null(); + break; + case 0xC2: // false + size = 1; + accepted = sax->boolean(false); + break; + case 0xC3: // true + size = 1; + accepted = sax->boolean(true); + break; + case 0xCA: // float 32 + size = 5; + accepted = emit_float(load_big_endian(data + 1)); + break; + case 0xCB: // float 64 + size = 9; + accepted = emit_float(load_big_endian(data + 1)); + break; + case 0xCC: // uint 8 + size = 2; + accepted = emit_unsigned(data[1]); + break; + case 0xCD: // uint 16 + size = 3; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0xCE: // uint 32 + size = 5; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0xCF: // uint 64 + size = 9; + accepted = emit_unsigned(load_big_endian(data + 1)); + break; + case 0xD0: // int 8 + size = 2; + accepted = emit_signed(static_cast(data[1])); + break; + case 0xD1: // int 16 + size = 3; + accepted = emit_signed(load_big_endian(data + 1)); + break; + case 0xD2: // int 32 + size = 5; + accepted = emit_signed(load_big_endian(data + 1)); + break; + case 0xD3: // int 64 + size = 9; + accepted = emit_signed(load_big_endian(data + 1)); + break; + default: + if (head <= 0x7F) + { + // positive fixint + size = 1; + accepted = sax->number_unsigned(static_cast(head)); + } + else if (head >= 0xE0) + { + // negative fixint + size = 1; + accepted = sax->number_integer(static_cast(head)); + } + else + { + // any other value is read by parse_msgpack_value + return true; + } + break; + } + // as if read by get() and get_number(): current is the last byte + ia.bulk_skip(size); + chars_read += size; + current = data[size - 1]; + --remaining; + if (JSON_HEDLEY_UNLIKELY(!accepted)) + { + return false; + } + } + return true; + } + + /*! + @brief open a MessagePack array and read the scalars it begins with (see + @ref parse_msgpack_scalar_run) + @param[in] len the number of elements + @return whether the SAX parser accepted the array and the values read + */ + bool enter_msgpack_array(const std::size_t len) + { + return enter_array(len) && (len == 0 || parse_msgpack_scalar_run(std::integral_constant {})); + } + + /// input that is not contiguous: every value is read by parse_msgpack_value + bool parse_msgpack_scalar_run(std::false_type /*bulk*/) const noexcept + { + return true; + } + /*! @brief read a MessagePack value and everything nested inside it diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index 2890a240c..9b5891fdd 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -27,12 +27,15 @@ using nlohmann::json; #include #include #include +#include #include #include "make_test_data_available.hpp" #include "round_trip_corpus.hpp" #include "test_utils.hpp" #include "sax_countdown.hpp" +#include "sax_event_loggers.hpp" using utils::SaxCountdown; +using utils::SaxEventLogger; TEST_CASE("MessagePack") @@ -2212,3 +2215,99 @@ TEST_CASE("MessagePack large strings and binaries (chunked reader)") } } } + +TEST_CASE("MessagePack arrays of scalars from contiguous and non-contiguous input") +{ + // contiguous input reads the integers, floats, booleans, and nils an + // array begins with in one loop, other input value by value; both must + // give the same events, value, or error for the complete document and for + // every truncation of it, and stop at every event a SAX handler rejects + const std::vector msgpack = + { + 0x83, // map(3) + 0xA1, 'a', // "a" + 0xDC, 0x00, 0x16, // array 16 (22) + 0x00, 0x7F, 0xCC, 0xFF, 0xCD, 0x01, 0x00, // 0, 127, 255, 256 + 0xCE, 0x00, 0x01, 0x00, 0x00, // 65536 + 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615 + 0xFF, 0xE0, 0xD0, 0x80, 0xD1, 0xFF, 0x00, // -1, -32, -128, -256 + 0xD2, 0xFF, 0xFE, 0xFF, 0xFF, // -65537 + 0xD3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -9223372036854775808 + 0xC2, 0xC3, 0xC0, // false, true, nil + 0xCA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float 32) + 0xCB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1 + 0xA1, 'x', // "x" (ends the loop) + 0x01, // 1 + 0xCB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5 + 0xD9, 0x01, 'y', // "y" (str 8) + 0xC0, // nil + 0xA1, 'b', // "b" + 0x93, // fixarray(3) + 0x92, 0xCB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25, + 0xCB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75] + 0x92, 0xCB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25, + 0xCB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75] + 0x90, // [] + 0xA1, 'c', // "c" + 0x94, 0x80, 0x01, 0xC4, 0x01, 0x05, 0x91, 0x03 // [{}, 1, bin 8, [3]] + }; + + const json j = json::from_msgpack(msgpack); + CHECK(j["a"].size() == 22); + CHECK(j["a"][5] == 18446744073709551615u); + CHECK(j["a"][7] == -32); + CHECK(j["a"][11] == (std::numeric_limits::min)()); + CHECK(j["a"][15] == 1.5); + CHECK(j["a"][16] == -0.1); + CHECK(j["a"][17] == "x"); + CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]")); + CHECK(j["c"][2].is_binary()); + + for (std::size_t length = 0; length <= msgpack.size(); ++length) + { + CAPTURE(length) + const std::vector contiguous(msgpack.begin(), msgpack.begin() + static_cast(length)); + const std::list bytewise(contiguous.begin(), contiguous.end()); + + CHECK(json::from_msgpack(contiguous, true, false).dump() == json::from_msgpack(bytewise.begin(), bytewise.end(), true, false).dump()); +#if !defined(JSON_NOEXCEPTION) + std::string contiguous_error; + std::string bytewise_error; + try + { + json _; + _ = json::from_msgpack(contiguous); + } + catch (const json::parse_error& e) + { + contiguous_error = e.what(); + } + try + { + json _; + _ = json::from_msgpack(bytewise.begin(), bytewise.end()); + } + catch (const json::parse_error& e) + { + bytewise_error = e.what(); + } + CHECK(contiguous_error == bytewise_error); +#endif + + SaxEventLogger from_contiguous; + SaxEventLogger from_bytewise; + CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::msgpack) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack)); + CHECK(from_contiguous.events == from_bytewise.events); + } + + for (int events = 0; events <= 60; ++events) + { + CAPTURE(events) + const std::list bytewise(msgpack.begin(), msgpack.end()); + SaxCountdown from_contiguous(events); + SaxCountdown from_bytewise(events); + CHECK(json::sax_parse(msgpack, &from_contiguous, json::input_format_t::msgpack) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack)); + } +} From fb515ddc34565b3d78b56be2f43919846d45fa24 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:22:33 +0100 Subject: [PATCH 56/63] UBJSON/BJData readers: read optimized arrays of numbers in one loop (#5981) * UBJSON/BJData readers: read optimized arrays of numbers in one loop An array with a type marker for a fixed-size number ($U, $i, $I, $l, $L, $d, $D, and BJData's $u, $m, $M), including the data of a BJData ndarray, gives its elements no markers. When the input is contiguous, read the elements that are in the input in one loop - load, byte swap for the format's byte order, emit - instead of one trip through parse_ubjson_internal and get_ubjson_value per element. Elements the input ends before, other types, and non-contiguous inputs are read as before. Signed-off-by: Niels Lohmann * UBJSON/BJData tests: assign the parsed value instead of casting it to void GCC reports -Werror=unused-result for static_cast(json::from_ubjson(...)) and json::from_bjdata(...): a cast to void does not silence warn_unused_result on GCC. Assign to a json, as the other tests do. Signed-off-by: Niels Lohmann --------- Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 114 +++++++++++++++++- single_include/nlohmann/json.hpp | 114 +++++++++++++++++- tests/src/unit-bjdata.cpp | 77 ++++++++++++ tests/src/unit-ubjson.cpp | 72 +++++++++++ 4 files changed, 373 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 0c345fea0..190edb112 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -3545,6 +3545,107 @@ class binary_reader return unexpected_byte("invalid byte", "value"); } + /*! + @brief read the elements of an optimized array of numbers (type marker + @a type) that are in the input, in one loop, without the + per-element bookkeeping of @ref parse_ubjson_internal + + Values are emitted as @ref get_ubjson_value emits them. Types other than + the fixed-size integers and floats are left to @ref get_ubjson_value, as + are the elements the input ends before. + + @param[in,out] remaining the number of elements not yet read + @param[in] type the type marker of the elements + @return whether the SAX parser accepted the values + */ + JSON_HEDLEY_NEVER_INLINE + bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/) + { + const bool bjdata = input_format == input_format_t::bjdata; + switch (type) + { + case 'U': + return parse_ubjson_typed_run(remaining); + case 'i': + return parse_ubjson_typed_run(remaining); + case 'I': + return parse_ubjson_typed_run(remaining); + case 'l': + return parse_ubjson_typed_run(remaining); + case 'L': + return parse_ubjson_typed_run(remaining); + case 'u': + return !bjdata || parse_ubjson_typed_run(remaining); + case 'm': + return !bjdata || parse_ubjson_typed_run(remaining); + case 'M': + return !bjdata || parse_ubjson_typed_run(remaining); + case 'd': + return parse_ubjson_typed_run(remaining); + case 'D': + return parse_ubjson_typed_run(remaining); + default: + return true; + } + } + + /// input that is not contiguous: every element is read by get_ubjson_value + bool parse_ubjson_typed_run(std::size_t& /*remaining*/, const char_int_type /*type*/, std::false_type /*bulk*/) const noexcept + { + return true; + } + + template + bool parse_ubjson_typed_run(std::size_t& remaining) + { + constexpr std::size_t size = sizeof(NumberType); + const std::size_t count = (std::min)(remaining, ia.bulk_remaining() / size); + if (count == 0) + { + return true; + } + const auto* const data = reinterpret_cast(ia.bulk_data()); + // UBJSON stores numbers big-endian, BJData little-endian + const bool swap = is_little_endian != (input_format == input_format_t::bjdata); + std::size_t read = 0; + bool accepted = true; + while (read < count && accepted) + { + NumberType number{}; + std::memcpy(&number, data + (read * size), size); + if (swap) + { + byte_swap(number); + } + ++read; + accepted = emit_ubjson_number(number); + } + // as if read by get_number(): current is the last byte read + ia.bulk_skip(read * size); + chars_read += read * size; + current = data[(read * size) - 1]; + remaining -= read; + return accepted; + } + + template::value, int> = 0> + bool emit_ubjson_number(const NumberType number) + { + return emit_float(number); + } + + template < typename NumberType, enable_if_t < std::is_integral::value&& std::is_signed::value, int > = 0 > + bool emit_ubjson_number(const NumberType number) + { + return emit_signed(number); + } + + template < typename NumberType, enable_if_t < std::is_integral::value&& !std::is_signed::value, int > = 0 > + bool emit_ubjson_number(const NumberType number) + { + return emit_unsigned(number); + } + /*! @return whether array creation completed */ @@ -3580,7 +3681,15 @@ class binary_reader return false; } - for (std::size_t i = 0; i < size_and_type.first; ++i) + // the numbers that are in the input, in one loop; the rest (if + // the input ends early) one by one below + std::size_t remaining = size_and_type.first; + if (!nd_is_char && JSON_HEDLEY_UNLIKELY(!parse_ubjson_typed_run(remaining, size_and_type.second, std::integral_constant {}))) + { + return false; + } + + for (std::size_t i = size_and_type.first - remaining; i < size_and_type.first; ++i) { if (nd_is_char) { @@ -3640,7 +3749,8 @@ class binary_reader container_stack.back().remaining = 0; } - return true; + return size_and_type.second == 0 + || parse_ubjson_typed_run(container_stack.back().remaining, size_and_type.second, std::integral_constant {}); } return enter_array(detail::unknown_size()); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index d440efd8f..84720a816 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -21350,6 +21350,107 @@ class binary_reader return unexpected_byte("invalid byte", "value"); } + /*! + @brief read the elements of an optimized array of numbers (type marker + @a type) that are in the input, in one loop, without the + per-element bookkeeping of @ref parse_ubjson_internal + + Values are emitted as @ref get_ubjson_value emits them. Types other than + the fixed-size integers and floats are left to @ref get_ubjson_value, as + are the elements the input ends before. + + @param[in,out] remaining the number of elements not yet read + @param[in] type the type marker of the elements + @return whether the SAX parser accepted the values + */ + JSON_HEDLEY_NEVER_INLINE + bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/) + { + const bool bjdata = input_format == input_format_t::bjdata; + switch (type) + { + case 'U': + return parse_ubjson_typed_run(remaining); + case 'i': + return parse_ubjson_typed_run(remaining); + case 'I': + return parse_ubjson_typed_run(remaining); + case 'l': + return parse_ubjson_typed_run(remaining); + case 'L': + return parse_ubjson_typed_run(remaining); + case 'u': + return !bjdata || parse_ubjson_typed_run(remaining); + case 'm': + return !bjdata || parse_ubjson_typed_run(remaining); + case 'M': + return !bjdata || parse_ubjson_typed_run(remaining); + case 'd': + return parse_ubjson_typed_run(remaining); + case 'D': + return parse_ubjson_typed_run(remaining); + default: + return true; + } + } + + /// input that is not contiguous: every element is read by get_ubjson_value + bool parse_ubjson_typed_run(std::size_t& /*remaining*/, const char_int_type /*type*/, std::false_type /*bulk*/) const noexcept + { + return true; + } + + template + bool parse_ubjson_typed_run(std::size_t& remaining) + { + constexpr std::size_t size = sizeof(NumberType); + const std::size_t count = (std::min)(remaining, ia.bulk_remaining() / size); + if (count == 0) + { + return true; + } + const auto* const data = reinterpret_cast(ia.bulk_data()); + // UBJSON stores numbers big-endian, BJData little-endian + const bool swap = is_little_endian != (input_format == input_format_t::bjdata); + std::size_t read = 0; + bool accepted = true; + while (read < count && accepted) + { + NumberType number{}; + std::memcpy(&number, data + (read * size), size); + if (swap) + { + byte_swap(number); + } + ++read; + accepted = emit_ubjson_number(number); + } + // as if read by get_number(): current is the last byte read + ia.bulk_skip(read * size); + chars_read += read * size; + current = data[(read * size) - 1]; + remaining -= read; + return accepted; + } + + template::value, int> = 0> + bool emit_ubjson_number(const NumberType number) + { + return emit_float(number); + } + + template < typename NumberType, enable_if_t < std::is_integral::value&& std::is_signed::value, int > = 0 > + bool emit_ubjson_number(const NumberType number) + { + return emit_signed(number); + } + + template < typename NumberType, enable_if_t < std::is_integral::value&& !std::is_signed::value, int > = 0 > + bool emit_ubjson_number(const NumberType number) + { + return emit_unsigned(number); + } + /*! @return whether array creation completed */ @@ -21385,7 +21486,15 @@ class binary_reader return false; } - for (std::size_t i = 0; i < size_and_type.first; ++i) + // the numbers that are in the input, in one loop; the rest (if + // the input ends early) one by one below + std::size_t remaining = size_and_type.first; + if (!nd_is_char && JSON_HEDLEY_UNLIKELY(!parse_ubjson_typed_run(remaining, size_and_type.second, std::integral_constant {}))) + { + return false; + } + + for (std::size_t i = size_and_type.first - remaining; i < size_and_type.first; ++i) { if (nd_is_char) { @@ -21445,7 +21554,8 @@ class binary_reader container_stack.back().remaining = 0; } - return true; + return size_and_type.second == 0 + || parse_ubjson_typed_run(container_stack.back().remaining, size_and_type.second, std::integral_constant {}); } return enter_array(detail::unknown_size()); diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index e4dffb6d0..04ee3acc4 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -26,11 +26,14 @@ using nlohmann::ordered_json; #include #include #include +#include #include #include "make_test_data_available.hpp" #include "test_utils.hpp" #include "sax_countdown.hpp" +#include "sax_event_loggers.hpp" using utils::SaxCountdown; +using utils::SaxEventLogger; // trait_test_arg and the "value_in_range_of trait" TEST_CASE_TEMPLATE_DEFINE @@ -4746,3 +4749,77 @@ TEST_CASE("BJData optimized container type marker is validated for count 0") CHECK(json::from_bjdata(vb) == json::binary({})); } } + +TEST_CASE("BJData optimized arrays from contiguous and non-contiguous input") +{ + // contiguous input reads the numbers of an array with a type marker in + // one loop, other input number by number; both must give the same events, + // value, or error for the complete document and for every truncation of + // it, and stop at every event a SAX handler rejects + json document = + { + {"u8", {0, 200, 255}}, + {"i8", {-5, 100, -128}}, + {"i16", {-30000, 1000, 30000}}, + {"i32", {-2000000000, 5, 70000}}, + {"i64", {-9000000000000000000LL, 1, 5000000000LL}}, + {"f64", {1.5, -0.1, 2.5}}, + {"pairs", {{1.25, -3.75}, {1.25, -3.75}, json::array()}}, + {"mixed", {1, "a", 2.5, nullptr}}, + {"u16", {40000, 1, 65535}}, + {"u32", {4000000000U, 1, 70000}}, + {"u64", {18446744073709551615ULL, 1, 5000000000ULL}}, + {"nd", {{"_ArrayType_", "double"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {1.5, -2.5, 3.5, -4.5}}}}, + {"nd8", {{"_ArrayType_", "uint8"}, {"_ArraySize_", {3}}, {"_ArrayData_", {1, 2, 3}}}}, + }; + const std::vector encoded = json::to_bjdata(document, true, true); + CHECK(json::from_bjdata(encoded) == document); + + for (std::size_t length = 0; length <= encoded.size(); ++length) + { + CAPTURE(length) + const std::vector contiguous(encoded.begin(), encoded.begin() + static_cast(length)); + const std::list bytewise(contiguous.begin(), contiguous.end()); + + CHECK(json::from_bjdata(contiguous, true, false).dump() == json::from_bjdata(bytewise.begin(), bytewise.end(), true, false).dump()); +#if !defined(JSON_NOEXCEPTION) + std::string contiguous_error; + std::string bytewise_error; + try + { + json _; + _ = json::from_bjdata(contiguous); + } + catch (const json::exception& e) + { + contiguous_error = e.what(); + } + try + { + json _; + _ = json::from_bjdata(bytewise.begin(), bytewise.end()); + } + catch (const json::exception& e) + { + bytewise_error = e.what(); + } + CHECK(contiguous_error == bytewise_error); +#endif + + SaxEventLogger from_contiguous; + SaxEventLogger from_bytewise; + CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::bjdata) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::bjdata)); + CHECK(from_contiguous.events == from_bytewise.events); + } + + for (int events = 0; events <= 80; ++events) + { + CAPTURE(events) + const std::list bytewise(encoded.begin(), encoded.end()); + SaxCountdown from_contiguous(events); + SaxCountdown from_bytewise(events); + CHECK(json::sax_parse(encoded, &from_contiguous, json::input_format_t::bjdata) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::bjdata)); + } +} diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 5c6378216..04983da1c 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -21,12 +21,15 @@ using nlohmann::json; #include #include +#include #include #include "make_test_data_available.hpp" #include "round_trip_corpus.hpp" #include "test_utils.hpp" #include "sax_countdown.hpp" +#include "sax_event_loggers.hpp" using utils::SaxCountdown; +using utils::SaxEventLogger; TEST_CASE("UBJSON") @@ -3400,3 +3403,72 @@ TEST_CASE("UBJSON optimized container type marker is validated for count 0") } } } + +TEST_CASE("UBJSON optimized arrays from contiguous and non-contiguous input") +{ + // contiguous input reads the numbers of an array with a type marker in + // one loop, other input number by number; both must give the same events, + // value, or error for the complete document and for every truncation of + // it, and stop at every event a SAX handler rejects + json document = + { + {"u8", {0, 200, 255}}, + {"i8", {-5, 100, -128}}, + {"i16", {-30000, 1000, 30000}}, + {"i32", {-2000000000, 5, 70000}}, + {"i64", {-9000000000000000000LL, 1, 5000000000LL}}, + {"f64", {1.5, -0.1, 2.5}}, + {"pairs", {{1.25, -3.75}, {1.25, -3.75}, json::array()}}, + {"mixed", {1, "a", 2.5, nullptr}}, + }; + const std::vector encoded = json::to_ubjson(document, true, true); + CHECK(json::from_ubjson(encoded) == document); + + for (std::size_t length = 0; length <= encoded.size(); ++length) + { + CAPTURE(length) + const std::vector contiguous(encoded.begin(), encoded.begin() + static_cast(length)); + const std::list bytewise(contiguous.begin(), contiguous.end()); + + CHECK(json::from_ubjson(contiguous, true, false).dump() == json::from_ubjson(bytewise.begin(), bytewise.end(), true, false).dump()); +#if !defined(JSON_NOEXCEPTION) + std::string contiguous_error; + std::string bytewise_error; + try + { + json _; + _ = json::from_ubjson(contiguous); + } + catch (const json::exception& e) + { + contiguous_error = e.what(); + } + try + { + json _; + _ = json::from_ubjson(bytewise.begin(), bytewise.end()); + } + catch (const json::exception& e) + { + bytewise_error = e.what(); + } + CHECK(contiguous_error == bytewise_error); +#endif + + SaxEventLogger from_contiguous; + SaxEventLogger from_bytewise; + CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::ubjson) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::ubjson)); + CHECK(from_contiguous.events == from_bytewise.events); + } + + for (int events = 0; events <= 80; ++events) + { + CAPTURE(events) + const std::list bytewise(encoded.begin(), encoded.end()); + SaxCountdown from_contiguous(events); + SaxCountdown from_bytewise(events); + CHECK(json::sax_parse(encoded, &from_contiguous, json::input_format_t::ubjson) + == json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::ubjson)); + } +} From 517444b504c4035e603a6500675dde4d28eaca59 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:22:58 +0100 Subject: [PATCH 57/63] accept() checks strings without copying them (#5978) Signed-off-by: Niels Lohmann --- include/nlohmann/detail/input/lexer.hpp | 134 +++++++++++++------- include/nlohmann/detail/input/parser.hpp | 9 +- include/nlohmann/json.hpp | 11 +- single_include/nlohmann/json.hpp | 154 +++++++++++++++-------- tests/src/unit-class_parser.cpp | 61 +++++++++ 5 files changed, 265 insertions(+), 104 deletions(-) diff --git a/include/nlohmann/detail/input/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index 14d14f42a..66cb14b4f 100644 --- a/include/nlohmann/detail/input/lexer.hpp +++ b/include/nlohmann/detail/input/lexer.hpp @@ -209,8 +209,13 @@ constexpr bool input_adapter_bulk_window(std::false_type /*detected*/) @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. + +DiscardValues: the caller only needs the token types and never looks at the values of number and string tokens (only +accept(), which parses through json_sax_acceptor). A template parameter rather than a member, so that the lexer of +parse() is compiled without the paths of accept(): with both string scans in one scan(), parse() was up to 2 % slower +on Apple M1. */ -template +template class lexer : public lexer_base { using number_integer_t = typename BasicJsonType::number_integer_t; @@ -268,10 +273,9 @@ class lexer : public lexer_base public: using token_type = typename lexer_base::token_type; - explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept : ia(std::move(adapter)) , ignore_comments(ignore_comments_) - , discard_number_values(discard_number_values_) { // the token string of an error before the first token is everything // read so far @@ -388,6 +392,8 @@ class lexer : public lexer_base checks if it is inside the range. If a violation was detected, set up an error message and return false. Otherwise, return true. + @param[in] keep whether the bytes are added to token_buffer (false for + accept(), see scan()) @param[in] ranges list of integers; interpreted as list of pairs of inclusive lower and upper bound, respectively @@ -396,17 +402,18 @@ class lexer : public lexer_base @return true if and only if no range violation was detected */ - bool next_byte_in_range(std::initializer_list ranges) + template + bool next_byte_in_range(std::integral_constant keep, std::initializer_list ranges) { JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); - add(current); + add_string_char(keep, current); for (auto range = ranges.begin(); range != ranges.end(); ++range) { get(); if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions) { - add(current); + add_string_char(keep, current); } else { @@ -419,14 +426,16 @@ class lexer : public lexer_base } /// contiguous input: bulk-append the run of ordinary characters and complete - /// well-formed UTF-8 sequences starting at the current read position, then + /// well-formed UTF-8 sequences starting at the current read position (only + /// skip it if @a keep is false), then /// read the first byte that needs individual handling (the closing quote, an /// escape, a control character, or an ill-formed UTF-8 byte) like get(). /// That byte is taken from the buffer that was just scanned. Through get(), /// Apple Clang merged this read with the other get() calls of the scan /// function, which load the read position again right after it was stored /// (3% of json::accept() on jeopardy.json on Apple M1). - char_int_type get_after_string_run(std::true_type /*bulk*/) + template + char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant keep) { // a pending unget must be consumed through the normal path first if (next_unget) @@ -444,7 +453,7 @@ class lexer : public lexer_base const std::size_t pos = lexer_string_bulk_run(data, remaining); if (pos != 0) { - token_buffer.append(reinterpret_cast(data), pos); + append_string_run(keep, data, pos); bulk_consume(reinterpret_cast(data), pos); } if (pos == remaining) @@ -488,7 +497,8 @@ class lexer : public lexer_base } /// streaming input: no bulk fast path - char_int_type get_after_string_run(std::false_type /*bulk*/) + template + char_int_type get_after_string_run(std::false_type /*bulk*/, std::integral_constant /*keep*/) { return get(); } @@ -502,13 +512,18 @@ class lexer : public lexer_base may contain \0 bytes), and token_buffer.size() is the number of bytes in the string. + @param[in] keep whether the string is copied into token_buffer; if false + (accept(), see scan()), it is only checked, with the same + errors, and token_buffer stays empty + @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @note In case of errors, variable error_message contains a textual description. */ - token_type scan_string() + template + token_type scan_string(std::integral_constant keep) { // reset token_buffer (ignore opening quote) reset(); @@ -520,7 +535,7 @@ class lexer : public lexer_base { // bulk-consume ordinary characters from contiguous input, then // get the next character, the one that ends the run - switch (get_after_string_run(std::integral_constant())) + switch (get_after_string_run(std::integral_constant(), keep)) { // end of file while parsing the string case char_traits::eof(): @@ -542,35 +557,35 @@ class lexer : public lexer_base { // quotation mark case '\"': - add('\"'); + add_string_char(keep, '\"'); break; // reverse solidus case '\\': - add('\\'); + add_string_char(keep, '\\'); break; // solidus case '/': - add('/'); + add_string_char(keep, '/'); break; // backspace case 'b': - add('\b'); + add_string_char(keep, '\b'); break; // form feed case 'f': - add('\f'); + add_string_char(keep, '\f'); break; // line feed case 'n': - add('\n'); + add_string_char(keep, '\n'); break; // carriage return case 'r': - add('\r'); + add_string_char(keep, '\r'); break; // tab case 't': - add('\t'); + add_string_char(keep, '\t'); break; // unicode escapes @@ -638,10 +653,7 @@ class lexer : public lexer_base JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); // translate codepoint into bytes - encode_utf8(static_cast(codepoint), [this](std::uint32_t byte) - { - add(static_cast(byte)); - }); + add_codepoint(keep, codepoint); break; } @@ -824,7 +836,7 @@ class lexer : public lexer_base case 0x7E: case 0x7F: { - add(current); + add_string_char(keep, current); break; } @@ -860,7 +872,7 @@ class lexer : public lexer_base case 0xDE: case 0xDF: { - if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range(keep, {0x80, 0xBF}))) { return token_type::parse_error; } @@ -870,7 +882,7 @@ class lexer : public lexer_base // U+0800..U+0FFF: bytes E0 A0..BF 80..BF case 0xE0: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0xA0, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -894,7 +906,7 @@ class lexer : public lexer_base case 0xEE: case 0xEF: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -904,7 +916,7 @@ class lexer : public lexer_base // U+D000..U+D7FF: bytes ED 80..9F 80..BF case 0xED: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x9F, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -914,7 +926,7 @@ class lexer : public lexer_base // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF case 0xF0: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -926,7 +938,7 @@ class lexer : public lexer_base case 0xF2: case 0xF3: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -936,7 +948,7 @@ class lexer : public lexer_base // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF case 0xF4: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -1427,7 +1439,7 @@ scan_number_done: token_type convert_number(token_type number_type, std::size_t mantissa_end) { // accept() only needs to know whether the input is valid, so it sets - // discard_number_values (see json.hpp), and an integer token whose + // discard_values (see json.hpp), and an integer token whose // digit count shows that it fits is reported without calling // convert_integer(). A number with up to 18 digits always fits into // both std::uint64_t and std::int64_t (18 nines is about 1e18, below @@ -1442,7 +1454,7 @@ scan_number_done: // json_sax_acceptor, whose number callbacks discard their argument and // return true, and the parser rejects neither integers nor finite // floats. value_unsigned/value_integer are left unset here, so a caller - // that reads the converted value must not set discard_number_values. + // that reads the converted value must not set discard_values. // // On contiguous input, scan_number_bulk_contiguous() converts integer // tokens itself and does not pass them to this function, unless @@ -1450,7 +1462,7 @@ scan_number_done: // reached for input without bulk access (e.g. streams), with // JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous() // falls back to scan_number(). - if (discard_number_values) + if (discard_values()) { constexpr std::size_t safe_digit_count = 18; if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count) @@ -1701,7 +1713,7 @@ scan_number_done: // w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a // value that underflows is a finite number as well. Only a token that // may overflow has to be converted to find out. - if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits::max_exponent10)) + if (JSON_HEDLEY_UNLIKELY(discard_values() && s.exponent + 19 <= std::numeric_limits::max_exponent10)) { value_float = 0; return true; @@ -2033,6 +2045,34 @@ scan_number_done: token_buffer.push_back(static_cast(c)); } + /// add a character of a string token; accept() checks the string but does + /// not keep its value (@a keep is false) + void add_string_char(std::true_type /*keep*/, char_int_type c) + { + token_buffer.push_back(static_cast(c)); + } + + static void add_string_char(std::false_type /*keep*/, char_int_type /*c*/) noexcept {} + + /// add the UTF-8 bytes of a code point from a \\u escape to a string token + void add_codepoint(std::true_type /*keep*/, int codepoint) + { + encode_utf8(static_cast(codepoint), [this](std::uint32_t byte) + { + add(static_cast(byte)); + }); + } + + static void add_codepoint(std::false_type /*keep*/, int /*codepoint*/) noexcept {} + + /// append a run of ordinary string characters (see get_after_string_run()) + void append_string_run(std::true_type /*keep*/, const unsigned char* data, std::size_t n) + { + token_buffer.append(reinterpret_cast(data), n); + } + + static void append_string_run(std::false_type /*keep*/, const unsigned char* /*data*/, std::size_t /*n*/) noexcept {} + public: ///////////////////// // value getters @@ -2427,7 +2467,9 @@ scan_number_done: // string case '\"': - return scan_string(); + // accept() (DiscardValues) checks strings without copying them + // into token_buffer + return scan_string(keep_strings {}); // number case '-': @@ -2512,11 +2554,19 @@ scan_number_done: std::size_t decimal_point_position = std::string::npos; /// whether the caller only needs the token types and never looks at the - /// converted numeric values; set only by accept(), which parses through - /// json_sax_acceptor. When set, convert_number() skips converting integer - /// tokens whose digit count guarantees that they fit into 64 bits (see - /// there) - const bool discard_number_values = false; + /// values of number and string tokens; DiscardValues, set only by accept(), which parses + /// through json_sax_acceptor. When set, convert_number() skips converting + /// integer tokens whose digit count guarantees that they fit into 64 bits + /// (see there), floats are converted only when they may overflow (see + /// convert_accumulated_float()), and strings are checked without being + /// copied into token_buffer (see scan()) + static bool discard_values() noexcept + { + return DiscardValues; + } + + /// the tag of scan_string(): whether string tokens are kept + using keep_strings = typename std::conditional::type; }; } // namespace detail diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index 5bf8f1b6b..023327166 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -72,14 +72,14 @@ This class implements a parser for JSON text. Nested arrays and objects are trac stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read is reported as SAX events. */ -template +template class parser { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; - using lexer_t = lexer; + using lexer_t = lexer; using token_type = typename lexer_t::token_type; public: @@ -88,10 +88,9 @@ class parser parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool ignore_comments = false, - const bool ignore_trailing_commas_ = false, - const bool discard_number_values_ = false) + const bool ignore_trailing_commas_ = false) : callback(std::move(cb)) - , m_lexer(std::move(adapter), ignore_comments, discard_number_values_) + , m_lexer(std::move(adapter), ignore_comments) , allow_exceptions(allow_exceptions_) , ignore_trailing_commas(ignore_trailing_commas_) { diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 4d4f0a1cd..7bed813df 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -150,7 +150,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // can be restored when json_pointer backwards compatibility is removed // friend ::nlohmann::json_pointer; - template + template friend class ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; template @@ -183,12 +183,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::parser_callback_tcb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false, - const bool ignore_trailing_commas = false, - const bool discard_number_values = false + const bool ignore_trailing_commas = false ) { return ::nlohmann::detail::parser(std::move(adapter), - std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values); + std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); } private: @@ -3117,7 +3116,9 @@ public: const bool ignore_comments, const bool ignore_trailing_commas) { - return parser(std::forward(adapter), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); + // the parser and lexer that discard the values of tokens (see lexer) + return ::nlohmann::detail::parser::type, true>(std::forward(adapter), + nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } /// generate SAX events from the input behind an already created adapter diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 84720a816..4bca3a3a5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -13095,8 +13095,13 @@ constexpr bool input_adapter_bulk_window(std::false_type /*detected*/) @brief lexical analysis This class organizes the lexical analysis during JSON deserialization. + +DiscardValues: the caller only needs the token types and never looks at the values of number and string tokens (only +accept(), which parses through json_sax_acceptor). A template parameter rather than a member, so that the lexer of +parse() is compiled without the paths of accept(): with both string scans in one scan(), parse() was up to 2 % slower +on Apple M1. */ -template +template class lexer : public lexer_base { using number_integer_t = typename BasicJsonType::number_integer_t; @@ -13154,10 +13159,9 @@ class lexer : public lexer_base public: using token_type = typename lexer_base::token_type; - explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept : ia(std::move(adapter)) , ignore_comments(ignore_comments_) - , discard_number_values(discard_number_values_) { // the token string of an error before the first token is everything // read so far @@ -13274,6 +13278,8 @@ class lexer : public lexer_base checks if it is inside the range. If a violation was detected, set up an error message and return false. Otherwise, return true. + @param[in] keep whether the bytes are added to token_buffer (false for + accept(), see scan()) @param[in] ranges list of integers; interpreted as list of pairs of inclusive lower and upper bound, respectively @@ -13282,17 +13288,18 @@ class lexer : public lexer_base @return true if and only if no range violation was detected */ - bool next_byte_in_range(std::initializer_list ranges) + template + bool next_byte_in_range(std::integral_constant keep, std::initializer_list ranges) { JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); - add(current); + add_string_char(keep, current); for (auto range = ranges.begin(); range != ranges.end(); ++range) { get(); if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions) { - add(current); + add_string_char(keep, current); } else { @@ -13305,14 +13312,16 @@ class lexer : public lexer_base } /// contiguous input: bulk-append the run of ordinary characters and complete - /// well-formed UTF-8 sequences starting at the current read position, then + /// well-formed UTF-8 sequences starting at the current read position (only + /// skip it if @a keep is false), then /// read the first byte that needs individual handling (the closing quote, an /// escape, a control character, or an ill-formed UTF-8 byte) like get(). /// That byte is taken from the buffer that was just scanned. Through get(), /// Apple Clang merged this read with the other get() calls of the scan /// function, which load the read position again right after it was stored /// (3% of json::accept() on jeopardy.json on Apple M1). - char_int_type get_after_string_run(std::true_type /*bulk*/) + template + char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant keep) { // a pending unget must be consumed through the normal path first if (next_unget) @@ -13330,7 +13339,7 @@ class lexer : public lexer_base const std::size_t pos = lexer_string_bulk_run(data, remaining); if (pos != 0) { - token_buffer.append(reinterpret_cast(data), pos); + append_string_run(keep, data, pos); bulk_consume(reinterpret_cast(data), pos); } if (pos == remaining) @@ -13374,7 +13383,8 @@ class lexer : public lexer_base } /// streaming input: no bulk fast path - char_int_type get_after_string_run(std::false_type /*bulk*/) + template + char_int_type get_after_string_run(std::false_type /*bulk*/, std::integral_constant /*keep*/) { return get(); } @@ -13388,13 +13398,18 @@ class lexer : public lexer_base may contain \0 bytes), and token_buffer.size() is the number of bytes in the string. + @param[in] keep whether the string is copied into token_buffer; if false + (accept(), see scan()), it is only checked, with the same + errors, and token_buffer stays empty + @return token_type::value_string if string could be successfully scanned, token_type::parse_error otherwise @note In case of errors, variable error_message contains a textual description. */ - token_type scan_string() + template + token_type scan_string(std::integral_constant keep) { // reset token_buffer (ignore opening quote) reset(); @@ -13406,7 +13421,7 @@ class lexer : public lexer_base { // bulk-consume ordinary characters from contiguous input, then // get the next character, the one that ends the run - switch (get_after_string_run(std::integral_constant())) + switch (get_after_string_run(std::integral_constant(), keep)) { // end of file while parsing the string case char_traits::eof(): @@ -13428,35 +13443,35 @@ class lexer : public lexer_base { // quotation mark case '\"': - add('\"'); + add_string_char(keep, '\"'); break; // reverse solidus case '\\': - add('\\'); + add_string_char(keep, '\\'); break; // solidus case '/': - add('/'); + add_string_char(keep, '/'); break; // backspace case 'b': - add('\b'); + add_string_char(keep, '\b'); break; // form feed case 'f': - add('\f'); + add_string_char(keep, '\f'); break; // line feed case 'n': - add('\n'); + add_string_char(keep, '\n'); break; // carriage return case 'r': - add('\r'); + add_string_char(keep, '\r'); break; // tab case 't': - add('\t'); + add_string_char(keep, '\t'); break; // unicode escapes @@ -13524,10 +13539,7 @@ class lexer : public lexer_base JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); // translate codepoint into bytes - encode_utf8(static_cast(codepoint), [this](std::uint32_t byte) - { - add(static_cast(byte)); - }); + add_codepoint(keep, codepoint); break; } @@ -13710,7 +13722,7 @@ class lexer : public lexer_base case 0x7E: case 0x7F: { - add(current); + add_string_char(keep, current); break; } @@ -13746,7 +13758,7 @@ class lexer : public lexer_base case 0xDE: case 0xDF: { - if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range(keep, {0x80, 0xBF}))) { return token_type::parse_error; } @@ -13756,7 +13768,7 @@ class lexer : public lexer_base // U+0800..U+0FFF: bytes E0 A0..BF 80..BF case 0xE0: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0xA0, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -13780,7 +13792,7 @@ class lexer : public lexer_base case 0xEE: case 0xEF: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -13790,7 +13802,7 @@ class lexer : public lexer_base // U+D000..U+D7FF: bytes ED 80..9F 80..BF case 0xED: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x9F, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -13800,7 +13812,7 @@ class lexer : public lexer_base // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF case 0xF0: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -13812,7 +13824,7 @@ class lexer : public lexer_base case 0xF2: case 0xF3: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -13822,7 +13834,7 @@ class lexer : public lexer_base // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF case 0xF4: { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { return token_type::parse_error; } @@ -14313,7 +14325,7 @@ scan_number_done: token_type convert_number(token_type number_type, std::size_t mantissa_end) { // accept() only needs to know whether the input is valid, so it sets - // discard_number_values (see json.hpp), and an integer token whose + // discard_values (see json.hpp), and an integer token whose // digit count shows that it fits is reported without calling // convert_integer(). A number with up to 18 digits always fits into // both std::uint64_t and std::int64_t (18 nines is about 1e18, below @@ -14328,7 +14340,7 @@ scan_number_done: // json_sax_acceptor, whose number callbacks discard their argument and // return true, and the parser rejects neither integers nor finite // floats. value_unsigned/value_integer are left unset here, so a caller - // that reads the converted value must not set discard_number_values. + // that reads the converted value must not set discard_values. // // On contiguous input, scan_number_bulk_contiguous() converts integer // tokens itself and does not pass them to this function, unless @@ -14336,7 +14348,7 @@ scan_number_done: // reached for input without bulk access (e.g. streams), with // JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous() // falls back to scan_number(). - if (discard_number_values) + if (discard_values()) { constexpr std::size_t safe_digit_count = 18; if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count) @@ -14587,7 +14599,7 @@ scan_number_done: // w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a // value that underflows is a finite number as well. Only a token that // may overflow has to be converted to find out. - if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits::max_exponent10)) + if (JSON_HEDLEY_UNLIKELY(discard_values() && s.exponent + 19 <= std::numeric_limits::max_exponent10)) { value_float = 0; return true; @@ -14919,6 +14931,34 @@ scan_number_done: token_buffer.push_back(static_cast(c)); } + /// add a character of a string token; accept() checks the string but does + /// not keep its value (@a keep is false) + void add_string_char(std::true_type /*keep*/, char_int_type c) + { + token_buffer.push_back(static_cast(c)); + } + + static void add_string_char(std::false_type /*keep*/, char_int_type /*c*/) noexcept {} + + /// add the UTF-8 bytes of a code point from a \\u escape to a string token + void add_codepoint(std::true_type /*keep*/, int codepoint) + { + encode_utf8(static_cast(codepoint), [this](std::uint32_t byte) + { + add(static_cast(byte)); + }); + } + + static void add_codepoint(std::false_type /*keep*/, int /*codepoint*/) noexcept {} + + /// append a run of ordinary string characters (see get_after_string_run()) + void append_string_run(std::true_type /*keep*/, const unsigned char* data, std::size_t n) + { + token_buffer.append(reinterpret_cast(data), n); + } + + static void append_string_run(std::false_type /*keep*/, const unsigned char* /*data*/, std::size_t /*n*/) noexcept {} + public: ///////////////////// // value getters @@ -15313,7 +15353,9 @@ scan_number_done: // string case '\"': - return scan_string(); + // accept() (DiscardValues) checks strings without copying them + // into token_buffer + return scan_string(keep_strings {}); // number case '-': @@ -15398,11 +15440,19 @@ scan_number_done: std::size_t decimal_point_position = std::string::npos; /// whether the caller only needs the token types and never looks at the - /// converted numeric values; set only by accept(), which parses through - /// json_sax_acceptor. When set, convert_number() skips converting integer - /// tokens whose digit count guarantees that they fit into 64 bits (see - /// there) - const bool discard_number_values = false; + /// values of number and string tokens; DiscardValues, set only by accept(), which parses + /// through json_sax_acceptor. When set, convert_number() skips converting + /// integer tokens whose digit count guarantees that they fit into 64 bits + /// (see there), floats are converted only when they may overflow (see + /// convert_accumulated_float()), and strings are checked without being + /// copied into token_buffer (see scan()) + static bool discard_values() noexcept + { + return DiscardValues; + } + + /// the tag of scan_string(): whether string tokens are kept + using keep_strings = typename std::conditional::type; }; } // namespace detail @@ -23023,14 +23073,14 @@ This class implements a parser for JSON text. Nested arrays and objects are trac stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read is reported as SAX events. */ -template +template class parser { using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; - using lexer_t = lexer; + using lexer_t = lexer; using token_type = typename lexer_t::token_type; public: @@ -23039,10 +23089,9 @@ class parser parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool ignore_comments = false, - const bool ignore_trailing_commas_ = false, - const bool discard_number_values_ = false) + const bool ignore_trailing_commas_ = false) : callback(std::move(cb)) - , m_lexer(std::move(adapter), ignore_comments, discard_number_values_) + , m_lexer(std::move(adapter), ignore_comments) , allow_exceptions(allow_exceptions_) , ignore_trailing_commas(ignore_trailing_commas_) { @@ -37439,7 +37488,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // can be restored when json_pointer backwards compatibility is removed // friend ::nlohmann::json_pointer; - template + template friend class ::nlohmann::detail::parser; friend ::nlohmann::detail::serializer; template @@ -37472,12 +37521,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::parser_callback_tcb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false, - const bool ignore_trailing_commas = false, - const bool discard_number_values = false + const bool ignore_trailing_commas = false ) { return ::nlohmann::detail::parser(std::move(adapter), - std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values); + std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); } private: @@ -40406,7 +40454,9 @@ public: const bool ignore_comments, const bool ignore_trailing_commas) { - return parser(std::forward(adapter), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); + // the parser and lexer that discard the values of tokens (see lexer) + return ::nlohmann::detail::parser::type, true>(std::forward(adapter), + nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); } /// generate SAX events from the input behind an already created adapter diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 090ab4b41..987e25858 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -2982,6 +2982,67 @@ TEST_CASE("accept() agrees with parse() on floats around the overflow limit") } } +TEST_CASE("accept() agrees with parse() on strings, which it checks without copying them") +{ + // accept() scans strings and object keys with a separate instantiation of + // the string scanner that does not copy them into the token buffer; it + // must accept and reject exactly what parse() does + const std::vector pieces = + { + "", "abc", R"(\"\\\/\b\f\n\r\t)", R"(\u0041\u00e4\u20AC)", "\\ud83d\\ude00", "\\uD83D\\uDE00", + "\\ud800", "\\udc00", "\\ud800\\u0041", "\\ud800x", "\\udbff\\udfff", "\\u12", "\\u12g4", "\\x", "\\", + "\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf", "\xc0\xaf", "\xed\xa0\x80", "\xf4\x90\x80\x80", + "\xff", "\xe2\x82", "\xc3", "\x01", "\x1f", "\x7f", "\n", std::string(1, '\0') + }; + + for (std::size_t run = 0; run <= 70; ++run) + { + for (const std::string& piece : pieces) + { + // a run of ordinary characters puts the piece at every offset of the bulk scanners + const std::string str = std::string(run, 'a') + piece; + std::string opened = "\""; + opened += str; + std::string closed = opened; + closed += '"'; + std::vector texts; + texts.push_back(closed); + texts.emplace_back("["); + texts.back() += closed; + texts.back() += ']'; + texts.emplace_back("{"); + texts.back() += closed; + texts.back() += ":1}"; + texts.emplace_back("{\"k\":"); + texts.back() += closed; + texts.back() += ','; + texts.back() += closed; + texts.back() += ":["; + texts.back() += closed; + texts.back() += "]}"; + texts.emplace_back("["); + texts.back() += opened; + for (const std::string& text : texts) + { + CAPTURE(text) + for (int options = 0; options < 4; ++options) + { + const bool ignore_comments = (options & 1) != 0; + const bool ignore_trailing_commas = (options & 2) != 0; + const bool parses = !json::parse(text, nullptr, false, ignore_comments, ignore_trailing_commas).is_discarded(); + CHECK(json::accept(text, ignore_comments, ignore_trailing_commas) == parses); + + std::istringstream stream(text); + CHECK(json::accept(stream, ignore_comments, ignore_trailing_commas) == parses); + + const std::list chars(text.begin(), text.end()); + CHECK(json::accept(chars.begin(), chars.end(), ignore_comments, ignore_trailing_commas) == parses); + } + } + } + } +} + namespace { // a DOM parser that reports parse errors without throwing, as in the sax_no_exception example From 9742edc294a0ae08fac79539da02ebef8f946948 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:23:12 +0100 Subject: [PATCH 58/63] BSON writer: write to_bson(j) in one pass, with sizes written afterwards (#5979) Signed-off-by: Niels Lohmann --- .../nlohmann/detail/output/binary_writer.hpp | 229 +++++++++++++++- .../detail/output/output_adapters.hpp | 26 ++ single_include/nlohmann/json.hpp | 255 +++++++++++++++++- tests/src/unit-bson.cpp | 151 +++++++++++ 4 files changed, 659 insertions(+), 2 deletions(-) diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 8732e52b4..9d21d2a7c 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -2088,7 +2088,8 @@ class binary_writer /// arrays: the first index with one more digit; only maintained by /// @ref calc_bson_sizes std::uint64_t index_boundary = 10; - /// @ref calc_bson_sizes only: where its size goes in the table + /// @ref calc_bson_sizes: where its size goes in the table; @ref + /// write_bson_document_single_pass: the position of its size field std::size_t size_slot; /// @ref calc_bson_sizes only: the size of its entries seen so far std::size_t entries_size = 0; @@ -2339,6 +2340,199 @@ class binary_writer } } + template + using sink_written_t = decltype(std::declval().written()); + + /// whether every integer fits the 64 bits BSON has, so that writing an + /// array of numbers needs no range check + static constexpr bool bson_integers_fit_64_bits = + std::numeric_limits::is_specialized + && std::numeric_limits::digits <= std::numeric_limits::digits + && std::numeric_limits::is_specialized + && std::numeric_limits::digits <= std::numeric_limits::digits; + + /*! + @brief whether BSON documents can be written in a single pass: the sink + can overwrite what it has written (output_vector_sink), and + @ref bson_integers_fit_64_bits + */ + using bson_single_pass_sink = conjunction, std::integral_constant>; + + /// write a placeholder for the size of an object or array whose entries + /// follow; @return its position, for @ref close_bson_container + std::size_t write_bson_size_placeholder() + { + const std::size_t pos = oa.written(); + // zero in any byte order + const std::array zero{}; + oa.write_characters(zero.data(), zero.size()); + return pos; + } + + /// end the object or array whose size is at @a pos and write its size + /// there: everything from that field to the terminating null byte + /// @return false (and the size is not written) if it does not fit + bool close_bson_container(const std::size_t pos) + { + oa.write_character(to_char_type(0x00)); + const std::size_t size = oa.written() - pos; + if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(size))) + { + return false; + } + const auto le_size = static_cast(size); + oa.overwrite(pos, is_little_endian ? le_size : byteswap(le_size)); + return true; + } + + /*! + @brief check the value @a j of a BSON entry as @ref calc_bson_value_size + does, for @ref write_bson_document_single_pass + @return at least the size of the value + A number, boolean, or null needs no check there (its integers have at most + 64 bits) and has at most 8 characters; a discarded value is rejected with + the same exception when it is written. + */ + std::size_t check_bson_value(const BasicJsonType& j) + { + return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8; + } + + /// whether an entry of @a entry_size characters keeps the document that + /// begins at @a start, closed by @a open_containers null bytes, within + /// the 32-bit size of BSON + bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept + { + const auto limit = static_cast((std::numeric_limits::max)()); + const std::size_t written = oa.written() - start; + return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size; + } + + /*! + @brief Serializes the JSON object @a document as a BSON document in a + single pass, writing the size of each object and array once it is + complete + + Checks every key and value as @ref calc_bson_sizes does, in the same + order, so the same exception is thrown; the output written up to then is + discarded with the vector it was written to. + + @return false as soon as the document or a string, binary value, object, + or array in it would be larger than the 32-bit BSON sizes allow; + nothing large has been written then, and the caller writes the + document in two passes, which reports that error + */ + template::value, int>::type = 0> + bool write_bson_document_single_pass(const BasicJsonType& document) + { + const std::size_t start = oa.written(); + // the object or array whose entries are being written (with the + // position of its size), and the ones it is in + bson_frame current(&document, write_bson_size_placeholder()); + std::vector parents; + + while (true) + { + const BasicJsonType* nested = nullptr; + std::size_t nested_size_pos = 0; + if (current.value->is_object()) + { + const auto& object = *current.value->m_data.m_value.object; + while (nested == nullptr && current.member != object.cend()) + { + const auto& el = *current.member; + ++current.member; + const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second); + const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second); + if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1))) + { + return false; + } + if (el.second.is_structured()) + { + write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04); + nested_size_pos = write_bson_size_placeholder(); + nested = &el.second; + } + else + { + write_bson_value(el.first, el.second); + } + } + } + else + { + const auto& array = *current.value->m_data.m_value.array; + while (nested == nullptr && current.index < array.size()) + { + if (is_bson_fixed_size(array[current.index])) + { + write_bson_array_fixed_entries(current); + if (current.index == array.size()) + { + break; + } + } + + // a string, binary value, object or array + const BasicJsonType& el = array[current.index]; + const string_t index_name(current.index_name.data(), current.index_name_size); + current.next_index(); + const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el); + if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1))) + { + return false; + } + if (el.is_structured()) + { + write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04); + nested_size_pos = write_bson_size_placeholder(); + nested = ⪙ + } + else + { + write_bson_value(index_name, el); + } + } + } + + if (nested != nullptr) + { + if (nested->is_array()) + { + // an array of booleans, numbers and null is written without a frame on the stack + bson_frame leaf(nested, nested_size_pos); + write_bson_array_fixed_entries(leaf); + if (leaf.index == nested->m_data.m_value.array->size()) + { + if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos))) + { + return false; + } + continue; + } + parents.push_back(std::move(current)); + current = std::move(leaf); + continue; + } + parents.push_back(std::move(current)); + current = bson_frame(nested, nested_size_pos); + continue; + } + + if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot))) + { + return false; + } + if (parents.empty()) + { + return true; + } + current = std::move(parents.back()); + parents.pop_back(); + } + } + /*! @brief Serializes the JSON object @a document as a BSON document @@ -2350,6 +2544,39 @@ class binary_writer @pre document.type() == value_t::object */ void write_bson_document(const BasicJsonType& document) + { + write_bson_document(document, bson_single_pass_sink()); + } + + /// the sink can overwrite: one pass, unless the error handler replaces or + /// drops ill-formed UTF-8, which changes the sizes the checks compute + void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/) + { + const std::size_t start = oa.written(); + if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict) + && write_bson_document_single_pass(document)) + { + return; + } + // a length does not fit: start over, so that it is reported as the + // two passes report it, before anything is written + oa.rewind(start); + write_bson_document_two_pass(document); + } + + void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/) + { + write_bson_document_two_pass(document); + } + + /*! + @brief Serializes the JSON object @a document as a BSON document: the + sizes of all objects and arrays first, then the entries + + @param[in] document the JSON object to serialize + @pre document.type() == value_t::object + */ + void write_bson_document_two_pass(const BasicJsonType& document) { std::vector nested_sizes; const std::size_t document_size = calc_bson_sizes(document, nested_sizes); diff --git a/include/nlohmann/detail/output/output_adapters.hpp b/include/nlohmann/detail/output/output_adapters.hpp index e40d98548..e9e57ab9a 100644 --- a/include/nlohmann/detail/output/output_adapters.hpp +++ b/include/nlohmann/detail/output/output_adapters.hpp @@ -11,6 +11,7 @@ #include // max #include // array #include // size_t +#include // uint32_t #include // memcpy #include // shared_ptr, make_shared #include // basic_string @@ -227,6 +228,31 @@ class output_vector_sink void flush() noexcept {} + /// the number of characters written so far, the position of the next one + std::size_t written() const noexcept + { + return position(); + } + + /// @brief replace the 4 bytes written before at position @a pos with + /// those of @a n, in the byte order of the machine + /// + /// Lets a writer put a length in front of data it has written since, such + /// as the size of a BSON document, without computing it in advance. + void overwrite(const std::size_t pos, const std::uint32_t n) noexcept + { + static_assert(sizeof(CharType) == 1, "overwrite() writes bytes"); + JSON_ASSERT(pos + sizeof(n) <= position()); + std::memcpy(v->data() + pos, &n, sizeof(n)); + } + + /// drop the characters written from position @a pos on + void rewind(const std::size_t pos) noexcept + { + JSON_ASSERT(pos <= position()); + cursor = v->data() + pos; + } + private: /// number of characters of *v written so far std::size_t position() const noexcept diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4bca3a3a5..3c2670039 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -27161,6 +27161,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // max #include // array #include // size_t +#include // uint32_t #include // memcpy #include // shared_ptr, make_shared #include // basic_string @@ -27378,6 +27379,31 @@ class output_vector_sink void flush() noexcept {} + /// the number of characters written so far, the position of the next one + std::size_t written() const noexcept + { + return position(); + } + + /// @brief replace the 4 bytes written before at position @a pos with + /// those of @a n, in the byte order of the machine + /// + /// Lets a writer put a length in front of data it has written since, such + /// as the size of a BSON document, without computing it in advance. + void overwrite(const std::size_t pos, const std::uint32_t n) noexcept + { + static_assert(sizeof(CharType) == 1, "overwrite() writes bytes"); + JSON_ASSERT(pos + sizeof(n) <= position()); + std::memcpy(v->data() + pos, &n, sizeof(n)); + } + + /// drop the characters written from position @a pos on + void rewind(const std::size_t pos) noexcept + { + JSON_ASSERT(pos <= position()); + cursor = v->data() + pos; + } + private: /// number of characters of *v written so far std::size_t position() const noexcept @@ -29822,7 +29848,8 @@ class binary_writer /// arrays: the first index with one more digit; only maintained by /// @ref calc_bson_sizes std::uint64_t index_boundary = 10; - /// @ref calc_bson_sizes only: where its size goes in the table + /// @ref calc_bson_sizes: where its size goes in the table; @ref + /// write_bson_document_single_pass: the position of its size field std::size_t size_slot; /// @ref calc_bson_sizes only: the size of its entries seen so far std::size_t entries_size = 0; @@ -30073,6 +30100,199 @@ class binary_writer } } + template + using sink_written_t = decltype(std::declval().written()); + + /// whether every integer fits the 64 bits BSON has, so that writing an + /// array of numbers needs no range check + static constexpr bool bson_integers_fit_64_bits = + std::numeric_limits::is_specialized + && std::numeric_limits::digits <= std::numeric_limits::digits + && std::numeric_limits::is_specialized + && std::numeric_limits::digits <= std::numeric_limits::digits; + + /*! + @brief whether BSON documents can be written in a single pass: the sink + can overwrite what it has written (output_vector_sink), and + @ref bson_integers_fit_64_bits + */ + using bson_single_pass_sink = conjunction, std::integral_constant>; + + /// write a placeholder for the size of an object or array whose entries + /// follow; @return its position, for @ref close_bson_container + std::size_t write_bson_size_placeholder() + { + const std::size_t pos = oa.written(); + // zero in any byte order + const std::array zero{}; + oa.write_characters(zero.data(), zero.size()); + return pos; + } + + /// end the object or array whose size is at @a pos and write its size + /// there: everything from that field to the terminating null byte + /// @return false (and the size is not written) if it does not fit + bool close_bson_container(const std::size_t pos) + { + oa.write_character(to_char_type(0x00)); + const std::size_t size = oa.written() - pos; + if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(size))) + { + return false; + } + const auto le_size = static_cast(size); + oa.overwrite(pos, is_little_endian ? le_size : byteswap(le_size)); + return true; + } + + /*! + @brief check the value @a j of a BSON entry as @ref calc_bson_value_size + does, for @ref write_bson_document_single_pass + @return at least the size of the value + A number, boolean, or null needs no check there (its integers have at most + 64 bits) and has at most 8 characters; a discarded value is rejected with + the same exception when it is written. + */ + std::size_t check_bson_value(const BasicJsonType& j) + { + return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8; + } + + /// whether an entry of @a entry_size characters keeps the document that + /// begins at @a start, closed by @a open_containers null bytes, within + /// the 32-bit size of BSON + bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept + { + const auto limit = static_cast((std::numeric_limits::max)()); + const std::size_t written = oa.written() - start; + return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size; + } + + /*! + @brief Serializes the JSON object @a document as a BSON document in a + single pass, writing the size of each object and array once it is + complete + + Checks every key and value as @ref calc_bson_sizes does, in the same + order, so the same exception is thrown; the output written up to then is + discarded with the vector it was written to. + + @return false as soon as the document or a string, binary value, object, + or array in it would be larger than the 32-bit BSON sizes allow; + nothing large has been written then, and the caller writes the + document in two passes, which reports that error + */ + template::value, int>::type = 0> + bool write_bson_document_single_pass(const BasicJsonType& document) + { + const std::size_t start = oa.written(); + // the object or array whose entries are being written (with the + // position of its size), and the ones it is in + bson_frame current(&document, write_bson_size_placeholder()); + std::vector parents; + + while (true) + { + const BasicJsonType* nested = nullptr; + std::size_t nested_size_pos = 0; + if (current.value->is_object()) + { + const auto& object = *current.value->m_data.m_value.object; + while (nested == nullptr && current.member != object.cend()) + { + const auto& el = *current.member; + ++current.member; + const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second); + const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second); + if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1))) + { + return false; + } + if (el.second.is_structured()) + { + write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04); + nested_size_pos = write_bson_size_placeholder(); + nested = &el.second; + } + else + { + write_bson_value(el.first, el.second); + } + } + } + else + { + const auto& array = *current.value->m_data.m_value.array; + while (nested == nullptr && current.index < array.size()) + { + if (is_bson_fixed_size(array[current.index])) + { + write_bson_array_fixed_entries(current); + if (current.index == array.size()) + { + break; + } + } + + // a string, binary value, object or array + const BasicJsonType& el = array[current.index]; + const string_t index_name(current.index_name.data(), current.index_name_size); + current.next_index(); + const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el); + if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1))) + { + return false; + } + if (el.is_structured()) + { + write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04); + nested_size_pos = write_bson_size_placeholder(); + nested = ⪙ + } + else + { + write_bson_value(index_name, el); + } + } + } + + if (nested != nullptr) + { + if (nested->is_array()) + { + // an array of booleans, numbers and null is written without a frame on the stack + bson_frame leaf(nested, nested_size_pos); + write_bson_array_fixed_entries(leaf); + if (leaf.index == nested->m_data.m_value.array->size()) + { + if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos))) + { + return false; + } + continue; + } + parents.push_back(std::move(current)); + current = std::move(leaf); + continue; + } + parents.push_back(std::move(current)); + current = bson_frame(nested, nested_size_pos); + continue; + } + + if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot))) + { + return false; + } + if (parents.empty()) + { + return true; + } + current = std::move(parents.back()); + parents.pop_back(); + } + } + /*! @brief Serializes the JSON object @a document as a BSON document @@ -30084,6 +30304,39 @@ class binary_writer @pre document.type() == value_t::object */ void write_bson_document(const BasicJsonType& document) + { + write_bson_document(document, bson_single_pass_sink()); + } + + /// the sink can overwrite: one pass, unless the error handler replaces or + /// drops ill-formed UTF-8, which changes the sizes the checks compute + void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/) + { + const std::size_t start = oa.written(); + if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict) + && write_bson_document_single_pass(document)) + { + return; + } + // a length does not fit: start over, so that it is reported as the + // two passes report it, before anything is written + oa.rewind(start); + write_bson_document_two_pass(document); + } + + void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/) + { + write_bson_document_two_pass(document); + } + + /*! + @brief Serializes the JSON object @a document as a BSON document: the + sizes of all objects and arrays first, then the entries + + @param[in] document the JSON object to serialize + @pre document.type() == value_t::object + */ + void write_bson_document_two_pass(const BasicJsonType& document) { std::vector nested_sizes; const std::size_t document_size = calc_bson_sizes(document, nested_sizes); diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index f93e0a71b..f1f43ccd7 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -19,6 +19,7 @@ #include using nlohmann::json; +#include #include #include #include @@ -2300,3 +2301,153 @@ TEST_CASE("BSON large strings and binaries (chunked reader)") } } } + +TEST_CASE("BSON written in one pass and in two passes") +{ + // to_bson() into a returned vector writes each size once the object or + // array is complete; through an output adapter, all sizes are computed + // first. Both must produce the same bytes and throw the same exceptions. + const auto two_pass = [](const json & j, const json::error_handler_t handler) + { + std::vector result; + json::to_bson(j, result, handler); + return result; + }; + + const auto check_same = [&](const json & j, const json::error_handler_t handler) + { + CAPTURE(j.dump(-1, ' ', true, json::error_handler_t::replace)) + CAPTURE(static_cast(handler)) +#if !defined(JSON_NOEXCEPTION) + std::string one_pass_error; + std::string two_pass_error; + std::vector one; + std::vector two; + try + { + one = json::to_bson(j, handler); + } + catch (const json::exception& e) + { + one_pass_error = e.what(); + } + try + { + two = two_pass(j, handler); + } + catch (const json::exception& e) + { + two_pass_error = e.what(); + } + CHECK(one_pass_error == two_pass_error); + CHECK(one == two); +#else + CHECK(json::to_bson(j, handler) == two_pass(j, handler)); +#endif + }; + + const std::array handlers = {{ + json::error_handler_t::keep, json::error_handler_t::strict, + json::error_handler_t::replace, json::error_handler_t::ignore + } + }; + + SECTION("corpus") + { + for (const auto& value : utils::round_trip_corpus::values()) + { + const json document = value.is_object() ? value : json{{"value", value}}; + for (const auto handler : handlers) + { + check_same(document, handler); + } + } + } + + SECTION("nesting and arrays of every length class") + { + json document = json::object(); + document["empty object"] = json::object(); + document["empty array"] = json::array(); + for (const std::size_t n : + { + 1u, 9u, 10u, 11u, 99u, 100u, 101u, 1000u + }) + { + json numbers = json::array(); + json mixed = json::array(); + for (std::size_t i = 0; i < n; ++i) + { + numbers.push_back(i % 3 == 0 ? json(static_cast(i) / 3) : json(static_cast(i) * 1000003)); + switch (i % 4) + { + case 0: + mixed.push_back(json("s" + std::to_string(i))); + break; + case 1: + mixed.push_back(json::array({i, json::object({{"k", i}})})); + break; + case 2: + mixed.push_back(json(nullptr)); + break; + default: + mixed.push_back(json(i % 2 == 0)); + break; + } + } + document["numbers " + std::to_string(n)] = numbers; + document["mixed " + std::to_string(n)] = mixed; + document["nested " + std::to_string(n)] = json::object({{"a", numbers}, {"b", json::array({mixed, json::object()})}}); + } + document["binary"] = json::binary({1, 2, 3}, 0x80); + document["binary old"] = json::binary({1, 2, 3}, 0x02); + for (const auto handler : handlers) + { + check_same(document, handler); + } + } + +#if !defined(JSON_NOEXCEPTION) + // every entry here throws, which aborts without exceptions + SECTION("errors at every position") + { + // an entry that cannot be written, at each place among valid ones + const std::string bad_utf8 = "\xC3\x28"; + const std::array bad_entries = + { + { + json::object({{std::string("a\0b", 3), 1}}), + json::object({{"s", bad_utf8}}), + json::object({{bad_utf8, 1}}), + json::array({1, 2, bad_utf8}), + json::object({{"b", json::binary({1}, 0x100)}}), + json::object({{"d", json(json::value_t::discarded)}}), + json::array({json(json::value_t::discarded)}), + } + }; + for (const auto& bad : bad_entries) + { + for (std::size_t pos = 0; pos < 4; ++pos) + { + json document = json::object(); + json list = json::array(); + for (std::size_t i = 0; i < 4; ++i) + { + if (i == pos) + { + document["x" + std::to_string(i)] = bad; + list.push_back(bad); + } + document["n" + std::to_string(i)] = json::array({i, "str", json::object({{"k", i}})}); + list.push_back(i); + } + document["list"] = list; + for (const auto handler : handlers) + { + check_same(document, handler); + } + } + } + } +#endif +} From f022e3c59c21fff5be9a571070f43a45d978dd2f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:23:27 +0100 Subject: [PATCH 59/63] Binary readers and writers: faster UTF-8 checks of strings (#5982) Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 24 +- include/nlohmann/detail/input/simd_scan.hpp | 72 ++++++ include/nlohmann/detail/input/string_scan.hpp | 58 +++++ .../nlohmann/detail/output/binary_writer.hpp | 89 ++++--- single_include/nlohmann/json.hpp | 243 +++++++++++++++--- tests/src/unit-guard-page.cpp | 1 + tests/src/unit-unicode.cpp | 38 +++ 7 files changed, 461 insertions(+), 64 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 190edb112..da5a0263e 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -4719,6 +4719,19 @@ class binary_reader return get_bytes(len, "string", result); } + /// whether @a s is valid UTF-8: ASCII eight bytes at a time, the rest + /// sequence by sequence (as the binary writers check strings) + static bool is_valid_utf8_string(const string_t& s, std::true_type /*is_byte*/) noexcept + { + return valid_utf8_string(reinterpret_cast(s.data()), s.size()); + } + + /// whether @a s, of a character type wider than a byte, is valid UTF-8 + static bool is_valid_utf8_string(const string_t& s, std::false_type /*is_byte*/) noexcept + { + return is_valid_utf8(s); + } + /*! @brief validate a decoded text string (value or object key) against @ref error_handler @@ -4738,7 +4751,16 @@ class binary_reader */ bool check_string_utf8(string_t& result, const char* context) { - if (error_handler == error_handler_t::keep || is_valid_utf8(result)) + return error_handler == error_handler_t::keep || check_string_utf8_handler(result, context); + } + + /// @ref check_string_utf8 for an error handler other than @ref + /// error_handler_t::keep; out of line, so that reading strings without a + /// check does not pay for the code of the check + JSON_HEDLEY_NEVER_INLINE + bool check_string_utf8_handler(string_t& result, const char* context) + { + if (is_valid_utf8_string(result, std::integral_constant())) { return true; } diff --git a/include/nlohmann/detail/input/simd_scan.hpp b/include/nlohmann/detail/input/simd_scan.hpp index 3abb911ee..6c34421fe 100644 --- a/include/nlohmann/detail/input/simd_scan.hpp +++ b/include/nlohmann/detail/input/simd_scan.hpp @@ -322,6 +322,78 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons block_out = block; return false; } + +/*! +@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary) + +Like vector_utf8_run, but quotes, backslashes, and control characters are +ordinary characters. Returns the start of the first block in which the check +found an error (which may belong to a sequence begun in the block before) or +where fewer than 16 bytes are left; a sequence may be cut at that point, so the +caller resumes at its start, one sequence at a time. +*/ +JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept +{ + using lookup = utf8_lookup4<>; +#if JSON_SCAN_NEON + const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data()); + const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data()); + const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data()); + uint8x16_t prev = vdupq_n_u8(0); + while (e - block >= 16) + { + const uint8x16_t in = vld1q_u8(block); + if (vmaxvq_u8(vorrq_u8(in, prev)) < 0x80) + { + // ASCII here, and no sequence from the block before to finish + prev = in; + block += 16; + continue; + } + const uint8x16_t prev1 = vextq_u8(prev, in, 15); + const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4))); + const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80))); + const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc); + if (vmaxvq_u8(err) != 0) + { + break; + } + prev = in; + block += 16; + } +#else + const __m128i t1h = _mm_loadu_si128(static_cast(static_cast(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void) + const __m128i t1l = _mm_loadu_si128(static_cast(static_cast(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void) + const __m128i t2h = _mm_loadu_si128(static_cast(static_cast(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void) + const __m128i nibble = _mm_set1_epi8(0x0F); + const __m128i zero = _mm_setzero_si128(); + __m128i prev = zero; + while (e - block >= 16) + { + const __m128i in = _mm_loadu_si128(static_cast(static_cast(block))); // NOLINT(bugprone-casting-through-void) + if (_mm_movemask_epi8(_mm_or_si128(in, prev)) == 0) + { + prev = in; + block += 16; + continue; + } + const __m128i prev1 = _mm_alignr_epi8(in, prev, 15); + const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)), + _mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))), + _mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble))); + const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)), + _mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80))); + const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast(-128))), sc); + if (_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero)) != 0xFFFF) + { + break; + } + prev = in; + block += 16; + } +#endif + return block; +} #endif } // namespace detail diff --git a/include/nlohmann/detail/input/string_scan.hpp b/include/nlohmann/detail/input/string_scan.hpp index daa9697e1..10e4cc900 100644 --- a/include/nlohmann/detail/input/string_scan.hpp +++ b/include/nlohmann/detail/input/string_scan.hpp @@ -264,6 +264,64 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n return pos; } +/*! +@brief whether [data, data+n) is valid UTF-8, for checking whole strings + +The same answer as valid_utf8_prefix(data, n) == n. Short ASCII strings, the +most common keys and values, are decided in one test, and long strings are +checked 16 bytes at a time where the vector check is available. +*/ +inline bool valid_utf8_string(const unsigned char* data, std::size_t n) noexcept +{ + constexpr std::uint64_t high = 0x8080808080808080ull; + if (n <= 16) + { + // two overlapping words cover 8 to 16 bytes, one partial word fewer + std::uint64_t first = 0; + std::uint64_t last = 0; + if (n >= 8) + { + std::memcpy(&first, data, sizeof(first)); + std::memcpy(&last, data + n - 8, sizeof(last)); + } + else if (n != 0) + { + std::memcpy(&first, data, n); + } + if (((first | last) & high) == 0) + { + return true; + } + } +#if JSON_SCAN_VECTOR_UTF8 + else if (n >= 32 +#if JSON_SCAN_SSSE3_DISPATCH + && cpu_has_ssse3() +#endif + ) + { + const unsigned char* block = vector_utf8_valid_blocks(data, data + n); + // the rest from the start of a sequence that may be cut at block + for (int i = 1; i <= 3 && block - i >= data; ++i) + { + const unsigned char c = block[-i]; + if (c < 0x80) + { + break; + } + if (c >= 0xC0) + { + block -= i; + break; + } + } + const auto checked = static_cast(block - data); + return checked + valid_utf8_prefix(block, n - checked) == n; + } +#endif + return valid_utf8_prefix(data, n) == n; +} + #if JSON_SCAN_VECTOR_UTF8 /// the end of scan_string_vector from block, where the vector loop stopped /// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 9d21d2a7c..0c3c823ac 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -3307,6 +3307,25 @@ class binary_writer } } + /// write the head and the characters of @a value, at most + /// max_text_in_reservation of them, in one reservation + JSON_HEDLEY_ALWAYS_INLINE void write_cbor_string_reserved(const string_t& value) + { + CharType* const out = oa.reserve(value.size() + 2); + CharType* p = out; + if (value.size() <= 0x17) + { + *p++ = to_char_type(static_cast(0x60 + value.size())); + } + else + { + *p++ = to_char_type(0x78); + *p++ = to_char_type(static_cast(value.size())); + } + copy_characters(p, reinterpret_cast(value.data()), value.size()); + oa.commit(static_cast(p - out) + value.size()); + } + /*! @brief write a CBOR text string @@ -3328,19 +3347,7 @@ class binary_writer // the string is written as it is: head and characters in one reservation if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation)) { - CharType* const out = oa.reserve(value.size() + 2); - CharType* p = out; - if (value.size() <= 0x17) - { - *p++ = to_char_type(static_cast(0x60 + value.size())); - } - else - { - *p++ = to_char_type(0x78); - *p++ = to_char_type(static_cast(value.size())); - } - copy_characters(p, reinterpret_cast(value.data()), value.size()); - oa.commit(static_cast(p - out) + value.size()); + write_cbor_string_reserved(value); return; } } @@ -3354,6 +3361,13 @@ class binary_writer JSON_HEDLEY_NEVER_INLINE void write_cbor_checked_string(const string_t& value, const BasicJsonType& context) { + if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation) + { + check_utf8(value, context); + write_cbor_string_reserved(value); + return; + } + string_t storage; const string_t& sanitized = sanitize_utf8_for_write(value, context, storage); @@ -3368,6 +3382,27 @@ class binary_writer // MsgPack // ///////////// + /// write the head and the characters of @a value, at most + /// max_text_in_reservation of them, in one reservation + JSON_HEDLEY_ALWAYS_INLINE void write_msgpack_string_reserved(const string_t& value) + { + CharType* const out = oa.reserve(value.size() + 2); + CharType* p = out; + if (value.size() <= 31) + { + // fixstr + *p++ = to_char_type(static_cast(0xA0 | value.size())); + } + else + { + // str 8 + *p++ = to_char_type(0xD9); + *p++ = to_char_type(static_cast(value.size())); + } + copy_characters(p, reinterpret_cast(value.data()), value.size()); + oa.commit(static_cast(p - out) + value.size()); + } + /*! @brief write a MessagePack str @@ -3385,21 +3420,7 @@ class binary_writer // the string is written as it is: head and characters in one reservation if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation)) { - CharType* const out = oa.reserve(value.size() + 2); - CharType* p = out; - if (value.size() <= 31) - { - // fixstr - *p++ = to_char_type(static_cast(0xA0 | value.size())); - } - else - { - // str 8 - *p++ = to_char_type(0xD9); - *p++ = to_char_type(static_cast(value.size())); - } - copy_characters(p, reinterpret_cast(value.data()), value.size()); - oa.commit(static_cast(p - out) + value.size()); + write_msgpack_string_reserved(value); return; } } @@ -3412,6 +3433,13 @@ class binary_writer JSON_HEDLEY_NEVER_INLINE void write_msgpack_checked_string(const string_t& value, const BasicJsonType& context) { + if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation) + { + check_utf8(value, context); + write_msgpack_string_reserved(value); + return; + } + string_t storage; const string_t& sanitized = sanitize_utf8_for_write(value, context, storage); @@ -5021,10 +5049,9 @@ class binary_writer static void check_utf8(const string_t& s, const BasicJsonType& context) { const auto* data = reinterpret_cast(s.data()); - const std::size_t valid = valid_utf8_prefix(data, s.size()); - if (JSON_HEDLEY_UNLIKELY(valid != s.size())) + if (JSON_HEDLEY_UNLIKELY(!valid_utf8_string(data, s.size()))) { - throw_invalid_utf8(data, valid, context); + throw_invalid_utf8(data, valid_utf8_prefix(data, s.size()), context); } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3c2670039..7b54e0a89 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -12379,6 +12379,78 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons block_out = block; return false; } + +/*! +@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary) + +Like vector_utf8_run, but quotes, backslashes, and control characters are +ordinary characters. Returns the start of the first block in which the check +found an error (which may belong to a sequence begun in the block before) or +where fewer than 16 bytes are left; a sequence may be cut at that point, so the +caller resumes at its start, one sequence at a time. +*/ +JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept +{ + using lookup = utf8_lookup4<>; +#if JSON_SCAN_NEON + const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data()); + const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data()); + const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data()); + uint8x16_t prev = vdupq_n_u8(0); + while (e - block >= 16) + { + const uint8x16_t in = vld1q_u8(block); + if (vmaxvq_u8(vorrq_u8(in, prev)) < 0x80) + { + // ASCII here, and no sequence from the block before to finish + prev = in; + block += 16; + continue; + } + const uint8x16_t prev1 = vextq_u8(prev, in, 15); + const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4))); + const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80))); + const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc); + if (vmaxvq_u8(err) != 0) + { + break; + } + prev = in; + block += 16; + } +#else + const __m128i t1h = _mm_loadu_si128(static_cast(static_cast(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void) + const __m128i t1l = _mm_loadu_si128(static_cast(static_cast(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void) + const __m128i t2h = _mm_loadu_si128(static_cast(static_cast(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void) + const __m128i nibble = _mm_set1_epi8(0x0F); + const __m128i zero = _mm_setzero_si128(); + __m128i prev = zero; + while (e - block >= 16) + { + const __m128i in = _mm_loadu_si128(static_cast(static_cast(block))); // NOLINT(bugprone-casting-through-void) + if (_mm_movemask_epi8(_mm_or_si128(in, prev)) == 0) + { + prev = in; + block += 16; + continue; + } + const __m128i prev1 = _mm_alignr_epi8(in, prev, 15); + const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)), + _mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))), + _mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble))); + const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)), + _mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80))); + const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast(-128))), sc); + if (_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero)) != 0xFFFF) + { + break; + } + prev = in; + block += 16; + } +#endif + return block; +} #endif } // namespace detail @@ -12634,6 +12706,64 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n return pos; } +/*! +@brief whether [data, data+n) is valid UTF-8, for checking whole strings + +The same answer as valid_utf8_prefix(data, n) == n. Short ASCII strings, the +most common keys and values, are decided in one test, and long strings are +checked 16 bytes at a time where the vector check is available. +*/ +inline bool valid_utf8_string(const unsigned char* data, std::size_t n) noexcept +{ + constexpr std::uint64_t high = 0x8080808080808080ull; + if (n <= 16) + { + // two overlapping words cover 8 to 16 bytes, one partial word fewer + std::uint64_t first = 0; + std::uint64_t last = 0; + if (n >= 8) + { + std::memcpy(&first, data, sizeof(first)); + std::memcpy(&last, data + n - 8, sizeof(last)); + } + else if (n != 0) + { + std::memcpy(&first, data, n); + } + if (((first | last) & high) == 0) + { + return true; + } + } +#if JSON_SCAN_VECTOR_UTF8 + else if (n >= 32 +#if JSON_SCAN_SSSE3_DISPATCH + && cpu_has_ssse3() +#endif + ) + { + const unsigned char* block = vector_utf8_valid_blocks(data, data + n); + // the rest from the start of a sequence that may be cut at block + for (int i = 1; i <= 3 && block - i >= data; ++i) + { + const unsigned char c = block[-i]; + if (c < 0x80) + { + break; + } + if (c >= 0xC0) + { + block -= i; + break; + } + } + const auto checked = static_cast(block - data); + return checked + valid_utf8_prefix(block, n - checked) == n; + } +#endif + return valid_utf8_prefix(data, n) == n; +} + #if JSON_SCAN_VECTOR_UTF8 /// the end of scan_string_vector from block, where the vector loop stopped /// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a @@ -22574,6 +22704,19 @@ class binary_reader return get_bytes(len, "string", result); } + /// whether @a s is valid UTF-8: ASCII eight bytes at a time, the rest + /// sequence by sequence (as the binary writers check strings) + static bool is_valid_utf8_string(const string_t& s, std::true_type /*is_byte*/) noexcept + { + return valid_utf8_string(reinterpret_cast(s.data()), s.size()); + } + + /// whether @a s, of a character type wider than a byte, is valid UTF-8 + static bool is_valid_utf8_string(const string_t& s, std::false_type /*is_byte*/) noexcept + { + return is_valid_utf8(s); + } + /*! @brief validate a decoded text string (value or object key) against @ref error_handler @@ -22593,7 +22736,16 @@ class binary_reader */ bool check_string_utf8(string_t& result, const char* context) { - if (error_handler == error_handler_t::keep || is_valid_utf8(result)) + return error_handler == error_handler_t::keep || check_string_utf8_handler(result, context); + } + + /// @ref check_string_utf8 for an error handler other than @ref + /// error_handler_t::keep; out of line, so that reading strings without a + /// check does not pay for the code of the check + JSON_HEDLEY_NEVER_INLINE + bool check_string_utf8_handler(string_t& result, const char* context) + { + if (is_valid_utf8_string(result, std::integral_constant())) { return true; } @@ -31067,6 +31219,25 @@ class binary_writer } } + /// write the head and the characters of @a value, at most + /// max_text_in_reservation of them, in one reservation + JSON_HEDLEY_ALWAYS_INLINE void write_cbor_string_reserved(const string_t& value) + { + CharType* const out = oa.reserve(value.size() + 2); + CharType* p = out; + if (value.size() <= 0x17) + { + *p++ = to_char_type(static_cast(0x60 + value.size())); + } + else + { + *p++ = to_char_type(0x78); + *p++ = to_char_type(static_cast(value.size())); + } + copy_characters(p, reinterpret_cast(value.data()), value.size()); + oa.commit(static_cast(p - out) + value.size()); + } + /*! @brief write a CBOR text string @@ -31088,19 +31259,7 @@ class binary_writer // the string is written as it is: head and characters in one reservation if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation)) { - CharType* const out = oa.reserve(value.size() + 2); - CharType* p = out; - if (value.size() <= 0x17) - { - *p++ = to_char_type(static_cast(0x60 + value.size())); - } - else - { - *p++ = to_char_type(0x78); - *p++ = to_char_type(static_cast(value.size())); - } - copy_characters(p, reinterpret_cast(value.data()), value.size()); - oa.commit(static_cast(p - out) + value.size()); + write_cbor_string_reserved(value); return; } } @@ -31114,6 +31273,13 @@ class binary_writer JSON_HEDLEY_NEVER_INLINE void write_cbor_checked_string(const string_t& value, const BasicJsonType& context) { + if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation) + { + check_utf8(value, context); + write_cbor_string_reserved(value); + return; + } + string_t storage; const string_t& sanitized = sanitize_utf8_for_write(value, context, storage); @@ -31128,6 +31294,27 @@ class binary_writer // MsgPack // ///////////// + /// write the head and the characters of @a value, at most + /// max_text_in_reservation of them, in one reservation + JSON_HEDLEY_ALWAYS_INLINE void write_msgpack_string_reserved(const string_t& value) + { + CharType* const out = oa.reserve(value.size() + 2); + CharType* p = out; + if (value.size() <= 31) + { + // fixstr + *p++ = to_char_type(static_cast(0xA0 | value.size())); + } + else + { + // str 8 + *p++ = to_char_type(0xD9); + *p++ = to_char_type(static_cast(value.size())); + } + copy_characters(p, reinterpret_cast(value.data()), value.size()); + oa.commit(static_cast(p - out) + value.size()); + } + /*! @brief write a MessagePack str @@ -31145,21 +31332,7 @@ class binary_writer // the string is written as it is: head and characters in one reservation if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation)) { - CharType* const out = oa.reserve(value.size() + 2); - CharType* p = out; - if (value.size() <= 31) - { - // fixstr - *p++ = to_char_type(static_cast(0xA0 | value.size())); - } - else - { - // str 8 - *p++ = to_char_type(0xD9); - *p++ = to_char_type(static_cast(value.size())); - } - copy_characters(p, reinterpret_cast(value.data()), value.size()); - oa.commit(static_cast(p - out) + value.size()); + write_msgpack_string_reserved(value); return; } } @@ -31172,6 +31345,13 @@ class binary_writer JSON_HEDLEY_NEVER_INLINE void write_msgpack_checked_string(const string_t& value, const BasicJsonType& context) { + if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation) + { + check_utf8(value, context); + write_msgpack_string_reserved(value); + return; + } + string_t storage; const string_t& sanitized = sanitize_utf8_for_write(value, context, storage); @@ -32781,10 +32961,9 @@ class binary_writer static void check_utf8(const string_t& s, const BasicJsonType& context) { const auto* data = reinterpret_cast(s.data()); - const std::size_t valid = valid_utf8_prefix(data, s.size()); - if (JSON_HEDLEY_UNLIKELY(valid != s.size())) + if (JSON_HEDLEY_UNLIKELY(!valid_utf8_string(data, s.size()))) { - throw_invalid_utf8(data, valid, context); + throw_invalid_utf8(data, valid_utf8_prefix(data, s.size()), context); } } diff --git a/tests/src/unit-guard-page.cpp b/tests/src/unit-guard-page.cpp index a4b12bb1a..24eaf33b7 100644 --- a/tests/src/unit-guard-page.cpp +++ b/tests/src/unit-guard-page.cpp @@ -403,6 +403,7 @@ TEST_CASE("guard page") CHECK(nlohmann::detail::find_string_special(p, n) <= n); CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n); CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n); + CHECK(nlohmann::detail::valid_utf8_string(p, n) == (nlohmann::detail::valid_utf8_prefix(p, n) == n)); #if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n); #endif diff --git a/tests/src/unit-unicode.cpp b/tests/src/unit-unicode.cpp index 0207659ea..72c8f812f 100644 --- a/tests/src/unit-unicode.cpp +++ b/tests/src/unit-unicode.cpp @@ -13,6 +13,7 @@ using nlohmann::json; #include #include +#include #include #include "make_test_data_available.hpp" #include "test_utils.hpp" @@ -1631,3 +1632,40 @@ TEST_CASE("Unicode RFC 3629") } DOCTEST_CLANG_SUPPRESS_WARNING_POP + +TEST_CASE("UTF-8 check of whole strings") +{ + // valid_utf8_string() decides short strings with a word test and long ones + // 16 bytes at a time where a vector check is available; it must agree with + // the byte-wise check for every mix of ASCII, sequences of each length, + // ill-formed and truncated sequences, at every length and offset + const char* const pieces[] = // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + { + "a", "\x7F", "\xC3\xA9", "\xE2\x82\xAC", "\xE0\xA0\x80", "\xEF\xBF\xBF", "\xF0\x9F\x98\x80", "\xF4\x8F\xBF\xBF", + "\x80", "\xC0\xAF", "\xC3", "\xE2\x82", "\xED\xA0\x80", "\xF0\x9F", "\xF4\x90\x80\x80", "\xFF" + }; + // std::mt19937's sequence is fixed by the standard; no distribution is used + std::mt19937 rng(5); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::size_t valid = 0; + for (int i = 0; i < 100000; ++i) + { + std::string s; + const std::size_t length = rng() % 100; + const auto ascii = static_cast(rng() % 101); + while (s.size() < length) + { + // mostly well-formed pieces (the first eight), so that long valid + // strings are common + s += (rng() % 100 < ascii) ? std::string("x") : std::string(pieces[rng() % (rng() % 8 == 0 ? 16 : 8)]); + } + const auto* data = reinterpret_cast(s.data()); + const bool expected = nlohmann::detail::is_valid_utf8(s); + valid += expected ? 1 : 0; + CAPTURE(s) + CHECK(nlohmann::detail::valid_utf8_string(data, s.size()) == expected); + CHECK((nlohmann::detail::valid_utf8_prefix(data, s.size()) == s.size()) == expected); + } + // both outcomes are common enough to be tested + CHECK(valid > 20000); + CHECK(valid < 80000); +} From ddc34f546a652cd098dd78225d80f1909b921871 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 17:25:26 +0100 Subject: [PATCH 60/63] json_view: exceptions name the path and the bytes of the value (#5974) Signed-off-by: Niels Lohmann --- docs/mkdocs/docs/api/basic_json_view/at.md | 7 +- docs/mkdocs/docs/api/basic_json_view/back.md | 6 +- docs/mkdocs/docs/api/basic_json_view/front.md | 6 +- docs/mkdocs/docs/api/basic_json_view/get.md | 15 +- .../docs/api/basic_json_view/get_string.md | 6 +- .../docs/api/basic_json_view/number_token.md | 6 +- .../docs/api/basic_json_view/operator[].md | 7 +- docs/mkdocs/docs/api/basic_json_view/value.md | 7 +- docs/mkdocs/docs/features/json_view.md | 34 +- include/nlohmann/detail/exceptions.hpp | 24 +- include/nlohmann/detail/macro_scope.hpp | 2 +- include/nlohmann/detail/view/edit.hpp | 22 +- include/nlohmann/detail/view/errors.hpp | 324 +++- include/nlohmann/detail/view/iterator.hpp | 5 +- include/nlohmann/detail/view/pointer.hpp | 9 +- include/nlohmann/detail/view/value.hpp | 6 +- include/nlohmann/json_view.hpp | 59 +- single_include/nlohmann/json.hpp | 26 +- single_include/nlohmann/json_view.hpp | 1332 ++++++++++------- tests/src/json_view_test_helpers.hpp | 22 + tests/src/unit-json_view.cpp | 110 +- tests/src/unit-json_view_edit.cpp | 22 +- tests/src/unit-json_view_from_json.cpp | 11 +- tests/src/unit-json_view_large.cpp | 2 +- tests/src/unit-json_view_positions.cpp | 226 +++ 25 files changed, 1600 insertions(+), 696 deletions(-) diff --git a/docs/mkdocs/docs/api/basic_json_view/at.md b/docs/mkdocs/docs/api/basic_json_view/at.md index 747b46299..7b810c4ed 100644 --- a/docs/mkdocs/docs/api/basic_json_view/at.md +++ b/docs/mkdocs/docs/api/basic_json_view/at.md @@ -68,9 +68,10 @@ Strong guarantee: there are no changes to the view or the document it refers to. - Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved because a reference token is used on a primitive value. -None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no -`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with -`JSON_DIAGNOSTICS` enabled. +As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON +Pointer path of the value within the document, and with +[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when +[`materialize()`](materialize.md) sets them). ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json_view/back.md b/docs/mkdocs/docs/api/basic_json_view/back.md index 473502f0a..f594928b0 100644 --- a/docs/mkdocs/docs/api/basic_json_view/back.md +++ b/docs/mkdocs/docs/api/basic_json_view/back.md @@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to. Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is [null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object. -This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no -`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with -`JSON_DIAGNOSTICS` enabled. +As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer +path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) +its bytes in the source text (when [`materialize()`](materialize.md) sets them). ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json_view/front.md b/docs/mkdocs/docs/api/basic_json_view/front.md index 4cefcbd33..da48fcf88 100644 --- a/docs/mkdocs/docs/api/basic_json_view/front.md +++ b/docs/mkdocs/docs/api/basic_json_view/front.md @@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to. Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is [null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object. -This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no -`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with -`JSON_DIAGNOSTICS` enabled. +As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer +path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) +its bytes in the source text (when [`materialize()`](materialize.md) sets them). ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json_view/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md index bb9ac6f46..7ee7952bf 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -80,14 +80,13 @@ Strong guarantee: there are no changes to the view or the document it refers to. - For every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws — typically `type_error.302`, or whatever a user-provided `from_json()` throws. -None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four -bullets above) carry a -[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An -exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real -`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled. -That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent -pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole -document. +The exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four bullets +above) carry the context of those of `BasicJsonType`: with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the +JSON Pointer path of the value within the document, and with +[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text. An exception +thrown while converting through `materialize()` (the last bullet) is thrown by the materialized `BasicJsonType` value +instead: its path is relative to the root of the view's subtree, because `materialize()` builds only the subtree +(with parent pointers), so it is empty if the failing value is the subtree's root itself. ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json_view/get_string.md b/docs/mkdocs/docs/api/basic_json_view/get_string.md index 7539c540d..b7d4d4203 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get_string.md +++ b/docs/mkdocs/docs/api/basic_json_view/get_string.md @@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to. Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example: `"type must be string, but is array"`. -This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no -`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with -`JSON_DIAGNOSTICS` enabled. +As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer +path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) +its bytes in the source text (when [`materialize()`](materialize.md) sets them). ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json_view/number_token.md b/docs/mkdocs/docs/api/basic_json_view/number_token.md index 97d7a2ed2..6451cb1fa 100644 --- a/docs/mkdocs/docs/api/basic_json_view/number_token.md +++ b/docs/mkdocs/docs/api/basic_json_view/number_token.md @@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to. Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example: `"type must be number, but is string"`. -This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no -`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with -`JSON_DIAGNOSTICS` enabled. +As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer +path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) +its bytes in the source text (when [`materialize()`](materialize.md) sets them). ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json_view/operator[].md b/docs/mkdocs/docs/api/basic_json_view/operator[].md index 9c09a807b..a04bfd3b8 100644 --- a/docs/mkdocs/docs/api/basic_json_view/operator[].md +++ b/docs/mkdocs/docs/api/basic_json_view/operator[].md @@ -73,9 +73,10 @@ Strong guarantee: there are no changes to the view or the document it refers to. - [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a number. -None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no -`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with -`JSON_DIAGNOSTICS` enabled. +As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON +Pointer path of the value within the document, and with +[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when +[`materialize()`](materialize.md) sets them). ## Complexity diff --git a/docs/mkdocs/docs/api/basic_json_view/value.md b/docs/mkdocs/docs/api/basic_json_view/value.md index 7159325b2..ebce7b4bf 100644 --- a/docs/mkdocs/docs/api/basic_json_view/value.md +++ b/docs/mkdocs/docs/api/basic_json_view/value.md @@ -61,9 +61,10 @@ Strong guarantee: there are no changes to the view or the document it refers to. `default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches `out_of_range` and returns `default_value`. -None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no -`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with -`JSON_DIAGNOSTICS` enabled. +As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON +Pointer path of the value within the document, and with +[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when +[`materialize()`](materialize.md) sets them). ## Complexity diff --git a/docs/mkdocs/docs/features/json_view.md b/docs/mkdocs/docs/features/json_view.md index 08529fd7d..44bf581a5 100644 --- a/docs/mkdocs/docs/features/json_view.md +++ b/docs/mkdocs/docs/features/json_view.md @@ -134,9 +134,21 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile. - **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and [`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md) behave the same way they do for ``. These are the macros that change what the parser accepts or - how it reports errors. The others either do not apply ([`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) - needs parent pointers in `basic_json` values) or only affect the values `materialize()` produces, through - `BasicJsonType`. + how it reports errors. Two more macros add context to the exceptions of views, as they do for those of + `#!cpp basic_json`: + + - With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md), an exception names the JSON Pointer path of the + value within the document. + - With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), an exception names the bytes of + the value in the source text. + + A view has neither parent pointers nor stored positions, so both are found from the document's index when the + exception is thrown. This takes time linear in the size of the document. One exception has neither: + `key()` of an iterator over an array or a primitive value + ([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does + not know the value it iterates over. + + The other macros only affect the values `materialize()` produces, through `BasicJsonType`. ## What is different @@ -167,10 +179,6 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile. [`at`](../api/basic_json_view/at.md) throws for every missing value. - **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys) below. -- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own - element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the - view has no `basic_json` value to point at, so the exception is created without one, regardless of how - `BasicJsonType` was built. - **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`. [`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when @@ -357,14 +365,10 @@ Its body may only use what views and `basic_json` have in common: the type check a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is converted through `materialize()` as well. -Two things differ when a `from_json()` reads the view: - -- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member - with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get()` would see the last one. - Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as - [`parse()`](../api/basic_json/parse.md) does. -- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the - value. +One thing differs when a `from_json()` reads the view: members looked up by name (`at()`, `value()`, ... and thus +the macros) find the **first** member with a repeated key, see [Duplicate keys](#duplicate-keys); +`#!cpp materialize().get()` would see the last one. Containers built from an object, such as `#!cpp std::map`, +keep the **last** value either way, as [`parse()`](../api/basic_json/parse.md) does. `to_json()` is unaffected: it writes `basic_json` values, never views. diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index ee2c0feec..144bb770c 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -431,6 +431,28 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept return nullptr; } +namespace view +{ +// the path and bytes of the value of a view (defined in ) +template +std::string diagnostics_of(const View& v); +} // namespace view + +// what goes in front of the message of an exception about j: nothing for a +// basic_json value (exception_context() passes the value itself), the context +// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view +template::value, int> = 0> +const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept +{ + return ""; +} + +template::value, int> = 0> +std::string exception_prefix(const View& v) +{ + return view::diagnostics_of(v); +} + /*! @brief throws because @a j does not have the type a conversion expects @param[in] expected the expected type(s), e.g. "array" or "binary or array" @@ -442,7 +464,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); - JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j))); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 65394b504..208c1c3ce 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -347,7 +347,7 @@ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat(nlohmann::detail::exception_prefix(j), "enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They diff --git a/include/nlohmann/detail/view/edit.hpp b/include/nlohmann/detail/view/edit.hpp index c542c1141..b572b4848 100644 --- a/include/nlohmann/detail/view/edit.hpp +++ b/include/nlohmann/detail/view/edit.hpp @@ -157,7 +157,7 @@ class editor node* const o = own(object); if (o->kind != node_kind::object && o->kind != node_kind::null) { - throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); + throw_type_error(site_of(object), exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); @@ -208,9 +208,9 @@ class editor node* const a = own(array); if (a->kind != node_kind::array) { - throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); + throw_type_error(site_of(array), exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } - check_index(idx, a->len); + check_index(a, idx, a->len); const encoded e = encode(std::forward(value)); node* const slot = const_cast(nav::value(element_at(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast) assign(slot, e, a, true); @@ -225,7 +225,7 @@ class editor node* const a = own(array); if (a->kind != node_kind::array && a->kind != node_kind::null) { - throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); + throw_type_error(site_of(array), exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); if (a->kind == node_kind::null) @@ -249,9 +249,9 @@ class editor node* const a = own(array); if (a->kind != node_kind::array) { - throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); + throw_type_error(site_of(array), exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } - check_index(idx, a->len + 1); + check_index(a, idx, a->len + 1); const encoded e = encode(std::forward(value)); node* const slot = new_slot(e); node* const h = block_of(m_doc, a, 1); @@ -269,7 +269,7 @@ class editor node* const o = own(object); if (o->kind != node_kind::object) { - throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); + throw_type_error(site_of(object), exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1)) { @@ -287,9 +287,9 @@ class editor node* const a = own(array); if (a->kind != node_kind::array) { - throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); + throw_type_error(site_of(array), exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } - check_index(idx, a->len); + check_index(a, idx, a->len); node* const h = block_of(m_doc, a, 0); std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node)); --h->next; @@ -316,11 +316,11 @@ class editor return const_cast(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document } - static void check_index(std::size_t idx, std::size_t limit) + void check_index(const node* array, std::size_t idx, std::size_t limit) const { if (idx >= limit) { - throw_array_index_out_of_range(idx); + throw_array_index_out_of_range(error_site(&m_doc, array), idx); } } diff --git a/include/nlohmann/detail/view/errors.hpp b/include/nlohmann/detail/view/errors.hpp index 590bb2edb..eeab3bbd5 100644 --- a/include/nlohmann/detail/view/errors.hpp +++ b/include/nlohmann/detail/view/errors.hpp @@ -10,11 +10,15 @@ #include // min #include // size_t -#include // string +#include // string, to_string +#include // vector #include #include +#include #include +#include +#include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -22,8 +26,293 @@ namespace detail namespace view { +/*! +@brief the value an exception is about + +basic_json passes the value to the exception, which then names its path +(JSON_DIAGNOSTICS) and its bytes in the source (JSON_DIAGNOSTIC_POSITIONS). A +view's value has no parent pointer and no stored positions: both are found +from the document and the node, when the exception is thrown (so only the +error path pays for them). Without the two macros, a site is empty. +*/ +class error_site +{ + public: + error_site() noexcept = default; + +#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS + error_site(const document_data* d, const node* n) noexcept + : m_doc(d), m_node(n) + {} + + const document_data* doc() const noexcept + { + return m_doc; + } + const node* value() const noexcept + { + return m_node; + } + + private: + const document_data* m_doc = nullptr; + const node* m_node = nullptr; +#else + error_site(const document_data* /*d*/, const node* /*n*/) noexcept {} +#endif +}; + +/// the site of a view's value (the views make this a friend) +struct view_access +{ + template + static error_site site(const View& v) noexcept + { + return {v.m_doc, v.m_node}; + } +}; + +template +error_site site_of(const View& v) noexcept +{ + static_assert(is_json_view::value, "the site of a value is only defined for views"); + return view_access::site(v); +} + +#if JSON_DIAGNOSTICS +/*! +@brief the JSON pointer tokens from the root of document @a d to node @a target + +Nodes have no parent index, so the path is found by descending from the root: +before the first edit, only into the subtree that contains the target (a +container spans the next `next` nodes); after edits, elements can live in +other sequences, so every container is searched. Iterative (the nesting depth +is limited by memory only). False if the target is not found (then tokens is +unspecified). +*/ +template +bool value_path(const document_data& d, const node* target, std::vector& tokens) +{ + using nav = navigation; + struct frame + { + const node* pos; + const node* end; + std::size_t index; + bool object; + }; + const node* const root = d.nodes; + if (target == root) + { + return true; + } + if (!is_container(*root)) + { + return false; + } + std::vector stack; + stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object}); + // (tokens[i] leads from stack[i] to stack[i + 1], or to the target) + for (;;) + { + // a copy: pop_back() and push_back() below change the stack + const frame f = stack.back(); + if (f.pos == f.end) + { + stack.pop_back(); + if (stack.empty()) + { + return false; + } + tokens.pop_back(); + continue; + } + const node* entry = f.pos; + std::string token; + if (f.object) + { + // (as basic_json: the key up to a null byte) + token.assign(d.str(*entry), entry->len); + const std::size_t nul = token.find('\0'); + if (nul != std::string::npos) + { + token.resize(nul); + } + ++entry; + } + else + { + token = std::to_string(f.index); + } + const node* const child = nav::value(entry); + if (child == target) + { + tokens.push_back(token); + return true; + } + stack.back().pos = document_data::after(entry); + ++stack.back().index; + if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next))) + { + tokens.push_back(token); + stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object}); + } + } +} +#endif + +#if JSON_DIAGNOSTIC_POSITIONS +/*! +@brief the bytes [start, end) of node @a n in the source, as materialize() sets them + +Scalars as position_source::set_scalar; an array or object from its opening +bracket to its closing bracket, which is found by following the chain of last +elements to a scalar (or an empty container) and scanning from there to the +closing brackets. None (npos) for values written by edits, and for containers +whose last element has none, as with materialize(). +*/ +template +void value_range(const document_data& d, const node* n, std::size_t& start, std::size_t& end) +{ + using nav = navigation; + position_source p; + start = std::string::npos; + end = std::string::npos; + if (!is_container(*n)) + { + p.set_scalar(d, *n); + start = p.start; + end = p.end; + return; + } + if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size) + { + return; + } + // the open brackets: n, then the last element as long as it is a non-empty container + std::size_t closing = 0; + std::size_t pos = std::string::npos; + for (const node* c = n;;) + { + if (!is_container(*c)) + { + p.set_scalar(d, *c); + pos = p.end; + break; + } + if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size) + { + return; + } + ++closing; + const node* const first = nav::first(d, c); + const node* const last_end = nav::end(d, c); + if (first == last_end) + { + pos = std::size_t{c->off} + 1; + break; + } + const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0; + const node* last = first; + for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset)) + { + last = e; + } + c = nav::value(last + value_offset); + } + for (; closing != 0 && pos != std::string::npos; --closing) + { + pos = position_source::container_end(d.base[0], d.size, pos); + } + if (pos != std::string::npos) + { + start = n->off; + end = pos; + } +} +#endif + +/// the context basic_json puts in front of the message of an exception about +/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then +/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only +/// if there is a path) +/// +/// This and the throw helpers that take an error_site are templates, so that +/// the path and range code is instantiated only where an exception can be +/// thrown: instantiated in every translation unit (as a non-template inline +/// function makes it), it changed GCC's early inlining of unrelated view +/// functions, so that GCC 13 no longer inlined a recursive walk over a view +/// into itself (2.7 times the instructions). +#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS +template +std::string diagnostics(const error_site site) +{ + if (site.doc() == nullptr || site.value() == nullptr) + { + return ""; + } + const document_data& d = *site.doc(); + const bool edited = d.edits != nullptr; + std::string context; +#if JSON_DIAGNOSTICS + std::vector tokens; + const bool found = edited ? value_path(d, site.value(), tokens) : value_path(d, site.value(), tokens); + if (!found || tokens.empty()) + { + // context is still empty; returning it keeps the copy elided (-Wnrvo) + return context; + } + std::string path; + for (const std::string& token : tokens) + { + path.push_back('/'); + path += detail::escape(token); + } + context = concat('(', path, ") "); +#endif +#if JSON_DIAGNOSTIC_POSITIONS + std::size_t start = 0; + std::size_t end = 0; + if (edited) + { + value_range(d, site.value(), start, end); + } + else + { + value_range(d, site.value(), start, end); + } + if (start != std::string::npos && end != std::string::npos) + { + context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") "); + } +#endif + return context; +} +#else +template +std::string diagnostics(const error_site /*site*/) +{ + return ""; +} +#endif + +/// the context of an exception about view @a v (see exception_prefix()) +template +std::string diagnostics_of(const View& v) +{ + return diagnostics<>(site_of(v)); +} + // Exceptions are thrown out of line, so that the accessors that may throw stay -// small enough to be inlined. +// small enough to be inlined. The overloads with an error_site put its context +// in front of the message. +// +// The overloads without one are for exceptions that are not about a value of a +// document: limits and invalid input of the editing and image functions, a +// discarded view (it has no node), a view of another document (its path would +// be one in that document), and those that basic_json creates without a +// context too (the errors of json_pointer, invalid UTF-8). The one exception +// about a value that has no site is key(), see view_iterator::key(). [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type) { @@ -45,6 +334,24 @@ namespace view NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); } +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_type_error(const error_site site, exception_id id, const char* prefix, const char* type) +{ + NLOHMANN_VIEW_THROW(type_error::create(id, concat(diagnostics<>(site), prefix, type), nullptr)); +} + +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_out_of_range(const error_site site, exception_id id, const std::string& msg) +{ + NLOHMANN_VIEW_THROW(out_of_range::create(id, concat(diagnostics<>(site), msg), nullptr)); +} + +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_invalid_iterator(const error_site site, exception_id id, const char* msg) +{ + NLOHMANN_VIEW_THROW(invalid_iterator::create(id, concat(diagnostics<>(site), msg), nullptr)); +} + /// a parse error without a position (as those of json_pointer) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg) { @@ -53,22 +360,23 @@ namespace view /// out_of_range.401: the array index @a idx is not below the size template -[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx) +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const error_site site, const Index idx) { - throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range")); + throw_out_of_range(site, exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range")); } /// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size) +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_past_end(const error_site site, std::size_t size) { - throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range")); + throw_out_of_range(site, exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range")); } /// out_of_range.403: the object has no member @a key template -[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key) +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const error_site site, const Key& key) { - throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found")); + throw_out_of_range(site, exception_id::key_not_found, concat("key '", key, "' not found")); } /// SAX handler of the error replay: accepts every event without storing it, diff --git a/include/nlohmann/detail/view/iterator.hpp b/include/nlohmann/detail/view/iterator.hpp index 9d8246210..fa0cc744e 100644 --- a/include/nlohmann/detail/view/iterator.hpp +++ b/include/nlohmann/detail/view/iterator.hpp @@ -105,7 +105,10 @@ class view_iterator } /// the key of the current object member; throws invalid_iterator.207 for - /// other iterators, like basic_json's iterators + /// other iterators, like basic_json's iterators (but without the context + /// JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS add: the iterator does not + /// know the value it iterates over, and keeping it would make iterators + /// larger, which keeps GCC from inlining recursive walks) string_view_t key() const { if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) diff --git a/include/nlohmann/detail/view/pointer.hpp b/include/nlohmann/detail/view/pointer.hpp index be5aa14de..c17561134 100644 --- a/include/nlohmann/detail/view/pointer.hpp +++ b/include/nlohmann/detail/view/pointer.hpp @@ -44,6 +44,7 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept } /// throws the exception json_pointer::array_index throws for this status +/// (without a context, as json_pointer::array_index) template [[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s) { @@ -83,7 +84,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (mode == pointer_mode::checked) { - throw_key_not_found(token); + throw_key_not_found(site_of(cur), token); } return View(); } @@ -95,7 +96,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (throwing) { - throw_array_index_past_end(cur.size()); + throw_array_index_past_end(site_of(cur), cur.size()); } return View(); } @@ -114,7 +115,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (mode == pointer_mode::checked) { - throw_array_index_out_of_range(idx); + throw_array_index_out_of_range(site_of(cur), idx); } return View(); } @@ -124,7 +125,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (throwing) { - throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); + throw_out_of_range(site_of(cur), exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); } return View(); } diff --git a/include/nlohmann/detail/view/value.hpp b/include/nlohmann/detail/view/value.hpp index 4b54a7785..ffdcc3605 100644 --- a/include/nlohmann/detail/view/value.hpp +++ b/include/nlohmann/detail/view/value.hpp @@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod case value_t::binary: case value_t::discarded: default: - throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast(n.kind))); + throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast(n.kind))); } } @@ -70,7 +70,7 @@ std::vector vector_value(const View& v) { if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) { - throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name()); + throw_type_error(site_of(v), exception_id::type_mismatch, "type must be array, but is ", v.type_name()); } std::vector r; r.reserve(v.size()); @@ -88,7 +88,7 @@ Map map_value(const View& v) { if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) { - throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name()); + throw_type_error(site_of(v), exception_id::type_mismatch, "type must be object, but is ", v.type_name()); } Map r; for (auto it = v.begin(); it != v.end(); ++it) diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 097e489a4..043d09036 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -285,7 +285,7 @@ class basic_json_view { return {}; } - detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); } return lookup(key); } @@ -311,7 +311,7 @@ class basic_json_view { return {}; } - detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name()); } return idx < m_node->len ? element(idx) : basic_json_view(); } @@ -344,12 +344,12 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); } const basic_json_view r = lookup(key); if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded())) { - detail::view::throw_key_not_found(std::string(key.data(), key.size())); + detail::view::throw_key_not_found(site(), std::string(key.data(), key.size())); } return r; } @@ -370,11 +370,11 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_array())) { - detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); } if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) { - detail::view::throw_array_index_out_of_range(idx); + detail::view::throw_array_index_out_of_range(site(), idx); } return element(idx); } @@ -402,7 +402,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = lookup(key); return r.is_discarded() ? default_value : r.template get(); @@ -412,7 +412,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } // (the default is made only if there is no such member) const basic_json_view r = lookup(key); @@ -427,7 +427,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); return r.is_discarded() ? default_value : r.template get(); @@ -437,7 +437,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); return r.is_discarded() ? string_t(default_value) : r.template get(); @@ -450,7 +450,7 @@ class basic_json_view const iterator it = begin(); if (NLOHMANN_VIEW_UNLIKELY(it == end())) { - detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value"); + detail::view::throw_invalid_iterator(site(), detail::exception_id::iterator_value_unavailable, "cannot get value"); } return *it; } @@ -606,7 +606,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_string())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); } return {m_doc->str(*m_node), m_node->len}; } @@ -618,7 +618,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_number())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); } return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; } @@ -764,12 +764,19 @@ class basic_json_view template friend class basic_json_view; template friend class detail::view::editor; template friend class detail::view::view_side; + friend struct detail::view::view_access; friend iterator; basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} + /// this value as the context of an exception + detail::view::error_site site() const noexcept + { + return {m_doc, m_node}; + } + using json_side_t = detail::view::json_side; static detail::view::view_side side(const basic_json_view& v) noexcept @@ -876,7 +883,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); } return (m_node->flags & detail::view::node_flags::is_true) != 0; } @@ -886,7 +893,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); } return detail::view::arithmetic_value(*m_doc, *m_node); } @@ -895,7 +902,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_null())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); } return nullptr; } @@ -1310,7 +1317,7 @@ class basic_json_document template::value, int>::type = 0> view_type set(view_type array, I idx, V && value) { - return editor().set(array, index(idx), std::forward(value)); + return editor().set(array, index(array, idx), std::forward(value)); } /// set the value at a JSON pointer: its parent must exist; an object @@ -1347,7 +1354,7 @@ class basic_json_document const std::size_t idx = pointer_index(token); if (idx >= 0xFFFFFFFFu) { - detail::view::throw_array_index_out_of_range(idx); + detail::view::throw_array_index_out_of_range(detail::view::site_of(parent), idx); } for (std::size_t i = 0; i < idx; ++i) { @@ -1381,7 +1388,7 @@ class basic_json_document template::value, int>::type = 0> view_type insert(view_type array, I idx, V && value) { - return editor().insert(array, index(idx), std::forward(value)); + return editor().insert(array, index(array, idx), std::forward(value)); } /// remove all members with this key; returns their number @@ -1394,7 +1401,7 @@ class basic_json_document template::value, int>::type = 0> void erase(view_type array, I idx) { - editor().erase(array, index(idx)); + editor().erase(array, index(array, idx)); } /// remove the value at a JSON pointer; returns the number of removed @@ -1428,25 +1435,25 @@ class basic_json_document return detail::view::editor(*m_data); } - /// an index (out_of_range.401 if negative) + /// an index into @a array (out_of_range.401 if negative) template - static std::size_t index(I idx) + static std::size_t index(const view_type& array, I idx) { - return index(idx, std::is_signed {}); + return index(array, idx, std::is_signed {}); } template - static std::size_t index(I idx, std::true_type /*signed*/) + static std::size_t index(const view_type& array, I idx, std::true_type /*signed*/) { if (idx < 0) { - detail::view::throw_array_index_out_of_range(idx); + detail::view::throw_array_index_out_of_range(detail::view::site_of(array), idx); } return static_cast(idx); } template - static std::size_t index(I idx, std::false_type /*unsigned*/) + static std::size_t index(const view_type& /*array*/, I idx, std::false_type /*unsigned*/) { return static_cast(idx); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 7b54e0a89..1fc214c5f 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2720,7 +2720,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat(nlohmann::detail::exception_prefix(j), "enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -5928,6 +5928,28 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept return nullptr; } +namespace view +{ +// the path and bytes of the value of a view (defined in ) +template +std::string diagnostics_of(const View& v); +} // namespace view + +// what goes in front of the message of an exception about j: nothing for a +// basic_json value (exception_context() passes the value itself), the context +// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view +template::value, int> = 0> +const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept +{ + return ""; +} + +template::value, int> = 0> +std::string exception_prefix(const View& v) +{ + return view::diagnostics_of(v); +} + /*! @brief throws because @a j does not have the type a conversion expects @param[in] expected the expected type(s), e.g. "array" or "binary or array" @@ -5939,7 +5961,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); - JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j))); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 408d845d4..e24f125bf 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -4395,13 +4395,44 @@ NLOHMANN_JSON_NAMESPACE_END #include // min #include // size_t -#include // string +#include // string, to_string +#include // vector // #include // #include +// #include + // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // int64_t, uint8_t +#include // string +#include // vector + +// #include +// #include + +// #include + +// #include + +// #include + +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail @@ -4409,8 +4440,721 @@ namespace detail namespace view { +#if JSON_DIAGNOSTIC_POSITIONS +/*! +@brief the source positions of the values materialize() replays + +Stands in for the lexer of json_sax_dom_parser, which reads the positions of +the values it stores from these three functions (diagnostic_positions). npos +is a value without a source position. One position_source serves a whole +materialize() call: before each value is handed to the handler, the range of +that value is set (or cleared), overwriting that of the value before. +*/ +struct position_source +{ + /// the token, of which diagnostic_positions only reads the length + struct token + { + std::size_t length; + std::size_t size() const noexcept + { + return length; + } + }; + + std::size_t start = std::string::npos; ///< the first byte of the next value + std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket) + + std::size_t get_chars_read_total() const noexcept + { + return end; + } + std::size_t get_token_start_position() const noexcept + { + return start; + } + token get_string() const noexcept + { + return token{end - start}; + } + + /// the range [first, last), or none if either is npos + void set(std::size_t first, std::size_t last) noexcept + { + if (first == std::string::npos || last == std::string::npos) + { + clear(); + return; + } + start = first; + end = last; + } + + /// no range: the next value has no source position + void clear() noexcept + { + start = std::string::npos; + end = std::string::npos; + } + + /// one past the closing quote of the string whose opening quote is at + /// src[quote], or npos (the scan is bounded by size, as images can be + /// loaded without a check) + static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept + { + for (std::size_t i = quote + 1; i < size; ++i) + { + if (src[i] == '\\') + { + ++i; + } + else if (src[i] == '"') + { + return i + 1; + } + } + return std::string::npos; + } + + /// one past the closing bracket of the array or object whose last element + /// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos; + /// in between, there can only be whitespace, comments, and a trailing comma + /// (the source was validated when it was parsed) + static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept + { + const auto* const b = reinterpret_cast(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it + const unsigned char* const e = b + size; + while (pos < size) + { + switch (src[pos]) + { + case ' ': + case '\t': + case '\n': + case '\r': + case ',': + ++pos; + break; + case ']': + case '}': + return pos + 1; + case '/': + if (pos + 1 < size && src[pos + 1] == '/') + { + pos = static_cast(skip_line_comment(b + pos + 2, e) - b); + break; + } + if (pos + 1 < size && src[pos + 1] == '*') + { + // (unterminated: pos is size, which ends the loop) + pos = static_cast(find_block_comment_end(b + pos + 2, e) - b) + 2; + break; + } + return std::string::npos; + default: + return std::string::npos; + } + } + return std::string::npos; + } + + /// the range of the scalar n of document d + void set_scalar(const document_data& d, const node& n) noexcept + { + if ((n.flags & node_flags::is_new) != 0) + { + clear(); + return; + } + std::size_t first = std::string::npos; + std::size_t last = std::string::npos; + switch (n.kind) + { + case node_kind::string: + if ((n.flags & node_flags::storage) == 0) + { + // off is the first byte after the opening quote + if (n.off != 0) + { + first = n.off - 1; + last = n.off + std::size_t{n.len} + 1; + } + } + else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0) + { + // next is the offset of the opening quote plus one + first = n.next - 1; + last = string_end(d.base[0], d.size, first); + } + break; + case node_kind::number_integer: + case node_kind::number_unsigned: + case node_kind::number_float: + if ((n.flags & node_flags::storage) == 0) + { + first = n.off; + last = n.off + number_length(n); + } + break; + case node_kind::boolean: + first = n.off; + last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u); + break; + case node_kind::null: + first = n.off; + last = n.off + 4u; + break; + case node_kind::object: + case node_kind::array: + case node_kind::link: + default: + break; + } + // (a crafted image can point past the text) + if (last == std::string::npos || last > d.size) + { + clear(); + } + else + { + set(first, last); + } + } +}; +#endif + +/*! +@brief the basic_json value of the subtree at n + +The subtree is replayed into the SAX handler that parse() uses to build its +values, so the result is the value parse() would produce: duplicate keys keep +the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is +iterative, so the nesting depth is limited by memory only, as for parse(). + +With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with +parse(): position_source stands in for the lexer. Values written by edits have +none (npos), and neither has an array or object whose elements changed, or +whose last element has none (the end of its closing bracket is found by +scanning from the end of the last element). +*/ +template +BasicJsonType materialize(const document_data& d, const node* n) +{ + using string_t = typename BasicJsonType::string_t; + using input_t = iterator_input_adapter; +#if JSON_DIAGNOSTIC_POSITIONS + using lexer_t = position_source; +#else + using lexer_t = lexer; // (the default: unused, as no lexer is passed) +#endif + using sax_t = json_sax_dom_parser; + using nav = navigation; + + // the open containers: their next element (or key of the next member), + // their end, and whether they are objects; for the first 16 levels on the + // stack, else sized once from the document's deepest nesting (for a + // subtree an upper bound) + struct frame + { + const node* pos; + const node* end; + bool object; +#if JSON_DIAGNOSTIC_POSITIONS + std::size_t start; ///< the opening bracket (npos: unknown) + std::size_t last_end; ///< the end of the last element read (npos: unknown) +#endif + }; + +#if !JSON_DIAGNOSTIC_POSITIONS + // a scalar needs neither the handler nor the stack of open containers + // (with positions, it goes through the handler, which sets them) + switch (static_cast(n->kind)) + { + case value_t::string: + return BasicJsonType(string_t(d.str(*n), n->len)); + case value_t::number_integer: + return BasicJsonType(static_cast(static_cast(integer_bits(*n)))); + case value_t::number_unsigned: + return BasicJsonType(static_cast(integer_bits(*n))); + case value_t::number_float: + return BasicJsonType(float_value(d, *n)); + case value_t::boolean: + return BasicJsonType((n->flags & node_flags::is_true) != 0); + case value_t::null: + return BasicJsonType(nullptr); + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::discarded: + default: + break; + } +#endif + + BasicJsonType result; +#if JSON_DIAGNOSTIC_POSITIONS + position_source positions; + sax_t sax(result, true, &positions); +#else + sax_t sax(result, true); +#endif + const string_t no_token {}; + // (shallow subtrees on the stack: a call for a small value then allocates only for the value) + std::array shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open + std::vector deep{}; + frame* open = shallow.data(); + std::size_t capacity = shallow.size(); + if (d.max_depth > capacity) + { + deep.resize(d.max_depth); + open = deep.data(); + capacity = deep.size(); + } + std::size_t depth = 0; + for (;;) + { +#if JSON_DIAGNOSTIC_POSITIONS + if (!is_container(*n)) + { + positions.set_scalar(d, *n); + if (depth != 0) + { + open[depth - 1].last_end = positions.end; + } + } +#endif + // false positive: n comes from nav::value(), which never returns null for a valid index + // @infer-ignore NULLPTR_DEREFERENCE + switch (static_cast(n->kind)) + { + case value_t::object: + case value_t::array: + { + const bool object = n->kind == node_kind::object; +#if JSON_DIAGNOSTIC_POSITIONS + // (moved containers are also new) + if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size) + { + positions.clear(); + } + else + { + positions.set(std::size_t{n->off}, std::size_t{n->off} + 1); + } +#endif + if (object) + { + sax.start_object(n->len); + } + else + { + sax.start_array(n->len); + } + if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) + { + // (deeper than the recorded nesting, e.g. after edits) + deep.assign(open, open + depth); + deep.resize(depth * 2); + open = deep.data(); + capacity = deep.size(); + } + // (with positions: until an element is read, the container's + // own bracket is the last thing read) + open[depth++] = frame{nav::first(d, n), nav::end(d, n), object +#if JSON_DIAGNOSTIC_POSITIONS + , positions.start, positions.end +#endif + }; + break; + } + case value_t::string: + { + string_t s(d.str(*n), n->len); + sax.string(s); + break; + } + case value_t::number_integer: + sax.number_integer(static_cast(static_cast(integer_bits(*n)))); + break; + case value_t::number_unsigned: + sax.number_unsigned(static_cast(integer_bits(*n))); + break; + case value_t::number_float: + sax.number_float(float_value(d, *n), no_token); + break; + case value_t::boolean: + sax.boolean((n->flags & node_flags::is_true) != 0); + break; + case value_t::null: + case value_t::binary: + case value_t::discarded: + default: + // false positive: the parser's frame stack points into its own storage (never null) + // @infer-ignore NULLPTR_DEREFERENCE + sax.null(); + break; + } + + // the next value: close finished containers, then read the key + for (;;) + { + if (depth == 0) + { + return result; + } + frame& f = open[depth - 1]; + if (f.pos == f.end) + { +#if JSON_DIAGNOSTIC_POSITIONS + if (f.start == std::string::npos || f.last_end == std::string::npos) + { + positions.clear(); + } + else + { + const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end); + if (end == std::string::npos) + { + positions.clear(); + } + else + { + // the closing bracket, as the lexer reports it (end_object() + // and end_array() only read its end; the start of the + // container was set when it was opened) + positions.set(end - 1, end); + } + } + if (depth > 1) + { + open[depth - 2].last_end = positions.end; + } +#endif + if (f.object) + { + sax.end_object(); + } + else + { + sax.end_array(); + } + --depth; + continue; + } + const node* entry = f.pos; + if (f.object) + { + string_t key(d.str(*entry), entry->len); + sax.key(key); + ++entry; + } + n = nav::value(entry); + f.pos = document_data::after(entry); + break; + } + } +} + +} // namespace view +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +namespace view +{ + +/*! +@brief the value an exception is about + +basic_json passes the value to the exception, which then names its path +(JSON_DIAGNOSTICS) and its bytes in the source (JSON_DIAGNOSTIC_POSITIONS). A +view's value has no parent pointer and no stored positions: both are found +from the document and the node, when the exception is thrown (so only the +error path pays for them). Without the two macros, a site is empty. +*/ +class error_site +{ + public: + error_site() noexcept = default; + +#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS + error_site(const document_data* d, const node* n) noexcept + : m_doc(d), m_node(n) + {} + + const document_data* doc() const noexcept + { + return m_doc; + } + const node* value() const noexcept + { + return m_node; + } + + private: + const document_data* m_doc = nullptr; + const node* m_node = nullptr; +#else + error_site(const document_data* /*d*/, const node* /*n*/) noexcept {} +#endif +}; + +/// the site of a view's value (the views make this a friend) +struct view_access +{ + template + static error_site site(const View& v) noexcept + { + return {v.m_doc, v.m_node}; + } +}; + +template +error_site site_of(const View& v) noexcept +{ + static_assert(is_json_view::value, "the site of a value is only defined for views"); + return view_access::site(v); +} + +#if JSON_DIAGNOSTICS +/*! +@brief the JSON pointer tokens from the root of document @a d to node @a target + +Nodes have no parent index, so the path is found by descending from the root: +before the first edit, only into the subtree that contains the target (a +container spans the next `next` nodes); after edits, elements can live in +other sequences, so every container is searched. Iterative (the nesting depth +is limited by memory only). False if the target is not found (then tokens is +unspecified). +*/ +template +bool value_path(const document_data& d, const node* target, std::vector& tokens) +{ + using nav = navigation; + struct frame + { + const node* pos; + const node* end; + std::size_t index; + bool object; + }; + const node* const root = d.nodes; + if (target == root) + { + return true; + } + if (!is_container(*root)) + { + return false; + } + std::vector stack; + stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object}); + // (tokens[i] leads from stack[i] to stack[i + 1], or to the target) + for (;;) + { + // a copy: pop_back() and push_back() below change the stack + const frame f = stack.back(); + if (f.pos == f.end) + { + stack.pop_back(); + if (stack.empty()) + { + return false; + } + tokens.pop_back(); + continue; + } + const node* entry = f.pos; + std::string token; + if (f.object) + { + // (as basic_json: the key up to a null byte) + token.assign(d.str(*entry), entry->len); + const std::size_t nul = token.find('\0'); + if (nul != std::string::npos) + { + token.resize(nul); + } + ++entry; + } + else + { + token = std::to_string(f.index); + } + const node* const child = nav::value(entry); + if (child == target) + { + tokens.push_back(token); + return true; + } + stack.back().pos = document_data::after(entry); + ++stack.back().index; + if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next))) + { + tokens.push_back(token); + stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object}); + } + } +} +#endif + +#if JSON_DIAGNOSTIC_POSITIONS +/*! +@brief the bytes [start, end) of node @a n in the source, as materialize() sets them + +Scalars as position_source::set_scalar; an array or object from its opening +bracket to its closing bracket, which is found by following the chain of last +elements to a scalar (or an empty container) and scanning from there to the +closing brackets. None (npos) for values written by edits, and for containers +whose last element has none, as with materialize(). +*/ +template +void value_range(const document_data& d, const node* n, std::size_t& start, std::size_t& end) +{ + using nav = navigation; + position_source p; + start = std::string::npos; + end = std::string::npos; + if (!is_container(*n)) + { + p.set_scalar(d, *n); + start = p.start; + end = p.end; + return; + } + if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size) + { + return; + } + // the open brackets: n, then the last element as long as it is a non-empty container + std::size_t closing = 0; + std::size_t pos = std::string::npos; + for (const node* c = n;;) + { + if (!is_container(*c)) + { + p.set_scalar(d, *c); + pos = p.end; + break; + } + if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size) + { + return; + } + ++closing; + const node* const first = nav::first(d, c); + const node* const last_end = nav::end(d, c); + if (first == last_end) + { + pos = std::size_t{c->off} + 1; + break; + } + const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0; + const node* last = first; + for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset)) + { + last = e; + } + c = nav::value(last + value_offset); + } + for (; closing != 0 && pos != std::string::npos; --closing) + { + pos = position_source::container_end(d.base[0], d.size, pos); + } + if (pos != std::string::npos) + { + start = n->off; + end = pos; + } +} +#endif + +/// the context basic_json puts in front of the message of an exception about +/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then +/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only +/// if there is a path) +/// +/// This and the throw helpers that take an error_site are templates, so that +/// the path and range code is instantiated only where an exception can be +/// thrown: instantiated in every translation unit (as a non-template inline +/// function makes it), it changed GCC's early inlining of unrelated view +/// functions, so that GCC 13 no longer inlined a recursive walk over a view +/// into itself (2.7 times the instructions). +#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS +template +std::string diagnostics(const error_site site) +{ + if (site.doc() == nullptr || site.value() == nullptr) + { + return ""; + } + const document_data& d = *site.doc(); + const bool edited = d.edits != nullptr; + std::string context; +#if JSON_DIAGNOSTICS + std::vector tokens; + const bool found = edited ? value_path(d, site.value(), tokens) : value_path(d, site.value(), tokens); + if (!found || tokens.empty()) + { + // context is still empty; returning it keeps the copy elided (-Wnrvo) + return context; + } + std::string path; + for (const std::string& token : tokens) + { + path.push_back('/'); + path += detail::escape(token); + } + context = concat('(', path, ") "); +#endif +#if JSON_DIAGNOSTIC_POSITIONS + std::size_t start = 0; + std::size_t end = 0; + if (edited) + { + value_range(d, site.value(), start, end); + } + else + { + value_range(d, site.value(), start, end); + } + if (start != std::string::npos && end != std::string::npos) + { + context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") "); + } +#endif + return context; +} +#else +template +std::string diagnostics(const error_site /*site*/) +{ + return ""; +} +#endif + +/// the context of an exception about view @a v (see exception_prefix()) +template +std::string diagnostics_of(const View& v) +{ + return diagnostics<>(site_of(v)); +} + // Exceptions are thrown out of line, so that the accessors that may throw stay -// small enough to be inlined. +// small enough to be inlined. The overloads with an error_site put its context +// in front of the message. +// +// The overloads without one are for exceptions that are not about a value of a +// document: limits and invalid input of the editing and image functions, a +// discarded view (it has no node), a view of another document (its path would +// be one in that document), and those that basic_json creates without a +// context too (the errors of json_pointer, invalid UTF-8). The one exception +// about a value that has no site is key(), see view_iterator::key(). [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type) { @@ -4432,6 +5176,24 @@ namespace view NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr)); } +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_type_error(const error_site site, exception_id id, const char* prefix, const char* type) +{ + NLOHMANN_VIEW_THROW(type_error::create(id, concat(diagnostics<>(site), prefix, type), nullptr)); +} + +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_out_of_range(const error_site site, exception_id id, const std::string& msg) +{ + NLOHMANN_VIEW_THROW(out_of_range::create(id, concat(diagnostics<>(site), msg), nullptr)); +} + +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_invalid_iterator(const error_site site, exception_id id, const char* msg) +{ + NLOHMANN_VIEW_THROW(invalid_iterator::create(id, concat(diagnostics<>(site), msg), nullptr)); +} + /// a parse error without a position (as those of json_pointer) [[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg) { @@ -4440,22 +5202,23 @@ namespace view /// out_of_range.401: the array index @a idx is not below the size template -[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx) +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const error_site site, const Index idx) { - throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range")); + throw_out_of_range(site, exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range")); } /// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements -[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size) +template +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_past_end(const error_site site, std::size_t size) { - throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range")); + throw_out_of_range(site, exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range")); } /// out_of_range.403: the object has no member @a key template -[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key) +[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const error_site site, const Key& key) { - throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found")); + throw_out_of_range(site, exception_id::key_not_found, concat("key '", key, "' not found")); } /// SAX handler of the error replay: accepts every event without storing it, @@ -5492,7 +6255,7 @@ class editor node* const o = own(object); if (o->kind != node_kind::object && o->kind != node_kind::null) { - throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); + throw_type_error(site_of(object), exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); } check_utf8(key.data(), key.size()); const encoded e = encode(std::forward(value)); @@ -5543,9 +6306,9 @@ class editor node* const a = own(array); if (a->kind != node_kind::array) { - throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); + throw_type_error(site_of(array), exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); } - check_index(idx, a->len); + check_index(a, idx, a->len); const encoded e = encode(std::forward(value)); node* const slot = const_cast(nav::value(element_at(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast) assign(slot, e, a, true); @@ -5560,7 +6323,7 @@ class editor node* const a = own(array); if (a->kind != node_kind::array && a->kind != node_kind::null) { - throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); + throw_type_error(site_of(array), exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); } const encoded e = encode(std::forward(value)); if (a->kind == node_kind::null) @@ -5584,9 +6347,9 @@ class editor node* const a = own(array); if (a->kind != node_kind::array) { - throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); + throw_type_error(site_of(array), exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); } - check_index(idx, a->len + 1); + check_index(a, idx, a->len + 1); const encoded e = encode(std::forward(value)); node* const slot = new_slot(e); node* const h = block_of(m_doc, a, 1); @@ -5604,7 +6367,7 @@ class editor node* const o = own(object); if (o->kind != node_kind::object) { - throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); + throw_type_error(site_of(object), exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); } for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1)) { @@ -5622,9 +6385,9 @@ class editor node* const a = own(array); if (a->kind != node_kind::array) { - throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); + throw_type_error(site_of(array), exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); } - check_index(idx, a->len); + check_index(a, idx, a->len); node* const h = block_of(m_doc, a, 0); std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node)); --h->next; @@ -5651,11 +6414,11 @@ class editor return const_cast(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document } - static void check_index(std::size_t idx, std::size_t limit) + void check_index(const node* array, std::size_t idx, std::size_t limit) const { if (idx >= limit) { - throw_array_index_out_of_range(idx); + throw_array_index_out_of_range(error_site(&m_doc, array), idx); } } @@ -7379,7 +8142,10 @@ class view_iterator } /// the key of the current object member; throws invalid_iterator.207 for - /// other iterators, like basic_json's iterators + /// other iterators, like basic_json's iterators (but without the context + /// JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS add: the iterator does not + /// know the value it iterates over, and keeping it would make iterators + /// larger, which keeps GCC from inlining recursive walks) string_view_t key() const { if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0)) @@ -7543,458 +8309,6 @@ NLOHMANN_JSON_NAMESPACE_END // #include // #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // int64_t, uint8_t -#include // string -#include // vector - -// #include -// #include - -// #include - -// #include - -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -namespace view -{ - -#if JSON_DIAGNOSTIC_POSITIONS -/*! -@brief the source positions of the values materialize() replays - -Stands in for the lexer of json_sax_dom_parser, which reads the positions of -the values it stores from these three functions (diagnostic_positions). npos -is a value without a source position. One position_source serves a whole -materialize() call: before each value is handed to the handler, the range of -that value is set (or cleared), overwriting that of the value before. -*/ -struct position_source -{ - /// the token, of which diagnostic_positions only reads the length - struct token - { - std::size_t length; - std::size_t size() const noexcept - { - return length; - } - }; - - std::size_t start = std::string::npos; ///< the first byte of the next value - std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket) - - std::size_t get_chars_read_total() const noexcept - { - return end; - } - std::size_t get_token_start_position() const noexcept - { - return start; - } - token get_string() const noexcept - { - return token{end - start}; - } - - /// the range [first, last), or none if either is npos - void set(std::size_t first, std::size_t last) noexcept - { - if (first == std::string::npos || last == std::string::npos) - { - clear(); - return; - } - start = first; - end = last; - } - - /// no range: the next value has no source position - void clear() noexcept - { - start = std::string::npos; - end = std::string::npos; - } - - /// one past the closing quote of the string whose opening quote is at - /// src[quote], or npos (the scan is bounded by size, as images can be - /// loaded without a check) - static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept - { - for (std::size_t i = quote + 1; i < size; ++i) - { - if (src[i] == '\\') - { - ++i; - } - else if (src[i] == '"') - { - return i + 1; - } - } - return std::string::npos; - } - - /// one past the closing bracket of the array or object whose last element - /// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos; - /// in between, there can only be whitespace, comments, and a trailing comma - /// (the source was validated when it was parsed) - static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept - { - const auto* const b = reinterpret_cast(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it - const unsigned char* const e = b + size; - while (pos < size) - { - switch (src[pos]) - { - case ' ': - case '\t': - case '\n': - case '\r': - case ',': - ++pos; - break; - case ']': - case '}': - return pos + 1; - case '/': - if (pos + 1 < size && src[pos + 1] == '/') - { - pos = static_cast(skip_line_comment(b + pos + 2, e) - b); - break; - } - if (pos + 1 < size && src[pos + 1] == '*') - { - // (unterminated: pos is size, which ends the loop) - pos = static_cast(find_block_comment_end(b + pos + 2, e) - b) + 2; - break; - } - return std::string::npos; - default: - return std::string::npos; - } - } - return std::string::npos; - } - - /// the range of the scalar n of document d - void set_scalar(const document_data& d, const node& n) noexcept - { - if ((n.flags & node_flags::is_new) != 0) - { - clear(); - return; - } - std::size_t first = std::string::npos; - std::size_t last = std::string::npos; - switch (n.kind) - { - case node_kind::string: - if ((n.flags & node_flags::storage) == 0) - { - // off is the first byte after the opening quote - if (n.off != 0) - { - first = n.off - 1; - last = n.off + std::size_t{n.len} + 1; - } - } - else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0) - { - // next is the offset of the opening quote plus one - first = n.next - 1; - last = string_end(d.base[0], d.size, first); - } - break; - case node_kind::number_integer: - case node_kind::number_unsigned: - case node_kind::number_float: - if ((n.flags & node_flags::storage) == 0) - { - first = n.off; - last = n.off + number_length(n); - } - break; - case node_kind::boolean: - first = n.off; - last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u); - break; - case node_kind::null: - first = n.off; - last = n.off + 4u; - break; - case node_kind::object: - case node_kind::array: - case node_kind::link: - default: - break; - } - // (a crafted image can point past the text) - if (last == std::string::npos || last > d.size) - { - clear(); - } - else - { - set(first, last); - } - } -}; -#endif - -/*! -@brief the basic_json value of the subtree at n - -The subtree is replayed into the SAX handler that parse() uses to build its -values, so the result is the value parse() would produce: duplicate keys keep -the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is -iterative, so the nesting depth is limited by memory only, as for parse(). - -With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with -parse(): position_source stands in for the lexer. Values written by edits have -none (npos), and neither has an array or object whose elements changed, or -whose last element has none (the end of its closing bracket is found by -scanning from the end of the last element). -*/ -template -BasicJsonType materialize(const document_data& d, const node* n) -{ - using string_t = typename BasicJsonType::string_t; - using input_t = iterator_input_adapter; -#if JSON_DIAGNOSTIC_POSITIONS - using lexer_t = position_source; -#else - using lexer_t = lexer; // (the default: unused, as no lexer is passed) -#endif - using sax_t = json_sax_dom_parser; - using nav = navigation; - - // the open containers: their next element (or key of the next member), - // their end, and whether they are objects; for the first 16 levels on the - // stack, else sized once from the document's deepest nesting (for a - // subtree an upper bound) - struct frame - { - const node* pos; - const node* end; - bool object; -#if JSON_DIAGNOSTIC_POSITIONS - std::size_t start; ///< the opening bracket (npos: unknown) - std::size_t last_end; ///< the end of the last element read (npos: unknown) -#endif - }; - -#if !JSON_DIAGNOSTIC_POSITIONS - // a scalar needs neither the handler nor the stack of open containers - // (with positions, it goes through the handler, which sets them) - switch (static_cast(n->kind)) - { - case value_t::string: - return BasicJsonType(string_t(d.str(*n), n->len)); - case value_t::number_integer: - return BasicJsonType(static_cast(static_cast(integer_bits(*n)))); - case value_t::number_unsigned: - return BasicJsonType(static_cast(integer_bits(*n))); - case value_t::number_float: - return BasicJsonType(float_value(d, *n)); - case value_t::boolean: - return BasicJsonType((n->flags & node_flags::is_true) != 0); - case value_t::null: - return BasicJsonType(nullptr); - case value_t::object: - case value_t::array: - case value_t::binary: - case value_t::discarded: - default: - break; - } -#endif - - BasicJsonType result; -#if JSON_DIAGNOSTIC_POSITIONS - position_source positions; - sax_t sax(result, true, &positions); -#else - sax_t sax(result, true); -#endif - const string_t no_token {}; - // (shallow subtrees on the stack: a call for a small value then allocates only for the value) - std::array shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open - std::vector deep{}; - frame* open = shallow.data(); - std::size_t capacity = shallow.size(); - if (d.max_depth > capacity) - { - deep.resize(d.max_depth); - open = deep.data(); - capacity = deep.size(); - } - std::size_t depth = 0; - for (;;) - { -#if JSON_DIAGNOSTIC_POSITIONS - if (!is_container(*n)) - { - positions.set_scalar(d, *n); - if (depth != 0) - { - open[depth - 1].last_end = positions.end; - } - } -#endif - // false positive: n comes from nav::value(), which never returns null for a valid index - // @infer-ignore NULLPTR_DEREFERENCE - switch (static_cast(n->kind)) - { - case value_t::object: - case value_t::array: - { - const bool object = n->kind == node_kind::object; -#if JSON_DIAGNOSTIC_POSITIONS - // (moved containers are also new) - if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size) - { - positions.clear(); - } - else - { - positions.set(std::size_t{n->off}, std::size_t{n->off} + 1); - } -#endif - if (object) - { - sax.start_object(n->len); - } - else - { - sax.start_array(n->len); - } - if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) - { - // (deeper than the recorded nesting, e.g. after edits) - deep.assign(open, open + depth); - deep.resize(depth * 2); - open = deep.data(); - capacity = deep.size(); - } - // (with positions: until an element is read, the container's - // own bracket is the last thing read) - open[depth++] = frame{nav::first(d, n), nav::end(d, n), object -#if JSON_DIAGNOSTIC_POSITIONS - , positions.start, positions.end -#endif - }; - break; - } - case value_t::string: - { - string_t s(d.str(*n), n->len); - sax.string(s); - break; - } - case value_t::number_integer: - sax.number_integer(static_cast(static_cast(integer_bits(*n)))); - break; - case value_t::number_unsigned: - sax.number_unsigned(static_cast(integer_bits(*n))); - break; - case value_t::number_float: - sax.number_float(float_value(d, *n), no_token); - break; - case value_t::boolean: - sax.boolean((n->flags & node_flags::is_true) != 0); - break; - case value_t::null: - case value_t::binary: - case value_t::discarded: - default: - // false positive: the parser's frame stack points into its own storage (never null) - // @infer-ignore NULLPTR_DEREFERENCE - sax.null(); - break; - } - - // the next value: close finished containers, then read the key - for (;;) - { - if (depth == 0) - { - return result; - } - frame& f = open[depth - 1]; - if (f.pos == f.end) - { -#if JSON_DIAGNOSTIC_POSITIONS - if (f.start == std::string::npos || f.last_end == std::string::npos) - { - positions.clear(); - } - else - { - const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end); - if (end == std::string::npos) - { - positions.clear(); - } - else - { - // the closing bracket, as the lexer reports it (end_object() - // and end_array() only read its end; the start of the - // container was set when it was opened) - positions.set(end - 1, end); - } - } - if (depth > 1) - { - open[depth - 2].last_end = positions.end; - } -#endif - if (f.object) - { - sax.end_object(); - } - else - { - sax.end_array(); - } - --depth; - continue; - } - const node* entry = f.pos; - if (f.object) - { - string_t key(d.str(*entry), entry->len); - sax.key(key); - ++entry; - } - n = nav::value(entry); - f.pos = document_data::after(entry); - break; - } - } -} - -} // namespace view -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END // #include @@ -8049,6 +8363,7 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept } /// throws the exception json_pointer::array_index throws for this status +/// (without a context, as json_pointer::array_index) template [[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s) { @@ -8088,7 +8403,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (mode == pointer_mode::checked) { - throw_key_not_found(token); + throw_key_not_found(site_of(cur), token); } return View(); } @@ -8100,7 +8415,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (throwing) { - throw_array_index_past_end(cur.size()); + throw_array_index_past_end(site_of(cur), cur.size()); } return View(); } @@ -8119,7 +8434,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (mode == pointer_mode::checked) { - throw_array_index_out_of_range(idx); + throw_array_index_out_of_range(site_of(cur), idx); } return View(); } @@ -8129,7 +8444,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode) { if (throwing) { - throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); + throw_out_of_range(site_of(cur), exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'")); } return View(); } @@ -9779,7 +10094,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod case value_t::binary: case value_t::discarded: default: - throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast(n.kind))); + throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast(n.kind))); } } @@ -9789,7 +10104,7 @@ std::vector vector_value(const View& v) { if (NLOHMANN_VIEW_UNLIKELY(!v.is_array())) { - throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name()); + throw_type_error(site_of(v), exception_id::type_mismatch, "type must be array, but is ", v.type_name()); } std::vector r; r.reserve(v.size()); @@ -9807,7 +10122,7 @@ Map map_value(const View& v) { if (NLOHMANN_VIEW_UNLIKELY(!v.is_object())) { - throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name()); + throw_type_error(site_of(v), exception_id::type_mismatch, "type must be object, but is ", v.type_name()); } Map r; for (auto it = v.begin(); it != v.end(); ++it) @@ -10064,7 +10379,7 @@ class basic_json_view { return {}; } - detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name()); } return lookup(key); } @@ -10090,7 +10405,7 @@ class basic_json_view { return {}; } - detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name()); } return idx < m_node->len ? element(idx) : basic_json_view(); } @@ -10123,12 +10438,12 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); } const basic_json_view r = lookup(key); if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded())) { - detail::view::throw_key_not_found(std::string(key.data(), key.size())); + detail::view::throw_key_not_found(site(), std::string(key.data(), key.size())); } return r; } @@ -10149,11 +10464,11 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_array())) { - detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name()); } if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len)) { - detail::view::throw_array_index_out_of_range(idx); + detail::view::throw_array_index_out_of_range(site(), idx); } return element(idx); } @@ -10181,7 +10496,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = lookup(key); return r.is_discarded() ? default_value : r.template get(); @@ -10191,7 +10506,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_object())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } // (the default is made only if there is no such member) const basic_json_view r = lookup(key); @@ -10206,7 +10521,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); return r.is_discarded() ? default_value : r.template get(); @@ -10216,7 +10531,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) { - detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name()); } const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value); return r.is_discarded() ? string_t(default_value) : r.template get(); @@ -10229,7 +10544,7 @@ class basic_json_view const iterator it = begin(); if (NLOHMANN_VIEW_UNLIKELY(it == end())) { - detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value"); + detail::view::throw_invalid_iterator(site(), detail::exception_id::iterator_value_unavailable, "cannot get value"); } return *it; } @@ -10385,7 +10700,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_string())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be string, but is ", type_name()); } return {m_doc->str(*m_node), m_node->len}; } @@ -10397,7 +10712,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_number())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); } return {m_doc->str(*m_node), detail::view::number_length(*m_node)}; } @@ -10543,12 +10858,19 @@ class basic_json_view template friend class basic_json_view; template friend class detail::view::editor; template friend class detail::view::view_side; + friend struct detail::view::view_access; friend iterator; basic_json_view(const document_data* d, const node* n) noexcept : m_doc(d), m_node(n) {} + /// this value as the context of an exception + detail::view::error_site site() const noexcept + { + return {m_doc, m_node}; + } + using json_side_t = detail::view::json_side; static detail::view::view_side side(const basic_json_view& v) noexcept @@ -10655,7 +10977,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_boolean())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name()); } return (m_node->flags & detail::view::node_flags::is_true) != 0; } @@ -10665,7 +10987,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name()); } return detail::view::arithmetic_value(*m_doc, *m_node); } @@ -10674,7 +10996,7 @@ class basic_json_view { if (NLOHMANN_VIEW_UNLIKELY(!is_null())) { - detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); + detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be null, but is ", type_name()); } return nullptr; } @@ -11089,7 +11411,7 @@ class basic_json_document template::value, int>::type = 0> view_type set(view_type array, I idx, V && value) { - return editor().set(array, index(idx), std::forward(value)); + return editor().set(array, index(array, idx), std::forward(value)); } /// set the value at a JSON pointer: its parent must exist; an object @@ -11126,7 +11448,7 @@ class basic_json_document const std::size_t idx = pointer_index(token); if (idx >= 0xFFFFFFFFu) { - detail::view::throw_array_index_out_of_range(idx); + detail::view::throw_array_index_out_of_range(detail::view::site_of(parent), idx); } for (std::size_t i = 0; i < idx; ++i) { @@ -11160,7 +11482,7 @@ class basic_json_document template::value, int>::type = 0> view_type insert(view_type array, I idx, V && value) { - return editor().insert(array, index(idx), std::forward(value)); + return editor().insert(array, index(array, idx), std::forward(value)); } /// remove all members with this key; returns their number @@ -11173,7 +11495,7 @@ class basic_json_document template::value, int>::type = 0> void erase(view_type array, I idx) { - editor().erase(array, index(idx)); + editor().erase(array, index(array, idx)); } /// remove the value at a JSON pointer; returns the number of removed @@ -11207,25 +11529,25 @@ class basic_json_document return detail::view::editor(*m_data); } - /// an index (out_of_range.401 if negative) + /// an index into @a array (out_of_range.401 if negative) template - static std::size_t index(I idx) + static std::size_t index(const view_type& array, I idx) { - return index(idx, std::is_signed {}); + return index(array, idx, std::is_signed {}); } template - static std::size_t index(I idx, std::true_type /*signed*/) + static std::size_t index(const view_type& array, I idx, std::true_type /*signed*/) { if (idx < 0) { - detail::view::throw_array_index_out_of_range(idx); + detail::view::throw_array_index_out_of_range(detail::view::site_of(array), idx); } return static_cast(idx); } template - static std::size_t index(I idx, std::false_type /*unsigned*/) + static std::size_t index(const view_type& /*array*/, I idx, std::false_type /*unsigned*/) { return static_cast(idx); } diff --git a/tests/src/json_view_test_helpers.hpp b/tests/src/json_view_test_helpers.hpp index b5af61798..87822815b 100644 --- a/tests/src/json_view_test_helpers.hpp +++ b/tests/src/json_view_test_helpers.hpp @@ -15,6 +15,7 @@ #include #include // any_of +#include // size_t #include // function #include // mt19937 #include // string @@ -88,6 +89,27 @@ inline bool has_duplicate_keys(const nlohmann::ordered_json_view& v) }); } +// the message of an exception about the value at path (a JSON pointer, "" for +// the root) whose source bytes are [start, end): the context basic_json adds +// with JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS, if enabled, then what +inline std::string message(const std::string& id, const std::string& path, std::size_t start, std::size_t end, const std::string& what) +{ +#if JSON_DIAGNOSTIC_POSITIONS + const std::string positions = "(bytes " + std::to_string(start) + "-" + std::to_string(end) + ") "; +#else + const std::string positions; + static_cast(start); + static_cast(end); +#endif +#if JSON_DIAGNOSTICS + const std::string context = path.empty() ? "" : "(" + path + ") " + positions; +#else + const std::string& context = positions; + static_cast(path); +#endif + return "[json.exception." + id + "] " + context + what; +} + #if !defined(JSON_NOEXCEPTION) // the exception a call throws, or "" if it throws none inline std::string exception_of_call(const std::function& f) diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index 77f7aa4fa..c823ef9e2 100644 --- a/tests/src/unit-json_view.cpp +++ b/tests/src/unit-json_view.cpp @@ -225,7 +225,7 @@ TEST_CASE("json_view") #if JSON_DIAGNOSTICS // the parents are set, so errors name the path const json m = materialized(R"({"a":{"b":[1]}})"); - CHECK_THROWS_WITH_AS(m.at("a").at("b").at(0).at("x"), "[json.exception.type_error.304] (/a/b/0) cannot use at() with number", json::type_error&); + CHECK_THROWS_WITH_AS(m.at("a").at("b").at(0).at("x"), json_view_test::message("type_error.304", "/a/b/0", 11, 12, "cannot use at() with number").c_str(), json::type_error&); #endif } @@ -750,19 +750,6 @@ std::string exception_of(F f) return ""; } -// the exception a call on a basic_json value throws, without the byte range of -// the value (JSON_DIAGNOSTIC_POSITIONS), which the exceptions of views lack -template -std::string json_exception_of(F f) -{ - std::string what = exception_of(f); - const std::size_t at = what.find("] (bytes "); - if (at != std::string::npos) - { - what.erase(at + 2, what.find(") ", at) - at); - } - return what; -} #endif // compares a view with the ordered_json value materialize() gives for it: @@ -971,24 +958,25 @@ TEST_CASE("json_view element access and iteration") #if !defined(JSON_NOEXCEPTION) if (!j.is_object()) { - CHECK(exception_of([&] { static_cast(v["a"]); }) == json_exception_of([&] { static_cast(j["a"]); })); + CHECK(exception_of([&] { static_cast(v["a"]); }) == exception_of([&] { static_cast(j["a"]); })); } if (!j.is_array()) { - CHECK(exception_of([&] { static_cast(v[0]); }) == json_exception_of([&] { static_cast(j[0]); })); + CHECK(exception_of([&] { static_cast(v[0]); }) == exception_of([&] { static_cast(j[0]); })); } - CHECK(exception_of([&] { static_cast(v.at("a")); }) == json_exception_of([&] { static_cast(j.at("a")); })); - CHECK(exception_of([&] { static_cast(v.at("missing")); }) == json_exception_of([&] { static_cast(j.at("missing")); })); - CHECK(exception_of([&] { static_cast(v.at(0)); }) == json_exception_of([&] { static_cast(j.at(0)); })); - CHECK(exception_of([&] { static_cast(v.at(5)); }) == json_exception_of([&] { static_cast(j.at(5)); })); + CHECK(exception_of([&] { static_cast(v.at("a")); }) == exception_of([&] { static_cast(j.at("a")); })); + CHECK(exception_of([&] { static_cast(v.at("missing")); }) == exception_of([&] { static_cast(j.at("missing")); })); + CHECK(exception_of([&] { static_cast(v.at(0)); }) == exception_of([&] { static_cast(j.at(0)); })); + CHECK(exception_of([&] { static_cast(v.at(5)); }) == exception_of([&] { static_cast(j.at(5)); })); if (!(j.is_object() && j.empty())) // (key() of an end iterator) { - CHECK(exception_of([&] { static_cast(v.begin().key()); }) == json_exception_of([&] { static_cast(j.begin().key()); })); + // (without the context of basic_json: view iterators do not know the value they iterate over) + CHECK(exception_of([&] { static_cast(v.begin().key()); }) == (j.is_object() ? "" : "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators")); } if (!j.empty() || j.is_null()) { - CHECK(exception_of([&] { static_cast(v.front()); }) == json_exception_of([&] { static_cast(j.front()); })); - CHECK(exception_of([&] { static_cast(v.back()); }) == json_exception_of([&] { static_cast(j.back()); })); + CHECK(exception_of([&] { static_cast(v.front()); }) == exception_of([&] { static_cast(j.front()); })); + CHECK(exception_of([&] { static_cast(v.back()); }) == exception_of([&] { static_cast(j.back()); })); } #endif CHECK(v.contains("a") == j.contains("a")); @@ -1000,8 +988,8 @@ TEST_CASE("json_view element access and iteration") const json_document d = json_document::parse(R"({"a":[]})"); CHECK(d.root()["b"].is_discarded()); CHECK(d.root()["a"][0].is_discarded()); - CHECK_THROWS_WITH_AS(d.root()["a"].front(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); - CHECK_THROWS_WITH_AS(d.root()["a"].back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&); + CHECK_THROWS_WITH_AS(d.root()["a"].front(), json_view_test::message("invalid_iterator.214", "/a", 5, 7, "cannot get value").c_str(), json::invalid_iterator&); + CHECK_THROWS_WITH_AS(d.root()["a"].back(), json_view_test::message("invalid_iterator.214", "/a", 5, 7, "cannot get value").c_str(), json::invalid_iterator&); const json_view invalid{}; CHECK(invalid.begin() == invalid.end()); CHECK(std::string(invalid.type_name()) == "discarded"); @@ -1032,10 +1020,10 @@ TEST_CASE("json_view element access and iteration") CHECK(v[json_view::json_pointer("/a/b/5")][0].is_discarded()); #if !defined(JSON_NOEXCEPTION) // type errors on values that are not discarded stay - CHECK_THROWS_WITH_AS(v[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&); - CHECK_THROWS_WITH_AS(v["a"]["b"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&); - CHECK_THROWS_WITH_AS(v["s"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", json::type_error&); - CHECK_THROWS_WITH_AS(v["s"][0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with string", json::type_error&); + CHECK_THROWS_WITH_AS(v[0], json_view_test::message("type_error.305", "", 0, 29, "cannot use operator[] with a numeric argument with object").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(v["a"]["b"]["c"], json_view_test::message("type_error.305", "/a/b", 10, 17, "cannot use operator[] with a string argument with array").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(v["s"]["c"], json_view_test::message("type_error.305", "/s", 23, 28, "cannot use operator[] with a string argument with string").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(v["s"][0], json_view_test::message("type_error.305", "/s", 23, 28, "cannot use operator[] with a numeric argument with string").c_str(), json::type_error&); CHECK_THROWS_AS(v["a"]["b"][0]["c"], json::type_error&); CHECK_THROWS_AS(v["s"][json_view::json_pointer("/x")], json::out_of_range&); // at() keeps throwing on a discarded view @@ -1110,9 +1098,9 @@ TEST_CASE("json_view element access and iteration") CHECK(v[(std::numeric_limits::max)()].is_discarded()); CHECK(v[std::numeric_limits::max()].is_discarded()); #if !defined(JSON_NOEXCEPTION) - CHECK_THROWS_WITH_AS(v.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&); - CHECK_THROWS_WITH_AS(v.at(3u), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&); - CHECK_THROWS_WITH_AS(v.at(std::int64_t(3)), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&); + CHECK_THROWS_WITH_AS(v.at(3), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(v.at(3u), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(v.at(std::int64_t(3)), json_view_test::message("out_of_range.401", "", 0, 10, "array index 3 is out of range").c_str(), json::out_of_range&); CHECK_THROWS_AS(v.at(-1), json::out_of_range&); CHECK_THROWS_AS(v.at(-1L), json::out_of_range&); CHECK_THROWS_AS(v.at(std::int64_t(-1)), json::out_of_range&); @@ -1246,26 +1234,6 @@ TEST_CASE("json_view element access and iteration") namespace { -#if !defined(JSON_NOEXCEPTION) -// an exception message without the context that basic_json adds with -// JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) "); -// the view's exceptions have no such context -std::string without_path(std::string msg) -{ - for (const char* prefix : - {"] (/", "] (bytes " - }) - { - const std::size_t open = msg.find(prefix); - if (open != std::string::npos) - { - msg.erase(open + 2, msg.find(") ", open) + 2 - (open + 2)); - } - } - return msg; -} -#endif - // the bits of a float, to compare values bit for bit std::uint64_t bits(double x) { @@ -1336,18 +1304,18 @@ void check_values(const ordered_json_view& v, const ordered_json& j, const std:: // conversions to the wrong type throw what basic_json throws if (!j.is_number()) { - CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); + CHECK(exception_of([&] { static_cast(v.get()); }) == exception_of([&] { static_cast(j.get()); })); } - CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); - CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); - CHECK(exception_of([&] { static_cast(v.get()); }) == without_path(exception_of([&] { static_cast(j.get()); }))); + CHECK(exception_of([&] { static_cast(v.get()); }) == exception_of([&] { static_cast(j.get()); })); + CHECK(exception_of([&] { static_cast(v.get()); }) == exception_of([&] { static_cast(j.get()); })); + CHECK(exception_of([&] { static_cast(v.get()); }) == exception_of([&] { static_cast(j.get()); })); if (!j.is_array()) { - CHECK(exception_of([&] { static_cast(v.get>()); }) == without_path(exception_of([&] { static_cast(j.get>()); }))); + CHECK(exception_of([&] { static_cast(v.get>()); }) == exception_of([&] { static_cast(j.get>()); })); } if (!j.is_object()) { - CHECK(exception_of([&] { static_cast(v.get>()); }) == without_path(exception_of([&] { static_cast(j.get>()); }))); + CHECK(exception_of([&] { static_cast(v.get>()); }) == exception_of([&] { static_cast(j.get>()); })); } #endif @@ -1490,8 +1458,8 @@ TEST_CASE("json_view values") CHECK(v[3].number_token() == "123456789012345678901234567890"); CHECK(v[4].number_token() == "-12"); CHECK(v[5].number_token() == "7"); - CHECK_THROWS_WITH_AS(v[6].number_token(), "[json.exception.type_error.302] type must be number, but is string", json::type_error&); - CHECK_THROWS_WITH_AS(v.get_string(), "[json.exception.type_error.302] type must be string, but is array", json::type_error&); + CHECK_THROWS_WITH_AS(v[6].number_token(), json_view_test::message("type_error.302", "/6", 56, 59, "type must be number, but is string").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(v.get_string(), json_view_test::message("type_error.302", "", 0, 60, "type must be string, but is array").c_str(), json::type_error&); } SECTION("conversions") @@ -1565,15 +1533,15 @@ TEST_CASE("json_view values") CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0)); CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3)); #if !defined(JSON_NOEXCEPTION) - CHECK(exception_of([&] { static_cast(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast(j["o"]["x"].value("k", 0)); }))); - CHECK(exception_of([&] { static_cast(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast(j.value("s", 0)); }))); - CHECK(exception_of([&] { static_cast(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast(j["n"].value("x", 0)); }))); - CHECK(exception_of([&] { static_cast(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast(j["n"].value(json::json_pointer("/x"), 0)); }))); + CHECK(exception_of([&] { static_cast(v["o"]["x"].value("k", 0)); }) == exception_of([&] { static_cast(j["o"]["x"].value("k", 0)); })); + CHECK(exception_of([&] { static_cast(v.value("s", 0)); }) == exception_of([&] { static_cast(j.value("s", 0)); })); + CHECK(exception_of([&] { static_cast(v["n"].value("x", 0)); }) == exception_of([&] { static_cast(j["n"].value("x", 0)); })); + CHECK(exception_of([&] { static_cast(v["n"].value(json::json_pointer("/x"), 0)); }) == exception_of([&] { static_cast(j["n"].value(json::json_pointer("/x"), 0)); })); // string defaults: the member is not a string, or the value is neither an object nor an array - CHECK(exception_of([&] { static_cast(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast(j.value("n", "default")); }))); - CHECK(exception_of([&] { static_cast(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast(j["n"].value("x", "default")); }))); - CHECK(exception_of([&] { static_cast(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast(j.value(json::json_pointer("/n"), "default")); }))); - CHECK(exception_of([&] { static_cast(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast(j["s"].value(json::json_pointer("/x"), "default")); }))); + CHECK(exception_of([&] { static_cast(v.value("n", "default")); }) == exception_of([&] { static_cast(j.value("n", "default")); })); + CHECK(exception_of([&] { static_cast(v["n"].value("x", "default")); }) == exception_of([&] { static_cast(j["n"].value("x", "default")); })); + CHECK(exception_of([&] { static_cast(v.value(json::json_pointer("/n"), "default")); }) == exception_of([&] { static_cast(j.value(json::json_pointer("/n"), "default")); })); + CHECK(exception_of([&] { static_cast(v["s"].value(json::json_pointer("/x"), "default")); }) == exception_of([&] { static_cast(j["s"].value(json::json_pointer("/x"), "default")); })); #endif // (defaults and members longer than the short string optimization) CHECK(v.value("s", "a default longer than fifteen characters") == "text"); @@ -1632,7 +1600,7 @@ TEST_CASE("json_view JSON pointers") { CAPTURE(pointer) const json::json_pointer p(pointer); - const std::string at_error = without_path(exception_of([&] { static_cast(j.at(p)); })); + const std::string at_error = exception_of([&] { static_cast(j.at(p)); }); CHECK(exception_of([&] { static_cast(v.at(p)); }) == at_error); if (at_error.empty()) { @@ -1646,7 +1614,7 @@ TEST_CASE("json_view JSON pointers") } else { - CHECK(exception_of([&] { static_cast(v[p]); }) == without_path(exception_of([&] { static_cast(j[p]); }))); + CHECK(exception_of([&] { static_cast(v[p]); }) == exception_of([&] { static_cast(j[p]); })); } // (basic_json::contains() throws out_of_range.404 for an empty // array index token, although it is not meant to throw; the view @@ -1657,7 +1625,7 @@ TEST_CASE("json_view JSON pointers") static_cast(found); }); CHECK(v.contains(p) == (contains_error.empty() && j.contains(p))); - CHECK(exception_of([&] { static_cast(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast(j.value(p, 5)); }))); + CHECK(exception_of([&] { static_cast(v.value(p, 5)); }) == exception_of([&] { static_cast(j.value(p, 5)); })); } } #endif diff --git a/tests/src/unit-json_view_edit.cpp b/tests/src/unit-json_view_edit.cpp index 1b1a24ec8..2cc3d4701 100644 --- a/tests/src/unit-json_view_edit.cpp +++ b/tests/src/unit-json_view_edit.cpp @@ -532,18 +532,18 @@ TEST_CASE("json_view edits: errors") CHECK_THROWS_WITH_AS(d.set(other_editable.root(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&); CHECK_THROWS_WITH_AS(d.set(json_editable_view(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&); - CHECK_THROWS_WITH_AS(d.set(root["a"], "k", 1), "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&); - CHECK_THROWS_WITH_AS(d.set(root["o"], 0, 1), "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&); - CHECK_THROWS_WITH_AS(d.set(root["a"], 2, 1), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&); - CHECK_THROWS_WITH_AS(d.set(root["a"], -1, 1), "[json.exception.out_of_range.401] array index -1 is out of range", json::out_of_range&); - CHECK_THROWS_WITH_AS(d.push_back(root["o"], 1), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&); - CHECK_THROWS_WITH_AS(d.insert(root["n"], 0, 1), "[json.exception.type_error.309] cannot use insert() with number", json::type_error&); - CHECK_THROWS_WITH_AS(d.insert(root["a"], 3, 1), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&); - CHECK_THROWS_WITH_AS(d.erase(root["n"], "k"), "[json.exception.type_error.307] cannot use erase() with number", json::type_error&); - CHECK_THROWS_WITH_AS(d.erase(root["o"], 0), "[json.exception.type_error.307] cannot use erase() with object", json::type_error&); - CHECK_THROWS_WITH_AS(d.erase(root["a"], 2), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&); + CHECK_THROWS_WITH_AS(d.set(root["a"], "k", 1), json_view_test::message("type_error.305", "/a", 21, 27, "cannot use operator[] with a string argument with array").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(d.set(root["o"], 0, 1), json_view_test::message("type_error.305", "/o", 6, 14, "cannot use operator[] with a numeric argument with object").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(d.set(root["a"], 2, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 2 is out of range").c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(d.set(root["a"], -1, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index -1 is out of range").c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(d.push_back(root["o"], 1), json_view_test::message("type_error.308", "/o", 6, 14, "cannot use push_back() with object").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(d.insert(root["n"], 0, 1), json_view_test::message("type_error.309", "/n", 34, 35, "cannot use insert() with number").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(d.insert(root["a"], 3, 1), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 3 is out of range").c_str(), json::out_of_range&); + CHECK_THROWS_WITH_AS(d.erase(root["n"], "k"), json_view_test::message("type_error.307", "/n", 34, 35, "cannot use erase() with number").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(d.erase(root["o"], 0), json_view_test::message("type_error.307", "/o", 6, 14, "cannot use erase() with object").c_str(), json::type_error&); + CHECK_THROWS_WITH_AS(d.erase(root["a"], 2), json_view_test::message("out_of_range.401", "/a", 21, 27, "array index 2 is out of range").c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("")), "[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&); - CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("/missing/x")), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&); + CHECK_THROWS_WITH_AS(d.erase(json::json_pointer("/missing/x")), json_view_test::message("out_of_range.403", "", 0, 47, "key 'missing' not found").c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&); CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&); CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&); diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index fa01b639d..824585a56 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -338,8 +338,7 @@ TEST_CASE("json_view from_json") CHECK(d.root().get>().at("a") == 3); } - // (the exceptions of views have neither the path nor the byte range of the value) -#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS +#if !defined(JSON_NOEXCEPTION) SECTION("std types: the same errors as from the materialized value") { const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])"); @@ -404,10 +403,10 @@ TEST_CASE("json_view from_json") CHECK(d.root()[4].get() == strict_color::red); #if !defined(JSON_NOEXCEPTION) const json j = d.root().materialize(); - CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\""); #if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS - CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == exception_of_call([&] { static_cast(j[2].get()); })); + CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == "[json.exception.out_of_range.410] enum value out of range for strict_color: \"purple\""); #endif + CHECK(exception_of_call([&] { static_cast(d.root()[2].get()); }) == exception_of_call([&] { static_cast(j[2].get()); })); #endif } @@ -477,11 +476,9 @@ TEST_CASE("json_view from_json") CHECK(o.root().get().center.y == 2); } -#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS +#if !defined(JSON_NOEXCEPTION) SECTION("errors are those of the same conversion of a json value") { - // (with JSON_DIAGNOSTICS, those of json name the path of the value, - // and with JSON_DIAGNOSTIC_POSITIONS its bytes) const json_document d = json_document::parse(R"([{"x": 1}, {"x": "1", "y": 2}, [1, 2], {"id": "7", "c": "red"}])"); const json j = d.root().materialize(); for (std::size_t i = 0; i < 3; ++i) diff --git a/tests/src/unit-json_view_large.cpp b/tests/src/unit-json_view_large.cpp index 89d7db326..5ad65c812 100644 --- a/tests/src/unit-json_view_large.cpp +++ b/tests/src/unit-json_view_large.cpp @@ -163,7 +163,7 @@ TEST_CASE("json_view large objects") CHECK(v[""].get_string() == "empty key"); CHECK(v["k1"].get() == 1); // the first of duplicate keys, as for small objects CHECK(!v.contains("missing")); - CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&); + CHECK_THROWS_WITH_AS(v.at("missing"), json_view_test::message("out_of_range.403", "", 0, text.size(), "key 'missing' not found").c_str(), json::out_of_range&); CHECK(v == j); CHECK(v.materialize() == j); } diff --git a/tests/src/unit-json_view_positions.cpp b/tests/src/unit-json_view_positions.cpp index 08ea47a08..c6afd7fcb 100644 --- a/tests/src/unit-json_view_positions.cpp +++ b/tests/src/unit-json_view_positions.cpp @@ -81,6 +81,112 @@ std::string range(const json& j) { return j.start_pos() == npos && j.end_pos() == npos ? "none" : std::to_string(j.start_pos()) + "-" + std::to_string(j.end_pos()); } + +#if !defined(JSON_NOEXCEPTION) +using json_view_test::exception_of_call; + +// the exceptions about every value of v are those about the value at the same +// place in j, a value of the whole document from parse() or materialize() (so +// that its parents and positions are set); members whose key repeats are +// skipped (the view finds the first one, j holds the last) +template +void check_exceptions(const View& v, const json& j) +{ + CAPTURE(j.dump()) + if (!v.is_number()) + { + CHECK(exception_of_call([&] { static_cast(v.template get()); }) == exception_of_call([&] { static_cast(j.get()); })); + } + if (!v.is_string()) + { + CHECK(exception_of_call([&] { static_cast(v.template get()); }) == exception_of_call([&] { static_cast(j.get()); })); + } + if (!v.is_array()) + { + CHECK(exception_of_call([&] { static_cast(v.template get>()); }) == exception_of_call([&] { static_cast(j.get>()); })); + } + CHECK(exception_of_call([&] { static_cast(v.at("missing")); }) == exception_of_call([&] { static_cast(j.at("missing")); })); + CHECK(exception_of_call([&] { static_cast(v.at(1000)); }) == exception_of_call([&] { static_cast(j.at(1000)); })); + CHECK(exception_of_call([&] { static_cast(v.at(json::json_pointer("/missing"))); }) == exception_of_call([&] { static_cast(j.at(json::json_pointer("/missing"))); })); + CHECK(exception_of_call([&] { static_cast(v.at(json::json_pointer("/-"))); }) == exception_of_call([&] { static_cast(j.at(json::json_pointer("/-"))); })); + CHECK(exception_of_call([&] { static_cast(v.value("k", 0)); }) == exception_of_call([&] { static_cast(j.value("k", 0)); })); + if (!v.is_object()) + { + CHECK(exception_of_call([&] { static_cast(v["k"]); }) == exception_of_call([&] { static_cast(j["k"]); })); + // (without the context of basic_json: view iterators do not know the value they iterate over) + CHECK(exception_of_call([&] { static_cast(v.begin().key()); }) == "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators"); + } + if (!v.is_array()) + { + CHECK(exception_of_call([&] { static_cast(v[0]); }) == exception_of_call([&] { static_cast(j[0]); })); + } + if (v.is_null()) + { + CHECK(exception_of_call([&] { static_cast(v.front()); }) == exception_of_call([&] { static_cast(j.front()); })); + } + if (v.is_object()) + { + for (auto it = v.begin(); it != v.end(); ++it) + { + const std::string key(it.key().data(), it.key().size()); + if (v.count(key) == 1) + { + check_exceptions(it.value(), j.at(key)); + } + } + } + else if (v.is_array()) + { + for (std::size_t i = 0; i < v.size(); ++i) + { + check_exceptions(v[i], j[i]); + } + } +} + +// as check_exceptions, for edits that fail (they change neither d nor j) +void check_edit_exceptions(json_editable_document& d, const json_editable_document::view_type& v, json& j) +{ + CAPTURE(j.dump()) + if (!v.is_object() && !v.is_null()) + { + CHECK(exception_of_call([&] { d.set(v, "k", 1); }) == exception_of_call([&] { j["k"] = 1; })); + CHECK(exception_of_call([&] { d.erase(v, "k"); }) == exception_of_call([&] { j.erase("k"); })); + } + if (!v.is_array() && !v.is_null()) + { + CHECK(exception_of_call([&] { d.set(v, 0, 1); }) == exception_of_call([&] { j[0] = 1; })); + CHECK(exception_of_call([&] { d.push_back(v, 1); }) == exception_of_call([&] { j.push_back(1); })); + } + if (!v.is_array()) + { + CHECK(exception_of_call([&] { d.insert(v, 0, 1); }) == exception_of_call([&] { j.insert(j.cbegin(), 1); })); + CHECK(exception_of_call([&] { d.erase(v, 0); }) == exception_of_call([&] { j.erase(0); })); + } + else + { + CHECK(exception_of_call([&] { d.erase(v, 1000); }) == exception_of_call([&] { j.erase(1000); })); + } + if (v.is_object()) + { + for (auto it = v.begin(); it != v.end(); ++it) + { + const std::string key(it.key().data(), it.key().size()); + if (v.count(key) == 1) + { + check_edit_exceptions(d, it.value(), j.at(key)); + } + } + } + else if (v.is_array()) + { + for (std::size_t i = 0; i < v.size(); ++i) + { + check_edit_exceptions(d, v[i], j[i]); + } + } +} +#endif } // namespace TEST_CASE("json_view materialize() source positions") @@ -345,3 +451,123 @@ TEST_CASE("json_view materialize() source positions") } #endif } + +#if !defined(JSON_NOEXCEPTION) +TEST_CASE("json_view exceptions name the path and the bytes of the value") +{ + SECTION("as those of basic_json") + { + const std::vector texts = + { + "null", "true", "42", "\"s\"", "[]", "{}", "[1, [2, [3, []]]]", R"({"a": {"b": [1, {"c": null}]}, "d": "e"})", + R"({"a/b": {"~": [1]}, "\u0041\n": {"x": "\t"}, "": [{}]})", + R"([1 /* ] */, {"a": [] // } +, "b": {} } , ])", + "\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n", + }; + for (const auto& text : texts) + { + CAPTURE(text) + const json j = json::parse(text, nullptr, true, true, true); + const json_document d = json_document::parse(text, true, true, true); + check_exceptions(d.root(), j); + json_editable_document e = json_editable_document::parse(text, true, true, true); + check_exceptions(e.root(), j); + json m = e.root().materialize(); + check_edit_exceptions(e, e.root(), m); + } + } + + SECTION("generated documents") + { + json_view_test::generator gen; + for (int i = 0; i < 100; ++i) + { + std::string text; + gen.value(text, 0); + CAPTURE(text) + const json_document d = json_document::parse(text); + check_exceptions(d.root(), json::parse(text)); + } + } + + SECTION("edited documents: as those of materialize()") + { + const std::string text = R"({"a": [1, 2], "b": {"c": "\n", "d": true}, "e": [3, [4]], "f": "x"})"; + json_editable_document d = json_editable_document::parse(text); + d.set(d.root()["f"], 5); + d.push_back(d.root()["a"], json::parse(R"({"n": [null]})")); + d.erase(d.root()["e"], 0); + d.set(d.root()["b"], "c2", d.root()["e"]); + d.insert(d.root()["e"], 0, "new"); + json m = d.root().materialize(); + check_exceptions(d.root(), m); + check_edit_exceptions(d, d.root(), m); + // (a value inside a new container: a path, but no bytes) + std::string expected = "[json.exception.type_error.302] "; +#if JSON_DIAGNOSTICS + expected += "(/a/2/n/0) "; +#endif + expected += "type must be number, but is null"; + CHECK(exception_of_call([&] { static_cast(d.root()["a"][2]["n"][0].get()); }) == expected); + } + + SECTION("deep documents with siblings after every nested container") + { + // the search for the path pushes and pops one frame per level (the + // stack reallocates while it grows, and shrinks again at every + // sibling): run under a sanitizer, a reference to a popped or moved + // frame would show here + std::string text = "7"; + for (int level = 0; level < 40; ++level) + { + text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])"; + } + CAPTURE(text) + const json j = json::parse(text); + const json_document d = json_document::parse(text); + check_exceptions(d.root(), j); + json_editable_document e = json_editable_document::parse(text); + e.set(e.root()[0][0], 9); + check_exceptions(e.root(), e.root().materialize()); + } + + SECTION("examples") + { + const std::string text = R"({"people": [{"name": "a", "age": 1}, {"name": "c", "age": "x"}], "o": {"k": 1}})"; + const json_document d = json_document::parse(text); + std::string expected_age = "[json.exception.type_error.302] "; +#if JSON_DIAGNOSTICS + expected_age += "(/people/1/age) "; +#endif + expected_age += "(bytes 58-61) type must be number, but is string"; + CHECK(exception_of_call([&] { static_cast(d.root()["people"][1]["age"].get()); }) == expected_age); + + std::string expected_zz = "[json.exception.out_of_range.403] "; +#if JSON_DIAGNOSTICS + expected_zz += "(/people/0) "; +#endif + expected_zz += "(bytes 12-35) key 'zz' not found"; + CHECK(exception_of_call([&] { static_cast(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz); + + // the root has no path, and with JSON_DIAGNOSTICS then no bytes either (as basic_json) + std::string expected_missing = "[json.exception.out_of_range.403] "; +#if !JSON_DIAGNOSTICS + expected_missing += "(bytes 0-79) "; +#endif + expected_missing += "key 'missing' not found"; + CHECK(exception_of_call([&] { static_cast(d.root().at("missing")); }) == expected_missing); + } + + SECTION("duplicate keys: the value the view finds") + { + const json_document d = json_document::parse(R"({"x": "1", "x": "22"})"); + std::string expected = "[json.exception.type_error.302] "; +#if JSON_DIAGNOSTICS + expected += "(/x) "; +#endif + expected += "(bytes 6-9) type must be number, but is string"; + CHECK(exception_of_call([&] { static_cast(d.root()["x"].get()); }) == expected); + } +} +#endif From 67b103e5062b8fa443fd3a85d811d996bd39ae1d Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 20:42:58 +0100 Subject: [PATCH 61/63] Fix clang-tidy errors in json_view tests on develop (#5989) readability-math-missing-parentheses in unit-json_view_builder.cpp (#5974) and performance-inefficient-string-concatenation in unit-json_view_positions.cpp. Signed-off-by: Niels Lohmann --- tests/src/unit-json_view_builder.cpp | 2 +- tests/src/unit-json_view_positions.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/src/unit-json_view_builder.cpp b/tests/src/unit-json_view_builder.cpp index 06059aba8..988c25771 100644 --- a/tests/src/unit-json_view_builder.cpp +++ b/tests/src/unit-json_view_builder.cpp @@ -692,7 +692,7 @@ TEST_CASE("json_view builder: fraction digits") check_same(number); check_same("[" + number + "]"); std::string pair; - pair.reserve(number.size() * 2 + 3); + pair.reserve((number.size() * 2) + 3); pair += '['; pair += number; pair += ','; diff --git a/tests/src/unit-json_view_positions.cpp b/tests/src/unit-json_view_positions.cpp index c6afd7fcb..966c4b46a 100644 --- a/tests/src/unit-json_view_positions.cpp +++ b/tests/src/unit-json_view_positions.cpp @@ -521,7 +521,8 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value") std::string text = "7"; for (int level = 0; level < 40; ++level) { - text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])"; + text.insert(0, R"([[1, [2]], )"); + text += R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])"; } CAPTURE(text) const json j = json::parse(text); From e2423a18574b18eda1425a38089766cb60663303 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 20:43:07 +0100 Subject: [PATCH 62/63] CI: pin ubuntu-latest jobs to ubuntu-26.04 (#5983) GitHub is moving the ubuntu-latest label from Ubuntu 24.04 to 26.04 between October 19 and November 19, 2026 (actions/runner-images#14748). Pin every ubuntu-latest job to ubuntu-26.04 so the switch does not change the toolchain under our jobs, and so the behaviour does not depend on when GitHub reaches our runs. Jobs pinned to ubuntu-24.04 and ubuntu-24.04-arm are unchanged. Signed-off-by: Niels Lohmann --- .github/workflows/cancel_closed_pr_runs.yml | 2 +- .github/workflows/check_amalgamation.yml | 2 +- .github/workflows/check_docs_links.yml | 2 +- .../workflows/comment_check_amalgamation.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/flawfinder.yml | 2 +- .github/workflows/labeler.yml | 2 +- .github/workflows/publish_documentation.yml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/stale.yml | 2 +- .github/workflows/ubuntu.yml | 26 +++++++++---------- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/cancel_closed_pr_runs.yml b/.github/workflows/cancel_closed_pr_runs.yml index 93d65312d..91005de52 100644 --- a/.github/workflows/cancel_closed_pr_runs.yml +++ b/.github/workflows/cancel_closed_pr_runs.yml @@ -24,7 +24,7 @@ jobs: permissions: actions: write - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Harden Runner diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index 66af126fa..253531e40 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -19,7 +19,7 @@ permissions: jobs: save: if: github.event_name == 'pull_request' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Harden Runner uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 diff --git a/.github/workflows/check_docs_links.yml b/.github/workflows/check_docs_links.yml index 12f5ad074..f7e28ab69 100644 --- a/.github/workflows/check_docs_links.yml +++ b/.github/workflows/check_docs_links.yml @@ -16,7 +16,7 @@ concurrency: jobs: check_docs_links: if: github.repository == 'nlohmann/json' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 30 steps: - name: Harden Runner diff --git a/.github/workflows/comment_check_amalgamation.yml b/.github/workflows/comment_check_amalgamation.yml index 04bfaae88..ba2fcad7b 100644 --- a/.github/workflows/comment_check_amalgamation.yml +++ b/.github/workflows/comment_check_amalgamation.yml @@ -12,7 +12,7 @@ jobs: comment: # push runs on develop have no PR to comment on (and no "pr" artifact) if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: read actions: read diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index bd2d295a4..27c229572 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -18,7 +18,7 @@ permissions: jobs: dependency-review: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Harden Runner uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 diff --git a/.github/workflows/flawfinder.yml b/.github/workflows/flawfinder.yml index b3e2511ae..ffc8c2a43 100644 --- a/.github/workflows/flawfinder.yml +++ b/.github/workflows/flawfinder.yml @@ -24,7 +24,7 @@ on: jobs: flawfinder: name: Flawfinder - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: actions: read contents: read diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 35549b4e0..85c7a513d 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -19,7 +19,7 @@ jobs: contents: read pull-requests: write - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Harden Runner diff --git a/.github/workflows/publish_documentation.yml b/.github/workflows/publish_documentation.yml index 12c55a0d4..ad53308d7 100644 --- a/.github/workflows/publish_documentation.yml +++ b/.github/workflows/publish_documentation.yml @@ -34,7 +34,7 @@ jobs: contents: write if: github.repository == 'nlohmann/json' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Harden Runner uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 14b987200..62e1d504a 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -24,7 +24,7 @@ permissions: jobs: analysis: name: Scorecard analysis - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: # Needed to upload the results to code-scanning dashboard. security-events: write diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 41928ab20..da7d91fce 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ permissions: jobs: stale: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: issues: write diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index a8cd05766..c93c0a706 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -18,7 +18,7 @@ concurrency: jobs: ci_test_gcc: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: gcc:latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -130,7 +130,7 @@ jobs: run: cmake --build build --target ${{ matrix.target }} ci_static_analysis_clang: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: silkeh/clang:22 strategy: matrix: @@ -149,7 +149,7 @@ jobs: run: cmake --build build --target ${{ matrix.target }} ci_cmake_options: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: ubuntu:24.04 strategy: matrix: @@ -209,7 +209,7 @@ jobs: fail-on-error: false ci_test_compilers_gcc_old: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: matrix: compiler: ['4.8', '4.9', '5', '6'] @@ -251,7 +251,7 @@ jobs: run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }} ci_test_compilers_gcc: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: matrix: # older GCC docker images (4, 5, 6) fail to check out code @@ -269,7 +269,7 @@ jobs: run: cmake --build build --target ci_test_compiler_default ci_test_compilers_clang: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: matrix: compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest'] @@ -289,7 +289,7 @@ jobs: run: cmake --build build --target ci_test_compiler_default ci_test_standards_gcc: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: gcc:latest strategy: matrix: @@ -306,7 +306,7 @@ jobs: run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }} ci_test_standards_clang: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: silkeh/clang:latest strategy: matrix: @@ -330,7 +330,7 @@ jobs: if: ${{ matrix.stdlib == 'libstdcxx' }} ci_cuda_example: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -356,7 +356,7 @@ jobs: strategy: matrix: container: ['gcc:latest', 'silkeh/clang:latest'] - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: ${{ matrix.container }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -383,7 +383,7 @@ jobs: run: cmake --build build --target ci_module_cpp20 ci_icpc: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is # Intel's own last officially published image that still includes it. container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04 @@ -402,7 +402,7 @@ jobs: run: cmake --build build --target ci_icpc ci_icpx: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: intel/oneapi-hpckit:latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -416,7 +416,7 @@ jobs: run: cmake --build build --target ci_icpx ci_nvhpc: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 From b2674a4a2fb6de549973208dc01c5c1864e2c1b9 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 1 Nov 2026 21:03:35 +0100 Subject: [PATCH 63/63] BSON reader: check array element names in place in strict mode (#5984) --- .../nlohmann/detail/input/binary_reader.hpp | 46 +++++++++++++++++-- single_include/nlohmann/json.hpp | 46 +++++++++++++++++-- tests/src/unit-bson.cpp | 38 +++++++++++++++ 3 files changed, 122 insertions(+), 8 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index da5a0263e..8a6a0a6d5 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -467,11 +467,13 @@ class binary_reader const std::size_t element_type_parse_position = chars_read; // an array's elements are named "0", "1", ... in the wire format, - // and those names are not passed on: they are skipped unless - // error_handler_t::strict must reject ill-formed UTF-8 in them + // and those names are not passed on: they are skipped, and only + // checked if error_handler_t::strict must reject ill-formed UTF-8 const bool is_object = container_stack.back().is_object; key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict))) + if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict + ? get_bson_cstr(key, is_object) + : check_bson_cstr(key, std::integral_constant {})))) { return false; } @@ -495,7 +497,7 @@ class binary_reader @param[in] store whether the characters are needed; if not (the names of an array's elements), they are skipped and @a result may be left unchanged, and they are not checked against - @ref error_handler + @ref error_handler (@ref check_bson_cstr checks them) @return `true` if the \\x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ @@ -554,6 +556,42 @@ class binary_reader return true; } + /*! + @brief skip a C-style string that error_handler_t::strict must check + + The names of an array's elements are not passed on. On contiguous input, + a well-formed name is checked in place and skipped without being copied. + Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed + UTF-8), it is read by @ref get_bson_cstr, which reports the error. + + @param[in,out] result an empty string, which may receive the name + @return whether the name was read and is well-formed + */ + bool check_bson_cstr(string_t& result, std::true_type /*bulk*/) + { + const std::size_t remaining = ia.bulk_remaining(); + if (remaining != 0) + { + const auto* const data = reinterpret_cast(ia.bulk_data()); + const std::size_t length = find_zero_byte(data, remaining); + if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length))) + { + // consume the string and its \x00-byte, as get_bson_cstr does + ia.bulk_skip(length + 1); + chars_read += length + 1; + current = 0x00; + return true; + } + } + return get_bson_cstr(result); + } + + /// input that is not contiguous: the name is read and checked byte by byte + bool check_bson_cstr(string_t& result, std::false_type /*bulk*/) + { + return get_bson_cstr(result); + } + /// input that is not contiguous: C-style strings are read byte by byte bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 1fc214c5f..693320ed5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -18474,11 +18474,13 @@ class binary_reader const std::size_t element_type_parse_position = chars_read; // an array's elements are named "0", "1", ... in the wire format, - // and those names are not passed on: they are skipped unless - // error_handler_t::strict must reject ill-formed UTF-8 in them + // and those names are not passed on: they are skipped, and only + // checked if error_handler_t::strict must reject ill-formed UTF-8 const bool is_object = container_stack.back().is_object; key.clear(); - if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, is_object || error_handler == error_handler_t::strict))) + if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict + ? get_bson_cstr(key, is_object) + : check_bson_cstr(key, std::integral_constant {})))) { return false; } @@ -18502,7 +18504,7 @@ class binary_reader @param[in] store whether the characters are needed; if not (the names of an array's elements), they are skipped and @a result may be left unchanged, and they are not checked against - @ref error_handler + @ref error_handler (@ref check_bson_cstr checks them) @return `true` if the \\x00-byte indicating the end of the string was encountered before the EOF; false` indicates an unexpected EOF. */ @@ -18561,6 +18563,42 @@ class binary_reader return true; } + /*! + @brief skip a C-style string that error_handler_t::strict must check + + The names of an array's elements are not passed on. On contiguous input, + a well-formed name is checked in place and skipped without being copied. + Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed + UTF-8), it is read by @ref get_bson_cstr, which reports the error. + + @param[in,out] result an empty string, which may receive the name + @return whether the name was read and is well-formed + */ + bool check_bson_cstr(string_t& result, std::true_type /*bulk*/) + { + const std::size_t remaining = ia.bulk_remaining(); + if (remaining != 0) + { + const auto* const data = reinterpret_cast(ia.bulk_data()); + const std::size_t length = find_zero_byte(data, remaining); + if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length))) + { + // consume the string and its \x00-byte, as get_bson_cstr does + ia.bulk_skip(length + 1); + chars_read += length + 1; + current = 0x00; + return true; + } + } + return get_bson_cstr(result); + } + + /// input that is not contiguous: the name is read and checked byte by byte + bool check_bson_cstr(string_t& result, std::false_type /*bulk*/) + { + return get_bson_cstr(result); + } + /// input that is not contiguous: C-style strings are read byte by byte bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept { diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index f1f43ccd7..ac8e2b24f 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -23,6 +23,7 @@ using nlohmann::json; #include #include #include +#include #include #include #include "make_test_data_available.hpp" @@ -1199,6 +1200,31 @@ TEST_CASE("BSON keys from contiguous and stream input") from_stream = e.what(); } CHECK(from_vector == from_stream); + + // error_handler_t::strict checks the array element names in place on + // contiguous input and byte by byte otherwise; the names are + // well-formed, so both give the same value or error as above + std::string strict_from_vector; + std::string strict_from_list; + try + { + strict_from_vector = json::from_bson(input, true, true, json::error_handler_t::strict).dump(); + } + catch (const json::parse_error& e) + { + strict_from_vector = e.what(); + } + try + { + const std::list list(input.begin(), input.end()); + strict_from_list = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict).dump(); + } + catch (const json::parse_error& e) + { + strict_from_list = e.what(); + } + CHECK(strict_from_vector == from_vector); + CHECK(strict_from_list == from_vector); } } @@ -1227,6 +1253,18 @@ TEST_CASE("BSON array element names") std::istringstream stream(std::string(reinterpret_cast(bson.data()), bson.size())); CHECK(json::from_bson(stream) == expected); + + // input that is not contiguous reads the names byte by byte and must reject + // the same name + const std::list list(bson.begin(), bson.end()); + CHECK_THROWS_WITH_AS(_ = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&); + + // without exceptions, the rejected name ends the parse + CHECK(json::from_bson(bson, true, false, json::error_handler_t::strict).is_discarded()); + + // well-formed names pass the check without being stored + const json valid = {{"a", {true, false, {{"k", json::array({1, 2})}}}}}; + CHECK(json::from_bson(json::to_bson(valid), true, true, json::error_handler_t::strict) == valid); } #endif
    JSONBytesMeaning
    {8Aobject with 4 members
    "id": 300,69 64 C4 04key: UTF-8 bytes; the string ends at the next byte, which cannot continue a UTF-8 character; value: 2-byte positive integer (lead byte, then a byte below 0x80)
    "id": 300,69 64 C4 04key: UTF-8 bytes; the string ends at 0xC4, because the byte after it (0x04) is not a UTF-8 continuation byte, so 0xC4 starts an integer; value: 2-byte positive integer (lead byte, then a byte below 0x80)
    "name": "json",6E 61 6D 65 FF 6A 73 6F 6E FFkey: UTF-8 bytes; 0xFF terminates the string as another string follows; value: UTF-8 bytes; terminated as a string key follows
    "pi": 3.14,70 69 8F 40 09 1E B8 51 EB 85 1Fkey: UTF-8 bytes; value: binary64 (IEEE 754, big-endian); its marker also ends "pi"
    "tags": [74 61 67 73 82key: UTF-8 bytes; value: array with 2 elements; its marker also ends "tags"