diff --git a/.github/workflows/cancel_closed_pr_runs.yml b/.github/workflows/cancel_closed_pr_runs.yml index 99010d81b..91005de52 100644 --- a/.github/workflows/cancel_closed_pr_runs.yml +++ b/.github/workflows/cancel_closed_pr_runs.yml @@ -24,11 +24,11 @@ jobs: permissions: actions: write - runs-on: ubuntu-latest + 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/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index 8104a20af..253531e40 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -19,10 +19,10 @@ 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@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 @@ -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 ' 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 da13c22a2..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. @@ -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 @@ -117,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/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/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 55a7954d5..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 @@ -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 @@ -40,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 c6e2e7d77..65ccca142 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 @@ -134,9 +141,10 @@ Logarithmic in the size of the JSON object. ## 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 c4e710a7d..162882cdb 100644 --- a/docs/mkdocs/docs/api/basic_json/diff.md +++ b/docs/mkdocs/docs/api/basic_json/diff.md @@ -2,17 +2,28 @@ ```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 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 `source` (in) @@ -21,6 +32,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 +48,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 @@ -55,11 +93,12 @@ Currently, only `remove`, `add`, and `replace` operations are generated. ## 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 370124c8a..db4a56854 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 @@ -107,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 e566b8688..c3cf9f18a 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. @@ -96,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 6e0beafce..2f121c987 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 @@ -108,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 4fcd7fe98..78dc695fc 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 @@ -114,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 3881f9ea4..d3601c63a 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 @@ -107,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 acb67401f..9349ca6fb 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 @@ -188,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 @@ -217,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 @@ -358,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 1fafb1ece..fe83f6cc3 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 @@ -65,9 +85,10 @@ returned by `#!cpp (*this)[key]`); it is read as it was when `merge_patch()` was ## 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 4356969a9..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 @@ -137,10 +145,21 @@ 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 "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" @@ -271,13 +290,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 @@ -291,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/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..643610cb2 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: @@ -58,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" @@ -92,8 +106,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 ed26135a9..cd553553e 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" @@ -98,16 +105,19 @@ is thrown. In any case, the original value is not changed: the patch is applied ## 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 - 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..ff65e8dbd 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,23 @@ 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. + +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" @@ -96,16 +116,18 @@ function throws an exception. ## 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 - 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 @@ -113,3 +135,5 @@ function throws an exception. - 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/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 60e41e1c7..c3aa92ffb 100644 --- a/docs/mkdocs/docs/api/basic_json/sax_parse.md +++ b/docs/mkdocs/docs/api/basic_json/sax_parse.md @@ -149,12 +149,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 a85d8ff8a..14ead0612 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) @@ -108,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 315477e19..9c81cdce4 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. @@ -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 6508864e1..483259f85 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 @@ -77,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 dca66271d..155db8e20 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` @@ -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 661ffa3e8..8b9f2cbc1 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 @@ -75,7 +80,8 @@ Apart from these two 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 aee09a537..1f0bb2a97 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[]`" @@ -222,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..766fd20fa 100644 --- a/docs/mkdocs/docs/api/basic_json_document/accept.md +++ b/docs/mkdocs/docs/api/basic_json_document/accept.md @@ -9,13 +9,19 @@ 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 -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 @@ -69,8 +75,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 4cbc50d19..db6dc523b 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 @@ -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 ea5f449db..b38105623 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 @@ -108,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. @@ -118,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 b513dfc0d..207e7bae8 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 @@ -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 36223f63c..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,16 +49,13 @@ 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. ## 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 | @@ -66,7 +63,7 @@ Otherwise throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse | `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" @@ -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 @@ -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 37ce10bc5..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,12 +8,12 @@ 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, ...). -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 @@ -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 f69d5c9f8..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 `#!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 @@ -28,14 +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. -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 +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" @@ -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 ``` @@ -61,25 +55,25 @@ 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 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 ??? 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 @@ -94,8 +88,8 @@ 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 ae26ed516..e3f05b151 100644 --- a/docs/mkdocs/docs/api/basic_json_document/set.md +++ b/docs/mkdocs/docs/api/basic_json_document/set.md @@ -18,13 +18,13 @@ 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`. 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 f8f8a4312..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 @@ -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 @@ -49,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..7b810c4ed 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,21 +62,22 @@ 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 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 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 +96,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 +106,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 +136,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..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 @@ -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 df1e939fa..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 @@ -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 @@ -43,8 +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 +- [`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..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 @@ -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 7fe30ca7f..7ee7952bf 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -7,28 +7,48 @@ 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` -- 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 +- `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 + key, the view of the *last* member is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) keeps its value -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, 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 +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 [Writing generic code](../../features/json_view.md#writing-generic-code). + +!!! 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` — 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. @@ -49,22 +69,24 @@ 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. -- 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 every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws -- typically `type_error.302`, +- 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. -None of the exceptions thrown directly by this function (the first three 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 @@ -76,6 +98,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). @@ -107,7 +131,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 @@ -122,11 +146,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..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 @@ -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 4f1c6a5d9..ae8a8228a 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,13 @@ 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`, +- 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 called, i.e. `from_json()` is invoked with the existing `v`, exactly as [`BasicJsonType::get_to()`](../basic_json/get_to.md) does. @@ -72,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 5f0d90694..f363d6433 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; ``` @@ -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 -**editable** document (`#!cpp Editable == true`) sees every edit made through +`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 @@ -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 @@ -63,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` @@ -146,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..982b1597f 100644 --- a/docs/mkdocs/docs/api/basic_json_view/materialize.md +++ b/docs/mkdocs/docs/api/basic_json_view/materialize.md @@ -28,13 +28,28 @@ 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. -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. @@ -43,7 +58,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 +75,10 @@ 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()`) +- [`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/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..6451cb1fa 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`). @@ -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 @@ -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..a04bfd3b8 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 @@ -73,15 +73,16 @@ 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 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 +98,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 +127,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 +138,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 +169,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..ebce7b4bf 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,22 +48,23 @@ 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`. -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 @@ -73,7 +74,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 +90,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 +132,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 b184d8496..55e8cb88f 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 @@ -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/is_json_view.md b/docs/mkdocs/docs/api/is_json_view.md new file mode 100644 index 000000000..6153adf30 --- /dev/null +++ b/docs/mkdocs/docs/api/is_json_view.md @@ -0,0 +1,56 @@ +# 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 +- [Writing generic code](../features/json_view.md#writing-generic-code) - functions and `from_json()` for views + +## Version history + +Added in version 3.13.0. 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 cfa22e80b..2000128ac 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) @@ -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 ec5226b78..c455c8343 100644 --- a/docs/mkdocs/docs/api/json_editable_document.md +++ b/docs/mkdocs/docs/api/json_editable_document.md @@ -3,20 +3,20 @@ 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 [`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 ddee4e372..eede89703 100644 --- a/docs/mkdocs/docs/api/json_editable_view.md +++ b/docs/mkdocs/docs/api/json_editable_view.md @@ -3,12 +3,12 @@ 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 [`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..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 @@ -63,11 +64,13 @@ 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 -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/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 d37889e7e..1cb26c920 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 @@ -36,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 084503d92..f3c732ce9 100644 --- a/docs/mkdocs/docs/api/json_sax/index.md +++ b/docs/mkdocs/docs/api/json_sax/index.md @@ -26,6 +26,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 @@ -63,9 +66,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..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 @@ -37,10 +54,15 @@ 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 - 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/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 213831abb..e575ef992 100644 --- a/docs/mkdocs/docs/api/json_sax/parse_error.md +++ b/docs/mkdocs/docs/api/json_sax/parse_error.md @@ -62,10 +62,11 @@ Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!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 - [Error Recovery](../../features/parsing/error_recovery.md) - the article on recovering from parse errors +- [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 89229ed6f..408214fe4 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). @@ -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 ae11a6084..2ddf6f245 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -43,8 +43,6 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers - [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace - [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation -- [**JSON_VIEW_NO_SIMD**](json_view_no_simd.md) - use only portable code in the parser of `json_view.hpp` -- [**JSON_VIEW_USE_SSSE3**](json_view_use_ssse3.md) - validate non-ASCII strings with SSSE3 in the parser of `json_view.hpp` ## Library version @@ -142,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..8a772791a 100644 --- a/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md +++ b/docs/mkdocs/docs/api/macros/json_diagnostic_positions.md @@ -112,7 +112,9 @@ 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 +- [`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/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 9c92fcc1d..b16202b12 100644 --- a/docs/mkdocs/docs/api/macros/json_no_simd.md +++ b/docs/mkdocs/docs/api/macros/json_no_simd.md @@ -5,7 +5,8 @@ ``` When defined, the library uses only portable C++ code where it would otherwise use vector instructions: when scanning -strings during parsing, and when writing floating-point numbers during serialization. +strings during parsing, and when writing floating-point numbers during serialization. This includes the parser of +[`basic_json_document`](../basic_json_document/index.md) (``). The macro is an escape hatch, for example for toolchains that cannot compile the vector code, for sanitizers or analyzers that do not understand it, or to compare the speed of both variants. It does not change any result: values, @@ -72,11 +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) +- [`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/json_view_no_simd.md b/docs/mkdocs/docs/api/macros/json_view_no_simd.md deleted file mode 100644 index 6ef59605c..000000000 --- a/docs/mkdocs/docs/api/macros/json_view_no_simd.md +++ /dev/null @@ -1,52 +0,0 @@ -# JSON_VIEW_NO_SIMD - -```cpp -#define JSON_VIEW_NO_SIMD -``` - -When defined, the parser of [`basic_json_document`](../basic_json_document/index.md) (``) -uses only portable C++ to scan strings. By default, it scans long runs of string bytes 16 at a time with NEON on -AArch64 (with GCC and Clang) and SSE2 on x86-64, which are part of the baseline instruction sets of these -architectures, and validates non-ASCII text with NEON (or SSSE3, see -[`JSON_VIEW_USE_SSSE3`](json_view_use_ssse3.md)). - -The same input is accepted or rejected either way, with the same values, and errors are reported the same way; only -the speed differs. The macro exists for platforms whose compilers lack the intrinsics headers, and to test the portable -code. - -!!! note "ABI compatibility" - - The macro selects between two definitions of the same inline functions. Whether it is defined is encoded in the - [namespace](../../features/namespace.md) (tag `_vns`), resulting in distinct symbol names. Translation units - compiled with and without it can therefore be linked into the same program without One Definition Rule (ODR) - violations, but they cannot exchange instances of library types. - -## Default definition - -By default, `#!cpp JSON_VIEW_NO_SIMD` is not defined, and the vector code is used where available. - -```cpp -#undef JSON_VIEW_NO_SIMD -``` - -## Examples - -??? example - - The code below uses the portable string scanning of the view. - - ```cpp - #define JSON_VIEW_NO_SIMD - #include - - ... - ``` - -## See also - -- [JSON_VIEW_USE_SSSE3](json_view_use_ssse3.md) - validate non-ASCII strings with SSSE3 on x86-64 -- [json_view](../../features/json_view.md) - the zero-copy view - -## Version history - -- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/api/macros/json_view_use_ssse3.md b/docs/mkdocs/docs/api/macros/json_view_use_ssse3.md deleted file mode 100644 index 6ac80b012..000000000 --- a/docs/mkdocs/docs/api/macros/json_view_use_ssse3.md +++ /dev/null @@ -1,53 +0,0 @@ -# JSON_VIEW_USE_SSSE3 - -```cpp -#define JSON_VIEW_USE_SSSE3 -``` - -When defined on x86-64, the parser of [`basic_json_document`](../basic_json_document/index.md) -(``) validates non-ASCII text in strings with SSSE3 without asking the CPU first. - -By default, the parser checks once at run time whether the CPU has SSSE3 (all x86-64 CPUs since about 2011 have it) -and then validates non-ASCII text 16 bytes at a time, using the "lookup4" algorithm of -[simdjson](https://github.com/simdjson/simdjson); on CPUs without SSSE3, it validates one UTF-8 sequence at a time. -The vector check is compiled for SSSE3 with a function attribute (GCC 4.9 and later, Clang), so this needs no compiler -option. With MSVC, the check uses `__cpuid`. On AArch64, the vector check uses NEON and is always on. - -Define the macro only together with a compiler option that enables SSSE3 (e.g. `-mssse3`, or `-march=` with a CPU that -has it), and only for programs that run on such CPUs. It saves the check of the CPU, which costs little. The same -input is accepted or rejected either way; only the speed of non-ASCII text differs. - -!!! note "ABI compatibility" - - The macro selects between two definitions of the same inline functions. Whether it is defined is encoded in the - [namespace](../../features/namespace.md) (tag `_vssse3`, on every architecture), resulting in distinct symbol - names. Translation units compiled with and without it can therefore be linked into the same program without One - Definition Rule (ODR) violations, but they cannot exchange instances of library types. - -## Default definition - -By default, `#!cpp JSON_VIEW_USE_SSSE3` is not defined. - -```cpp -#undef JSON_VIEW_USE_SSSE3 -``` - -## Examples - -??? example - - With CMake, for a program that only runs on CPUs with SSSE3: - - ```cmake - target_compile_definitions(your_target PRIVATE JSON_VIEW_USE_SSSE3) - target_compile_options(your_target PRIVATE -mssse3) - ``` - -## See also - -- [JSON_VIEW_NO_SIMD](json_view_no_simd.md) - use only portable code in the view's parser -- [JSON_USE_SIMDUTF](json_use_simdutf.md) - validate UTF-8 with simdutf in `basic_json`'s parser - -## 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..ec5f4ec21 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" @@ -174,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 @@ -192,3 +200,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..99ceffd2e 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" @@ -171,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 @@ -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..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 @@ -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" @@ -170,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 @@ -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_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 42ee51f38..83bad6efc 100644 --- a/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md +++ b/docs/mkdocs/docs/api/macros/nlohmann_json_namespace.md @@ -33,12 +33,15 @@ 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 - Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in - version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_ns`, `_vns`, - and `_vssse3` in version 3.13.0; see + version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, and `_ns` in + version 3.13.0; see [`nlohmann` Namespace](../../features/namespace.md#structure). 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 4af3b07b8..d5cf8f53a 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" @@ -80,11 +86,15 @@ 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 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..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 @@ -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" @@ -99,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 6a821b7fb..813409abd 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 @@ -85,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 b737c2e4d..23bde281a 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) @@ -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 cf86307e5..de0d041ab 100644 --- a/docs/mkdocs/docs/api/ordered_json_editable_document.md +++ b/docs/mkdocs/docs/api/ordered_json_editable_document.md @@ -3,15 +3,15 @@ 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 [`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 1c9953d57..e87cb9bb5 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 @@ -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 9df72040f..7ca5356d6 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 @@ -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/api/view_mode.md b/docs/mkdocs/docs/api/view_mode.md new file mode 100644 index 000000000..c754c7ed4 --- /dev/null +++ b/docs/mkdocs/docs/api/view_mode.md @@ -0,0 +1,89 @@ +# 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); + +// views of either document type +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 +- [Writing generic code](../features/json_view.md#writing-generic-code) - functions that accept views + +## Version history + +Added in version 3.13.0. 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 000000000..b3530f3f5 Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 differ diff --git a/docs/mkdocs/docs/css/fonts/Inter.woff2 b/docs/mkdocs/docs/css/fonts/Inter.woff2 new file mode 100644 index 000000000..21d9a7f9e Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/Inter.woff2 differ 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 000000000..d60c270e8 Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 differ diff --git a/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 b/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 new file mode 100644 index 000000000..6b604cec6 Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 differ 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/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/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/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/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 3a7a1798a..c458af6b3 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" @@ -290,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 b972dc035..c6db009db 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`. @@ -191,3 +201,12 @@ Non-negative integers are read as number_unsigned, negative integers as number_i ```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 9c82594dd..ae3ddeccc 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" @@ -168,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 8933630da..9b126a331 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" @@ -257,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 8024b99cf..c2e7d24f4 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 | complete | -| 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. @@ -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 90e4c295e..17c05625d 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 @@ -215,3 +242,12 @@ The library maps MessagePack types to JSON value types as follows: ```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 ba12ee4f6..578d13e3a 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" @@ -186,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 b4c3bab6d..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): @@ -132,8 +134,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 +176,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 +185,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 @@ -213,7 +218,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: @@ -227,7 +232,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" @@ -301,6 +309,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,11 +320,21 @@ 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 -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. @@ -412,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 878f0084c..ea22b4e15 100644 --- a/docs/mkdocs/docs/features/json_patch.md +++ b/docs/mkdocs/docs/features/json_patch.md @@ -7,12 +7,36 @@ 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. 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. @@ -39,14 +63,28 @@ 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 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 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. + ??? 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. @@ -68,3 +106,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 1bc5eabdb..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 | @@ -37,6 +71,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 +92,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), @@ -127,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 4f0938273..44bf581a5 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 @@ -120,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 @@ -135,10 +161,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 @@ -151,11 +179,7 @@ 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<`. +- **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 @@ -185,6 +209,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 @@ -210,40 +238,152 @@ 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: - [`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. + +### `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): +[`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. + +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. ## Writing a view back [`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 @@ -252,14 +392,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. -`#!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. @@ -269,15 +409,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. @@ -296,14 +456,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 @@ -312,16 +472,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. @@ -332,10 +529,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. @@ -354,9 +551,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 @@ -370,8 +568,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 773ea5ac3..33b2273b9 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -287,23 +287,6 @@ external dependency and is not defined by default. See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md). -## `JSON_VIEW_NO_SIMD` - -When defined, the parser of [`basic_json_document`](../api/basic_json_document/index.md) -(``) uses only portable C++ to scan strings, instead of vector instructions (SSE2 on x86-64, -NEON on AArch64). The same input is accepted or rejected either way; only the speed differs. It is not defined by -default. - -See [full documentation of `JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md). - -## `JSON_VIEW_USE_SSSE3` - -When defined on x86-64, the parser of [`basic_json_document`](../api/basic_json_document/index.md) -(``) validates non-ASCII text in strings with SSSE3 without asking the CPU first. It is not -defined by default. - -See [full documentation of `JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md). - ## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)` The library defines 12 macros to simplify the serialization/deserialization of types. See the page on @@ -339,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 ac732c7bf..ceafe8724 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 @@ -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. @@ -19,6 +91,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. @@ -37,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 39cd58762..3eb52ab7a 100644 --- a/docs/mkdocs/docs/features/namespace.md +++ b/docs/mkdocs/docs/features/namespace.md @@ -28,8 +28,6 @@ The complete default namespace name is derived as follows: - [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends `_dufio`. - [`JSON_NO_SIMD`](../api/macros/json_no_simd.md) defined appends `_ns`. - - [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`. - - [`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md) defined appends `_vssse3`. - The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component) below. @@ -40,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 @@ -57,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. @@ -104,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. @@ -115,7 +140,5 @@ follows: ([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo` ([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_dsbs` ([`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md)), `_dufio` - ([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_ns` - ([`JSON_NO_SIMD`](../api/macros/json_no_simd.md)), `_vns` - ([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3` - ([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0. + ([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), and `_ns` + ([`JSON_NO_SIMD`](../api/macros/json_no_simd.md)) in version 3.13.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 0e133a59f..0a5b7e4a8 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 @@ -155,4 +157,11 @@ options. To get as much as possible out of malformed input, a SAX parser can [re - [parsing and exceptions](parse_exceptions.md) - control error handling - [error recovery](error_recovery.md) - get as much as possible out of malformed input - [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 702980d20..1cc57dbef 100644 --- a/docs/mkdocs/docs/features/parsing/parse_exceptions.md +++ b/docs/mkdocs/docs/features/parsing/parse_exceptions.md @@ -144,3 +144,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 3384267a0..0a1cc722c 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(); @@ -63,6 +65,37 @@ 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. For `parse_error`, returning `#!cpp true` [recovers from the error](error_recovery.md). +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*: + +
+

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. @@ -73,5 +106,9 @@ Note the `sax_parse` function only returns a `#!cpp bool` indicating whether the ## 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..1fe7b64aa 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 @@ -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 @@ -139,12 +150,14 @@ 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`. - 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 @@ -160,3 +173,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 888590234..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" @@ -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 @@ -385,12 +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()`. -- `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 @@ -678,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). @@ -696,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" @@ -732,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` @@ -779,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..1b0d84dd7 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 | @@ -199,7 +203,9 @@ 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.

- **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 @@ -212,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 @@ -234,6 +246,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 +256,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`. @@ -267,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 @@ -287,8 +303,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 +323,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 +372,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 +391,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 +459,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 362afaa6b..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: @@ -21,15 +28,15 @@ ABI tags are not covered and may show raw internal fields instead. To generate a ``` 3. Generate a Natvis file with the tags of your configuration, for example for `JSON_DIAGNOSTICS`, - `JSON_DIAGNOSTIC_POSITIONS`, and `JSON_VIEW_NO_SIMD`: + `JSON_DIAGNOSTIC_POSITIONS`, and `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 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. @@ -50,5 +57,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 4b8f6edd9..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 @@ -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" @@ -346,7 +346,7 @@ been read. ### 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 @@ -418,13 +424,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 @@ -812,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" @@ -883,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 @@ -940,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 @@ -950,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 @@ -960,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 @@ -971,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 @@ -983,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 @@ -995,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 @@ -1006,7 +1013,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 @@ -1025,15 +1033,15 @@ 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" ``` - 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 @@ -1045,13 +1053,13 @@ 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" ``` - 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 @@ -1070,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 @@ -1082,10 +1090,15 @@ 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 18446744073709551616 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)` + 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. @@ -1093,13 +1106,15 @@ 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 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 @@ -1109,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 @@ -1129,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 @@ -1143,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 @@ -1153,14 +1168,18 @@ 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" +!!! 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. @@ -1229,7 +1248,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`. UBJSON requires a size marker (`#`) after a type marker (`$`). +`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" @@ -1241,3 +1262,15 @@ A JSON Patch operation 'test' failed. The unsuccessful operation is also printed 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/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/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 09aa2b383..000000000 Binary files a/docs/mkdocs/docs/images/callback_events.png and /dev/null differ 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 be23ffa69..000000000 Binary files a/docs/mkdocs/docs/images/json_syntax_number.png and /dev/null differ 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:
    + 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 +

    +
    +{% 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'; +} +``` diff --git a/include/nlohmann/detail/abi_macros.hpp b/include/nlohmann/detail/abi_macros.hpp index 827a9eef8..c2283aaa2 100644 --- a/include/nlohmann/detail/abi_macros.hpp +++ b/include/nlohmann/detail/abi_macros.hpp @@ -128,26 +128,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( \ @@ -161,9 +149,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/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 51f26706c..8fb9d149b 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 @@ -43,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) { @@ -71,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)) @@ -108,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) { @@ -122,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) { @@ -155,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); } @@ -183,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)); @@ -193,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())) @@ -201,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())) @@ -218,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 @@ -250,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(); @@ -259,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(); @@ -274,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*/) @@ -322,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 @@ -335,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) @@ -406,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()) { @@ -452,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())) @@ -462,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); } @@ -476,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) { @@ -493,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 @@ -503,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(); @@ -547,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 {}); } @@ -593,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()) @@ -608,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*/) { @@ -616,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 @@ -629,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 @@ -653,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. @@ -665,16 +810,99 @@ 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: the probe +template +struct is_adl_only_from_json_source : std::is_same {}; + +struct from_json_fn +{ + 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))) + { + return from_json(j, std::forward(val)); + } + + 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))) + { + 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); +} + +// 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/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/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index cdef3019f..144bb770c 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 @@ -417,6 +417,42 @@ 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; +} + +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" @@ -428,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()), &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/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index e2b0ae774..1a0e65985 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 @@ -273,6 +274,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() @@ -526,6 +530,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; @@ -533,6 +554,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; } @@ -591,16 +613,27 @@ 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; + // an array's elements are named "0", "1", ... in the wire format, + // 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))) + 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; } - // 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; } @@ -619,14 +652,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 (@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. */ - 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); @@ -639,7 +676,7 @@ class binary_reader } if (current == 0x00) { - return check_string_utf8(result, "key"); + return !store || check_string_utf8(result, "key"); } *out++ = static_cast(current); } @@ -649,10 +686,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) @@ -660,18 +698,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; @@ -679,8 +714,44 @@ 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*/, 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; } @@ -749,9 +820,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 @@ -842,11 +950,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 @@ -1071,7 +1181,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) @@ -1080,7 +1190,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) @@ -1247,16 +1357,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) { @@ -1276,6 +1380,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 @@ -1408,6 +1520,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"))) @@ -1427,26 +1543,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 @@ -1745,6 +1917,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 @@ -1774,6 +2095,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) @@ -1785,7 +2107,7 @@ class binary_reader { // claim the element about to be read --container_stack.back().remaining; - if (top.is_object) + if (is_object) { get(); } @@ -1798,7 +2120,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) @@ -1815,8 +2137,12 @@ class binary_reader continue; } - if (top.is_object) + if (is_object) { + if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler))) + { + return false; + } key.clear(); if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key))) { @@ -2157,7 +2483,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: @@ -2288,13 +2614,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 @@ -2639,6 +2965,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 @@ -3406,6 +3863,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 != '#')) { @@ -3596,6 +4061,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 */ @@ -3618,6 +4184,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'; @@ -3630,8 +4198,34 @@ class binary_reader } ndarray_open = 2; - 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) + { + 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; @@ -3673,7 +4267,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()); @@ -4099,13 +4694,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 { @@ -4543,7 +5138,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; @@ -4589,6 +5189,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) { @@ -4729,7 +5350,7 @@ class binary_reader return false; } } - return sax->number_float(result, ""); + return sax->number_float(result, no_source); } /*! @@ -4789,7 +5410,7 @@ class binary_reader }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) - : static_cast(val), ""); + : static_cast(val), no_source); } /*! @@ -4815,12 +5436,25 @@ 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); } + /// 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 @@ -4840,7 +5474,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; } @@ -5312,6 +5955,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) + { + 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) { @@ -5408,6 +6057,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{}; @@ -5431,6 +6084,44 @@ class binary_reader std::uint8_t ndarray_open = 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', '[', '{' @@ -5473,6 +6164,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/input/json_sax.hpp b/include/nlohmann/detail/input/json_sax.hpp index 651c72003..7fd5f279f 100644 --- a/include/nlohmann/detail/input/json_sax.hpp +++ b/include/nlohmann/detail/input/json_sax.hpp @@ -232,7 +232,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; } } @@ -247,6 +250,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; + } } } @@ -263,6 +272,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()) { @@ -471,15 +486,19 @@ constructor contains the parsed value. Without @a Collect, every value is stored in place as it arrives, so after a parse error the value holds what was read up to it. -@tparam BasicJsonType the JSON type +@tparam BasicJsonType the JSON type @tparam InputAdapterType the input adapter of the lexer that can be passed to the constructor to record diagnostic positions; it does not matter if no lexer is passed -@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 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: @@ -488,7 +507,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/lexer.hpp b/include/nlohmann/detail/input/lexer.hpp index 6fba8f6cb..cd102b4db 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 @@ -402,6 +406,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 @@ -410,17 +416,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 { @@ -434,14 +441,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) @@ -459,7 +468,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) @@ -503,7 +512,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(); } @@ -517,13 +527,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(); @@ -535,7 +550,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(): @@ -558,35 +573,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 @@ -666,7 +681,7 @@ class lexer : public lexer_base JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); // translate codepoint into bytes - add_codepoint(codepoint); + add_codepoint(keep, codepoint); break; } @@ -851,7 +866,7 @@ class lexer : public lexer_base case 0x7E: case 0x7F: { - add(current); + add_string_char(keep, current); break; } @@ -887,7 +902,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; } @@ -897,7 +912,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; } @@ -921,7 +936,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; } @@ -931,7 +946,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; } @@ -941,7 +956,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; } @@ -953,7 +968,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; } @@ -963,7 +978,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; } @@ -1461,7 +1476,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 @@ -1476,7 +1491,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 @@ -1484,7 +1499,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) @@ -1735,7 +1750,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; @@ -2068,6 +2083,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 @@ -2471,7 +2514,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 '-': @@ -3151,11 +3196,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/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/parser.hpp b/include/nlohmann/detail/input/parser.hpp index 73f84533d..6382449c8 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 {}; @@ -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/detail/input/simd_scan.hpp b/include/nlohmann/detail/input/simd_scan.hpp index 8caed1814..6c34421fe 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 @@ -315,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 d01a2f1a4..10e4cc900 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 - @@ -236,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/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 4560714ab..6df49fbc0 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 @@ -243,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()); @@ -589,6 +619,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,43 +1221,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 number - const bool nums = - 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 numbers 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 { @@ -1016,16 +1467,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/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index dd210564d..1a0989c17 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -302,15 +302,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; \ }); \ @@ -341,20 +343,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(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 @@ -614,6 +618,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 @@ -666,18 +672,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 @@ -687,7 +693,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) @@ -696,7 +702,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 @@ -723,18 +729,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 @@ -744,7 +750,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) @@ -753,7 +759,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 @@ -780,18 +786,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 @@ -801,7 +807,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) @@ -810,7 +816,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 @@ -838,18 +844,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 @@ -859,7 +865,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) @@ -868,7 +874,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/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 7ff828074..0c3c823ac 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; } @@ -2069,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; @@ -2320,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 @@ -2331,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); @@ -3061,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 @@ -3082,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; } } @@ -3108,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); @@ -3122,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 @@ -3139,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; } } @@ -3166,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); @@ -3829,7 +4103,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) { @@ -4037,10 +4311,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; @@ -4772,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/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/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/include/nlohmann/detail/view/builder.hpp b/include/nlohmann/detail/view/builder.hpp index 3e4ea268a..938d6d73b 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: @@ -173,31 +174,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); @@ -260,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 = { @@ -358,6 +420,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)) { @@ -543,7 +620,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(); } @@ -617,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) { @@ -639,11 +727,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 +753,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 '-': \ @@ -804,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: @@ -886,17 +977,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,8 +1053,14 @@ 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 (!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); @@ -978,7 +1075,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 +1087,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,14 +1114,17 @@ 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() { - 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)) @@ -1047,7 +1147,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)) @@ -1089,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); @@ -1126,23 +1226,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 +1252,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 +1275,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,41 +1285,58 @@ indent_done: return false; } p = r.p; - push_node(value_t::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; } }; }; /// 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/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/document_data.hpp b/include/nlohmann/detail/view/document_data.hpp index 55f9d8c76..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) @@ -232,7 +235,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..b572b4848 100644 --- a/include/nlohmann/detail/view/edit.hpp +++ b/include/nlohmann/detail/view/edit.hpp @@ -26,12 +26,11 @@ #include #include #include +#include +#include NLOHMANN_JSON_NAMESPACE_BEGIN -template -class basic_json_view; - namespace detail { namespace view @@ -48,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) @@ -81,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 @@ -120,15 +155,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()); + 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)); - 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,11 +206,11 @@ 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()); + 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); @@ -188,14 +223,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()); + 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 == 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,11 +247,11 @@ 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()); + 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); @@ -232,9 +267,9 @@ 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()); + 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)) { @@ -250,11 +285,11 @@ 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()); + 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; @@ -281,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); } } @@ -324,11 +359,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 +440,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 +456,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 +469,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 +492,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 +528,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 +545,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 +576,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 +598,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 +613,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 +635,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 +647,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 +657,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 +666,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 +716,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 +763,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 +854,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/errors.hpp b/include/nlohmann/detail/view/errors.hpp index 2c7270b32..f4ab8db01 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/image.hpp b/include/nlohmann/detail/view/image.hpp index af9994712..9e57082de 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,21 +104,23 @@ 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 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); @@ -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) @@ -138,23 +141,24 @@ 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; + 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); @@ -182,12 +186,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; @@ -221,7 +223,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)); } @@ -243,6 +245,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 @@ -255,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) { @@ -273,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 { @@ -289,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; @@ -337,7 +408,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 +429,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; @@ -403,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) { @@ -428,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) { @@ -439,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()) @@ -449,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) @@ -516,13 +601,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; } @@ -540,7 +627,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 +668,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; } @@ -594,13 +681,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; } @@ -614,7 +701,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; } @@ -622,7 +709,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}); } @@ -636,11 +723,11 @@ 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; } - 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; } @@ -663,13 +750,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"); @@ -729,7 +814,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)); } @@ -738,6 +823,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/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/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/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/materialize.hpp b/include/nlohmann/detail/view/materialize.hpp index 62adb30a9..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)) @@ -101,7 +317,18 @@ 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 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 7a05c8e0a..e00ef9eb6 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,22 +64,22 @@ 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 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"); -// 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/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/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/scan.hpp b/include/nlohmann/detail/view/scan.hpp index e230afbdb..9cea6b05c 100644 --- a/include/nlohmann/detail/view/scan.hpp +++ b/include/nlohmann/detail/view/scan.hpp @@ -172,6 +172,83 @@ 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; +} + +/// 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/include/nlohmann/detail/view/serializer.hpp b/include/nlohmann/detail/view/serializer.hpp index 76df8f89b..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 { @@ -874,7 +1014,21 @@ 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; + // (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/include/nlohmann/detail/view/simd.hpp b/include/nlohmann/detail/view/simd.hpp index 7a0248f63..f4745f6b8 100644 --- a/include/nlohmann/detail/view/simd.hpp +++ b/include/nlohmann/detail/view/simd.hpp @@ -24,25 +24,32 @@ // 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 +// +// 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 #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 @@ -282,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/detail/view/value.hpp b/include/nlohmann/detail/view/value.hpp index 945071705..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) @@ -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/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.hpp b/include/nlohmann/json.hpp index 6dc2d7492..3eee1ec92 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 @@ -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; @@ -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 @@ -6951,6 +6952,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}; @@ -7004,7 +7024,6 @@ public: switch (parent.m_data.m_type) { - case value_t::null: case value_t::object: { // use operator[] to add value @@ -7036,6 +7055,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/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 85026008a..043d09036 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" @@ -84,6 +86,7 @@ #include #include #include +#include NLOHMANN_JSON_NAMESPACE_BEGIN @@ -93,15 +96,20 @@ 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: + /// 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; @@ -120,6 +128,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 // ////////// @@ -268,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); } @@ -294,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(); } @@ -327,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; } @@ -353,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); } @@ -385,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(); @@ -395,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); @@ -410,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(); @@ -420,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(); @@ -433,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; } @@ -446,7 +463,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 +521,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 @@ -563,8 +580,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 { @@ -572,11 +591,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; } @@ -586,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}; } @@ -598,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)}; } @@ -646,7 +666,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 +702,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 +747,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,15 +760,23 @@ 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; + 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 @@ -757,11 +785,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 +838,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 +852,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 +874,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 --- @@ -855,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; } @@ -865,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); } @@ -874,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; } @@ -901,22 +929,45 @@ 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 + /// 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, std::is_default_constructible>::value, int> = 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) @@ -929,14 +980,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); } @@ -952,16 +1009,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 @@ -1007,7 +1067,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; } @@ -1022,7 +1082,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; } @@ -1035,7 +1095,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(); } @@ -1051,7 +1112,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> {}); } @@ -1175,6 +1236,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, @@ -1216,6 +1281,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 // @@ -1251,14 +1317,14 @@ 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 /// 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) { @@ -1274,7 +1340,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'; }); @@ -1284,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) { @@ -1318,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 @@ -1331,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 @@ -1357,7 +1427,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"); @@ -1365,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); } @@ -1401,6 +1471,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) { @@ -1412,7 +1498,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); @@ -1422,7 +1508,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) { @@ -1443,7 +1529,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; @@ -1470,7 +1556,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)) { @@ -1483,7 +1570,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); } @@ -1491,13 +1578,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) { @@ -1511,7 +1598,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; @@ -1522,7 +1609,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()); @@ -1531,13 +1618,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) { @@ -1549,13 +1636,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 @@ -1566,14 +1653,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); } @@ -1633,22 +1720,31 @@ 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; +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/nlohmann_json.natvis b/nlohmann_json.natvis index 75898cd7d..33c0caa67 100644 --- a/nlohmann_json.natvis +++ b/nlohmann_json.natvis @@ -79,6 +79,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 @@ -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,302 +3967,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -2225,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 @@ -2299,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 @@ -2373,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 @@ -2447,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 @@ -2521,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 @@ -2595,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 @@ -2669,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 @@ -2743,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 @@ -2817,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 @@ -2891,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 @@ -2965,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 @@ -3039,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 @@ -3113,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 @@ -3187,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 @@ -3261,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 @@ -3335,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 @@ -3409,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 @@ -3483,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 @@ -3557,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 @@ -3631,302 +7207,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -4001,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 @@ -4075,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 @@ -4149,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 @@ -4223,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 @@ -4297,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 @@ -4371,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 @@ -4445,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 @@ -4519,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 @@ -4593,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 @@ -4667,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 @@ -4741,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 @@ -4815,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 @@ -4889,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 @@ -4963,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 @@ -5037,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 @@ -5111,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 @@ -5185,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 @@ -5259,302 +10123,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -5629,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 @@ -5703,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 @@ -5777,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 @@ -5851,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 @@ -5925,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 @@ -5999,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 @@ -6073,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 @@ -6147,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 @@ -6221,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 @@ -6295,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 @@ -6369,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 @@ -6443,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 @@ -6517,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 @@ -6591,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 @@ -6665,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 @@ -6739,302 +12715,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -7109,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 @@ -7183,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 @@ -7257,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 @@ -7331,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 @@ -7405,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 @@ -7479,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 @@ -7553,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 @@ -7627,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 @@ -7701,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 @@ -7775,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 @@ -7849,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 @@ -7923,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 @@ -7997,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 @@ -8071,302 +14983,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -8441,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 @@ -8515,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 @@ -8589,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 @@ -8663,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 @@ -8737,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 @@ -8811,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 @@ -8885,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 @@ -8959,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 @@ -9033,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 @@ -9107,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 @@ -9181,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 @@ -9255,302 +16927,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -9625,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 @@ -9699,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 @@ -9773,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 @@ -9847,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 @@ -9921,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 @@ -9995,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 @@ -10069,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 @@ -10143,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 @@ -10217,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 @@ -10291,302 +18547,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -10661,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 @@ -10735,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 @@ -10809,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 @@ -10883,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 @@ -10957,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 @@ -11031,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 @@ -11105,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 @@ -11179,302 +19843,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -11549,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 @@ -11623,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 @@ -11697,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 @@ -11771,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 @@ -11845,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 @@ -11919,302 +20815,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -12289,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 @@ -12363,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 @@ -12437,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 @@ -12511,302 +21463,94 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 @@ -12881,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 @@ -12955,744 +21787,92 @@ - - - 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 + + + + 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 - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + + + + + + + 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 + + - - - {second} + + 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} - second + + + + + + + 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 + + - - - 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 + + discarded + {*m_data._Mypair._Myval2->nodes} - - *(m_data.m_value.object),view(simple) - - - *(m_data.m_value.array),view(simple) - + 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 - {*(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 7f7e01f30..315b2fdb7 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) \ @@ -286,6 +272,7 @@ #include // min, transform #include // array +#include // nullptr_t #include // forward_list #include // inserter, front_inserter, end #include // map @@ -2688,15 +2675,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; \ }); \ @@ -2727,20 +2716,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(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 @@ -3000,6 +2991,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 @@ -3052,18 +3045,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 @@ -3073,7 +3066,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) @@ -3082,7 +3075,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 @@ -3109,18 +3102,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 @@ -3130,7 +3123,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) @@ -3139,7 +3132,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 @@ -3166,18 +3159,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 @@ -3187,7 +3180,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) @@ -3196,7 +3189,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 @@ -3224,18 +3217,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 @@ -3245,7 +3238,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) @@ -3254,7 +3247,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 @@ -4541,6 +4534,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. @@ -5576,7 +5610,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 @@ -5891,6 +5925,42 @@ 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; +} + +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" @@ -5902,7 +5972,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(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! @@ -6060,6 +6130,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 @@ -6078,6 +6201,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) { @@ -6106,7 +6293,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)) @@ -6143,10 +6330,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) { @@ -6157,31 +6356,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) { @@ -6190,23 +6389,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); } @@ -6218,8 +6417,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)); @@ -6228,7 +6427,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())) @@ -6236,16 +6435,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())) @@ -6253,11 +6452,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 @@ -6285,7 +6484,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(); @@ -6294,7 +6494,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(); @@ -6309,6 +6509,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*/) @@ -6357,9 +6572,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 @@ -6370,11 +6584,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) @@ -6441,24 +6655,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()) { @@ -6487,8 +6701,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())) @@ -6497,12 +6750,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); } @@ -6511,10 +6759,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) { @@ -6528,7 +6776,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 @@ -6538,7 +6786,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(); @@ -6582,7 +6830,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 {}); } @@ -6628,7 +6876,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()) @@ -6643,6 +6891,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*/) { @@ -6651,7 +6917,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 @@ -6664,7 +6930,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 @@ -6688,7 +6954,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. @@ -6700,16 +6966,99 @@ 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: the probe +template +struct is_adl_only_from_json_source : std::is_same {}; + +struct from_json_fn +{ + 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))) + { + return from_json(j, std::forward(val)); + } + + 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))) + { + 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); +} + +// 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 @@ -6805,57 +7154,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 @@ -8588,6 +8886,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 @@ -10057,231 +10582,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 @@ -10711,6 +11011,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 @@ -11819,6 +12126,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 @@ -12104,6 +12418,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 @@ -12185,6 +12571,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 - @@ -12331,6 +12745,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 @@ -12792,8 +13264,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; @@ -12851,10 +13328,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 @@ -12985,6 +13461,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 @@ -12993,17 +13471,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 { @@ -13017,14 +13496,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) @@ -13042,7 +13523,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) @@ -13086,7 +13567,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(); } @@ -13100,13 +13582,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(); @@ -13118,7 +13605,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(): @@ -13141,35 +13628,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 @@ -13249,7 +13736,7 @@ class lexer : public lexer_base JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); // translate codepoint into bytes - add_codepoint(codepoint); + add_codepoint(keep, codepoint); break; } @@ -13434,7 +13921,7 @@ class lexer : public lexer_base case 0x7E: case 0x7F: { - add(current); + add_string_char(keep, current); break; } @@ -13470,7 +13957,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; } @@ -13480,7 +13967,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; } @@ -13504,7 +13991,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; } @@ -13514,7 +14001,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; } @@ -13524,7 +14011,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; } @@ -13536,7 +14023,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; } @@ -13546,7 +14033,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; } @@ -14044,7 +14531,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 @@ -14059,7 +14546,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 @@ -14067,7 +14554,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) @@ -14318,7 +14805,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; @@ -14651,6 +15138,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 @@ -15054,7 +15569,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 '-': @@ -15734,11 +16251,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 @@ -16011,7 +16536,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; } } @@ -16026,6 +16554,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; + } } } @@ -16042,6 +16576,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()) { @@ -16250,15 +16790,19 @@ constructor contains the parsed value. Without @a Collect, every value is stored in place as it arrives, so after a parse error the value holds what was read up to it. -@tparam BasicJsonType the JSON type +@tparam BasicJsonType the JSON type @tparam InputAdapterType the input adapter of the lexer that can be passed to the constructor to record diagnostic positions; it does not matter if no lexer is passed -@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 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: @@ -16267,7 +16811,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 @@ -18403,6 +18947,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() @@ -18656,6 +19203,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; @@ -18663,6 +19227,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; } @@ -18721,16 +19286,27 @@ 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; + // an array's elements are named "0", "1", ... in the wire format, + // 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))) + 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; } - // 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; } @@ -18749,14 +19325,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 (@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. */ - 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); @@ -18769,7 +19349,7 @@ class binary_reader } if (current == 0x00) { - return check_string_utf8(result, "key"); + return !store || check_string_utf8(result, "key"); } *out++ = static_cast(current); } @@ -18779,10 +19359,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) @@ -18790,18 +19371,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; @@ -18809,8 +19387,44 @@ 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*/, 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; } @@ -18879,9 +19493,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 @@ -18972,11 +19623,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 @@ -19201,7 +19854,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) @@ -19210,7 +19863,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) @@ -19377,16 +20030,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) { @@ -19406,6 +20053,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 @@ -19538,6 +20193,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"))) @@ -19557,26 +20216,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 @@ -19875,6 +20590,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 @@ -19904,6 +20768,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) @@ -19915,7 +20780,7 @@ class binary_reader { // claim the element about to be read --container_stack.back().remaining; - if (top.is_object) + if (is_object) { get(); } @@ -19928,7 +20793,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) @@ -19945,8 +20810,12 @@ class binary_reader continue; } - if (top.is_object) + if (is_object) { + if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler))) + { + return false; + } key.clear(); if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key))) { @@ -20287,7 +21156,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: @@ -20418,13 +21287,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 @@ -20769,6 +21638,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 @@ -21536,6 +22536,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 != '#')) { @@ -21726,6 +22734,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 */ @@ -21748,6 +22857,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'; @@ -21760,8 +22871,34 @@ class binary_reader } ndarray_open = 2; - 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) + { + 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; @@ -21803,7 +22940,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()); @@ -22229,13 +23367,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 { @@ -22673,7 +23811,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; @@ -22719,6 +23862,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) { @@ -22859,7 +24023,7 @@ class binary_reader return false; } } - return sax->number_float(result, ""); + return sax->number_float(result, no_source); } /*! @@ -22919,7 +24083,7 @@ class binary_reader }(); return sax->number_float((half & 0x8000u) != 0 ? static_cast(-val) - : static_cast(val), ""); + : static_cast(val), no_source); } /*! @@ -22945,12 +24109,25 @@ 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); } + /// 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 @@ -22970,7 +24147,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; } @@ -23442,6 +24628,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) + { + 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) { @@ -23538,6 +24730,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{}; @@ -23561,6 +24757,44 @@ class binary_reader std::uint8_t ndarray_open = 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', '[', '{' @@ -23603,6 +24837,8 @@ class binary_reader return "uint8"; case 'd': return "single"; + case 'h': + return "half"; case 'i': return "int8"; case 'l': @@ -23698,8 +24934,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 {}; @@ -23714,14 +24950,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: @@ -23730,10 +24966,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_) { @@ -25961,7 +27196,7 @@ NLOHMANN_JSON_NAMESPACE_END -#include // all_of, copy +#include // copy #include // array #include // isdigit #include // errno, ERANGE @@ -25969,9 +27204,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 @@ -25985,6 +27222,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -26095,6 +27334,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 @@ -26204,6 +27463,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()); @@ -26550,6 +27816,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 { @@ -26933,43 +28418,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 number - const bool nums = - 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 numbers 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 { @@ -26977,16 +28664,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 @@ -28050,6 +29752,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 @@ -28267,6 +29970,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 @@ -30307,7 +32035,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; } /*! @@ -30320,10 +32056,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()); } @@ -30483,7 +32227,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 */ @@ -30544,7 +32289,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; } @@ -30692,7 +32439,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; @@ -30943,6 +32691,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 @@ -30954,6 +32895,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); @@ -31684,6 +33658,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 @@ -31705,19 +33698,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; } } @@ -31731,6 +33712,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); @@ -31745,6 +33733,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 @@ -31762,21 +33771,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; } } @@ -31789,6 +33784,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); @@ -32452,7 +34454,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) { @@ -32660,10 +34662,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; @@ -33395,10 +35400,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); } } @@ -33884,6 +35888,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))) @@ -37594,6 +39603,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' @@ -37739,6 +39765,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 * @@ -38316,7 +40359,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 @@ -38325,7 +40368,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; @@ -38349,12 +40392,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: @@ -41283,7 +43325,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 @@ -45117,6 +47161,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}; @@ -45170,7 +47233,6 @@ public: switch (parent.m_data.m_type) { - case value_t::null: case value_t::object: { // use operator[] to add value @@ -45202,6 +47264,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_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 4298c901b..0ee79fcc1 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" @@ -155,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) @@ -239,9 +250,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,22 +273,22 @@ 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 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"); -// 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 +308,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 +338,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 @@ -397,14 +421,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) @@ -547,7 +574,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 @@ -665,25 +692,32 @@ 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 +// +// 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 #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 @@ -923,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; @@ -1167,6 +1204,83 @@ 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; +} + +/// 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 { @@ -1260,7 +1374,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: @@ -1364,31 +1479,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); @@ -1451,11 +1551,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 = { @@ -1549,6 +1725,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)) { @@ -1734,7 +1925,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(); } @@ -1808,10 +2007,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) { @@ -1830,11 +2032,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 +2058,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 '-': \ @@ -1995,7 +2197,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: @@ -2077,17 +2282,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,8 +2358,14 @@ 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 (!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); @@ -2169,7 +2380,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 +2392,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,14 +2419,17 @@ 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() { - 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)) @@ -2238,7 +2452,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)) @@ -2280,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); @@ -2317,23 +2531,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 +2557,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 +2580,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,41 +2590,58 @@ indent_done: return false; } p = r.p; - push_node(value_t::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; } }; }; /// 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 @@ -2428,15 +2659,523 @@ 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 + +// #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); +} + +#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 (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 +@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 = 0; + std::int64_t q = 0; +#if NLOHMANN_VIEW_VECTOR + const unsigned count = int_digits + frac_digits; + if (count >= 16 && limit - p >= 32) + { + // (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 + // 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. 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 +{ + 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 +/// 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); +} + +#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 +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*/) +{ +#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") + { + // (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 @@ -2451,17 +3190,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(); @@ -2477,6 +3554,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 { @@ -2501,60 +3604,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: @@ -2567,6 +3657,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) @@ -2588,45 +3689,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 @@ -2637,20 +4121,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) @@ -2661,7 +4177,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; } @@ -2678,31 +4194,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()) @@ -2710,28 +4293,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; + } } } } @@ -2802,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 @@ -2816,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) { @@ -2839,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) { @@ -2847,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, @@ -3011,7 +5367,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 @@ -3169,7 +5525,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); @@ -3235,7 +5591,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;) { @@ -3702,7 +6058,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)) @@ -3724,10 +6080,34 @@ NLOHMANN_JSON_NAMESPACE_END // #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 // 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 @@ -3735,6 +6115,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 {}; @@ -3746,10 +6155,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) @@ -3779,6 +6187,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 @@ -3818,15 +6263,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()); + 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)); - 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) @@ -3869,11 +6314,11 @@ 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()); + 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); @@ -3886,14 +6331,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()); + 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 == 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); @@ -3910,11 +6355,11 @@ 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()); + 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); @@ -3930,9 +6375,9 @@ 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()); + 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)) { @@ -3948,11 +6393,11 @@ 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()); + 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; @@ -3979,11 +6424,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); } } @@ -4022,11 +6467,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; } @@ -4103,8 +6548,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) @@ -4119,8 +6564,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)) { @@ -4132,8 +6577,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; } @@ -4155,14 +6600,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; } @@ -4191,10 +6636,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; } @@ -4208,19 +6653,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 @@ -4239,7 +6684,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; } @@ -4261,7 +6706,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()); @@ -4276,7 +6721,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); @@ -4298,10 +6743,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; @@ -4310,7 +6755,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; } @@ -4320,7 +6765,7 @@ class editor case value_t::binary: case value_t::discarded: default: - return plain_node(value_t::null); + return plain_node(node_kind::null); } } @@ -4329,17 +6774,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: @@ -4379,7 +6824,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 (;;) @@ -4426,9 +6871,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 { @@ -4517,7 +6962,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}); } @@ -4590,232 +7035,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 @@ -4876,7 +7095,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 @@ -4899,21 +7119,23 @@ 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 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); @@ -4925,7 +7147,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) @@ -4934,23 +7156,24 @@ 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; + 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); @@ -4978,12 +7201,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; @@ -5017,7 +7238,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)); } @@ -5039,6 +7260,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 @@ -5051,6 +7273,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) { @@ -5069,11 +7366,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 { @@ -5085,10 +7378,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; @@ -5133,7 +7423,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)(); @@ -5154,7 +7444,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; @@ -5199,10 +7489,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) { @@ -5224,7 +7519,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) { @@ -5235,7 +7531,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()) @@ -5245,14 +7545,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) @@ -5312,13 +7616,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; } @@ -5336,7 +7642,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; } @@ -5377,7 +7683,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; } @@ -5390,13 +7696,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; } @@ -5410,7 +7716,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; } @@ -5418,7 +7724,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}); } @@ -5432,11 +7738,11 @@ 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; } - 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; } @@ -5459,13 +7765,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"); @@ -5525,7 +7829,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)); } @@ -5534,6 +7838,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 @@ -5847,7 +8152,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)) @@ -6011,199 +8319,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 - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ -namespace view -{ - -/*! -@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(). -Without a lexer the handler records no source positions -(JSON_DIAGNOSTIC_POSITIONS). -*/ -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 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; - }; - - // a scalar needs neither the handler nor the stack of open containers - 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; - } - - BasicJsonType result; - sax_t sax(result, true); - 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 (;;) - { - // 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 == static_cast(value_t::object); - 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(); - } - open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; - 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 (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 @@ -6258,6 +8373,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) { @@ -6297,7 +8413,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(); } @@ -6309,7 +8425,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(); } @@ -6328,7 +8444,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(); } @@ -6338,7 +8454,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(); } @@ -6367,7 +8483,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 @@ -6742,6 +8860,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 { @@ -7069,6 +9324,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(); @@ -7208,7 +9464,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 { @@ -7232,7 +9488,21 @@ 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; + // (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) { @@ -7342,6 +9612,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) { @@ -7517,120 +10035,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 // __ _____ _____ _____ @@ -7700,7 +10104,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))); } } @@ -7710,7 +10114,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()); @@ -7728,7 +10132,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) @@ -7770,19 +10174,23 @@ 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 NLOHMANN_JSON_NAMESPACE_END +// #include + NLOHMANN_JSON_NAMESPACE_BEGIN @@ -7792,15 +10200,20 @@ 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: + /// 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; @@ -7819,6 +10232,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 // ////////// @@ -7967,7 +10389,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); } @@ -7993,7 +10415,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(); } @@ -8026,12 +10448,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; } @@ -8052,11 +10474,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); } @@ -8084,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()); } const basic_json_view r = lookup(key); return r.is_discarded() ? default_value : r.template get(); @@ -8094,7 +10516,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); @@ -8109,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() ? default_value : r.template get(); @@ -8119,7 +10541,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(); @@ -8132,7 +10554,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; } @@ -8145,7 +10567,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(); } @@ -8203,7 +10625,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 @@ -8262,8 +10684,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 { @@ -8271,11 +10695,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; } @@ -8285,7 +10710,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}; } @@ -8297,7 +10722,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)}; } @@ -8345,7 +10770,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; } @@ -8381,14 +10806,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); } @@ -8426,7 +10851,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 @@ -8439,15 +10864,23 @@ 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; + 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 @@ -8456,11 +10889,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 @@ -8509,7 +10942,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(); } @@ -8523,7 +10956,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 @@ -8545,7 +10978,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 --- @@ -8554,7 +10987,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; } @@ -8564,7 +10997,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); } @@ -8573,7 +11006,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; } @@ -8600,22 +11033,45 @@ 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 + /// 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, std::is_default_constructible>::value, int> = 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) @@ -8628,14 +11084,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); } @@ -8651,16 +11113,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 @@ -8706,7 +11171,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; } @@ -8721,7 +11186,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; } @@ -8734,7 +11199,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(); } @@ -8750,7 +11216,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> {}); } @@ -8874,6 +11340,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, @@ -8915,6 +11385,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 // @@ -8950,14 +11421,14 @@ 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 /// 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) { @@ -8973,7 +11444,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'; }); @@ -8983,7 +11458,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) { @@ -9017,7 +11492,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 @@ -9030,7 +11505,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 @@ -9056,7 +11531,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"); @@ -9064,25 +11539,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); } @@ -9100,6 +11575,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) { @@ -9111,7 +11602,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); @@ -9121,7 +11612,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) { @@ -9142,7 +11633,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; @@ -9169,7 +11660,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)) { @@ -9182,7 +11674,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); } @@ -9190,13 +11682,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) { @@ -9210,7 +11702,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; @@ -9221,7 +11713,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()); @@ -9230,13 +11722,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) { @@ -9248,13 +11740,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 @@ -9265,14 +11757,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); } @@ -9332,22 +11824,31 @@ 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; +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 @@ -9394,6 +11895,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/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/tests/benchmarks/json_view/README.md b/tests/benchmarks/json_view/README.md index c4a05d35a..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 @@ -59,6 +64,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 @@ -66,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_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/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 39d5048f0..1e8ae2494 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. @@ -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,30 +284,46 @@ def main(): + ['-I' + d for d in lib.include]) objects.append(obj) - binaries = {} - for bench in ['bench_view', 'bench_corpus', 'bench_edit']: - 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 - 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}'), @@ -314,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)'), @@ -326,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']: - 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') 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-alt-string.cpp b/tests/src/unit-alt-string.cpp index 606cbeb52..b2832dcdf 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-bjdata.cpp b/tests/src/unit-bjdata.cpp index 67e9e0a05..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 @@ -2916,6 +2919,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 @@ -3134,7 +3166,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, ']'}; @@ -3324,6 +3356,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 _; @@ -3455,7 +3525,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 +4692,134 @@ 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({})); + } +} + +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-bson.cpp b/tests/src/unit-bson.cpp index 03c901cee..ac8e2b24f 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -19,9 +19,11 @@ #include using nlohmann::json; +#include #include #include #include +#include #include #include #include "make_test_data_available.hpp" @@ -802,12 +804,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 +816,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") @@ -1094,7 +1166,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); @@ -1114,7 +1192,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) @@ -1122,8 +1200,72 @@ 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); } } + +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); + + // 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 TEST_CASE("Negative size of binary value") @@ -1178,7 +1320,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 +1332,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 +1340,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 +1366,165 @@ 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 + { + // GCC's -Weffc++ wants the member initialized in the member initialization list + // NOLINTNEXTLINE(readability-redundant-member-init) + 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 +2229,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 +2244,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 +2275,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()); } @@ -2033,3 +2339,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 +} diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 9ee371495..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") @@ -1807,6 +1809,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 +2004,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 +2027,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; @@ -2301,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 diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 6b10ce5b9..73edfa1dd 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -3627,6 +3627,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 @@ -3675,3 +3736,144 @@ 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) + { + 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); + 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()); + } +} 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-json_patch.cpp b/tests/src/unit-json_patch.cpp index 67ffee6dd..83df15350 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") { @@ -823,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") @@ -1515,6 +1641,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}}; @@ -2103,3 +2247,209 @@ 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") + { + // 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 = + { + // 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 = make_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); + } +} diff --git a/tests/src/unit-json_pointer.cpp b/tests/src/unit-json_pointer.cpp index 7748a375a..0e225b86e 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") @@ -493,6 +548,89 @@ 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("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") { json j = @@ -779,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"}; @@ -1044,6 +1222,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 = diff --git a/tests/src/unit-json_view.cpp b/tests/src/unit-json_view.cpp index e87162d98..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 } @@ -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})"; @@ -694,6 +749,7 @@ std::string exception_of(F f) } return ""; } + #endif // compares a view with the ordered_json value materialize() gives for it: @@ -914,7 +970,8 @@ TEST_CASE("json_view element access and iteration") 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()); }) == 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()) { @@ -931,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"); @@ -963,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 @@ -1041,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&); @@ -1177,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) { @@ -1267,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 @@ -1421,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") @@ -1496,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"); @@ -1563,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()) { @@ -1577,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 @@ -1588,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_builder.cpp b/tests/src/unit-json_view_builder.cpp index 6935981fb..988c25771 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 : @@ -523,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 @@ -575,6 +768,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; @@ -592,7 +838,7 @@ TEST_CASE("json_view node integer bits") }) { CAPTURE(v) - n.kind = 0x5A; + n.kind = nlohmann::detail::view::node_kind::link; n.flags = 0xA5; n.extra = 0x1234; n.off = 0x89ABCDEFu; @@ -601,7 +847,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(n.kind == nlohmann::detail::view::node_kind::link); CHECK(n.flags == 0xA5); CHECK(n.extra == 0x1234); CHECK(n.off == 0x89ABCDEFu); 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..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,768 +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 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); - } } 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_edit.cpp b/tests/src/unit-json_view_edit.cpp index 4a6fbe69d..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&); @@ -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 diff --git a/tests/src/unit-json_view_edit_values.cpp b/tests/src/unit-json_view_edit_values.cpp index 59aee3a9d..881d957f8 100644 --- a/tests/src/unit-json_view_edit_values.cpp +++ b/tests/src/unit-json_view_edit_values.cpp @@ -26,12 +26,123 @@ 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; +} + +// 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 +int overload(nlohmann::basic_json_view 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 +211,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_from_json.cpp b/tests/src/unit-json_view_from_json.cpp new file mode 100644 index 000000000..824585a56 --- /dev/null +++ b/tests/src/unit-json_view_from_json.cpp @@ -0,0 +1,560 @@ +// __ _____ _____ _____ +// __| | __| | | | 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 + #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(). + +namespace view_from_json +{ +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 +{ + // 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) +}; + +struct with_defaults +{ + int a = 1; + 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; + // 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 +{ + NLOHMANN_DEFINE_TYPE_INTRUSIVE(empty) +}; + +enum class color +{ + red, + 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"}, + {color::green, "green"}, + {color::blue, nullptr}, +}) + +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"}, + {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; +}; +// 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() +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> {}; + +// 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") +{ + 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 read views") + { + 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"); +#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 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 + 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 auto 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) + 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}], + "center": {"x": 5, "y": 6}, "named": {"a": {"x": 7, "y": 8}}})"); + const auto 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 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 auto 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(); +#if !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS + 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 + } + + SECTION("derived types") + { + const json_document d = json_document::parse(R"({"id": 7, "c": "green", "extra": 3})"); + const auto x = d.root().get(); + CHECK(x.id == 7); + CHECK(x.c == color::green); + const auto y = d.root().get(); + CHECK(y.id == 7); + CHECK(y.label == "none"); + const auto 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("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})"); + 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) + SECTION("errors are those of the same conversion of a json 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 +} + +#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 diff --git a/tests/src/unit-json_view_image.cpp b/tests/src/unit-json_view_image.cpp index eaf27eb9f..5e392559d 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,11 @@ std::uint32_t rng() TEST_CASE("json_view images: round trips") { + 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") { for (const char* text : @@ -480,7 +499,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 : { @@ -682,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"})"); @@ -810,13 +887,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 +1037,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 +1090,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 +1104,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 +1274,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); @@ -1275,8 +1352,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 diff --git a/tests/src/unit-json_view_large.cpp b/tests/src/unit-json_view_large.cpp new file mode 100644 index 000000000..5ad65c812 --- /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_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); + } + + 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_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" diff --git a/tests/src/unit-json_view_positions.cpp b/tests/src/unit-json_view_positions.cpp new file mode 100644 index 000000000..966c4b46a --- /dev/null +++ b/tests/src/unit-json_view_positions.cpp @@ -0,0 +1,574 @@ +// __ _____ _____ _____ +// __| | __| | | | 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 +{ +// 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) +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()); +} + +#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") +{ + 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 +} + +#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.insert(0, 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 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()); + } +} diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index 95a6b8a1f..8fe399b58 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -27,10 +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") @@ -2137,3 +2142,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)); + } +} diff --git a/tests/src/unit-pointer_access.cpp b/tests/src/unit-pointer_access.cpp index 33a4e2047..08b331b3a 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(); @@ -286,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(); @@ -315,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(); @@ -344,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(); @@ -373,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(); @@ -402,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(); diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index 084eb79fd..b98f2b6f3 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -1499,6 +1499,26 @@ 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. 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.get_ref()); + 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}; 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}); diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 4cce74dff..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") @@ -2023,7 +2026,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 +3351,124 @@ 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()); + } + } +} + +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)); + } +} 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); +} diff --git a/tools/generate_natvis/README.md b/tools/generate_natvis/README.md index 8f45f1877..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 @@ -34,11 +39,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. | -| `--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`. | +| `--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 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_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 +57,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..5a7d1630b 100755 --- a/tools/generate_natvis/generate_natvis.py +++ b/tools/generate_natvis/generate_natvis.py @@ -91,11 +91,11 @@ 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 ' - '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 %}
    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"