Merge branch 'json-view/11-view-access' into json-view/13-view-dump

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-09 16:58:15 +02:00
commit bd0fc072fc
54 files changed
+1229 -628

No files matched your search

+1 -1
View File
@@ -1402,7 +1402,7 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
- The class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2008-2009 [Björn Hoehrmann](https://bjoern.hoehrmann.de/) <bjoern@hoehrmann.de> - The class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2008-2009 [Björn Hoehrmann](https://bjoern.hoehrmann.de/) <bjoern@hoehrmann.de>
- The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](https://florian.loitsch.com/) - The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](https://florian.loitsch.com/)
- The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2025 [Victor Zverovich](https://github.com/vitaut) - The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, including the conversion of the digits to text by Xiang JunBo and the SIMD instruction sequence of Dougall Johnson, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2025 [Victor Zverovich](https://github.com/vitaut)
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/). - The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0). - The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright &copy; 2021 The fast_float authors - 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 &copy; 2021 The fast_float authors
-1
View File
@@ -180,7 +180,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::items', 'Met
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::materialize', 'Method', 'api/basic_json_view/materialize/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::materialize', 'Method', 'api/basic_json_view/materialize/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_format', 'Enum', 'api/basic_json_view/number_format/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_format', 'Enum', 'api/basic_json_view/number_format/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_token', 'Method', 'api/basic_json_view/number_token/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::number_token', 'Method', 'api/basic_json_view/number_token/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator bool', 'Method', 'api/basic_json_view/operator_bool/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator<<', 'Operator', 'api/basic_json_view/operator_ltlt/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator<<', 'Operator', 'api/basic_json_view/operator_ltlt/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator[]', 'Operator', 'api/basic_json_view/operator[]/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator[]', 'Operator', 'api/basic_json_view/operator[]/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator==', 'Operator', 'api/basic_json_view/operator_eq/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::operator==', 'Operator', 'api/basic_json_view/operator_eq/index.html');
@@ -23,10 +23,10 @@ type to use.
## Template parameters ## Template parameters
`NumberFloatType` `NumberFloatType`
: the type to store floating-point numbers. The parser converts `#!cpp float`, `#!cpp double`, and a : the type to store floating-point numbers. The type must be `#!cpp float`, `#!cpp double`, or
`#!cpp long double` that is IEEE 754 binary64 itself and other `#!cpp long double` formats with `#!cpp long double`. The parser converts `#!cpp float`, `#!cpp double`, and a `#!cpp long double` that is IEEE 754
`#!cpp std::from_chars` or `#!cpp std::strtold`, and serialization falls back to `#!cpp std::snprintf`, so the binary64 itself. It converts other `#!cpp long double` formats with `#!cpp std::from_chars` where available, or
type must be `#!cpp float`, `#!cpp double`, or `#!cpp long double`. The with `#!cpp std::strtold` otherwise. Serialization falls back to `#!cpp std::snprintf`. The
[binary formats](../../features/binary_formats/index.md) additionally require `#!cpp float` or `#!cpp double`, [binary formats](../../features/binary_formats/index.md) additionally require `#!cpp float` or `#!cpp double`,
because they have no encoding for `#!cpp long double`. See because they have no encoding for `#!cpp long double`. See
[Template Parameter Requirements](../../features/types/template_parameters.md#numberfloattype). [Template Parameter Requirements](../../features/types/template_parameters.md#numberfloattype).
@@ -43,6 +43,11 @@ input's own copy (for inputs that are always read into a buffer) throws.
Linear in the length of the input. Linear in the length of the input.
## Notes
An integer argument that is not a `#!cpp bool` where the flags are expected, such as `#!cpp accept(ptr, len)`, does not
compile; see [`parse`](parse.md#notes).
## Examples ## Examples
??? example ??? example
@@ -19,24 +19,33 @@ static basic_json_document parse(IteratorType first, IteratorType last,
1. Deserialize from a compatible input, borrowing or owning it depending on its value category and type (see Notes). 1. Deserialize from a compatible input, borrowing or owning it depending on its value category and type (see Notes).
2. Deserialize from a pair of input iterators. 2. Deserialize from a pair of input iterators.
Both overloads accept exactly what [`BasicJsonType::parse()`](../basic_json/parse.md) accepts, with the same Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_json/parse.md), with the same
`ignore_comments`/`ignore_trailing_commas` options, but build a [`basic_json_document`](index.md) (a flat index into `ignore_comments`/`ignore_trailing_commas` options, but build a [`basic_json_document`](index.md) (a flat index into
the input) instead of a tree of `BasicJsonType` values. the input) instead of a tree of `BasicJsonType` values. The input must be byte-oriented (see the template parameters
below): not every input type of `BasicJsonType::parse()` is supported.
## Template parameters ## Template parameters
`InputType` `InputType`
: A compatible input, for instance: : A byte-oriented input, one of:
- a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of characters - a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters
- a pointer to a null-terminated string of single byte characters - a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`,
`#!cpp unsigned char`, `#!cpp std::uint8_t`)
- a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g. - a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g.
`#!cpp std::vector<char>` or `#!cpp std::vector<std::uint8_t>` `#!cpp std::vector<char>` or `#!cpp std::vector<std::uint8_t>`
- an `#!cpp std::istream` object, or anything else [`BasicJsonType::parse()`](../basic_json/parse.md) accepts - an `#!cpp std::istream` object
- a wide string object (`#!cpp std::wstring`, `#!cpp std::u16string`, `#!cpp std::u32string`), which is converted
to UTF-8
Other inputs are not supported: a `#!cpp FILE*`, and pointers to or arrays of wide characters (`#!cpp wchar_t`,
`#!cpp char16_t`, `#!cpp char32_t`) are rejected at compile time by a `#!cpp static_assert`. (Use
[`BasicJsonType::parse()`](../basic_json/parse.md) for these.)
`IteratorType` `IteratorType`
: a compatible iterator type, for instance a pair of pointers such as `ptr` and `ptr + len`, or a pair of : an input iterator type, for instance a pair of pointers such as `ptr` and `ptr + len`, or a pair of
`#!cpp std::string::iterator` `#!cpp std::string::iterator`; the iterators of single-byte characters are borrowed or read like the byte inputs
above, those of wide characters are converted to UTF-8
## Parameters ## Parameters
@@ -70,8 +79,8 @@ discarded; see [`is_discarded`](is_discarded.md).
Throws the same exception [`BasicJsonType::parse()`](../basic_json/parse.md) throws for the same input and options -- 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 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. Additionally throws same bytes to produce the diagnostic. Additionally throws
[`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the input is 4 GiB or larger, a size [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the input is 4294967280 bytes (4 GiB
[`BasicJsonType::parse()`](../basic_json/parse.md) does not reject. minus 16 bytes) or larger, a size [`BasicJsonType::parse()`](../basic_json/parse.md) does not reject.
## Complexity ## Complexity
@@ -84,8 +93,8 @@ Linear in the length of the input.
| `input` | ownership | | `input` | ownership |
|--------------------------------------------------------------------------------------|--------------------------------------------------------------| |--------------------------------------------------------------------------------------|--------------------------------------------------------------|
| lvalue byte container (`std::string`, `std::vector<char>`, ...), `std::string_view`, C string, character array | **borrowed** -- `input` must outlive the document | | lvalue byte container (`std::string`, `std::vector<char>`, ...), `std::string_view`, C string, character array | **borrowed** -- `input` must outlive the document |
| rvalue `#!cpp std::string` | **owned**, moved in without a copy | | non-const rvalue `#!cpp std::string` | **owned**, moved in without a copy |
| rvalue byte container other than `#!cpp std::string` | **owned**, copied | | other rvalue byte container (including a `#!cpp const` rvalue `#!cpp std::string`) | **owned**, copied |
| stream, wide string, or anything else read through the general input adapter | **owned**, read into a buffer (a stream is read to its end) | | stream, wide string, or anything else read through the general input adapter | **owned**, read into a buffer (a stream is read to its end) |
For overload (2), a pair of pointers to single-byte integers (e.g. `#!cpp const char*`, `#!cpp std::uint8_t*`) is For overload (2), a pair of pointers to single-byte integers (e.g. `#!cpp const char*`, `#!cpp std::uint8_t*`) is
@@ -100,6 +109,12 @@ 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 **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. 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
`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).
## Examples ## Examples
??? example "Example: (1) borrowed vs. owned input, and errors identical to `BasicJsonType::parse()`" ??? example "Example: (1) borrowed vs. owned input, and errors identical to `BasicJsonType::parse()`"
@@ -51,6 +51,9 @@ Linear in the length of the input.
only differs in that the input is always copied rather than sometimes borrowed. Prefer [`parse()`](parse.md) when the only differs in that the input is always copied rather than sometimes borrowed. Prefer [`parse()`](parse.md) when the
input's lifetime already covers the document's, since it avoids the copy for borrowed inputs. input's lifetime already covers the document's, since it avoids the copy for borrowed inputs.
An integer argument that is not a `#!cpp bool` where the flags are expected, such as `#!cpp parse_copy(ptr, len)`, does not
compile; see [`parse`](parse.md#notes).
## Examples ## Examples
??? example ??? example
@@ -49,6 +49,9 @@ whether or not the new parse succeeds; take fresh views from [`root()`](root.md)
`input` is borrowed or owned by the same rules as [`parse()`](parse.md#notes); a document can borrow on one call and `input` is borrowed or owned by the same rules as [`parse()`](parse.md#notes); a document can borrow on one call and
own on the next, since ownership is decided freshly each time. own on the next, since ownership is decided freshly each time.
An integer argument that is not a `#!cpp bool` where the flags are expected, such as `#!cpp read(ptr, len)`, does not
compile; see [`parse`](parse.md#notes).
## Examples ## Examples
??? example ??? example
@@ -1,10 +1,15 @@
# <small>nlohmann::basic_json_document::</small>root # <small>nlohmann::basic_json_document::</small>root
```cpp ```cpp
view_type root() const noexcept; // (1)
view_type root() const& noexcept;
// (2)
view_type root() const&& = delete;
``` ```
Returns a view of the root value of the document. 1. Returns a view of the root value of the document.
2. Deleted: the view of a temporary document would dangle.
## Return value ## Return value
@@ -21,6 +26,16 @@ Constant.
## Notes ## Notes
**Lifetime.** A view refers into the document, so the document must outlive it. `root()` can therefore only be called
on a document that has a name (an lvalue); calling it on a temporary does not compile:
```cpp
auto v = json_document::parse(text).root(); // error: the document is destroyed at the end of the statement
auto doc = json_document::parse(text); // OK: keep the document alive
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 `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 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 [Object inspection](../basic_json_view/index.md) -- in particular, it is invalidated by the next
+9 -6
View File
@@ -8,15 +8,18 @@ basic_json_view at(const string_t& key) const;
// (2) // (2)
basic_json_view at(size_type idx) const; basic_json_view at(size_type idx) const;
basic_json_view at(int idx) const; template<typename IntegerType>
basic_json_view at(IntegerType idx) const;
// (3) // (3)
basic_json_view at(const json_pointer& ptr) 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 last one, should the key occur more than once (see
[Notes on duplicate keys](operator[].md#notes)). [Notes on duplicate keys](operator[].md#notes)).
2. Returns the array element at index `idx`. 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
`idx` is out of range.
3. Returns the value a JSON pointer `ptr` refers to, starting at this value. 3. Returns the value a JSON pointer `ptr` refers to, starting at this value.
## Parameters ## Parameters
@@ -32,7 +35,7 @@ basic_json_view at(const json_pointer& ptr) const;
## Return value ## Return value
1. the value of the first member with key `key` 1. the value of the last member with key `key`
2. the element at index `idx` 2. the element at index `idx`
3. the value `ptr` resolves to, starting at this value 3. the value `ptr` resolves to, starting at this value
@@ -72,8 +75,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity ## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 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 -- another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
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.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). index (unlike `BasicJsonType`'s array, which is random-access).
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
@@ -4,8 +4,8 @@
basic_json_view() noexcept = default; basic_json_view() noexcept = default;
``` ```
Creates an invalid (discarded) view: [`type()`](type.md) is `#!cpp value_t::discarded`, Creates an invalid (discarded) view: [`type()`](type.md) is `#!cpp value_t::discarded` and
[`is_discarded()`](is_discarded.md) is `#!cpp true`, and `#!cpp explicit operator bool()` is `#!cpp false`. [`is_discarded()`](is_discarded.md) is `#!cpp true`.
This is the only constructor a caller can use directly. Every other view is obtained from a This is the only constructor a caller can use directly. Every other view is obtained from a
[`basic_json_document`](../basic_json_document/index.md), via [`root()`](../basic_json_document/root.md) or by [`basic_json_document`](../basic_json_document/index.md), via [`root()`](../basic_json_document/root.md) or by
@@ -44,7 +44,6 @@ placeholder for "no value yet" and later be assigned a real view.
## See also ## See also
- [is_discarded](is_discarded.md) - return whether the view is invalid - [is_discarded](is_discarded.md) - return whether the view is invalid
- [operator bool](operator_bool.md) - return whether the view refers to a value
- [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
## Version history ## Version history
@@ -24,7 +24,7 @@ Constant.
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), [`contains`](contains.md), and [`count`](count.md), [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md), and [`count`](count.md),
which all resolve to the *first* member with a given key. See the which all resolve to the *last* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`. [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
Because objects are iterated in document order rather than sorted by key, the order seen here can differ from what Because objects are iterated in document order rather than sorted by key, the order seen here can differ from what
@@ -33,8 +33,8 @@ No-throw guarantee: this function never throws exceptions.
## Complexity ## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 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 another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
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.
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 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. [`at`](at.md#complexity) with a JSON pointer.
@@ -23,9 +23,9 @@ No-throw guarantee: this function never throws exceptions.
## Complexity ## Complexity
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already in document order, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
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.
## Notes ## Notes
@@ -36,7 +36,7 @@ Unlike [`BasicJsonType::count()`](../basic_json/count.md), whose return value ca
an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly
[`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the [`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the
[Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require [Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require
counting every member with a matching key, not just finding the first one. counting every member with a matching key (the lookup functions resolve to the *last* one).
## Examples ## Examples
+4 -4
View File
@@ -6,7 +6,7 @@ iterator find(const char* key) const;
iterator find(const string_t& 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 last 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, [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. [`end()`](end.md) is returned.
@@ -25,9 +25,9 @@ No-throw guarantee: this function never throws exceptions.
## Complexity ## Complexity
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already in document order, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
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.
## Notes ## Notes
+3 -3
View File
@@ -87,9 +87,9 @@ exception thrown while converting through `materialize()` (the last bullet) is d
!!! info "Duplicate keys" !!! info "Duplicate keys"
`#!cpp std::map`/`#!cpp std::unordered_map` conversions keep the *last* value of a repeated key, like `#!cpp std::map`/`#!cpp std::unordered_map` conversions keep the *last* value of a repeated key, like
[`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the opposite [`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the member
of [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md), which resolve to the [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md) resolve to, too (see the
*first* occurrence (see the [Notes on duplicate keys](operator[].md#notes)). [Notes on duplicate keys](operator[].md#notes)).
!!! info "No pointers, references, or implicit conversion" !!! info "No pointers, references, or implicit conversion"
@@ -71,7 +71,6 @@ subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`
- [**is_primitive**](is_primitive.md) - return whether the type is primitive - [**is_primitive**](is_primitive.md) - return whether the type is primitive
- [**is_structured**](is_structured.md) - return whether the type is structured - [**is_structured**](is_structured.md) - return whether the type is structured
- [**is_discarded**](is_discarded.md) - return whether the view is invalid - [**is_discarded**](is_discarded.md) - return whether the view is invalid
- [**operator bool**](operator_bool.md) - return whether the view refers to a value
### Element access ### Element access
@@ -9,6 +9,10 @@ view (see [(constructor)](basic_json_view.md)), and for [`root()`](../basic_json
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`. [`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
JSON pointer that cannot be resolved, and for any access on a view that is itself discarded (so a chain such as
`#!cpp v["a"]["b"]` is safe). [`at`](at.md) throws instead.
## Return value ## Return value
`#!cpp true` if the view is discarded, `#!cpp false` otherwise. `#!cpp true` if the view is discarded, `#!cpp false` otherwise.
@@ -23,8 +27,9 @@ Constant.
## Notes ## Notes
`#!cpp v.is_discarded()` and `#!cpp !static_cast<bool>(v)` are equivalent; use whichever reads better at the call A `basic_json_view` is not convertible to `#!cpp bool`: such a conversion would mean "refers to a value", whereas
site. `basic_json` converts to the `#!cpp bool` it holds, so the same code would silently behave differently. Test
`#!cpp !v.is_discarded()` explicitly.
## Examples ## Examples
@@ -45,7 +50,7 @@ site.
## See also ## See also
- [operator bool](operator_bool.md) - return whether the view refers to a value - [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 - [(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 - [is_discarded (basic_json_document)](../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` - [`BasicJsonType::is_discarded`](../basic_json/is_discarded.md) - the corresponding function of `basic_json`
@@ -48,7 +48,7 @@ Constant.
As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all 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),
[`contains`](contains.md), and [`count`](count.md), which resolve to the *first* member with a given key. See the [`contains`](contains.md), and [`count`](count.md), which resolve to the *last* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`. [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
!!! danger "Lifetime issues" !!! danger "Lifetime issues"
@@ -63,8 +63,8 @@ occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](a
The example below shows a settings object whose source text records every update to a key as a duplicate 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 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 [`operator[]`](operator[].md) sees the *last* one, and so does [`materialize()`](materialize.md) -- like
[`BasicJsonType::parse()`](../basic_json/parse.md) -- keeps only the *last*. [`BasicJsonType::parse()`](../basic_json/parse.md).
```cpp ```cpp
--8<-- "examples/basic_json_view__items.cpp" --8<-- "examples/basic_json_view__items.cpp"
@@ -8,16 +8,19 @@ basic_json_view operator[](const string_t& key) const;
// (2) // (2)
basic_json_view operator[](size_type idx) const; basic_json_view operator[](size_type idx) const;
basic_json_view operator[](int idx) const; template<typename IntegerType>
basic_json_view operator[](IntegerType idx) const;
// (3) // (3)
basic_json_view operator[](const json_pointer& ptr) 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 1. Returns the value of the object member with key `key` -- the last 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. 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 2. Returns the array element at index `idx`, or a [discarded](is_discarded.md) view if `idx` is out of range. The
`#!cpp int` overload only exists so that an integer literal is not ambiguous between this overload and 1.) 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
not ambiguous between that overload and 1; a negative `idx` is out of range.
3. Returns the value a JSON pointer `ptr` refers to, starting at this value, or a [discarded](is_discarded.md) view 3. Returns the value a JSON pointer `ptr` refers to, starting at this value, or a [discarded](is_discarded.md) view
wherever resolving it further is not possible without inserting into or extending the document (see wherever resolving it further is not possible without inserting into or extending the document (see
[Return value](#return-value) and [Exceptions](#exceptions) below). [Return value](#return-value) and [Exceptions](#exceptions) below).
@@ -35,10 +38,12 @@ basic_json_view operator[](const json_pointer& ptr) const;
## Return value ## Return value
1. the value of the first member with key `key`, or a discarded view if `#!cpp is_object()` is `#!cpp false` or no 1. the value of the last member with key `key`, or a discarded view if no member has this key (or if this view is
member has this key [discarded](is_discarded.md))
2. the element at index `idx`, or a discarded view if `#!cpp is_array()` is `#!cpp false` or `#!cpp idx >= size()` 2. the element at index `idx`, or a discarded view if `#!cpp idx >= size()` or `idx` is negative (or if this view is
3. the value `ptr` resolves to, starting at this value, or a discarded view for exactly the reference tokens where the [discarded](is_discarded.md))
3. the value `ptr` resolves to, starting at this value, or a discarded view (also if this view is
[discarded](is_discarded.md)) for exactly the reference tokens where the
**const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) invokes undefined behavior for **const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) invokes undefined behavior for
the same pointer and the same document: an object member that does not exist, or an array index that is out of the same pointer and the same document: an object member that does not exist, or an array index that is out of
range range
@@ -49,10 +54,12 @@ Strong exception safety: if an exception is thrown, there are no changes to the
## Exceptions ## Exceptions
1. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an object -- 1. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an object and
not [discarded](is_discarded.md) --
the same exception, with the same message, that the **const** overload of 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. [`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 -- 2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an array and
not [discarded](is_discarded.md) --
the same exception, with the same message, that the **const** overload of 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. [`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 3. Throws the same exceptions, with the same messages, that the **const** overload of
@@ -73,9 +80,9 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity ## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 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 -- another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
already known from the index, without reading the key bytes -- before comparing its content, so a key of a key's length -- already known from the index, without reading the key bytes -- before comparing its content, so a
different length than `key` is rejected without touching the source text. key of a different length than `key` is rejected without touching the source text.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). index (unlike `BasicJsonType`'s array, which is random-access).
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
@@ -84,20 +91,29 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Notes ## Notes
Unlike `BasicJsonType::operator[]`, which is undefined behavior (guarded by a 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 always [runtime assertion](../../features/assertions.md)) for a missing key on a **const** value, this operator returns a
returns a safe, testable result: a [discarded](is_discarded.md) view, which is `#!cpp false` in a boolean context. 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. document.
!!! info "Chained access"
`#!cpp operator[]` on a [discarded](is_discarded.md) view returns a discarded view and does not throw, so a chain
like `#!cpp v["a"]["b"][0]` is safe even if `"a"` or `"b"` is missing: the first missing step makes the whole
result discarded, which is tested once at the end. Type errors on values that are *not* discarded still throw: a
key on an array or a primitive, or an index on an object or a primitive, is `type_error.305` as for
`BasicJsonType`. [`at`](at.md) still throws for a discarded view, as it does for a missing key.
!!! info "Duplicate keys" !!! info "Duplicate keys"
If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md), If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md),
[`contains`](contains.md), [`count`](count.md)) all resolve to the *first* member with that key, because a [`contains`](contains.md), [`count`](count.md), [`value`](value.md), and JSON pointer resolution) all resolve to
lookup can stop as soon as it finds a match. This is different from the *last* member with that key. This is the member [`materialize()`](materialize.md) (and
[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every [`BasicJsonType::parse()`](../basic_json/parse.md)) keeps, so a lookup in the view and in the materialized value
member in order and so end up keeping the *last* value for a repeated key -- there is no reason for them to stop agree. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
early. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including duplicates, in document order. A lookup scans all members for this: it cannot stop at the first match. See the
duplicates, in document order. See the example below and [`size()`](size.md#notes). example below and [`size()`](size.md#notes).
!!! info "JSON pointer resolution" !!! info "JSON pointer resolution"
@@ -1,46 +0,0 @@
# <small>nlohmann::basic_json_view::</small>operator bool
```cpp
explicit operator bool() const noexcept;
```
Returns whether this view refers to a value, i.e. the negation of [`is_discarded()`](is_discarded.md). Being
`#!cpp explicit`, this conversion is only considered in a boolean context (`#!cpp if (v)`, `#!cpp !v`, `#!cpp v &&
...`), not for implicit conversions to other types.
## Return value
`#!cpp true` if the view refers to a value, `#!cpp false` if it is [discarded](is_discarded.md).
## Exception safety
No-throw guarantee: this function never throws exceptions.
## Complexity
Constant.
## Examples
??? example
The example below classifies several parsed documents by the type of their root value, without materializing any
of them into a `BasicJsonType` value.
```cpp
--8<-- "examples/basic_json_view__type_predicates.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__type_predicates.output"
```
## See also
- [is_discarded](is_discarded.md) - return whether the view is invalid
## Version history
- Added in version 3.13.0.
@@ -12,7 +12,7 @@ T value(const json_pointer& ptr, const T& default_value) const;
string_t value(const json_pointer& ptr, const char* 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 1. Returns the value of the object member with key `key` -- the last 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. [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 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. `ptr` cannot be resolved.
@@ -39,7 +39,7 @@ equivalent) deduce `string_t`, not `const char*`, for their return type and for
## Return value ## Return value
1. the first member with key `key`, converted to `T`, or `default_value` 1. the last member with key `key`, converted to `T`, or `default_value`
2. the value `ptr` resolves to, converted to `T`, or `default_value` 2. the value `ptr` resolves to, converted to `T`, or `default_value`
## Exception safety ## Exception safety
@@ -68,8 +68,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity ## Complexity
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after 1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
another, in document order, stopping at the first match. Plus the complexity of converting the found member to another, in document order, scanning all of them, since the last match is wanted. Plus the complexity of converting
`T` (see [`get`](get.md)). the found member to `T` (see [`get`](get.md)).
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 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 [`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
@@ -8,10 +8,10 @@ int main()
// the default constructor is the only public one: it creates an invalid // the default constructor is the only public one: it creates an invalid
// (discarded) view, useful as a "no value yet" placeholder // (discarded) view, useful as a "no value yet" placeholder
nlohmann::json_view v; nlohmann::json_view v;
std::cout << static_cast<bool>(v) << ' ' << v.is_discarded() << '\n'; std::cout << v.is_discarded() << '\n';
// views are trivially copyable handles (two pointers); the document owns // views are trivially copyable handles (two pointers); the document owns
// the actual data // the actual data
nlohmann::json_view copy = v; nlohmann::json_view copy = v;
std::cout << static_cast<bool>(copy) << '\n'; std::cout << copy.is_discarded() << '\n';
} }
@@ -1,2 +1,2 @@
false true true
false true
@@ -7,8 +7,8 @@ int main()
{ {
// a settings object whose source text records every update to a key as // a settings object whose source text records every update to a key as
// a duplicate member. items() visits all of them, in document order, so // a duplicate member. items() visits all of them, in document order, so
// the update history is visible; operator[] only ever sees the first // the update history is visible; operator[] and materialize() -- like
// one, and materialize() -- like basic_json::parse() -- keeps the last // basic_json::parse() -- see the last one
json_document updates = json_document::parse(R"({"retries": 1, "timeout": 30, "retries": 5})"); json_document updates = json_document::parse(R"({"retries": 1, "timeout": 30, "retries": 5})");
const auto settings = updates.root(); const auto settings = updates.root();
@@ -17,6 +17,6 @@ int main()
std::cout << item.key() << '=' << item.value().materialize().dump() << '\n'; std::cout << item.key() << '=' << item.value().materialize().dump() << '\n';
} }
std::cout << "first \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n'; std::cout << "last \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n';
std::cout << "last \"retries\" kept by materialize(): " << settings.materialize()["retries"].dump() << '\n'; std::cout << "last \"retries\" kept by materialize(): " << settings.materialize()["retries"].dump() << '\n';
} }
@@ -1,5 +1,5 @@
retries=1 retries=1
timeout=30 timeout=30
retries=5 retries=5
first "retries" seen by operator[]: 1 last "retries" seen by operator[]: 5
last "retries" kept by materialize(): 5 last "retries" kept by materialize(): 5
@@ -22,17 +22,19 @@ int main()
std::cout << user["name"].materialize().dump(); std::cout << user["name"].materialize().dump();
// operator[] on a missing object key gives a discarded view -- test // operator[] on a missing object key gives a discarded view -- test
// it with a plain "if". The const overload of json::operator[] // it with is_discarded(). The const overload of json::operator[]
// would instead be undefined behavior (guarded by an assertion) for // would instead be undefined behavior (guarded by an assertion) for
// a missing key // a missing key
if (const auto email = user["email"]) const auto email = user["email"];
if (!email.is_discarded())
{ {
std::cout << " <" << email.materialize().dump() << ">"; std::cout << " <" << email.materialize().dump() << ">";
} }
// the same holds for an array index past the end: a discarded view, // the same holds for an array index past the end: a discarded view,
// not undefined behavior // not undefined behavior
if (const auto first_tag = user["tags"][0]) const auto first_tag = user["tags"][0];
if (!first_tag.is_discarded())
{ {
std::cout << " #" << first_tag.materialize().dump(); std::cout << " #" << first_tag.materialize().dump();
} }
@@ -25,7 +25,8 @@ int main()
// a missing key or an out-of-range index along the path gives a // a missing key or an out-of-range index along the path gives a
// discarded view, exactly where const json::operator[] would be // discarded view, exactly where const json::operator[] would be
// undefined behavior for the same pointer // undefined behavior for the same pointer
if (const auto missing = root[json_pointer("/region/servers/5/metrics/cpu")]) const auto missing = root[json_pointer("/region/servers/5/metrics/cpu")];
if (!missing.is_discarded())
{ {
std::cout << missing.materialize().dump() << '\n'; std::cout << missing.materialize().dump() << '\n';
} }
@@ -35,8 +35,8 @@ int main()
// parse without exceptions, are both discarded // parse without exceptions, are both discarded
nlohmann::json_view invalid; nlohmann::json_view invalid;
json_document failed = json_document::parse("not json", /* allow_exceptions */ false); json_document failed = json_document::parse("not json", /* allow_exceptions */ false);
std::cout << static_cast<bool>(invalid) << ' ' << invalid.is_discarded() << '\n'; std::cout << invalid.is_discarded() << '\n';
std::cout << static_cast<bool>(failed.root()) << ' ' << failed.root().is_discarded() << '\n'; std::cout << failed.root().is_discarded() << '\n';
// type() returns the same value_t enumeration as basic_json::type() // type() returns the same value_t enumeration as basic_json::type()
std::cout << (d_object.root().type() == nlohmann::json::value_t::object) << '\n'; std::cout << (d_object.root().type() == nlohmann::json::value_t::object) << '\n';
@@ -7,6 +7,6 @@ true
true true true true
true false true false
false false
false true true
false true true
true true
+12 -3
View File
@@ -81,6 +81,9 @@ Moving the document itself is fine and does **not** invalidate its views: the in
that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md) 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. whenever any of the other conditions above was not met.
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.
??? example "Example: borrowed and owned documents, and when views become invalid" ??? example "Example: borrowed and owned documents, and when views become invalid"
```cpp ```cpp
@@ -113,7 +116,7 @@ whenever any of the other conditions above was not met.
- **Only 64-bit integers.** `basic_json_document<BasicJsonType>` requires `BasicJsonType::number_integer_t` and - **Only 64-bit integers.** `basic_json_document<BasicJsonType>` requires `BasicJsonType::number_integer_t` and
`number_unsigned_t` to both be 64 bits wide; this is a compile-time `#!cpp static_assert`. `number_unsigned_t` to both be 64 bits wide; this is a compile-time `#!cpp static_assert`.
- **A 4 GiB input limit.** An input of 4 GiB or more throws - **A 4 GiB input limit.** An input of 4294967280 bytes (4 GiB minus 16 bytes) or more throws
[`out_of_range.416`](../home/exceptions.md#jsonexceptionout_of_range416), a limit [`out_of_range.416`](../home/exceptions.md#jsonexceptionout_of_range416), a limit
`#!cpp basic_json::parse()` does not have. `#!cpp basic_json::parse()` does not have.
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`. - **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`.
@@ -126,14 +129,20 @@ whenever any of the other conditions above was not met.
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
sorts by key, so iterating a [`materialize()`](../api/basic_json_view/materialize.md)d value can print members in sorts by key, so iterating a [`materialize()`](../api/basic_json_view/materialize.md)d value can print members in
a different order than iterating the view they came from. a different order than iterating the view they came from.
- **Chained access is safe.** [`operator[]`](../api/basic_json_view/operator%5B%5D.md) with a missing key, an index
out of range, or an unresolvable JSON pointer returns a [discarded](../api/basic_json_view/is_discarded.md) view, and
`operator[]` on a discarded view returns a discarded view without throwing: `#!cpp v["a"]["b"][0]` can be tested
once at the end. Type errors on values that exist (a key on an array, an index on an object) still throw, and
[`at`](../api/basic_json_view/at.md) throws for every missing value.
- **Duplicate keys are visible.** If an object in the source text repeats a key, - **Duplicate keys are visible.** If an object in the source text repeats a key,
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and [`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence (and [`size()`](../api/basic_json_view/size.md) [`items()`](../api/basic_json_view/items.md) visit *every* occurrence (and [`size()`](../api/basic_json_view/size.md)
counts all of them), while [`operator[]`](../api/basic_json_view/operator%5B%5D.md), counts all of them), while [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md), [`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
[`contains`](../api/basic_json_view/contains.md), and [`count`](../api/basic_json_view/count.md) resolve to the [`contains`](../api/basic_json_view/contains.md), and [`count`](../api/basic_json_view/count.md) resolve to the
*first* occurrence, since a lookup can stop as soon as it finds a match. `basic_json::parse()` (and so *last* occurrence -- the one `basic_json::parse()` (and so
[`materialize()`](../api/basic_json_view/materialize.md)) instead keeps only the *last* value for a repeated key. [`materialize()`](../api/basic_json_view/materialize.md)) keeps for a repeated key -- which makes a lookup scan all
members instead of stopping at a match.
See the [Notes on duplicate keys](../api/basic_json_view/operator%5B%5D.md#notes) of `operator[]`. See the [Notes on duplicate keys](../api/basic_json_view/operator%5B%5D.md#notes) of `operator[]`.
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own - **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 element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
@@ -353,16 +353,21 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
### Always required ### Always required
- A member type `value_type` that is one byte wide and `char`-compatible. The library stores and processes UTF-8 - A member type `value_type` that is one byte wide and `char`-compatible. The library stores and processes UTF-8
encoded `char` data and passes `data()` to functions that take a `#!cpp const char*`, such as `#!cpp std::strtod`. encoded `char` data and passes `data()` to functions that take a `#!cpp const char*`, such as `#!cpp std::strtold`
(only used to parse a `#!cpp long double` that is not IEEE 754 binary64, see
[`NumberFloatType`](#numberfloattype)).
`#!cpp std::wstring`, `#!cpp std::u16string`, and `#!cpp std::u32string` are **not** valid choices; see the FAQ on `#!cpp std::wstring`, `#!cpp std::u16string`, and `#!cpp std::u32string` are **not** valid choices; see the FAQ on
[wide string handling](../../home/faq.md#wide-string-handling). [wide string handling](../../home/faq.md#wide-string-handling).
- Constructors: default, copy, move, from `#!cpp const char*` (which must not be `#!cpp explicit`), from - Constructors: default, copy, move, from `#!cpp const char*` (which must not be `#!cpp explicit`), from
`#!cpp (const char*, size_type)`, and from `#!cpp (size_type, char)`; and copy or move assignment. `#!cpp (const char*, size_type)`, and from `#!cpp (size_type, char)`; and copy or move assignment.
- Member functions `size()`, `clear()`, `resize(n, c)`, `data()`, `push_back(char)`, and `operator[]` - Member functions `size()`, `clear()`, `resize(n, c)`, `data()`, `push_back(char)`, and `operator[]`
(const and non-const, returning references). `c_str()` and `back()` are **not** required. (const and non-const, returning references). `c_str()` and `back()` are **not** required.
- `data()` must return a pointer to a contiguous, **null-terminated** buffer -- the parser may hand it to - `data()` must return a pointer to a contiguous, **null-terminated** buffer. `#!cpp float`, `#!cpp double`, and a
`#!cpp std::strtod`, which reads up to the null character. A type whose `data()` is not null-terminated does not `#!cpp long double` that is IEEE 754 binary64 are converted by the library itself and do not depend on this. For any
fail to compile; it can silently misparse floating-point numbers. other `NumberFloatType` (a `#!cpp long double` of another format), the parser falls back to `#!cpp std::strtold` when
`#!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&)`, - `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 used by the CBOR reader for indefinite-length strings. 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+=`, to append a `#!cpp char` and a `#!cpp const char*`; for each it selects between `append(arg)`, `#!cpp operator+=`,
+2 -1
View File
@@ -210,7 +210,8 @@ packet-beta
- **Navigation** needs no pointers: the elements of an array or object follow its node, and the node after a value's - **Navigation** needs no pointers: the elements of an array or object follow its node, and the node after a value's
subtree is `next` nodes further for an array or object, and the next node otherwise (`document_data::after`). Views subtree is `next` nodes further for an array or object, and the next node otherwise (`document_data::after`). Views
step from element to element this way and skip whole subtrees in constant time. 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 4 GiB (`out_of_range.416`). - **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`).
For example, `#!json {"a": [1, 2.5]}` becomes five nodes. Each node's elements follow it, and `next` leads from an For example, `#!json {"a": [1, 2.5]}` becomes five nodes. Each node's elements follow it, and `next` leads from an
array or object past its subtree: array or object past its subtree:
+2 -2
View File
@@ -1048,12 +1048,12 @@ MessagePack's ext type and BSON's binary subtype are each stored in a single byt
[`basic_json_document::parse()`](../api/basic_json_document/parse.md) and the other parsing functions of [`basic_json_document::parse()`](../api/basic_json_document/parse.md) and the other parsing functions of
[`basic_json_document`](../api/basic_json_document/index.md) index a value's position in the source text in 32 bits, [`basic_json_document`](../api/basic_json_document/index.md) index a value's position in the source text in 32 bits,
so they do not support an input of 4 GiB or more. so they do not support an input of 4294967280 bytes (4 GiB minus 16 bytes) or more.
!!! failure "Example message" !!! failure "Example message"
``` ```
[json.exception.out_of_range.416] input of 4 GiB or more is not supported by json_document [json.exception.out_of_range.416] input of 4294967280 bytes or more is not supported by json_document
``` ```
!!! note !!! note
+1 -1
View File
@@ -18,7 +18,7 @@ The class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed und
The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](https://florian.loitsch.com/) The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](https://florian.loitsch.com/)
The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2025 [Victor Zverovich](https://github.com/vitaut) The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, including the conversion of the digits to text by Xiang JunBo and the SIMD instruction sequence of Dougall Johnson, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2025 [Victor Zverovich](https://github.com/vitaut)
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/). The class contains 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/).
-1
View File
@@ -283,7 +283,6 @@ nav:
- 'materialize': api/basic_json_view/materialize.md - 'materialize': api/basic_json_view/materialize.md
- 'number_format': api/basic_json_view/number_format.md - 'number_format': api/basic_json_view/number_format.md
- 'number_token': api/basic_json_view/number_token.md - 'number_token': api/basic_json_view/number_token.md
- 'operator bool': api/basic_json_view/operator_bool.md
- 'operator<<': api/basic_json_view/operator_ltlt.md - 'operator<<': api/basic_json_view/operator_ltlt.md
- 'operator[]': api/basic_json_view/operator[].md - 'operator[]': api/basic_json_view/operator[].md
- 'operator==': api/basic_json_view/operator_eq.md - 'operator==': api/basic_json_view/operator_eq.md
+3 -4
View File
@@ -15,10 +15,11 @@ namespace detail
{ {
/*! /*!
@brief the configuration macros that change the library's behavior @brief the configuration macros that json_view.hpp reads
json.hpp undefines these macros at its end (see macro_unscope.hpp), so code json.hpp undefines these macros at its end (see macro_unscope.hpp), so code
that builds on the library after it (json_view.hpp) reads them here. Like the that builds on the library after it (json_view.hpp) reads them here. A macro
is added when the view starts to depend on it. Like the
macros, they are part of the ABI namespace, so they always match the macros, they are part of the ABI namespace, so they always match the
basic_json they are used with. basic_json they are used with.
*/ */
@@ -26,8 +27,6 @@ struct abi_config
{ {
/// JSON_STRICT_NUL_HANDLING: a null byte is an error, not the end of input /// JSON_STRICT_NUL_HANDLING: a null byte is an error, not the end of input
static constexpr bool strict_nul_handling = JSON_STRICT_NUL_HANDLING != 0; static constexpr bool strict_nul_handling = JSON_STRICT_NUL_HANDLING != 0;
/// JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
static constexpr bool legacy_discarded_value_comparison = JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON != 0;
}; };
} // namespace detail } // namespace detail
+20 -5
View File
@@ -9,8 +9,9 @@
#pragma once #pragma once
#include <cstdint> // uint64_t #include <cstdint> // uint64_t
#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) #include <cstring> // memcpy
#include <intrin0.h> // __umulh, _umul128 #if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) && (!defined(__SIZEOF_INT128__) || (!defined(__GNUC__) && !defined(__clang__)))
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
#endif #endif
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN #include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -28,6 +29,10 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
{ {
#if defined(__GNUC__) || defined(__clang__) #if defined(__GNUC__) || defined(__clang__)
return __builtin_clzll(x); return __builtin_clzll(x);
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
unsigned long index = 0;
_BitScanReverse64(&index, x);
return 63 - static_cast<int>(index);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) for (int shift = 32; shift != 0; shift >>= 1)
@@ -47,6 +52,10 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
{ {
#if defined(__GNUC__) || defined(__clang__) #if defined(__GNUC__) || defined(__clang__)
return __builtin_ctzll(x); return __builtin_ctzll(x);
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
unsigned long index = 0;
_BitScanForward64(&index, x);
return static_cast<int>(index);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) for (int shift = 32; shift != 0; shift >>= 1)
@@ -94,15 +103,21 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
#endif #endif
} }
/// eight bytes as a little-endian word (compilers fold this into one load on /// eight bytes as a little-endian word (a single load on little-endian
/// little-endian targets; always inlined, as GCC otherwise calls it in the /// targets; always inlined, as GCC otherwise calls it in the number loops)
/// number loops)
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
{ {
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
// the byte order already matches (all MSVC targets are little-endian)
std::uint64_t result = 0;
std::memcpy(&result, b, sizeof(result));
return result;
#else
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u) return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u) | (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
| (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u) | (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u)
| (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u); | (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u);
#endif
} }
/// eight bytes as a little-endian word /// eight bytes as a little-endian word
+49 -124
View File
@@ -4,6 +4,7 @@
// |_____|_____|_____|_|___| https://github.com/nlohmann/json // |_____|_____|_____|_|___| https://github.com/nlohmann/json
// //
// SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/> // SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/>
// SPDX-FileCopyrightText: 2025 Victor Zverovich <https://github.com/vitaut/zmij>
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -939,88 +940,6 @@ void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
} }
/*!
@brief the shortest digits of a positive finite float (other than double): Grisu2
*/
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1)
void shortest_digits(char* buf, int& len, int& decimal_exponent, FloatType value)
{
grisu2(buf, len, decimal_exponent, value);
}
/*!
@brief the shortest digits of a positive finite double: the conversion of
Zmij (see zmij.hpp), which always finds the shortest digits that read back as
the same value (Grisu2 does not for about one double in a thousand), and the
closest of them if there are several
v = buf * 10^decimal_exponent, as for grisu2()
*/
JSON_HEDLEY_NON_NULL(1)
inline void shortest_digits(char* buf, int& len, int& decimal_exponent, double value)
{
static_assert(std::numeric_limits<double>::is_iec559 && std::numeric_limits<double>::digits == 53,
"internal error: the conversion of Zmij needs IEEE 754 binary64 doubles");
JSON_ASSERT(std::isfinite(value));
JSON_ASSERT(value > 0);
std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
zmij::decimal d = zmij::to_decimal(bits);
// without trailing zeros (up to 16): 8, 4, 2, 1 at a time
while (d.significand % 100000000 == 0)
{
d.significand /= 100000000;
d.exponent += 8;
}
if (d.significand % 10000 == 0)
{
d.significand /= 10000;
d.exponent += 4;
}
if (d.significand % 100 == 0)
{
d.significand /= 100;
d.exponent += 2;
}
if (d.significand % 10 == 0)
{
d.significand /= 10;
d.exponent += 1;
}
// at most 17 digits, written from the back two at a time
static constexpr const char* pairs =
"00010203040506070809101112131415161718192021222324252627282930313233343536373839"
"40414243444546474849505152535455565758596061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899";
std::array<char, 20> digits{};
std::size_t n = digits.size();
while (d.significand >= 100)
{
const std::uint64_t two_digits = d.significand % 100; // a variable: GCC calls a cast of the remainder useless where std::uint64_t is std::size_t
const auto i = static_cast<std::size_t>(two_digits) * 2;
d.significand /= 100;
n -= 2;
digits[n] = pairs[i];
digits[n + 1] = pairs[i + 1];
}
if (d.significand >= 10)
{
const auto i = static_cast<std::size_t>(d.significand) * 2;
n -= 2;
digits[n] = pairs[i];
digits[n + 1] = pairs[i + 1];
}
else
{
digits[--n] = static_cast<char>('0' + d.significand);
}
len = static_cast<int>(digits.size() - n);
std::memcpy(buf, digits.data() + n, static_cast<std::size_t>(len));
decimal_exponent = d.exponent;
}
/*! /*!
@brief appends a decimal representation of e to buf @brief appends a decimal representation of e to buf
@return a pointer to the element following the exponent. @return a pointer to the element following the exponent.
@@ -1423,53 +1342,30 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep
return end + (three ? 5 : 4); return end + (three ? 5 : 4);
} }
/// the powers of ten up to 10^16
inline const std::array<std::uint64_t, 17>& powers_of_ten_16() noexcept
{
static const std::array<std::uint64_t, 17> powers =
{
{
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u,
100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u, 10000000000000000u
}
};
return powers;
}
/*! /*!
@brief digits * 10^exp, as write_decimal() writes it, for the digits of a @brief whether FloatType is an IEEE 754 binary64 type (a double, or a long double
double that need no conversion (count digits, at most 15, the first not 0; that has the same format, as with MSVC and on Apple's Arm CPUs)
trailing zeros allowed): extended to 16 digits and written by write_shortest()
@return a pointer past the text; up to 41 bytes at @a first are written These are the types the conversion of Zmij (see zmij.hpp) is used for; all
(some beyond the returned end) others (binary32, or a format the library does not know) use Grisu2.
*/ */
JSON_HEDLEY_NON_NULL(1) template<typename FloatType>
JSON_HEDLEY_RETURNS_NON_NULL constexpr bool has_binary64_format() noexcept
inline char* write_short_decimal(char* first, std::uint64_t digits, int count, int exp) noexcept
{ {
JSON_ASSERT(digits >= powers_of_ten_16()[static_cast<std::size_t>(count - 1)] && count <= 15); return std::numeric_limits<FloatType>::is_iec559
const int scale = 16 - count; && std::numeric_limits<FloatType>::digits == 53
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten_16()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false}); && std::numeric_limits<FloatType>::max_exponent == 1024
&& sizeof(FloatType) == sizeof(std::uint64_t);
} }
/// as write_short_decimal(), counting the digits (not 0, less than 10^15) template<typename FloatType>
JSON_HEDLEY_NON_NULL(1) struct is_binary64 : std::integral_constant<bool, has_binary64_format<FloatType>()> {};
JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_short_decimal(char* first, std::uint64_t digits, int exp) noexcept
{
JSON_ASSERT(digits != 0 && digits < 1000000000000000u);
// floor(log10(2^bits)) + 1 digits, or one less
const int log2_bound = ((64 - count_leading_zeros(digits)) * 1233) >> 12;
const int count = log2_bound + (digits >= powers_of_ten_16()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
return write_short_decimal(first, digits, count, exp);
}
/// a positive finite float (other than double): Grisu2 and format_buffer() /// a positive finite float (other than binary64): Grisu2 and format_buffer()
template<typename FloatType> template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value) char* write_positive_grisu2(char* first, const char* last, FloatType value)
{ {
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
static_cast<void>(last); // (only used in the assertion) static_cast<void>(last); // (only used in the assertion)
@@ -1480,7 +1376,7 @@ char* write_positive(char* first, const char* last, FloatType value)
// len is the length of the buffer, i.e., the number of decimal digits. // len is the length of the buffer, i.e., the number of decimal digits.
int len = 0; int len = 0;
int decimal_exponent = 0; int decimal_exponent = 0;
shortest_digits(first, len, decimal_exponent, value); grisu2(first, len, decimal_exponent, value);
JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10); JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);
@@ -1496,15 +1392,16 @@ char* write_positive(char* first, const char* last, FloatType value)
return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
} }
/// a positive finite double: the shortest digits (Zmij), laid out by /// a positive finite binary64 number: the shortest digits (Zmij), laid out by
/// write_shortest() (through a local buffer if [first, last) is shorter than /// write_shortest() (through a local buffer if [first, last) is shorter than
/// the 41 bytes it may write) /// the 41 bytes it may write)
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_positive(char* first, const char* last, double value) char* write_positive_zmij(char* first, const char* last, FloatType value)
{ {
static_assert(std::numeric_limits<double>::is_iec559 && std::numeric_limits<double>::digits == 53, static_assert(is_binary64<FloatType>::value,
"internal error: the conversion of Zmij needs IEEE 754 binary64 doubles"); "internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
std::uint64_t bits = 0; std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits)); std::memcpy(&bits, &value, sizeof(bits));
const zmij::shortest_decimal d = zmij::to_shortest(bits); const zmij::shortest_decimal d = zmij::to_shortest(bits);
@@ -1519,6 +1416,34 @@ inline char* write_positive(char* first, const char* last, double value)
return first + len; return first + len;
} }
/// a positive finite binary64 number: Zmij (as a long double has the format of
/// a double here, its bits are those of the double of the same value)
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value, std::true_type /*is_binary64*/)
{
return write_positive_zmij(first, last, value);
}
/// a positive finite float of any other format: Grisu2
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value, std::false_type /*is_binary64*/)
{
return write_positive_grisu2(first, last, value);
}
/// a positive finite float: Zmij for binary64 numbers, Grisu2 otherwise
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value)
{
return write_positive(first, last, value, is_binary64<FloatType> {});
}
} // namespace dtoa_impl } // namespace dtoa_impl
/*! /*!
@@ -36,13 +36,6 @@ computed from the compressed tables of Zmij beyond it.
namespace zmij namespace zmij
{ {
/// significand * 10^exponent
struct decimal
{
std::uint64_t significand;
int exponent;
};
/// the compressed powers of ten of Zmij /// the compressed powers of ten of Zmij
inline const std::array<std::uint64_t, 28>& pow10_minor() noexcept inline const std::array<std::uint64_t, 28>& pow10_minor() noexcept
{ {
@@ -221,18 +214,6 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
return shortest_decimal{integral, dec_exp, static_cast<unsigned char>(digit), !round_up && !round_down}; return shortest_decimal{integral, dec_exp, static_cast<unsigned char>(digit), !round_up && !round_down};
} }
/// The shortest decimal in the rounding interval of a positive finite double
/// given by its bits, as one number. The significand can end in zeros.
inline decimal to_decimal(std::uint64_t bits) noexcept
{
const shortest_decimal d = to_shortest(bits);
if (d.has_digit)
{
return decimal{(d.integral * 10) + d.digit, d.exponent};
}
return decimal{d.integral, d.exponent + 1};
}
} // namespace zmij } // namespace zmij
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+16 -3
View File
@@ -9,7 +9,7 @@
#pragma once #pragma once
#include <algorithm> // find, find_if, max #include <algorithm> // find, find_if, max, min
#include <array> // array #include <array> // array
#include <cstddef> // size_t, ptrdiff_t #include <cstddef> // size_t, ptrdiff_t
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t #include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
@@ -195,8 +195,18 @@ class builder
const std::uint64_t done = static_cast<std::uint64_t>(at - b) + 1; const std::uint64_t done = static_cast<std::uint64_t>(at - b) + 1;
const std::uint64_t guess = static_cast<std::uint64_t>(n) * static_cast<std::uint64_t>(e - b + 1) / done; const std::uint64_t guess = static_cast<std::uint64_t>(n) * static_cast<std::uint64_t>(e - b + 1) / done;
const std::uint64_t grown = guess + (guess / 4) + 64; // a variable: GCC calls a cast of the sum useless where std::uint64_t is std::size_t const std::uint64_t grown = guess + (guess / 4) + 64; // a variable: GCC calls a cast of the sum useless where std::uint64_t is std::size_t
// (n is below 2^32: the input is smaller than 4 GiB; the sum cannot wrap)
const std::uint64_t wanted = (std::max)(grown, static_cast<std::uint64_t>(n) + (n / 2) + 64);
const std::uint64_t limit = document_data::max_nodes();
doc.tape_size = n; doc.tape_size = n;
doc.reserve((std::max)(static_cast<std::size_t>(grown), n + (n / 2) + 64)); // LCOV_EXCL_START (a node array that fills the address space)
if (NLOHMANN_VIEW_UNLIKELY(n >= limit))
{
document_data::throw_bad_alloc(); // no room for another node
}
// LCOV_EXCL_STOP
// (a count beyond the limit is cut: the index does not grow beyond what can be addressed)
doc.reserve(static_cast<std::size_t>((std::min)(wanted, limit)));
return doc.tape; return doc.tape;
} }
@@ -801,7 +811,10 @@ indent_done:
n->flags = flags; n->flags = flags;
n->extra = extra; n->extra = extra;
n->off = static_cast<std::uint32_t>(off); n->off = static_cast<std::uint32_t>(off);
set_integer_bits(*n, second); // len is the low half of the second word, next the high half
// (not a native word over both, which swaps them on big-endian)
n->len = static_cast<std::uint32_t>(second);
n->next = static_cast<std::uint32_t>(second >> 32);
#endif #endif
return n; return n;
} }
+20 -2
View File
@@ -11,7 +11,8 @@
#include <array> // array #include <array> // array
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstring> // memcpy #include <cstring> // memcpy
#include <new> // operator new, placement new #include <limits> // numeric_limits
#include <new> // bad_alloc, operator new, placement new
#include <string> // string #include <string> // string
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -84,13 +85,30 @@ struct document_data
tape_cap = inline_cap; tape_cap = inline_cap;
} }
/// make room for n nodes; keeps the first tape_size nodes /// the largest node count whose size in bytes fits a std::size_t
static constexpr std::size_t max_nodes() noexcept
{
return (std::numeric_limits<std::size_t>::max)() / sizeof(node);
}
[[noreturn]] NLOHMANN_VIEW_NOINLINE static void throw_bad_alloc()
{
NLOHMANN_VIEW_THROW(std::bad_alloc());
}
/// make room for n nodes; keeps the first tape_size nodes (throws
/// std::bad_alloc for a count that does not fit the address space,
/// instead of wrapping around in n * sizeof(node))
void reserve(std::size_t n) void reserve(std::size_t n)
{ {
if (n <= tape_cap) if (n <= tape_cap)
{ {
return; return;
} }
if (NLOHMANN_VIEW_UNLIKELY(n > max_nodes()))
{
throw_bad_alloc();
}
node* fresh = static_cast<node*>(::operator new (n * sizeof(node))); node* fresh = static_cast<node*>(::operator new (n * sizeof(node)));
if (tape_size != 0) if (tape_size != 0)
{ {
+2 -3
View File
@@ -61,9 +61,8 @@ template<typename BasicJsonType>
{ {
if (f.code == error_code::input_too_large) if (f.code == error_code::input_too_large)
{ {
// LCOV_EXCL_START (4 GiB) // (the limit is detail::view::max_input_size: 4 GiB minus 16 bytes)
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4 GiB or more is not supported by json_document", nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
// LCOV_EXCL_STOP
} }
const BasicJsonType accepted = BasicJsonType::parse(src, src + size, nullptr, true, ignore_comments, ignore_trailing_commas); const BasicJsonType accepted = BasicJsonType::parse(src, src + size, nullptr, true, ignore_comments, ignore_trailing_commas);
// LCOV_EXCL_START (only if parse() accepts what the view rejects: a bug) // LCOV_EXCL_START (only if parse() accepts what the view rejects: a bug)
+12 -5
View File
@@ -9,7 +9,7 @@
#pragma once #pragma once
#include <string> // basic_string, char_traits, string #include <string> // basic_string, char_traits, string
#include <type_traits> // decay, integral_constant, is_array, is_lvalue_reference, is_pointer, is_same, remove_reference #include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_reference
#include <utility> // forward #include <utility> // forward
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -28,12 +28,12 @@ namespace view
/// how a document takes its input /// how a document takes its input
enum class input_kind enum class input_kind
{ {
move_string, ///< rvalue std::string: owned without a copy move_string, ///< non-const rvalue std::string: owned without a copy
c_string, ///< const char* (NUL-terminated): borrowed c_string, ///< const char* (NUL-terminated): borrowed
char_array, ///< char array (e.g. a string literal): borrowed char_array, ///< char array (e.g. a string literal): borrowed
borrow_range, ///< lvalue contiguous byte container, or std::string_view: borrowed borrow_range, ///< lvalue contiguous byte container, or std::string_view: borrowed
copy_range, ///< rvalue contiguous byte container: copied copy_range, ///< rvalue contiguous byte container (a const rvalue std::string too): copied
adapter, ///< anything else parse() accepts (streams, wide strings, ...): read into a buffer adapter, ///< streams, wide strings, and the rest of what the library's input adapter reads: read into a buffer
}; };
template<typename InputType> template<typename InputType>
@@ -52,13 +52,20 @@ struct classify_input
static constexpr input_kind value = static constexpr input_kind value =
std::is_array<R>::value ? input_kind::char_array std::is_array<R>::value ? input_kind::char_array
: std::is_pointer<D>::value ? input_kind::c_string : std::is_pointer<D>::value ? input_kind::c_string
: (is_rvalue && std::is_same<D, std::string>::value) ? input_kind::move_string : (is_rvalue && !std::is_const<R>::value && std::is_same<D, std::string>::value) ? input_kind::move_string
: (is_bytes && (!is_rvalue || is_string_view)) ? input_kind::borrow_range : (is_bytes && (!is_rvalue || is_string_view)) ? input_kind::borrow_range
: is_bytes ? input_kind::copy_range : is_bytes ? input_kind::copy_range
: input_kind::adapter; : input_kind::adapter;
// NOLINTEND(readability-avoid-nested-conditional-operator) // NOLINTEND(readability-avoid-nested-conditional-operator)
}; };
/// an integer type other than bool: a length passed where a flag is expected
template<typename T>
struct is_integer_not_bool : std::is_integral<T> {};
template<>
struct is_integer_not_bool<bool> : std::false_type {};
/// std::basic_string guarantees a NUL at data()[size()] (the parser's sentinel) /// std::basic_string guarantees a NUL at data()[size()] (the parser's sentinel)
template<typename T> template<typename T>
struct is_std_string : std::false_type {}; struct is_std_string : std::false_type {};
+28 -6
View File
@@ -11,6 +11,8 @@
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint16_t, uint32_t, uint64_t #include <cstdint> // uint16_t, uint32_t, uint64_t
#include <cstring> // memcmp, memcpy #include <cstring> // memcmp, memcpy
#include <limits> // numeric_limits
#include <type_traits> // integral_constant, is_integral, is_same
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp> #include <nlohmann/detail/view/document_data.hpp>
@@ -81,12 +83,14 @@ class short_key
std::uint64_t m_b = 0; std::uint64_t m_b = 0;
}; };
/// the key node of the first member of an object with the given key, or /// the key node of the last member of an object with the given key, or
/// nullptr; most keys are rejected by their length, from the index alone /// nullptr (the last one, as materialize() and parse() keep it); most keys are
/// rejected by their length, from the index alone
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
{ {
const node* const end = document_data::child_end(object); const node* const end = document_data::child_end(object);
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const node* last = nullptr;
if (NLOHMANN_VIEW_LIKELY(n <= 16)) if (NLOHMANN_VIEW_LIKELY(n <= 16))
{ {
const short_key probe(k, n); const short_key probe(k, n);
@@ -94,19 +98,37 @@ inline const node* find_member(const document_data& d, const node* object, const
{ {
if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{ {
return m; last = m;
} }
} }
return nullptr; return last;
} }
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
{ {
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
{ {
return m; last = m;
} }
} }
return nullptr; return last;
}
/// whether an integer type is accepted as an array index by the view's
/// operator[] and at(): every integer type but bool and size_t, which has its
/// own overload
template<typename T>
struct is_index_type : std::integral_constant < bool,
std::is_integral<T>::value && !std::is_same<T, bool>::value && !std::is_same<T, std::size_t>::value >
{};
/// an integer as an index: negative values, and values that do not fit a
/// size_t, map to the largest size_t (out of range for every array)
template<typename SizeType, typename IntegerType>
SizeType to_index(IntegerType idx) noexcept
{
const IntegerType zero = 0;
const auto result = static_cast<SizeType>(idx);
return (idx < zero || static_cast<IntegerType>(result) != idx) ? (std::numeric_limits<SizeType>::max)() : result;
} }
/// the element of an array at an index below its size /// the element of an array at an index below its size
+16 -1
View File
@@ -29,6 +29,11 @@ static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::
&& static_cast<std::uint8_t>(value_t::number_unsigned) == 6 && static_cast<std::uint8_t>(value_t::number_float) == 7, && static_cast<std::uint8_t>(value_t::number_unsigned) == 6 && static_cast<std::uint8_t>(value_t::number_float) == 7,
"the node format depends on the numbering of value_t"); "the node format depends on the numbering of value_t");
/// The largest input a document accepts, in bytes. Offsets and node counts are
/// 32 bits wide; the limit keeps 16 bytes (the width of the scanner's steps)
/// below 2^32, so that a position one step past the end of the text fits.
static constexpr std::size_t max_input_size = 0xFFFFFFEFu;
/// node flags /// node flags
struct node_flags struct node_flags
{ {
@@ -57,17 +62,27 @@ NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept
return static_cast<unsigned>(n.kind) - 1u <= 1u; return static_cast<unsigned>(n.kind) - 1u <= 1u;
} }
/// the converted value of an integer node (stored in len/next) /// the converted value of an integer node: len is its low half, next its high
/// half (on little-endian targets the two words are the value in memory)
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept
{ {
#if NLOHMANN_VIEW_LITTLE_ENDIAN
std::uint64_t v = 0; std::uint64_t v = 0;
std::memcpy(&v, reinterpret_cast<const unsigned char*>(&n) + 8, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) std::memcpy(&v, reinterpret_cast<const unsigned char*>(&n) + 8, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
return v; return v;
#else
return static_cast<std::uint64_t>(n.len) | (static_cast<std::uint64_t>(n.next) << 32);
#endif
} }
NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noexcept NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noexcept
{ {
#if NLOHMANN_VIEW_LITTLE_ENDIAN
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, &v, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, &v, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
#else
n.len = static_cast<std::uint32_t>(v);
n.next = static_cast<std::uint32_t>(v >> 32);
#endif
} }
/// token length of a number node /// token length of a number node
+66 -29
View File
@@ -16,7 +16,7 @@
* the read-only part of the basic_json interface; materialize() turns a * * the read-only part of the basic_json interface; materialize() turns a *
* subtree into the basic_json value that parse() would produce. * * subtree into the basic_json value that parse() would produce. *
* * * *
* The source text must outlive a document that borrows it (lvalue byte * * The source text must outlive a document that borrows it (lvalue byte *
* containers, C strings); rvalue strings, streams, and other inputs are * * containers, C strings); rvalue strings, streams, and other inputs are *
* owned by the document. * * owned by the document. *
\****************************************************************************/ \****************************************************************************/
@@ -175,12 +175,6 @@ class basic_json_view
return type() == value_t::discarded; return type() == value_t::discarded;
} }
/// false for discarded views
explicit operator bool() const noexcept
{
return m_node != nullptr;
}
/// the name of the type, as basic_json::type_name() /// the name of the type, as basic_json::type_name()
const char* type_name() const noexcept const char* type_name() const noexcept
{ {
@@ -240,13 +234,18 @@ class basic_json_view
// element access // // element access //
//////////////////// ////////////////////
/// the value of the member with this key (the first one, should the key /// the value of the member with this key (the last one, should the key
/// occur more than once); a discarded view if there is none. Throws /// occur more than once); a discarded view if there is none, or if this
/// type_error.305 if this is not an object. /// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
/// if this is any other value but an object.
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
if (is_discarded())
{
return basic_json_view();
}
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
return lookup(key); return lookup(key);
@@ -263,31 +262,43 @@ class basic_json_view
} }
/// the element at this index; a discarded view if the index is out of /// the element at this index; a discarded view if the index is out of
/// range. Throws type_error.305 if this is not an array. /// range, or if this is a discarded view. Throws type_error.305 if this is
/// any other value but an array.
basic_json_view operator[](size_type idx) const basic_json_view operator[](size_type idx) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_array())) if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{ {
if (is_discarded())
{
return basic_json_view();
}
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view();
} }
/// (an int argument would be ambiguous between size_type and const char*) /// any other integer type (int, unsigned, long, std::int64_t, ...; a
basic_json_view operator[](int idx) const /// single overload for size_type alone would be ambiguous for all of them
/// and for const char*); negative values are out of range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view operator[](IntegerType idx) const
{ {
return operator[](static_cast<size_type>(idx)); return operator[](detail::view::to_index<size_type>(idx));
} }
/// the value a JSON pointer refers to; a discarded view if a key is /// the value a JSON pointer refers to; a discarded view if a key is
/// missing or an index is out of range. Other errors throw what const /// missing or an index is out of range, or if this is a discarded view.
/// basic_json::operator[] throws. /// Other errors throw what const basic_json::operator[] throws.
basic_json_view operator[](const json_pointer& ptr) const basic_json_view operator[](const json_pointer& ptr) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
{
return basic_json_view();
}
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked); return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
} }
/// the value of the member with this key (the first one, should the key /// the value of the member with this key (the last one, should the key
/// occur more than once). Throws type_error.304 if this is not an object, /// occur more than once). Throws type_error.304 if this is not an object,
/// and out_of_range.403 if there is no such member. /// and out_of_range.403 if there is no such member.
basic_json_view at(string_view_t key) const basic_json_view at(string_view_t key) const
@@ -297,7 +308,7 @@ class basic_json_view
detail::view::throw_type_error(304, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(!r)) if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{ {
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found")); detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
} }
@@ -329,9 +340,12 @@ class basic_json_view
return basic_json_view(m_doc, detail::view::element_at(m_node, idx)); return basic_json_view(m_doc, detail::view::element_at(m_node, idx));
} }
basic_json_view at(int idx) const /// any other integer type, see operator[]; negative values are out of
/// range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view at(IntegerType idx) const
{ {
return at(static_cast<size_type>(idx)); return at(detail::view::to_index<size_type>(idx));
} }
/// the value a JSON pointer refers to; throws what basic_json::at() /// the value a JSON pointer refers to; throws what basic_json::at()
@@ -342,7 +356,7 @@ class basic_json_view
} }
/// the member with this key converted to T, or the default value if there /// the member with this key converted to T, or the default value if there
/// is no such member (the first one, should the key occur more than /// is no such member (the last one, should the key occur more than
/// once). Throws type_error.306 if this is not an object. /// once). Throws type_error.306 if this is not an object.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 > template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
T value(string_view_t key, const T& default_value) const T value(string_view_t key, const T& default_value) const
@@ -352,7 +366,7 @@ class basic_json_view
detail::view::throw_type_error(306, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
return r ? r.template get<T>() : default_value; return r.is_discarded() ? default_value : r.template get<T>();
} }
string_t value(string_view_t key, const char* default_value) const string_t value(string_view_t key, const char* default_value) const
@@ -371,7 +385,7 @@ class basic_json_view
detail::view::throw_type_error(306, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "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); const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r ? r.template get<T>() : default_value; return r.is_discarded() ? default_value : r.template get<T>();
} }
string_t value(const json_pointer& ptr, const char* default_value) const string_t value(const json_pointer& ptr, const char* default_value) const
@@ -407,7 +421,7 @@ class basic_json_view
// lookup // // lookup //
//////////// ////////////
/// an iterator to the member with this key (the first one, should the /// an iterator to the member with this key (the last one, should the
/// key occur more than once), or end(); end() also for non-objects /// key occur more than once), or end(); end() also for non-objects
iterator find(string_view_t key) const iterator find(string_view_t key) const
{ {
@@ -449,7 +463,7 @@ class basic_json_view
/// basic_json::contains()) /// basic_json::contains())
bool contains(const json_pointer& ptr) const bool contains(const json_pointer& ptr) const
{ {
return static_cast<bool>(detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains)); return !detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains).is_discarded();
} }
/// 1 if this is an object with a member with this key, else 0 (duplicate /// 1 if this is an object with a member with this key, else 0 (duplicate
@@ -712,7 +726,7 @@ class basic_json_view
return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16); return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16);
} }
/// the value of the first member with this key, or a discarded view /// the value of the last member with this key, or a discarded view
/// (object required) /// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{ {
@@ -846,6 +860,13 @@ class basic_json_document
return d; return d;
} }
/// parse(ptr, len) does not compile: len would convert to allow_exceptions
/// and ptr be read as a C string (as for the overloads of parse_copy,
/// accept, and read below)
template<typename InputType, typename IntegerType, typename... Flags>
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
parse(InputType&& input, IntegerType value, Flags&&... flags) = delete;
/// parse [first, last) /// parse [first, last)
template<typename IteratorType, typename std::enable_if< template<typename IteratorType, typename std::enable_if<
std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
@@ -873,6 +894,10 @@ class basic_json_document
return d; return d;
} }
template<typename InputType, typename IntegerType, typename... Flags>
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
parse_copy(InputType&& input, IntegerType value, Flags&&... flags) = delete;
/// check whether the input is valid JSON (the result of basic_json::accept) /// check whether the input is valid JSON (the result of basic_json::accept)
template<typename InputType> template<typename InputType>
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
@@ -882,6 +907,10 @@ class basic_json_document
return !d.is_discarded(); return !d.is_discarded();
} }
template<typename InputType, typename IntegerType, typename... Flags>
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, bool>::type
accept(InputType&& input, IntegerType value, Flags&&... flags) = delete;
/// parse into this document, reusing its memory /// parse into this document, reusing its memory
template<typename InputType> template<typename InputType>
// flawfinder: ignore (a member function, not POSIX read()) // flawfinder: ignore (a member function, not POSIX read())
@@ -894,12 +923,17 @@ class basic_json_document
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {}); std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
} }
template<typename InputType, typename IntegerType, typename... Flags>
// flawfinder: ignore (a member function, not POSIX read())
typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, void>::type
read(InputType&& input, IntegerType value, Flags&&... flags) = delete;
//////////// ////////////
// access // // access //
//////////// ////////////
/// the root value (discarded if parsing failed without exceptions) /// the root value (discarded if parsing failed without exceptions)
view_type root() const noexcept view_type root() const& noexcept
{ {
if (!m_data || m_data->discarded) if (!m_data || m_data->discarded)
{ {
@@ -908,6 +942,9 @@ class basic_json_document
return view_type(m_data.get(), m_data->tape); return view_type(m_data.get(), m_data->tape);
} }
/// deleted: the view of a temporary document would dangle
view_type root() const&& = delete;
bool is_discarded() const noexcept bool is_discarded() const noexcept
{ {
return !m_data || m_data->discarded; return !m_data || m_data->discarded;
@@ -1013,9 +1050,9 @@ class basic_json_document
d.discarded = true; d.discarded = true;
detail::view::parse_failure failure; detail::view::parse_failure failure;
bool ok = false; bool ok = false;
if (NLOHMANN_VIEW_UNLIKELY(size >= 0xFFFFFFF0u)) if (NLOHMANN_VIEW_UNLIKELY(size > detail::view::max_input_size))
{ {
failure.code = detail::view::error_code::input_too_large; // LCOV_EXCL_LINE (4 GiB) failure.code = detail::view::error_code::input_too_large;
} }
else else
{ {
+72 -152
View File
@@ -7544,10 +7544,11 @@ namespace detail
{ {
/*! /*!
@brief the configuration macros that change the library's behavior @brief the configuration macros that json_view.hpp reads
json.hpp undefines these macros at its end (see macro_unscope.hpp), so code json.hpp undefines these macros at its end (see macro_unscope.hpp), so code
that builds on the library after it (json_view.hpp) reads them here. Like the that builds on the library after it (json_view.hpp) reads them here. A macro
is added when the view starts to depend on it. Like the
macros, they are part of the ABI namespace, so they always match the macros, they are part of the ABI namespace, so they always match the
basic_json they are used with. basic_json they are used with.
*/ */
@@ -7555,8 +7556,6 @@ struct abi_config
{ {
/// JSON_STRICT_NUL_HANDLING: a null byte is an error, not the end of input /// JSON_STRICT_NUL_HANDLING: a null byte is an error, not the end of input
static constexpr bool strict_nul_handling = JSON_STRICT_NUL_HANDLING != 0; static constexpr bool strict_nul_handling = JSON_STRICT_NUL_HANDLING != 0;
/// JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
static constexpr bool legacy_discarded_value_comparison = JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON != 0;
}; };
} // namespace detail } // namespace detail
@@ -8863,8 +8862,9 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstdint> // uint64_t #include <cstdint> // uint64_t
#if !defined(__SIZEOF_INT128__) && defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) #include <cstring> // memcpy
#include <intrin0.h> // __umulh, _umul128 #if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) && (!defined(__SIZEOF_INT128__) || (!defined(__GNUC__) && !defined(__clang__)))
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
#endif #endif
// #include <nlohmann/detail/macro_scope.hpp> // #include <nlohmann/detail/macro_scope.hpp>
@@ -8883,6 +8883,10 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
{ {
#if defined(__GNUC__) || defined(__clang__) #if defined(__GNUC__) || defined(__clang__)
return __builtin_clzll(x); return __builtin_clzll(x);
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
unsigned long index = 0;
_BitScanReverse64(&index, x);
return 63 - static_cast<int>(index);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) for (int shift = 32; shift != 0; shift >>= 1)
@@ -8902,6 +8906,10 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
{ {
#if defined(__GNUC__) || defined(__clang__) #if defined(__GNUC__) || defined(__clang__)
return __builtin_ctzll(x); return __builtin_ctzll(x);
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64))
unsigned long index = 0;
_BitScanForward64(&index, x);
return static_cast<int>(index);
#else #else
int n = 0; int n = 0;
for (int shift = 32; shift != 0; shift >>= 1) for (int shift = 32; shift != 0; shift >>= 1)
@@ -8949,15 +8957,21 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
#endif #endif
} }
/// eight bytes as a little-endian word (compilers fold this into one load on /// eight bytes as a little-endian word (a single load on little-endian
/// little-endian targets; always inlined, as GCC otherwise calls it in the /// targets; always inlined, as GCC otherwise calls it in the number loops)
/// number loops)
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
{ {
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
// the byte order already matches (all MSVC targets are little-endian)
std::uint64_t result = 0;
std::memcpy(&result, b, sizeof(result));
return result;
#else
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u) return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u) | (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
| (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u) | (static_cast<std::uint64_t>(b[4]) << 32u) | (static_cast<std::uint64_t>(b[5]) << 40u)
| (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u); | (static_cast<std::uint64_t>(b[6]) << 48u) | (static_cast<std::uint64_t>(b[7]) << 56u);
#endif
} }
/// eight bytes as a little-endian word /// eight bytes as a little-endian word
@@ -25172,6 +25186,7 @@ NLOHMANN_JSON_NAMESPACE_END
// |_____|_____|_____|_|___| https://github.com/nlohmann/json // |_____|_____|_____|_|___| https://github.com/nlohmann/json
// //
// SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/> // SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/>
// SPDX-FileCopyrightText: 2025 Victor Zverovich <https://github.com/vitaut/zmij>
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -25247,13 +25262,6 @@ computed from the compressed tables of Zmij beyond it.
namespace zmij namespace zmij
{ {
/// significand * 10^exponent
struct decimal
{
std::uint64_t significand;
int exponent;
};
/// the compressed powers of ten of Zmij /// the compressed powers of ten of Zmij
inline const std::array<std::uint64_t, 28>& pow10_minor() noexcept inline const std::array<std::uint64_t, 28>& pow10_minor() noexcept
{ {
@@ -25432,18 +25440,6 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
return shortest_decimal{integral, dec_exp, static_cast<unsigned char>(digit), !round_up && !round_down}; return shortest_decimal{integral, dec_exp, static_cast<unsigned char>(digit), !round_up && !round_down};
} }
/// The shortest decimal in the rounding interval of a positive finite double
/// given by its bits, as one number. The significand can end in zeros.
inline decimal to_decimal(std::uint64_t bits) noexcept
{
const shortest_decimal d = to_shortest(bits);
if (d.has_digit)
{
return decimal{(d.integral * 10) + d.digit, d.exponent};
}
return decimal{d.integral, d.exponent + 1};
}
} // namespace zmij } // namespace zmij
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
@@ -26351,88 +26347,6 @@ void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
} }
/*!
@brief the shortest digits of a positive finite float (other than double): Grisu2
*/
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1)
void shortest_digits(char* buf, int& len, int& decimal_exponent, FloatType value)
{
grisu2(buf, len, decimal_exponent, value);
}
/*!
@brief the shortest digits of a positive finite double: the conversion of
Zmij (see zmij.hpp), which always finds the shortest digits that read back as
the same value (Grisu2 does not for about one double in a thousand), and the
closest of them if there are several
v = buf * 10^decimal_exponent, as for grisu2()
*/
JSON_HEDLEY_NON_NULL(1)
inline void shortest_digits(char* buf, int& len, int& decimal_exponent, double value)
{
static_assert(std::numeric_limits<double>::is_iec559 && std::numeric_limits<double>::digits == 53,
"internal error: the conversion of Zmij needs IEEE 754 binary64 doubles");
JSON_ASSERT(std::isfinite(value));
JSON_ASSERT(value > 0);
std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
zmij::decimal d = zmij::to_decimal(bits);
// without trailing zeros (up to 16): 8, 4, 2, 1 at a time
while (d.significand % 100000000 == 0)
{
d.significand /= 100000000;
d.exponent += 8;
}
if (d.significand % 10000 == 0)
{
d.significand /= 10000;
d.exponent += 4;
}
if (d.significand % 100 == 0)
{
d.significand /= 100;
d.exponent += 2;
}
if (d.significand % 10 == 0)
{
d.significand /= 10;
d.exponent += 1;
}
// at most 17 digits, written from the back two at a time
static constexpr const char* pairs =
"00010203040506070809101112131415161718192021222324252627282930313233343536373839"
"40414243444546474849505152535455565758596061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899";
std::array<char, 20> digits{};
std::size_t n = digits.size();
while (d.significand >= 100)
{
const std::uint64_t two_digits = d.significand % 100; // a variable: GCC calls a cast of the remainder useless where std::uint64_t is std::size_t
const auto i = static_cast<std::size_t>(two_digits) * 2;
d.significand /= 100;
n -= 2;
digits[n] = pairs[i];
digits[n + 1] = pairs[i + 1];
}
if (d.significand >= 10)
{
const auto i = static_cast<std::size_t>(d.significand) * 2;
n -= 2;
digits[n] = pairs[i];
digits[n + 1] = pairs[i + 1];
}
else
{
digits[--n] = static_cast<char>('0' + d.significand);
}
len = static_cast<int>(digits.size() - n);
std::memcpy(buf, digits.data() + n, static_cast<std::size_t>(len));
decimal_exponent = d.exponent;
}
/*! /*!
@brief appends a decimal representation of e to buf @brief appends a decimal representation of e to buf
@return a pointer to the element following the exponent. @return a pointer to the element following the exponent.
@@ -26835,53 +26749,30 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep
return end + (three ? 5 : 4); return end + (three ? 5 : 4);
} }
/// the powers of ten up to 10^16
inline const std::array<std::uint64_t, 17>& powers_of_ten_16() noexcept
{
static const std::array<std::uint64_t, 17> powers =
{
{
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u,
100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u, 10000000000000000u
}
};
return powers;
}
/*! /*!
@brief digits * 10^exp, as write_decimal() writes it, for the digits of a @brief whether FloatType is an IEEE 754 binary64 type (a double, or a long double
double that need no conversion (count digits, at most 15, the first not 0; that has the same format, as with MSVC and on Apple's Arm CPUs)
trailing zeros allowed): extended to 16 digits and written by write_shortest()
@return a pointer past the text; up to 41 bytes at @a first are written These are the types the conversion of Zmij (see zmij.hpp) is used for; all
(some beyond the returned end) others (binary32, or a format the library does not know) use Grisu2.
*/ */
JSON_HEDLEY_NON_NULL(1) template<typename FloatType>
JSON_HEDLEY_RETURNS_NON_NULL constexpr bool has_binary64_format() noexcept
inline char* write_short_decimal(char* first, std::uint64_t digits, int count, int exp) noexcept
{ {
JSON_ASSERT(digits >= powers_of_ten_16()[static_cast<std::size_t>(count - 1)] && count <= 15); return std::numeric_limits<FloatType>::is_iec559
const int scale = 16 - count; && std::numeric_limits<FloatType>::digits == 53
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten_16()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false}); && std::numeric_limits<FloatType>::max_exponent == 1024
&& sizeof(FloatType) == sizeof(std::uint64_t);
} }
/// as write_short_decimal(), counting the digits (not 0, less than 10^15) template<typename FloatType>
JSON_HEDLEY_NON_NULL(1) struct is_binary64 : std::integral_constant<bool, has_binary64_format<FloatType>()> {};
JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_short_decimal(char* first, std::uint64_t digits, int exp) noexcept
{
JSON_ASSERT(digits != 0 && digits < 1000000000000000u);
// floor(log10(2^bits)) + 1 digits, or one less
const int log2_bound = ((64 - count_leading_zeros(digits)) * 1233) >> 12;
const int count = log2_bound + (digits >= powers_of_ten_16()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
return write_short_decimal(first, digits, count, exp);
}
/// a positive finite float (other than double): Grisu2 and format_buffer() /// a positive finite float (other than binary64): Grisu2 and format_buffer()
template<typename FloatType> template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value) char* write_positive_grisu2(char* first, const char* last, FloatType value)
{ {
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
static_cast<void>(last); // (only used in the assertion) static_cast<void>(last); // (only used in the assertion)
@@ -26892,7 +26783,7 @@ char* write_positive(char* first, const char* last, FloatType value)
// len is the length of the buffer, i.e., the number of decimal digits. // len is the length of the buffer, i.e., the number of decimal digits.
int len = 0; int len = 0;
int decimal_exponent = 0; int decimal_exponent = 0;
shortest_digits(first, len, decimal_exponent, value); grisu2(first, len, decimal_exponent, value);
JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10); JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);
@@ -26908,15 +26799,16 @@ char* write_positive(char* first, const char* last, FloatType value)
return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
} }
/// a positive finite double: the shortest digits (Zmij), laid out by /// a positive finite binary64 number: the shortest digits (Zmij), laid out by
/// write_shortest() (through a local buffer if [first, last) is shorter than /// write_shortest() (through a local buffer if [first, last) is shorter than
/// the 41 bytes it may write) /// the 41 bytes it may write)
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2) JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_positive(char* first, const char* last, double value) char* write_positive_zmij(char* first, const char* last, FloatType value)
{ {
static_assert(std::numeric_limits<double>::is_iec559 && std::numeric_limits<double>::digits == 53, static_assert(is_binary64<FloatType>::value,
"internal error: the conversion of Zmij needs IEEE 754 binary64 doubles"); "internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
std::uint64_t bits = 0; std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits)); std::memcpy(&bits, &value, sizeof(bits));
const zmij::shortest_decimal d = zmij::to_shortest(bits); const zmij::shortest_decimal d = zmij::to_shortest(bits);
@@ -26931,6 +26823,34 @@ inline char* write_positive(char* first, const char* last, double value)
return first + len; return first + len;
} }
/// a positive finite binary64 number: Zmij (as a long double has the format of
/// a double here, its bits are those of the double of the same value)
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value, std::true_type /*is_binary64*/)
{
return write_positive_zmij(first, last, value);
}
/// a positive finite float of any other format: Grisu2
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value, std::false_type /*is_binary64*/)
{
return write_positive_grisu2(first, last, value);
}
/// a positive finite float: Zmij for binary64 numbers, Grisu2 otherwise
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value)
{
return write_positive(first, last, value, is_binary64<FloatType> {});
}
} // namespace dtoa_impl } // namespace dtoa_impl
/*! /*!
+160 -49
View File
@@ -16,7 +16,7 @@
* the read-only part of the basic_json interface; materialize() turns a * * the read-only part of the basic_json interface; materialize() turns a *
* subtree into the basic_json value that parse() would produce. * * subtree into the basic_json value that parse() would produce. *
* * * *
* The source text must outlive a document that borrows it (lvalue byte * * The source text must outlive a document that borrows it (lvalue byte *
* containers, C strings); rvalue strings, streams, and other inputs are * * containers, C strings); rvalue strings, streams, and other inputs are *
* owned by the document. * * owned by the document. *
\****************************************************************************/ \****************************************************************************/
@@ -59,7 +59,7 @@
#include <algorithm> // find, find_if, max #include <algorithm> // find, find_if, max, min
#include <array> // array #include <array> // array
#include <cstddef> // size_t, ptrdiff_t #include <cstddef> // size_t, ptrdiff_t
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t #include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
@@ -83,7 +83,8 @@
#include <array> // array #include <array> // array
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstring> // memcpy #include <cstring> // memcpy
#include <new> // operator new, placement new #include <limits> // numeric_limits
#include <new> // bad_alloc, operator new, placement new
#include <string> // string #include <string> // string
// #include <nlohmann/json.hpp> // #include <nlohmann/json.hpp>
@@ -193,6 +194,11 @@ static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::
&& static_cast<std::uint8_t>(value_t::number_unsigned) == 6 && static_cast<std::uint8_t>(value_t::number_float) == 7, && static_cast<std::uint8_t>(value_t::number_unsigned) == 6 && static_cast<std::uint8_t>(value_t::number_float) == 7,
"the node format depends on the numbering of value_t"); "the node format depends on the numbering of value_t");
/// The largest input a document accepts, in bytes. Offsets and node counts are
/// 32 bits wide; the limit keeps 16 bytes (the width of the scanner's steps)
/// below 2^32, so that a position one step past the end of the text fits.
static constexpr std::size_t max_input_size = 0xFFFFFFEFu;
/// node flags /// node flags
struct node_flags struct node_flags
{ {
@@ -221,17 +227,27 @@ NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept
return static_cast<unsigned>(n.kind) - 1u <= 1u; return static_cast<unsigned>(n.kind) - 1u <= 1u;
} }
/// the converted value of an integer node (stored in len/next) /// the converted value of an integer node: len is its low half, next its high
/// half (on little-endian targets the two words are the value in memory)
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept
{ {
#if NLOHMANN_VIEW_LITTLE_ENDIAN
std::uint64_t v = 0; std::uint64_t v = 0;
std::memcpy(&v, reinterpret_cast<const unsigned char*>(&n) + 8, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) std::memcpy(&v, reinterpret_cast<const unsigned char*>(&n) + 8, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
return v; return v;
#else
return static_cast<std::uint64_t>(n.len) | (static_cast<std::uint64_t>(n.next) << 32);
#endif
} }
NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noexcept NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noexcept
{ {
#if NLOHMANN_VIEW_LITTLE_ENDIAN
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, &v, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, &v, 8); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
#else
n.len = static_cast<std::uint32_t>(v);
n.next = static_cast<std::uint32_t>(v >> 32);
#endif
} }
/// token length of a number node /// token length of a number node
@@ -327,13 +343,30 @@ struct document_data
tape_cap = inline_cap; tape_cap = inline_cap;
} }
/// make room for n nodes; keeps the first tape_size nodes /// the largest node count whose size in bytes fits a std::size_t
static constexpr std::size_t max_nodes() noexcept
{
return (std::numeric_limits<std::size_t>::max)() / sizeof(node);
}
[[noreturn]] NLOHMANN_VIEW_NOINLINE static void throw_bad_alloc()
{
NLOHMANN_VIEW_THROW(std::bad_alloc());
}
/// make room for n nodes; keeps the first tape_size nodes (throws
/// std::bad_alloc for a count that does not fit the address space,
/// instead of wrapping around in n * sizeof(node))
void reserve(std::size_t n) void reserve(std::size_t n)
{ {
if (n <= tape_cap) if (n <= tape_cap)
{ {
return; return;
} }
if (NLOHMANN_VIEW_UNLIKELY(n > max_nodes()))
{
throw_bad_alloc();
}
node* fresh = static_cast<node*>(::operator new (n * sizeof(node))); node* fresh = static_cast<node*>(::operator new (n * sizeof(node)));
if (tape_size != 0) if (tape_size != 0)
{ {
@@ -740,8 +773,18 @@ class builder
const std::uint64_t done = static_cast<std::uint64_t>(at - b) + 1; const std::uint64_t done = static_cast<std::uint64_t>(at - b) + 1;
const std::uint64_t guess = static_cast<std::uint64_t>(n) * static_cast<std::uint64_t>(e - b + 1) / done; const std::uint64_t guess = static_cast<std::uint64_t>(n) * static_cast<std::uint64_t>(e - b + 1) / done;
const std::uint64_t grown = guess + (guess / 4) + 64; // a variable: GCC calls a cast of the sum useless where std::uint64_t is std::size_t const std::uint64_t grown = guess + (guess / 4) + 64; // a variable: GCC calls a cast of the sum useless where std::uint64_t is std::size_t
// (n is below 2^32: the input is smaller than 4 GiB; the sum cannot wrap)
const std::uint64_t wanted = (std::max)(grown, static_cast<std::uint64_t>(n) + (n / 2) + 64);
const std::uint64_t limit = document_data::max_nodes();
doc.tape_size = n; doc.tape_size = n;
doc.reserve((std::max)(static_cast<std::size_t>(grown), n + (n / 2) + 64)); // LCOV_EXCL_START (a node array that fills the address space)
if (NLOHMANN_VIEW_UNLIKELY(n >= limit))
{
document_data::throw_bad_alloc(); // no room for another node
}
// LCOV_EXCL_STOP
// (a count beyond the limit is cut: the index does not grow beyond what can be addressed)
doc.reserve(static_cast<std::size_t>((std::min)(wanted, limit)));
return doc.tape; return doc.tape;
} }
@@ -1346,7 +1389,10 @@ indent_done:
n->flags = flags; n->flags = flags;
n->extra = extra; n->extra = extra;
n->off = static_cast<std::uint32_t>(off); n->off = static_cast<std::uint32_t>(off);
set_integer_bits(*n, second); // len is the low half of the second word, next the high half
// (not a native word over both, which swaps them on big-endian)
n->len = static_cast<std::uint32_t>(second);
n->next = static_cast<std::uint32_t>(second >> 32);
#endif #endif
return n; return n;
} }
@@ -1967,9 +2013,8 @@ template<typename BasicJsonType>
{ {
if (f.code == error_code::input_too_large) if (f.code == error_code::input_too_large)
{ {
// LCOV_EXCL_START (4 GiB) // (the limit is detail::view::max_input_size: 4 GiB minus 16 bytes)
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4 GiB or more is not supported by json_document", nullptr)); NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
// LCOV_EXCL_STOP
} }
const BasicJsonType accepted = BasicJsonType::parse(src, src + size, nullptr, true, ignore_comments, ignore_trailing_commas); const BasicJsonType accepted = BasicJsonType::parse(src, src + size, nullptr, true, ignore_comments, ignore_trailing_commas);
// LCOV_EXCL_START (only if parse() accepts what the view rejects: a bug) // LCOV_EXCL_START (only if parse() accepts what the view rejects: a bug)
@@ -2008,7 +2053,7 @@ NLOHMANN_JSON_NAMESPACE_END
#include <string> // basic_string, char_traits, string #include <string> // basic_string, char_traits, string
#include <type_traits> // decay, integral_constant, is_array, is_lvalue_reference, is_pointer, is_same, remove_reference #include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_reference
#include <utility> // forward #include <utility> // forward
// #include <nlohmann/json.hpp> // #include <nlohmann/json.hpp>
@@ -2028,12 +2073,12 @@ namespace view
/// how a document takes its input /// how a document takes its input
enum class input_kind enum class input_kind
{ {
move_string, ///< rvalue std::string: owned without a copy move_string, ///< non-const rvalue std::string: owned without a copy
c_string, ///< const char* (NUL-terminated): borrowed c_string, ///< const char* (NUL-terminated): borrowed
char_array, ///< char array (e.g. a string literal): borrowed char_array, ///< char array (e.g. a string literal): borrowed
borrow_range, ///< lvalue contiguous byte container, or std::string_view: borrowed borrow_range, ///< lvalue contiguous byte container, or std::string_view: borrowed
copy_range, ///< rvalue contiguous byte container: copied copy_range, ///< rvalue contiguous byte container (a const rvalue std::string too): copied
adapter, ///< anything else parse() accepts (streams, wide strings, ...): read into a buffer adapter, ///< streams, wide strings, and the rest of what the library's input adapter reads: read into a buffer
}; };
template<typename InputType> template<typename InputType>
@@ -2052,13 +2097,20 @@ struct classify_input
static constexpr input_kind value = static constexpr input_kind value =
std::is_array<R>::value ? input_kind::char_array std::is_array<R>::value ? input_kind::char_array
: std::is_pointer<D>::value ? input_kind::c_string : std::is_pointer<D>::value ? input_kind::c_string
: (is_rvalue && std::is_same<D, std::string>::value) ? input_kind::move_string : (is_rvalue && !std::is_const<R>::value && std::is_same<D, std::string>::value) ? input_kind::move_string
: (is_bytes && (!is_rvalue || is_string_view)) ? input_kind::borrow_range : (is_bytes && (!is_rvalue || is_string_view)) ? input_kind::borrow_range
: is_bytes ? input_kind::copy_range : is_bytes ? input_kind::copy_range
: input_kind::adapter; : input_kind::adapter;
// NOLINTEND(readability-avoid-nested-conditional-operator) // NOLINTEND(readability-avoid-nested-conditional-operator)
}; };
/// an integer type other than bool: a length passed where a flag is expected
template<typename T>
struct is_integer_not_bool : std::is_integral<T> {};
template<>
struct is_integer_not_bool<bool> : std::false_type {};
/// std::basic_string guarantees a NUL at data()[size()] (the parser's sentinel) /// std::basic_string guarantees a NUL at data()[size()] (the parser's sentinel)
template<typename T> template<typename T>
struct is_std_string : std::false_type {}; struct is_std_string : std::false_type {};
@@ -2370,6 +2422,8 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint16_t, uint32_t, uint64_t #include <cstdint> // uint16_t, uint32_t, uint64_t
#include <cstring> // memcmp, memcpy #include <cstring> // memcmp, memcpy
#include <limits> // numeric_limits
#include <type_traits> // integral_constant, is_integral, is_same
// #include <nlohmann/json.hpp> // #include <nlohmann/json.hpp>
// #include <nlohmann/detail/view/document_data.hpp> // #include <nlohmann/detail/view/document_data.hpp>
@@ -2443,12 +2497,14 @@ class short_key
std::uint64_t m_b = 0; std::uint64_t m_b = 0;
}; };
/// the key node of the first member of an object with the given key, or /// the key node of the last member of an object with the given key, or
/// nullptr; most keys are rejected by their length, from the index alone /// nullptr (the last one, as materialize() and parse() keep it); most keys are
/// rejected by their length, from the index alone
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
{ {
const node* const end = document_data::child_end(object); const node* const end = document_data::child_end(object);
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const node* last = nullptr;
if (NLOHMANN_VIEW_LIKELY(n <= 16)) if (NLOHMANN_VIEW_LIKELY(n <= 16))
{ {
const short_key probe(k, n); const short_key probe(k, n);
@@ -2456,19 +2512,37 @@ inline const node* find_member(const document_data& d, const node* object, const
{ {
if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{ {
return m; last = m;
} }
} }
return nullptr; return last;
} }
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
{ {
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
{ {
return m; last = m;
} }
} }
return nullptr; return last;
}
/// whether an integer type is accepted as an array index by the view's
/// operator[] and at(): every integer type but bool and size_t, which has its
/// own overload
template<typename T>
struct is_index_type : std::integral_constant < bool,
std::is_integral<T>::value && !std::is_same<T, bool>::value && !std::is_same<T, std::size_t>::value >
{};
/// an integer as an index: negative values, and values that do not fit a
/// size_t, map to the largest size_t (out of range for every array)
template<typename SizeType, typename IntegerType>
SizeType to_index(IntegerType idx) noexcept
{
const IntegerType zero = 0;
const auto result = static_cast<SizeType>(idx);
return (idx < zero || static_cast<IntegerType>(result) != idx) ? (std::numeric_limits<SizeType>::max)() : result;
} }
/// the element of an array at an index below its size /// the element of an array at an index below its size
@@ -3757,12 +3831,6 @@ class basic_json_view
return type() == value_t::discarded; return type() == value_t::discarded;
} }
/// false for discarded views
explicit operator bool() const noexcept
{
return m_node != nullptr;
}
/// the name of the type, as basic_json::type_name() /// the name of the type, as basic_json::type_name()
const char* type_name() const noexcept const char* type_name() const noexcept
{ {
@@ -3822,13 +3890,18 @@ class basic_json_view
// element access // // element access //
//////////////////// ////////////////////
/// the value of the member with this key (the first one, should the key /// the value of the member with this key (the last one, should the key
/// occur more than once); a discarded view if there is none. Throws /// occur more than once); a discarded view if there is none, or if this
/// type_error.305 if this is not an object. /// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
/// if this is any other value but an object.
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
if (is_discarded())
{
return basic_json_view();
}
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
return lookup(key); return lookup(key);
@@ -3845,31 +3918,43 @@ class basic_json_view
} }
/// the element at this index; a discarded view if the index is out of /// the element at this index; a discarded view if the index is out of
/// range. Throws type_error.305 if this is not an array. /// range, or if this is a discarded view. Throws type_error.305 if this is
/// any other value but an array.
basic_json_view operator[](size_type idx) const basic_json_view operator[](size_type idx) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_array())) if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{ {
if (is_discarded())
{
return basic_json_view();
}
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view();
} }
/// (an int argument would be ambiguous between size_type and const char*) /// any other integer type (int, unsigned, long, std::int64_t, ...; a
basic_json_view operator[](int idx) const /// single overload for size_type alone would be ambiguous for all of them
/// and for const char*); negative values are out of range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view operator[](IntegerType idx) const
{ {
return operator[](static_cast<size_type>(idx)); return operator[](detail::view::to_index<size_type>(idx));
} }
/// the value a JSON pointer refers to; a discarded view if a key is /// the value a JSON pointer refers to; a discarded view if a key is
/// missing or an index is out of range. Other errors throw what const /// missing or an index is out of range, or if this is a discarded view.
/// basic_json::operator[] throws. /// Other errors throw what const basic_json::operator[] throws.
basic_json_view operator[](const json_pointer& ptr) const basic_json_view operator[](const json_pointer& ptr) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
{
return basic_json_view();
}
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked); return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
} }
/// the value of the member with this key (the first one, should the key /// the value of the member with this key (the last one, should the key
/// occur more than once). Throws type_error.304 if this is not an object, /// occur more than once). Throws type_error.304 if this is not an object,
/// and out_of_range.403 if there is no such member. /// and out_of_range.403 if there is no such member.
basic_json_view at(string_view_t key) const basic_json_view at(string_view_t key) const
@@ -3879,7 +3964,7 @@ class basic_json_view
detail::view::throw_type_error(304, "cannot use at() with ", type_name()); detail::view::throw_type_error(304, "cannot use at() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(!r)) if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{ {
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found")); detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
} }
@@ -3911,9 +3996,12 @@ class basic_json_view
return basic_json_view(m_doc, detail::view::element_at(m_node, idx)); return basic_json_view(m_doc, detail::view::element_at(m_node, idx));
} }
basic_json_view at(int idx) const /// any other integer type, see operator[]; negative values are out of
/// range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view at(IntegerType idx) const
{ {
return at(static_cast<size_type>(idx)); return at(detail::view::to_index<size_type>(idx));
} }
/// the value a JSON pointer refers to; throws what basic_json::at() /// the value a JSON pointer refers to; throws what basic_json::at()
@@ -3924,7 +4012,7 @@ class basic_json_view
} }
/// the member with this key converted to T, or the default value if there /// the member with this key converted to T, or the default value if there
/// is no such member (the first one, should the key occur more than /// is no such member (the last one, should the key occur more than
/// once). Throws type_error.306 if this is not an object. /// once). Throws type_error.306 if this is not an object.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 > template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
T value(string_view_t key, const T& default_value) const T value(string_view_t key, const T& default_value) const
@@ -3934,7 +4022,7 @@ class basic_json_view
detail::view::throw_type_error(306, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "cannot use value() with ", type_name());
} }
const basic_json_view r = lookup(key); const basic_json_view r = lookup(key);
return r ? r.template get<T>() : default_value; return r.is_discarded() ? default_value : r.template get<T>();
} }
string_t value(string_view_t key, const char* default_value) const string_t value(string_view_t key, const char* default_value) const
@@ -3953,7 +4041,7 @@ class basic_json_view
detail::view::throw_type_error(306, "cannot use value() with ", type_name()); detail::view::throw_type_error(306, "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); const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r ? r.template get<T>() : default_value; return r.is_discarded() ? default_value : r.template get<T>();
} }
string_t value(const json_pointer& ptr, const char* default_value) const string_t value(const json_pointer& ptr, const char* default_value) const
@@ -3989,7 +4077,7 @@ class basic_json_view
// lookup // // lookup //
//////////// ////////////
/// an iterator to the member with this key (the first one, should the /// an iterator to the member with this key (the last one, should the
/// key occur more than once), or end(); end() also for non-objects /// key occur more than once), or end(); end() also for non-objects
iterator find(string_view_t key) const iterator find(string_view_t key) const
{ {
@@ -4031,7 +4119,7 @@ class basic_json_view
/// basic_json::contains()) /// basic_json::contains())
bool contains(const json_pointer& ptr) const bool contains(const json_pointer& ptr) const
{ {
return static_cast<bool>(detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains)); return !detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains).is_discarded();
} }
/// 1 if this is an object with a member with this key, else 0 (duplicate /// 1 if this is an object with a member with this key, else 0 (duplicate
@@ -4294,7 +4382,7 @@ class basic_json_view
return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16); return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16);
} }
/// the value of the first member with this key, or a discarded view /// the value of the last member with this key, or a discarded view
/// (object required) /// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{ {
@@ -4428,6 +4516,13 @@ class basic_json_document
return d; return d;
} }
/// parse(ptr, len) does not compile: len would convert to allow_exceptions
/// and ptr be read as a C string (as for the overloads of parse_copy,
/// accept, and read below)
template<typename InputType, typename IntegerType, typename... Flags>
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
parse(InputType&& input, IntegerType value, Flags&&... flags) = delete;
/// parse [first, last) /// parse [first, last)
template<typename IteratorType, typename std::enable_if< template<typename IteratorType, typename std::enable_if<
std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
@@ -4455,6 +4550,10 @@ class basic_json_document
return d; return d;
} }
template<typename InputType, typename IntegerType, typename... Flags>
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
parse_copy(InputType&& input, IntegerType value, Flags&&... flags) = delete;
/// check whether the input is valid JSON (the result of basic_json::accept) /// check whether the input is valid JSON (the result of basic_json::accept)
template<typename InputType> template<typename InputType>
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false) static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
@@ -4464,6 +4563,10 @@ class basic_json_document
return !d.is_discarded(); return !d.is_discarded();
} }
template<typename InputType, typename IntegerType, typename... Flags>
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, bool>::type
accept(InputType&& input, IntegerType value, Flags&&... flags) = delete;
/// parse into this document, reusing its memory /// parse into this document, reusing its memory
template<typename InputType> template<typename InputType>
// flawfinder: ignore (a member function, not POSIX read()) // flawfinder: ignore (a member function, not POSIX read())
@@ -4476,12 +4579,17 @@ class basic_json_document
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {}); std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
} }
template<typename InputType, typename IntegerType, typename... Flags>
// flawfinder: ignore (a member function, not POSIX read())
typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, void>::type
read(InputType&& input, IntegerType value, Flags&&... flags) = delete;
//////////// ////////////
// access // // access //
//////////// ////////////
/// the root value (discarded if parsing failed without exceptions) /// the root value (discarded if parsing failed without exceptions)
view_type root() const noexcept view_type root() const& noexcept
{ {
if (!m_data || m_data->discarded) if (!m_data || m_data->discarded)
{ {
@@ -4490,6 +4598,9 @@ class basic_json_document
return view_type(m_data.get(), m_data->tape); return view_type(m_data.get(), m_data->tape);
} }
/// deleted: the view of a temporary document would dangle
view_type root() const&& = delete;
bool is_discarded() const noexcept bool is_discarded() const noexcept
{ {
return !m_data || m_data->discarded; return !m_data || m_data->discarded;
@@ -4595,9 +4706,9 @@ class basic_json_document
d.discarded = true; d.discarded = true;
detail::view::parse_failure failure; detail::view::parse_failure failure;
bool ok = false; bool ok = false;
if (NLOHMANN_VIEW_UNLIKELY(size >= 0xFFFFFFF0u)) if (NLOHMANN_VIEW_UNLIKELY(size > detail::view::max_input_size))
{ {
failure.code = detail::view::error_code::input_too_large; // LCOV_EXCL_LINE (4 GiB) failure.code = detail::view::error_code::input_too_large;
} }
else else
{ {
+4 -1
View File
@@ -6,7 +6,10 @@ Each parser of the library (JSON, BJData, BON8, BSON, CBOR, MessagePack, and UBJ
Additionally, `parse_json_view_fuzzer` (`tests/src/fuzzer-parse_json_view.cpp`) cross-checks `json_document`/`json_view` Additionally, `parse_json_view_fuzzer` (`tests/src/fuzzer-parse_json_view.cpp`) cross-checks `json_document`/`json_view`
(the zero-copy, read-only view declared in `json_view.hpp`) against `basic_json` on the same JSON text: it asserts that (the zero-copy, read-only view declared in `json_view.hpp`) against `basic_json` on the same JSON text: it asserts that
`json_document::accept` agrees with `json::accept`, that an accepted input materializes to the same value `json::parse` `json_document::accept` agrees with `json::accept`, that an accepted input materializes to the same value `json::parse`
produces, and that a rejected input makes both parsers throw with an identical `what()`. It takes plain JSON text, so it produces, and that a rejected input makes both parsers throw with an identical `what()`. It checks this for a
`std::string` input (borrowed, with a NUL after the last byte) and for an exact-size `std::vector<std::uint8_t>`
(borrowed, with nothing after the last byte), and for the `ignore_comments` and `ignore_trailing_commas` options, which
are taken from the low bits of the first input byte (the byte stays part of the text). It takes plain JSON text, so it
reuses the `corpus_json` corpus rather than a format of its own. reuses the `corpus_json` corpus rather than a format of its own.
## What the fuzzers check ## What the fuzzers check
+89 -34
View File
@@ -9,7 +9,9 @@
/* /*
This file implements a parser test suitable for fuzz testing. It checks that This file implements a parser test suitable for fuzz testing. It checks that
json_document (the zero-copy, read-only view of a parsed JSON text declared in json_document (the zero-copy, read-only view of a parsed JSON text declared in
json_view.hpp) agrees with basic_json on every input: json_view.hpp) agrees with basic_json on every input, for the parse options
selected by the low bits of the first input byte (bit 0: ignore_comments, bit 1:
ignore_trailing_commas; the byte stays part of the text):
- json_document::accept(data) must equal json::accept(data) - json_document::accept(data) must equal json::accept(data)
- if the input is accepted, json_document::parse(data).root().materialize() - if the input is accepted, json_document::parse(data).root().materialize()
@@ -18,12 +20,20 @@ json_view.hpp) agrees with basic_json on every input:
enabled) must throw a json::parse_error or json::out_of_range whose what() enabled) must throw a json::parse_error or json::out_of_range whose what()
is identical to the one json::parse(data) throws is identical to the one json::parse(data) throws
This is checked for two kinds of input: a std::string, which the document
borrows and which ends in the NUL the parser uses as sentinel, and an
exact-size byte vector, which has no NUL after its last byte and takes the
parser's bounds-checked path (AddressSanitizer reports any read past the end).
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers. drivers.
*/ */
#include <cassert> #include <cassert>
#include <cstddef>
#include <cstdint>
#include <string> #include <string>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <nlohmann/json_view.hpp> #include <nlohmann/json_view.hpp>
@@ -35,65 +45,110 @@ drivers.
using json = nlohmann::json; using json = nlohmann::json;
using json_document = nlohmann::json_document; using json_document = nlohmann::json_document;
// see http://llvm.org/docs/LibFuzzer.html namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{ {
// json_document::accept only has a single-argument overload; wrap the raw // what json::parse does with a text: the value, or the message of the exception
// bytes in a (borrowed) std::string so the same bytes can be handed to it struct reference_result
const std::string input(reinterpret_cast<const char*>(data), size); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) {
bool accepted = false;
json value{};
std::string what{}; // NOLINT(readability-redundant-member-init)
};
const bool accepted_by_json = json::accept(data, data + size); reference_result parse_reference(const std::uint8_t* data, std::size_t size, bool comments, bool trailing_commas)
const bool accepted_by_view = json_document::accept(input); {
reference_result r;
r.accepted = json::accept(data, data + size, comments, trailing_commas);
bool json_threw = false;
try
{
r.value = json::parse(data, data + size, nullptr, true, comments, trailing_commas);
}
catch (const json::parse_error& e)
{
r.what = e.what();
json_threw = true;
}
catch (const json::out_of_range& e)
{
r.what = e.what();
json_threw = true;
}
// json::accept and json::parse must agree
assert(json_threw == !r.accepted);
static_cast<void>(json_threw);
return r;
}
// json_document must agree with the reference for this input (a container
// that json_document::parse borrows)
template<typename Input>
void check_input(const Input& input, const reference_result& expected, bool comments, bool trailing_commas)
{
// json_document::accept must agree with json::accept on every input // json_document::accept must agree with json::accept on every input
assert(accepted_by_json == accepted_by_view); const bool accepted_by_view = json_document::accept(input, comments, trailing_commas);
assert(expected.accepted == accepted_by_view);
static_cast<void>(accepted_by_view);
if (accepted_by_json) if (expected.accepted)
{ {
// both parsers must agree on the resulting value // both parsers must agree on the resulting value
json const j1 = json::parse(data, data + size); json_document const doc = json_document::parse(input, true, comments, trailing_commas);
json_document const doc = json_document::parse(input); assert(!doc.is_discarded());
json const j2 = doc.root().materialize(); json const j2 = doc.root().materialize();
assert(j1 == j2); assert(expected.value == j2);
static_cast<void>(j2);
// (without exceptions, the same document)
json_document const quiet = json_document::parse(input, false, comments, trailing_commas);
assert(!quiet.is_discarded());
assert(quiet.node_count() == doc.node_count());
} }
else else
{ {
// both parsers must reject the input the same way when exceptions are used // both parsers must reject the input the same way when exceptions are used
std::string expected_what;
bool json_threw = false;
try
{
static_cast<void>(json::parse(data, data + size));
}
catch (const json::parse_error& e)
{
expected_what = e.what();
json_threw = true;
}
catch (const json::out_of_range& e)
{
expected_what = e.what();
json_threw = true;
}
assert(json_threw);
bool view_threw = false; bool view_threw = false;
try try
{ {
static_cast<void>(json_document::parse(input)); static_cast<void>(json_document::parse(input, true, comments, trailing_commas));
} }
catch (const json::parse_error& e) catch (const json::parse_error& e)
{ {
assert(e.what() == expected_what); assert(e.what() == expected.what);
view_threw = true; view_threw = true;
} }
catch (const json::out_of_range& e) catch (const json::out_of_range& e)
{ {
assert(e.what() == expected_what); assert(e.what() == expected.what);
view_threw = true; view_threw = true;
} }
assert(view_threw); assert(view_threw);
static_cast<void>(view_threw);
// and without exceptions, the document is discarded
json_document const quiet = json_document::parse(input, false, comments, trailing_commas);
assert(quiet.is_discarded());
static_cast<void>(quiet);
} }
}
} // namespace
// see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
// the parse options are taken from the low bits of the first byte
const bool comments = size > 0 && (data[0] & 1U) != 0;
const bool trailing_commas = size > 0 && (data[0] & 2U) != 0;
const reference_result expected = parse_reference(data, size, comments, trailing_commas);
// a std::string: borrowed, with the NUL of std::string as sentinel
const std::string input(reinterpret_cast<const char*>(data), size); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
check_input(input, expected, comments, trailing_commas);
// an exact-size byte vector: borrowed, with nothing after its last byte
const std::vector<std::uint8_t> exact(data, data + size);
check_input(exact, expected, comments, trailing_commas);
// return 0 - non-zero return values are reserved for future use // return 0 - non-zero return values are reserved for future use
return 0; return 0;
+7
View File
@@ -1792,4 +1792,11 @@ TEST_CASE("string scanning kernels")
CHECK(nlohmann::detail::count_trailing_zeros(bit) == k); CHECK(nlohmann::detail::count_trailing_zeros(bit) == k);
CHECK(nlohmann::detail::count_trailing_zeros(bit | (bit << 1u) | 0x8000000000000000u) == k); CHECK(nlohmann::detail::count_trailing_zeros(bit | (bit << 1u) | 0x8000000000000000u) == k);
} }
// eight bytes as a little-endian word, at any alignment
const unsigned char bytes[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF};
CHECK(nlohmann::detail::read_eight_bytes(bytes) == 0x0807060504030201u);
CHECK(nlohmann::detail::read_eight_bytes(bytes + 1) == 0x0908070605040302u);
CHECK(nlohmann::detail::read_eight_bytes(bytes + 8) == 0xFF0F0E0D0C0B0A09u);
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes) + 3) == 0x0B0A090807060504u);
} }
+323 -37
View File
@@ -19,16 +19,19 @@ using nlohmann::ordered_json_view;
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <iomanip> #include <iomanip>
#include <iterator> #include <iterator>
#include <limits>
#include <list> #include <list>
#include <map> #include <map>
#include <random> #include <random>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -39,6 +42,55 @@ using nlohmann::ordered_json_view;
namespace namespace
{ {
// the value of a text, through a named document: the views of a temporary
// document would dangle (root() of an rvalue document does not compile)
template<typename Document, typename... Args>
auto materialized(Args&& ... args) -> decltype(std::declval<typename Document::view_type>().materialize())
{
const Document d = Document::parse(std::forward<Args>(args)...);
return d.root().materialize();
}
template<typename Document, typename Input>
auto materialized_copy(Input&& input) -> decltype(std::declval<typename Document::view_type>().materialize())
{
const Document d = Document::parse_copy(std::forward<Input>(input));
return d.root().materialize();
}
// a "byte container" that claims to hold `size` bytes, to reach the limit on
// the size of the input without allocating gigabytes; nothing past the first
// bytes is ever read, because the size is checked before the parse starts
struct oversized_input
{
using value_type = char;
std::size_t claimed;
const char* data() const
{
return "[1]";
}
std::size_t size() const
{
return claimed;
}
};
// detection of calls that must not compile
template<typename... Args>
using parse_call_t = decltype(json_document::parse(std::declval<Args>()...));
template<typename... Args>
using parse_copy_call_t = decltype(json_document::parse_copy(std::declval<Args>()...));
template<typename... Args>
using accept_call_t = decltype(json_document::accept(std::declval<Args>()...));
template<typename... Args>
using read_call_t = decltype(std::declval<json_document&>().read(std::declval<Args>()...));
template<typename Document>
using root_call_t = decltype(std::declval<Document>().root());
template<typename View>
using bool_conversion_t = decltype(static_cast<bool>(std::declval<View>()));
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
// the exception parse() throws for a text, or "" if it accepts it // the exception parse() throws for a text, or "" if it accepts it
std::string parse_exception(const std::string& text, bool comments = false, bool trailing_commas = false) std::string parse_exception(const std::string& text, bool comments = false, bool trailing_commas = false)
@@ -152,7 +204,6 @@ TEST_CASE("json_view")
CHECK(v.is_primitive() == j.is_primitive()); CHECK(v.is_primitive() == j.is_primitive());
CHECK(v.is_structured() == j.is_structured()); CHECK(v.is_structured() == j.is_structured());
CHECK(!v.is_discarded()); CHECK(!v.is_discarded());
CHECK(static_cast<bool>(v));
CHECK(v.size() == j.size()); CHECK(v.size() == j.size());
CHECK(v.empty() == j.empty()); CHECK(v.empty() == j.empty());
CHECK(v.materialize() == j); CHECK(v.materialize() == j);
@@ -160,7 +211,6 @@ TEST_CASE("json_view")
const json_view invalid{}; const json_view invalid{};
CHECK(invalid.is_discarded()); CHECK(invalid.is_discarded());
CHECK(!static_cast<bool>(invalid));
CHECK(invalid.type() == json::value_t::discarded); CHECK(invalid.type() == json::value_t::discarded);
CHECK(invalid.size() == 0); CHECK(invalid.size() == 0);
CHECK(invalid.empty()); CHECK(invalid.empty());
@@ -176,19 +226,19 @@ TEST_CASE("json_view")
std::string text; std::string text;
g.value(text, 0); g.value(text, 0);
CAPTURE(text) CAPTURE(text)
CHECK(json_document::parse(text).root().materialize() == json::parse(text)); CHECK(materialized<json_document>(text) == json::parse(text));
// member order as ordered_json::parse keeps it // member order as ordered_json::parse keeps it
CHECK(ordered_json_document::parse(text).root().materialize().dump() == ordered_json::parse(text).dump()); CHECK(materialized<ordered_json_document>(text).dump() == ordered_json::parse(text).dump());
} }
// duplicate keys: the last value, at the position of the first key // duplicate keys: the last value, at the position of the first key
CHECK(json_document::parse(R"({"a":1,"b":2,"a":3})").root().materialize() == json::parse(R"({"a":1,"b":2,"a":3})")); CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})"));
CHECK(ordered_json_document::parse(R"({"a":1,"b":2,"a":3})").root().materialize().dump() == R"({"a":3,"b":2})"); CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})");
// very deep nesting (iterative, as parse()) // very deep nesting (iterative, as parse())
const std::string deep = std::string(100000, '[') + std::string(100000, ']'); const std::string deep = std::string(100000, '[') + std::string(100000, ']');
CHECK(json_document::parse(deep).root().materialize() == json::parse(deep)); CHECK(materialized<json_document>(deep) == json::parse(deep));
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
// the parents are set, so errors name the path // the parents are set, so errors name the path
const json m = json_document::parse(R"({"a":{"b":[1]}})").root().materialize(); const json m = materialized<json_document>(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.exception.type_error.304] (/a/b/0) cannot use at() with number", json::type_error&);
#endif #endif
} }
@@ -261,7 +311,7 @@ TEST_CASE("json_view")
CHECK(json_document::accept(text)); CHECK(json_document::accept(text));
if (accepted) if (accepted)
{ {
CHECK(float_document::parse(text).root().materialize() == json_float::parse(text)); CHECK(materialized<float_document>(text) == json_float::parse(text));
} }
} }
float_document f; float_document f;
@@ -275,7 +325,7 @@ TEST_CASE("json_view")
CHECK(json_document::accept(with_nul) == json::accept(with_nul)); CHECK(json_document::accept(with_nul) == json::accept(with_nul));
const std::string nul_in_comment("[1, // c\0\n2]", 12); const std::string nul_in_comment("[1, // c\0\n2]", 12);
CHECK(json_document::accept(nul_in_comment, true) == json::accept(nul_in_comment, true)); CHECK(json_document::accept(nul_in_comment, true) == json::accept(nul_in_comment, true));
CHECK(json_document::parse("\xEF\xBB\xBF[1]").root().materialize() == json::parse("\xEF\xBB\xBF[1]")); CHECK(materialized<json_document>("\xEF\xBB\xBF[1]") == json::parse("\xEF\xBB\xBF[1]"));
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
CHECK(view_exception("\xEF\xBB") == parse_exception("\xEF\xBB")); CHECK(view_exception("\xEF\xBB") == parse_exception("\xEF\xBB"));
#endif #endif
@@ -291,18 +341,18 @@ TEST_CASE("json_view")
CHECK(!borrowed.owns_source()); CHECK(!borrowed.owns_source());
CHECK(borrowed.source().data() == text.data()); CHECK(borrowed.source().data() == text.data());
CHECK(borrowed.root().materialize() == expected); CHECK(borrowed.root().materialize() == expected);
CHECK(json_document::parse(text.c_str()).root().materialize() == expected); CHECK(materialized<json_document>(text.c_str()) == expected);
CHECK(json_document::parse(R"([1, "two", {"three": 3.5}])").root().materialize() == expected); CHECK(materialized<json_document>(R"([1, "two", {"three": 3.5}])") == expected);
CHECK(json_document::parse(text.data(), text.data() + text.size()).root().materialize() == expected); CHECK(materialized<json_document>(text.data(), text.data() + text.size()) == expected);
const std::vector<char> chars(text.begin(), text.end()); const std::vector<char> chars(text.begin(), text.end());
CHECK(!json_document::parse(chars).owns_source()); CHECK(!json_document::parse(chars).owns_source());
CHECK(json_document::parse(chars).root().materialize() == expected); CHECK(materialized<json_document>(chars) == expected);
const std::vector<std::uint8_t> bytes(text.begin(), text.end()); const std::vector<std::uint8_t> bytes(text.begin(), text.end());
CHECK(json_document::parse(bytes).root().materialize() == expected); CHECK(materialized<json_document>(bytes) == expected);
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
const std::string_view sv = text; const std::string_view sv = text;
CHECK(!json_document::parse(sv).owns_source()); CHECK(!json_document::parse(sv).owns_source());
CHECK(json_document::parse(sv).root().materialize() == expected); CHECK(materialized<json_document>(sv) == expected);
#endif #endif
// owned // owned
@@ -311,15 +361,28 @@ TEST_CASE("json_view")
CHECK(from_rvalue.owns_source()); CHECK(from_rvalue.owns_source());
CHECK(from_rvalue.root().materialize() == expected); CHECK(from_rvalue.root().materialize() == expected);
CHECK(json_document::parse(std::vector<char>(text.begin(), text.end())).owns_source()); CHECK(json_document::parse(std::vector<char>(text.begin(), text.end())).owns_source());
// a const rvalue cannot be moved from, and is not borrowed (it may be a
// temporary): it is copied, as is a const rvalue of any container
const std::string const_text = text;
const json_document from_const_rvalue = json_document::parse(std::move(const_text)); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
CHECK(from_const_rvalue.owns_source());
CHECK(from_const_rvalue.source().data() != const_text.data());
CHECK(from_const_rvalue.root().materialize() == expected);
json_document read_const_rvalue;
read_const_rvalue.read(std::move(const_text)); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
CHECK(read_const_rvalue.owns_source());
CHECK(read_const_rvalue.root().materialize() == expected);
const std::vector<char> const_chars(text.begin(), text.end());
CHECK(json_document::parse(std::move(const_chars)).owns_source()); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
CHECK(json_document::parse_copy(text).owns_source()); CHECK(json_document::parse_copy(text).owns_source());
CHECK(json_document::parse_copy(text).root().materialize() == expected); CHECK(materialized_copy<json_document>(text) == expected);
std::istringstream stream(text); std::istringstream stream(text);
const json_document from_stream = json_document::parse(stream); const json_document from_stream = json_document::parse(stream);
CHECK(from_stream.owns_source()); CHECK(from_stream.owns_source());
CHECK(from_stream.root().materialize() == expected); CHECK(from_stream.root().materialize() == expected);
const std::list<char> list(text.begin(), text.end()); const std::list<char> list(text.begin(), text.end());
CHECK(json_document::parse(list.begin(), list.end()).owns_source()); CHECK(json_document::parse(list.begin(), list.end()).owns_source());
CHECK(json_document::parse(list.begin(), list.end()).root().materialize() == expected); CHECK(materialized<json_document>(list.begin(), list.end()) == expected);
// iterator pairs: pointers are borrowed, and so are contiguous library // iterator pairs: pointers are borrowed, and so are contiguous library
// iterators where the input adapter detects them (C++20) // iterators where the input adapter detects them (C++20)
@@ -330,14 +393,85 @@ TEST_CASE("json_view")
CHECK((from_iterators.source().data() == chars.data()) == contiguous); CHECK((from_iterators.source().data() == chars.data()) == contiguous);
CHECK(from_iterators.root().materialize() == expected); CHECK(from_iterators.root().materialize() == expected);
const std::string padded = "x" + text + "x"; const std::string padded = "x" + text + "x";
CHECK(json_document::parse(padded.begin() + 1, padded.end() - 1).root().materialize() == expected); CHECK(materialized<json_document>(padded.begin() + 1, padded.end() - 1) == expected);
CHECK(json_document::parse(chars.cbegin(), chars.cbegin(), false).is_discarded()); CHECK(json_document::parse(chars.cbegin(), chars.cbegin(), false).is_discarded());
const std::wstring wide = L"[\"\u00e4\u20ac\", 1]"; const std::wstring wide = L"[\"\u00e4\u20ac\", 1]";
CHECK(json_document::parse(wide).root().materialize() == json::parse(wide)); CHECK(materialized<json_document>(wide) == json::parse(wide));
CHECK(json_document::parse(static_cast<const char*>(nullptr), false).is_discarded()); CHECK(json_document::parse(static_cast<const char*>(nullptr), false).is_discarded());
CHECK(json_document::parse("", false).is_discarded()); CHECK(json_document::parse("", false).is_discarded());
} }
SECTION("integer arguments do not compile")
{
using nlohmann::detail::is_detected;
// a length is not a flag: parse(ptr, len) would convert len to
// allow_exceptions and read ptr as a C string, which need not end
static_assert(is_detected<parse_call_t, const char*, bool>::value, "parse(ptr, bool) is valid");
static_assert(is_detected<parse_call_t, const char*, bool, bool, bool>::value, "parse(ptr, bool, bool, bool) is valid");
static_assert(is_detected<parse_call_t, const char*, const char*>::value, "parse(first, last) is valid");
static_assert(is_detected<parse_call_t, const char*, const char*, bool>::value, "parse(first, last, bool) is valid");
static_assert(!is_detected<parse_call_t, const char*, std::size_t>::value, "parse(ptr, len) must not compile");
static_assert(!is_detected<parse_call_t, const char*, int>::value, "parse(ptr, int) must not compile");
static_assert(!is_detected<parse_call_t, const char*, char>::value, "parse(ptr, char) must not compile");
static_assert(!is_detected<parse_call_t, const char*, std::size_t, bool>::value, "parse(ptr, len, bool) must not compile");
static_assert(!is_detected<parse_call_t, const std::string&, std::size_t>::value, "parse(string, len) must not compile");
static_assert(!is_detected<parse_call_t, const std::vector<char>&, std::size_t>::value, "parse(vector, len) must not compile");
static_assert(is_detected<parse_copy_call_t, const char*, bool>::value, "parse_copy(ptr, bool) is valid");
static_assert(!is_detected<parse_copy_call_t, const char*, std::size_t>::value, "parse_copy(ptr, len) must not compile");
static_assert(is_detected<accept_call_t, const char*, bool>::value, "accept(ptr, bool) is valid");
static_assert(!is_detected<accept_call_t, const char*, std::size_t>::value, "accept(ptr, len) must not compile");
static_assert(is_detected<read_call_t, const char*, bool>::value, "read(ptr, bool) is valid");
static_assert(!is_detected<read_call_t, const char*, std::size_t>::value, "read(ptr, len) must not compile");
// json_view has no conversion to bool: unlike basic_json's, it would
// mean "exists", not "is not null"; use is_discarded()
static_assert(!is_detected<bool_conversion_t, json_view>::value, "json_view must not convert to bool");
// the valid calls still work
const char* const text = "[1]";
CHECK(materialized<json_document>(text, true) == json::parse(text));
CHECK(json_document::accept(text, true, true));
}
SECTION("root of a temporary document does not compile")
{
using nlohmann::detail::is_detected;
// the view would dangle: auto v = json_document::parse(text).root();
static_assert(is_detected<root_call_t, json_document&>::value, "root() of an lvalue is valid");
static_assert(is_detected<root_call_t, const json_document&>::value, "root() of a const lvalue is valid");
static_assert(!is_detected<root_call_t, json_document>::value, "root() of an rvalue must not compile");
static_assert(!is_detected < root_call_t, json_document && >::value, "root() of an rvalue must not compile");
static_assert(!is_detected < root_call_t, const json_document && >::value, "root() of a const rvalue must not compile");
static_assert(!is_detected<root_call_t, ordered_json_document>::value, "root() of an rvalue must not compile");
// a named document is fine, also after a move
json_document d = json_document::parse("[1]");
CHECK(d.root().size() == 1);
const json_document moved = std::move(d);
CHECK(moved.root().size() == 1);
}
SECTION("input size limit")
{
// 32-bit offsets: the limit is 4 GiB minus 16 bytes (a margin below 2^32),
// which is what the exception message and the documentation say
const std::size_t limit = nlohmann::detail::view::max_input_size;
CHECK(limit == std::size_t{4294967279u});
const oversized_input input{limit + 1};
CHECK(!json_document::accept(input));
CHECK(json_document::parse(input, false).is_discarded());
#if !defined(JSON_NOEXCEPTION)
json_document d;
CHECK_THROWS_WITH_AS(d = json_document::parse(input), "[json.exception.out_of_range.416] input of 4294967280 bytes or more is not supported by json_document", json::out_of_range&);
#endif
}
SECTION("document lifetime and reuse") SECTION("document lifetime and reuse")
{ {
json_document d; json_document d;
@@ -443,7 +577,7 @@ std::string exception_of(F f)
// compares a view with the ordered_json value materialize() gives for it: // compares a view with the ordered_json value materialize() gives for it:
// types, sizes, elements and members (by index, key, and iteration), in // types, sizes, elements and members (by index, key, and iteration), in
// document order; duplicate keys are found as their first occurrence // document order; duplicate keys are found as their last occurrence
void check_access(const ordered_json_view& v, const ordered_json& j) void check_access(const ordered_json_view& v, const ordered_json& j)
{ {
REQUIRE(v.type() == j.type()); REQUIRE(v.type() == j.type());
@@ -460,7 +594,7 @@ void check_access(const ordered_json_view& v, const ordered_json& j)
++i; ++i;
} }
CHECK(i == v.size()); CHECK(i == v.size());
CHECK(!v[v.size()]); CHECK(v[v.size()].is_discarded());
std::size_t index = 0; std::size_t index = 0;
for (const auto& item : v.items()) for (const auto& item : v.items())
{ {
@@ -484,11 +618,23 @@ void check_access(const ordered_json_view& v, const ordered_json& j)
const std::string key(it.key().data(), it.key().size()); const std::string key(it.key().data(), it.key().size());
CHECK(v.contains(key)); CHECK(v.contains(key));
CHECK(v.count(key) == 1); CHECK(v.count(key) == 1);
if (std::find(keys.begin(), keys.end(), key) != keys.end()) if (std::find(keys.begin(), keys.end(), key) == keys.end())
{ {
continue; // a duplicate: lookups find the first one keys.push_back(key);
}
// lookups find the last member with the key, which is this one if
// there is no later one
auto next = it;
++next;
bool is_last = true;
for (; next != v.end(); ++next)
{
is_last = is_last && next.key() != it.key();
}
if (!is_last)
{
continue;
} }
keys.push_back(key);
CHECK(v.find(key) == it); CHECK(v.find(key) == it);
CHECK(v[key].materialize() == it->materialize()); CHECK(v[key].materialize() == it->materialize());
CHECK(v.at(key).materialize() == it.value().materialize()); CHECK(v.at(key).materialize() == it.value().materialize());
@@ -514,7 +660,7 @@ void check_access(const ordered_json_view& v, const ordered_json& j)
CHECK(v.back().materialize() == j.back()); CHECK(v.back().materialize() == j.back());
} }
} }
CHECK(!v["not a key in the generated documents"]); CHECK(v["not a key in the generated documents"].is_discarded());
CHECK(v.find("not a key in the generated documents") == v.end()); CHECK(v.find("not a key in the generated documents") == v.end());
} }
else else
@@ -579,15 +725,35 @@ TEST_CASE("json_view element access and iteration")
#endif #endif
} }
SECTION("duplicate keys: lookups find the first member, iteration all") SECTION("duplicate keys: lookups find the last member, iteration all")
{ {
const json_document d = json_document::parse(R"({"a":1,"b":2,"a":3})"); const json_document d = json_document::parse(R"({"a":1,"b":2,"a":3})");
const json_view v = d.root(); const json_view v = d.root();
CHECK(v.size() == 3); CHECK(v.size() == 3);
CHECK(v["a"].materialize() == 1); CHECK(v["a"].materialize() == 3);
CHECK(v.at("a").materialize() == 1); CHECK(v.at("a").materialize() == 3);
CHECK(v.find("a") == v.begin()); CHECK(v.find("a") == std::next(v.begin(), 2));
CHECK(v.find("a").value().materialize() == 3);
CHECK(v.find("b") == std::next(v.begin()));
CHECK(v.count("a") == 1); CHECK(v.count("a") == 1);
CHECK(v.contains("a"));
CHECK(v.value("a", 0) == 3);
CHECK(v["a"].materialize() == v.materialize()["a"]); // as materialize()
// keys of every length class (the 16-byte short compare and memcmp)
for (const std::size_t n :
{
0u, 1u, 3u, 7u, 8u, 15u, 16u, 17u, 40u
})
{
const std::string key(n, 'k');
const json_document dk = json_document::parse("{\"" + key + "\":1,\"" + key + "x\":2,\"" + key + "\":3,\"" + key + "\":4}");
CAPTURE(n)
CHECK(dk.root()[key].materialize() == 4);
CHECK(dk.root().at(key).materialize() == 4);
CHECK(dk.root().find(key) == std::next(dk.root().begin(), 3));
CHECK(dk.root().value(key, 0) == 4);
CHECK(dk.root()[key + "x"].materialize() == 2);
}
std::string order; std::string order;
for (auto it = v.begin(); it != v.end(); ++it) for (auto it = v.begin(); it != v.end(); ++it)
{ {
@@ -638,14 +804,131 @@ TEST_CASE("json_view element access and iteration")
// where basic_json has undefined behavior, the view answers safely // where basic_json has undefined behavior, the view answers safely
const json_document d = json_document::parse(R"({"a":[]})"); const json_document d = json_document::parse(R"({"a":[]})");
CHECK(!d.root()["b"]); CHECK(d.root()["b"].is_discarded());
CHECK(!d.root()["a"][0]); 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"].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"].back(), "[json.exception.invalid_iterator.214] cannot get value", json::invalid_iterator&);
const json_view invalid{}; const json_view invalid{};
CHECK(invalid.begin() == invalid.end()); CHECK(invalid.begin() == invalid.end());
CHECK(std::string(invalid.type_name()) == "discarded"); CHECK(std::string(invalid.type_name()) == "discarded");
CHECK_THROWS_WITH_AS(invalid["a"], "[json.exception.type_error.305] cannot use operator[] with a string argument with discarded", json::type_error&); }
SECTION("chained access is safe: operator[] of a discarded view is discarded")
{
const json_document d = json_document::parse(R"({"a":{"b":[10,20]},"s":"str"})");
const json_view v = d.root();
// missing keys and indexes
CHECK(v["x"].is_discarded());
CHECK(v["x"]["y"].is_discarded());
CHECK(v["x"]["y"]["z"].is_discarded());
CHECK(v["x"][0].is_discarded());
CHECK(v["x"][0u][1L].is_discarded());
CHECK(v["a"]["b"][2].is_discarded());
CHECK(v["a"]["b"][2]["c"].is_discarded());
CHECK(v["a"]["b"][2][json_view::json_pointer("/c")].is_discarded());
CHECK(v["x"][json_view::json_pointer("/a/b")].is_discarded());
CHECK(v["x"][json_view::json_pointer("")].is_discarded());
CHECK(v["x"]["y"].is_discarded());
CHECK(v["x"][std::string("y")].is_discarded());
// a resolvable path still resolves
CHECK(v["a"]["b"][1].materialize() == 20);
CHECK(v[json_view::json_pointer("/a/b/1")].materialize() == 20);
// the discarded view of an unresolved pointer is discarded too
CHECK(v[json_view::json_pointer("/x/y")]["z"].is_discarded());
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_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
const json_view invalid{};
CHECK(invalid["a"].is_discarded());
CHECK(invalid[0].is_discarded());
CHECK(invalid[json_view::json_pointer("/a")].is_discarded());
CHECK_THROWS_WITH_AS(invalid.at("a"), "[json.exception.type_error.304] cannot use at() with discarded", json::type_error&);
CHECK_THROWS_WITH_AS(invalid.at(0), "[json.exception.type_error.304] cannot use at() with discarded", json::type_error&);
CHECK_THROWS_AS(v.at("x").at("y"), json::out_of_range&);
CHECK_THROWS_AS(v["x"].at("y"), json::type_error&);
CHECK_THROWS_AS(v.at(json_view::json_pointer("/x/y")), json::out_of_range&);
CHECK_THROWS_AS(v["x"].at(json_view::json_pointer("/y")), json::out_of_range&);
#endif
}
SECTION("integer types as array indexes")
{
const json_document d = json_document::parse("[10,20,30]");
const json_view v = d.root();
const json j = v.materialize();
// (compile-time: no overload is ambiguous)
CHECK(v[0].materialize() == 10);
CHECK(v[1].materialize() == 20);
CHECK(v[0u].materialize() == 10);
CHECK(v[1u].materialize() == 20);
CHECK(v[1L].materialize() == 20);
CHECK(v[2UL].materialize() == 30);
CHECK(v[1LL].materialize() == 20);
CHECK(v[2ULL].materialize() == 30);
CHECK(v[static_cast<short>(1)].materialize() == 20);
CHECK(v[static_cast<unsigned short>(2)].materialize() == 30);
CHECK(v[static_cast<signed char>(1)].materialize() == 20);
CHECK(v[static_cast<unsigned char>(2)].materialize() == 30);
CHECK(v[std::int8_t(1)].materialize() == 20);
CHECK(v[std::int16_t(2)].materialize() == 30);
CHECK(v[std::int32_t(1)].materialize() == 20);
CHECK(v[std::int64_t(2)].materialize() == 30);
CHECK(v[std::uint32_t(0)].materialize() == 10);
CHECK(v[std::uint64_t(1)].materialize() == 20);
CHECK(v[std::size_t(2)].materialize() == 30);
CHECK(v[std::ptrdiff_t(1)].materialize() == 20);
CHECK(j[0u] == 10); // as basic_json
CHECK(v.at(0).materialize() == 10);
CHECK(v.at(1u).materialize() == 20);
CHECK(v.at(1L).materialize() == 20);
CHECK(v.at(2LL).materialize() == 30);
CHECK(v.at(2ULL).materialize() == 30);
CHECK(v.at(static_cast<short>(1)).materialize() == 20);
CHECK(v.at(static_cast<unsigned short>(2)).materialize() == 30);
CHECK(v.at(std::int32_t(0)).materialize() == 10);
CHECK(v.at(std::uint32_t(0)).materialize() == 10);
CHECK(v.at(std::int64_t(0)).materialize() == 10);
CHECK(v.at(std::uint64_t(1)).materialize() == 20);
CHECK(v.at(std::size_t(2)).materialize() == 30);
CHECK(j.at(std::uint32_t(0)) == 10); // as basic_json
// out of range, including negative values (no wrap-around)
CHECK(v[3].is_discarded());
CHECK(v[3u].is_discarded());
CHECK(v[3L].is_discarded());
CHECK(v[-1].is_discarded());
CHECK(v[-1L].is_discarded());
CHECK(v[-1LL].is_discarded());
CHECK(v[static_cast<short>(-1)].is_discarded());
CHECK(v[std::int64_t(-3)].is_discarded());
CHECK(v[(std::numeric_limits<std::int64_t>::min)()].is_discarded());
CHECK(v[(std::numeric_limits<std::uint64_t>::max)()].is_discarded());
CHECK(v[(std::numeric_limits<std::size_t>::max)()].is_discarded());
CHECK(v[std::numeric_limits<int>::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_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&);
CHECK_THROWS_AS(v.at((std::numeric_limits<std::int64_t>::min)()), json::out_of_range&);
CHECK_THROWS_AS(v.at((std::numeric_limits<std::uint64_t>::max)()), json::out_of_range&);
// not an array
const json_document o = json_document::parse("{}");
CHECK_THROWS_AS(o.root()[0u], json::type_error&);
CHECK_THROWS_AS(o.root()[1L], json::type_error&);
CHECK_THROWS_AS(o.root().at(std::uint32_t(0)), json::type_error&);
CHECK_THROWS_AS(o.root().at(std::int64_t(0)), json::type_error&);
#endif
} }
SECTION("iterators") SECTION("iterators")
@@ -919,10 +1202,12 @@ TEST_CASE("json_view values")
CAPTURE(token) CAPTURE(token)
const std::string text = "[" + token + "]"; const std::string text = "[" + token + "]";
const double b = json::parse(text)[0].get<double>(); const double b = json::parse(text)[0].get<double>();
CHECK(bits(json_document::parse(text).root()[0].get<double>()) == bits(b)); const json_document dd = json_document::parse(text);
CHECK(bits(dd.root()[0].get<double>()) == bits(b));
if (std::abs(b) < 1e38) if (std::abs(b) < 1e38)
{ {
CHECK(bits(nlohmann::basic_json_document<json_float>::parse(text).root()[0].get<float>()) == bits(json_float::parse(text)[0].get<float>())); const nlohmann::basic_json_document<json_float> df = nlohmann::basic_json_document<json_float>::parse(text);
CHECK(bits(df.root()[0].get<float>()) == bits(json_float::parse(text)[0].get<float>()));
} }
} }
} }
@@ -979,7 +1264,8 @@ TEST_CASE("json_view values")
CHECK(count == 3); CHECK(count == 3);
// a duplicate key: the last value, as parse() // a duplicate key: the last value, as parse()
CHECK((json_document::parse(R"({"a":1,"a":2})").root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}})); const json_document dup = json_document::parse(R"({"a":1,"a":2})");
CHECK((dup.root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}}));
const json_view invalid{}; const json_view invalid{};
CHECK_THROWS_WITH_AS(invalid.get<int>(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&); CHECK_THROWS_WITH_AS(invalid.get<int>(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&);
@@ -1070,7 +1356,7 @@ TEST_CASE("json_view JSON pointers")
else if (at_error.find("out_of_range.401") != std::string::npos || at_error.find("out_of_range.403") != std::string::npos) // NOLINT(abseil-string-find-str-contains) else if (at_error.find("out_of_range.401") != std::string::npos || at_error.find("out_of_range.403") != std::string::npos) // NOLINT(abseil-string-find-str-contains)
{ {
// undefined behavior for const basic_json::operator[] // undefined behavior for const basic_json::operator[]
CHECK(!v[p]); CHECK(v[p].is_discarded());
} }
else else
{ {
+88
View File
@@ -19,8 +19,10 @@ using nlohmann::json;
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <limits>
#include <map> #include <map>
#include <memory> #include <memory>
#include <new>
#include <random> #include <random>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -401,3 +403,89 @@ TEST_CASE("json_view builder")
} }
} }
} }
TEST_CASE("json_view node integer bits")
{
using nlohmann::detail::view::integer_bits;
using nlohmann::detail::view::set_integer_bits;
// an integer lives in len (low half) and next (high half), on any byte
// order; a big-endian target must not store the native word over both
SECTION("set_integer_bits and integer_bits")
{
node n = {};
for (const std::uint64_t v :
{
std::uint64_t{0}, std::uint64_t{1}, std::uint64_t{0xFFFFFFFFu}, std::uint64_t{0x100000000u},
std::uint64_t{0x0000000200000003u}, std::uint64_t{0x0123456789ABCDEFu}, std::uint64_t{0xFFFFFFFFFFFFFFFEu}
})
{
CAPTURE(v)
n.kind = 0x5A;
n.flags = 0xA5;
n.extra = 0x1234;
n.off = 0x89ABCDEFu;
set_integer_bits(n, v);
CHECK(integer_bits(n) == v);
CHECK(n.len == static_cast<std::uint32_t>(v));
CHECK(n.next == static_cast<std::uint32_t>(v >> 32));
// the other fields are untouched
CHECK(n.kind == 0x5A);
CHECK(n.flags == 0xA5);
CHECK(n.extra == 0x1234);
CHECK(n.off == 0x89ABCDEFu);
}
}
SECTION("parsed integers")
{
struct integer_case
{
const char* text;
std::uint64_t bits;
};
for (const integer_case c :
{
integer_case{"[8589934595]", 0x0000000200000003u}, integer_case{"[4294967296]", 0x100000000u}, integer_case{"[4294967295]", 0xFFFFFFFFu},
integer_case{"[-2]", 0xFFFFFFFFFFFFFFFEu}, integer_case{"[-4294967297]", 0xFFFFFFFEFFFFFFFFu}, integer_case{"[18446744073709551615]", 0xFFFFFFFFFFFFFFFFu},
integer_case{"[7]", 7u}
})
{
CAPTURE(c.text)
const built b = build(c.text, false, false, true);
REQUIRE(b.ok);
const node& n = b.data->tape[1];
CHECK(integer_bits(n) == c.bits);
CHECK(n.len == static_cast<std::uint32_t>(c.bits));
CHECK(n.next == static_cast<std::uint32_t>(c.bits >> 32));
}
}
}
TEST_CASE("json_view node array size limit")
{
// a node array larger than the address space is refused, not wrapped to a
// small allocation (the size computation overflows on 32-bit targets, and
// for absurd counts everywhere)
std::unique_ptr<document_data, document_data::deleter> d(document_data::create(0));
d->reserve(8);
REQUIRE(d->tape_cap >= 8);
d->tape_size = 2;
const std::size_t cap = d->tape_cap;
node* const tape = d->tape;
#if !defined(JSON_NOEXCEPTION)
const std::size_t too_many = document_data::max_nodes() + 1;
CHECK_THROWS_AS(d->reserve(too_many), std::bad_alloc&);
CHECK_THROWS_AS(d->reserve((std::numeric_limits<std::size_t>::max)()), std::bad_alloc&);
// the array is unchanged
CHECK(d->tape == tape);
CHECK(d->tape_cap == cap);
CHECK(d->tape_size == 2);
#endif
// the largest count that fits is not refused by the check (nothing is
// allocated for a count that is already there)
d->reserve(cap);
CHECK(d->tape == tape);
}
+72 -5
View File
@@ -15,6 +15,7 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::detail::dtoa_impl::reinterpret_bits; using nlohmann::detail::dtoa_impl::reinterpret_bits;
#include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
@@ -666,13 +667,24 @@ void check_shortest(double v)
const std::string text(buf.data(), end); const std::string text(buf.data(), end);
CAPTURE(text) CAPTURE(text)
CHECK(parse_double(text) == v); CHECK(parse_double(text) == v);
// the layout is that of format_buffer() for the same digits // the layout is that of format_buffer() for the digits of Zmij
const auto sd = nlohmann::detail::zmij::to_shortest(reinterpret_bits<std::uint64_t>(v));
const std::uint64_t significand = sd.has_digit ? (sd.integral * 10) + sd.digit : sd.integral;
int exponent = sd.has_digit ? sd.exponent : sd.exponent + 1;
std::string significand_digits = std::to_string(significand);
while (significand_digits.size() > 1 && significand_digits.back() == '0')
{
significand_digits.pop_back();
++exponent;
}
std::array<char, 64> reference{}; std::array<char, 64> reference{};
int len = 0; std::copy(significand_digits.begin(), significand_digits.end(), reference.begin());
int exponent = 0; const char* const reference_end = nlohmann::detail::dtoa_impl::format_buffer(reference.data(), static_cast<int>(significand_digits.size()), exponent, -4, 15);
nlohmann::detail::dtoa_impl::shortest_digits(reference.data(), len, exponent, v);
const char* const reference_end = nlohmann::detail::dtoa_impl::format_buffer(reference.data(), len, exponent, -4, 15);
CHECK(text == std::string(reference.data(), static_cast<std::size_t>(reference_end - reference.data()))); CHECK(text == std::string(reference.data(), static_cast<std::size_t>(reference_end - reference.data())));
// and write_positive() is what to_chars() calls
std::array<char, 64> positive{};
const char* const positive_end = nlohmann::detail::dtoa_impl::write_positive(positive.data(), positive.data() + positive.size(), v);
CHECK(text == std::string(positive.data(), static_cast<std::size_t>(positive_end - positive.data())));
const auto de = digits_and_exponent(text); const auto de = digits_and_exponent(text);
const std::string& digits = de.first; const std::string& digits = de.first;
if (digits.size() > 1) if (digits.size() > 1)
@@ -785,3 +797,58 @@ TEST_CASE("shortest digits of doubles")
} }
} }
} }
TEST_CASE("choice of the conversion")
{
using nlohmann::detail::dtoa_impl::is_binary64;
SECTION("by the format of the type")
{
// Zmij needs binary64 numbers; everything else uses Grisu2
static_assert(!is_binary64<float>::value, "float is not binary64");
static_assert(is_binary64<double>::value == (std::numeric_limits<double>::is_iec559 && std::numeric_limits<double>::digits == 53),
"double is binary64 where it is IEEE 754 with 53 digits");
static_assert(!is_binary64<int>::value, "integers are not binary64");
static_assert(is_binary64<long double>::value == (std::numeric_limits<long double>::is_iec559 && std::numeric_limits<long double>::digits == 53 && sizeof(long double) == 8),
"long double is binary64 where it has the format of a double");
CHECK(!is_binary64<float>::value);
CHECK(is_binary64<double>::value);
}
SECTION("float: Grisu2, double: Zmij")
{
// 5.3165205877497296e+16 is one of the doubles for which Grisu2 does not find the shortest digits
constexpr double value = 5.3165205877497296e+16;
std::array<char, 64> buf{};
const char* const last = buf.data() + buf.size();
char* end = nlohmann::detail::dtoa_impl::write_positive(buf.data(), last, value);
CHECK(std::string(buf.data(), end) == "5.31652058774973e+16");
end = nlohmann::detail::dtoa_impl::write_positive_grisu2(buf.data(), last, value);
CHECK(std::string(buf.data(), end) == "5.3165205877497296e+16");
constexpr float f = 1.1754944e-38f;
end = nlohmann::detail::dtoa_impl::write_positive(buf.data(), last, f);
const std::string dispatched(buf.data(), end);
end = nlohmann::detail::dtoa_impl::write_positive_grisu2(buf.data(), last, f);
CHECK(dispatched == std::string(buf.data(), end));
}
SECTION("long double with the format of a double: Zmij")
{
// (on platforms where long double is wider, Grisu2 does not apply either: the snprintf fallback does)
if (std::numeric_limits<long double>::digits == 53 && std::numeric_limits<long double>::is_iec559)
{
using long_double_json = nlohmann::json::with_float_t<long double>;
for (const double d :
{
5.3165205877497296e+16, 1.0, 0.1, 123456.789, 2.2250738585072014e-308, 1.7976931348623157e+308, -5.3165205877497296e+16
})
{
CAPTURE(d)
CHECK(long_double_json(static_cast<long double>(d)).dump() == nlohmann::json(d).dump());
}
CHECK(long_double_json(5.3165205877497296e+16L).dump() == "5.31652058774973e+16");
}
}
}