Compare commits

..
Author SHA1 Message Date
Niels Lohmann c2037f52b9 Merge branch 'json-view/11-view-access' into json-view/12-view-values
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 23:16:59 +02:00
Niels Lohmann dbbe96c42c Fix CI: useless cast in the array index check of the view's JSON pointers
GCC -Werror=useless-cast on Linux x86-64 rejected
static_cast<std::uint64_t>((std::numeric_limits<std::size_t>::max)()),
as both are the same type there. Compare without the cast: std::size_t
converts to std::uint64_t implicitly on every platform.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 23:05:48 +02:00
Niels Lohmann bba3fa0307 Merge branch 'json-view/11-view-access' into json-view/12-view-values
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 23:01:46 +02:00
Niels Lohmann 9549dce77b Fix CI: value-initialize a const json_view for clang 3.6
clang 3.6 rejects `const json_view invalid;` (no user-provided default
constructor, CWG 253), as fixed in json-view/10-view-document.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 22:44:44 +02:00
Niels Lohmann a210437b30 Fix CI: json_view value tests without exceptions and with GCC
- ci_test_noexceptions: exception_of() and without_path() exist only
  with exceptions (they catch outside a CHECK_THROWS, which aborts with
  JSON_NOEXCEPTION); compile the comparisons of the conversion, value(),
  and JSON pointer errors only with exceptions as well.
- ci_test_gcc: -Werror=unused-result for static_cast<void>(j.contains(p))
  (GCC's warn_unused_result ignores a cast to void); store the result.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 22:44:33 +02:00
Niels Lohmann fdcba786d8 Merge branch 'json-view/11-view-access' into json-view/12-view-values
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 22:42:18 +02:00
Niels Lohmann 2aedf52d54 Inline get() of arithmetic values of json_view
get<T>() of arithmetic types is inlined down to the conversion, so that
its checks of the node kind merge with those of the caller, and reading
an integer needs no call. Traversing every value: citm_catalog -6%,
marine_ik -5%, numbers and twitter -3%, mesh -2.5%, canada -1% (and
more above the float conversion from the digit layout: citm_catalog
-14%, marine_ik -11%).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 14:02:55 +02:00
Niels Lohmann bc36fd323b Address the clang-tidy findings of values and JSON pointers
get_string() and number_token() return braced lists; the test compares
floats by their bit patterns instead of with memcmp, uses std::any_of, and
marks a fixed seed, a default member initializer (needed by GCC's
-Weffc++), and a string search.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 14:02:54 +02:00
Niels Lohmann 9856da2561 Document values and JSON pointers of json_view
- API pages for get, get_to, get_string, number_token, and value of
  basic_json_view; JSON pointer overloads of operator[], at, and
  contains; links both ways with the basic_json pages
- the feature page describes which conversions copy nothing
- the examples show when the view helps: strings without copies, numbers
  exactly as written, and paths into a large text

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 14:02:53 +02:00
Niels Lohmann 85f414ae7a Add values and JSON pointers to json_view
basic_json_view gains get<T>(), get_to(), value() with keys and JSON
pointers, and operator[], at(), and contains() with JSON pointers, plus
two functions basic_json has no counterpart for:

- get_string(): the string without a copy (a string_view into the source,
  or into the decoded strings for strings with escapes)
- number_token(): the text of a number as it appears in the source

get<T>() converts arithmetic types, strings (also string_view_t),
std::nullptr_t, std::vector, maps with string keys, and views directly;
floats are converted from the digit layout recorded by the parser with the
library's conversion chain, so the values are bit-identical to parse().
Other types, including user types with from_json(), go through
materialize().

The exceptions are those of basic_json, message included. Where const
basic_json has undefined behavior (a missing key or an index out of range
with operator[] and a JSON pointer), the result is a discarded view;
value() returns the default wherever basic_json catches out_of_range, and
contains() never throws. Array indices of JSON pointers follow
json_pointer's rules (parse_error.106/109, out_of_range.404/410).

Tests compare the conversions of 2,000 generated documents, 20,000 float
tokens (double and float, bit for bit), and every JSON pointer of 1,000
documents with basic_json, and the exceptions for malformed pointers.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 14:02:52 +02:00
Niels Lohmann 358970ee7f Give code outside basic_json the reference tokens of a json_pointer
detail::json_pointer_access returns the reference tokens of a pointer, so
that code resolving pointers without a basic_json value (such as the
zero-copy view) does not have to parse to_string() again. No change in
behavior.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-29 14:02:51 +02:00
43 changed files with 2393 additions and 18 deletions
+2
View File
@@ -77,8 +77,10 @@ cc_library(
"include/nlohmann/detail/view/materialize.hpp",
"include/nlohmann/detail/view/node.hpp",
"include/nlohmann/detail/view/number.hpp",
"include/nlohmann/detail/view/pointer.hpp",
"include/nlohmann/detail/view/scan.hpp",
"include/nlohmann/detail/view/string_ref.hpp",
"include/nlohmann/detail/view/value.hpp",
"include/nlohmann/json.hpp",
"include/nlohmann/json_fwd.hpp",
"include/nlohmann/json_view.hpp",
+5
View File
@@ -154,6 +154,9 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::empty', 'Met
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::end', 'Method', 'api/basic_json_view/end/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::find', 'Method', 'api/basic_json_view/find/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::front', 'Method', 'api/basic_json_view/front/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get', 'Method', 'api/basic_json_view/get/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_string', 'Method', 'api/basic_json_view/get_string/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::get_to', 'Method', 'api/basic_json_view/get_to/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_array', 'Method', 'api/basic_json_view/is_array/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_binary', 'Method', 'api/basic_json_view/is_binary/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_boolean', 'Method', 'api/basic_json_view/is_boolean/index.html');
@@ -169,12 +172,14 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_string',
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::is_structured', 'Method', 'api/basic_json_view/is_structured/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::items', 'Method', 'api/basic_json_view/items/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_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[]/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::size', 'Method', 'api/basic_json_view/size/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::source_offset', 'Method', 'api/basic_json_view/source_offset/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type', 'Method', 'api/basic_json_view/type/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type_name', 'Method', 'api/basic_json_view/type_name/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::value', 'Method', 'api/basic_json_view/value/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('json_document', 'Class', 'api/json_document/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('json_view', 'Class', 'api/json_view/index.html');
+2
View File
@@ -163,6 +163,8 @@ overload (3).
- [get_ref](get_ref.md) get a reference to the stored value
- [operator ValueType](operator_ValueType.md) get a value via implicit conversion
- [Converting values](../../features/conversions.md) - the type conversions article
- [basic_json_view::get](../basic_json_view/get.md) - the same conversion on a zero-copy view (many types are
converted without ever building a `basic_json` value)
## Version history
@@ -61,6 +61,8 @@ Constant.
## See also
- [get_ptr()](get_ptr.md) get a pointer value
- [basic_json_view::get_string](../basic_json_view/get_string.md) - the closest counterpart on a zero-copy view: a
string without a copy, but as a view rather than a reference to a value that must already exist
## Version history
@@ -67,6 +67,7 @@ Depends on the `json_serializer<ValueType>::from_json()` implementation.
- [get_ref](get_ref.md) get a reference to the stored value
- [get_ptr](get_ptr.md) get a pointer to the stored value
- [Converting values](../../features/conversions.md) - the type conversions article
- [basic_json_view::get_to](../basic_json_view/get_to.md) - the same conversion on a zero-copy view
## Version history
+1
View File
@@ -181,6 +181,7 @@ changes to any JSON value.
- see [`at`](at.md) for access by reference with range checking
- see [`operator[]`](operator%5B%5D.md) for unchecked access by reference
- [basic_json_view::value](../basic_json_view/value.md) - the same access on a zero-copy view
## Version history
+44 -2
View File
@@ -9,11 +9,15 @@ basic_json_view at(const string_t& key) const;
// (2)
basic_json_view at(size_type idx) const;
basic_json_view at(int idx) const;
// (3)
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
[Notes on duplicate keys](operator[].md#notes)).
2. Returns the array element at index `idx`.
3. Returns the value a JSON pointer `ptr` refers to, starting at this value.
## Parameters
@@ -23,10 +27,14 @@ basic_json_view at(int idx) const;
`idx` (in)
: index of the element to access
`ptr` (in)
: JSON pointer to the element to access
## Return value
1. the value of the first member with key `key`
2. the element at index `idx`
3. the value `ptr` resolves to, starting at this value
## Exception safety
@@ -42,6 +50,20 @@ Strong exception safety: if an exception is thrown, there are no changes to the
[`BasicJsonType::at`](../basic_json/at.md):
- Throws [`type_error.304`](../../home/exceptions.md#jsonexceptiontype_error304) if the value is not an array.
- Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `#!cpp idx >= size()`.
3. The function can throw the following exceptions, all with the same message as the corresponding call to
[`BasicJsonType::at`](../basic_json/at.md):
- Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in `ptr`
begins with `#!cpp '0'`.
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is
not a number.
- Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if an array index in `ptr`
is out of range.
- Throws [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if a reference token is
`#!cpp "-"` at an array -- `at` never inserts an element, so `#!cpp "-"` is always invalid.
- Throws [`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403) if a reference token names
an object member that does not exist.
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
because a reference token is used on a primitive value.
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
@@ -54,16 +76,20 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
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
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
that level (as 1.) or the index into the array (as 2.).
## Notes
Unlike [`operator[]`](operator[].md), which returns a [discarded](is_discarded.md) view for a missing key or an
out-of-range index, `at` always throws -- exactly as `BasicJsonType::at` does, and with the same messages, so
existing error handling written against `BasicJsonType::at` keeps working unchanged when switched to a view.
existing error handling written against `BasicJsonType::at` keeps working unchanged when switched to a view. This
also holds for overload 3: unlike [`operator[]`](operator[].md) with a JSON pointer, which returns a discarded view
for a missing key or an out-of-range index, `at` throws for those too (`out_of_range.403`/`out_of_range.401`).
## Examples
??? example
??? example "Example: (1)/(2) access specified element with bounds checking"
The example below reads required fields out of a service configuration with `at`, and shows that the exceptions
it throws -- for a wrong type and for a missing key -- carry the same messages
@@ -79,11 +105,27 @@ existing error handling written against `BasicJsonType::at` keeps working unchan
--8<-- "examples/basic_json_view__at.output"
```
??? example "Example: (3) access specified element via JSON pointer with bounds checking"
The example below shows that `at` with a JSON pointer throws exactly the exceptions, with exactly the messages,
that [`BasicJsonType::at`](../basic_json/at.md) throws for the same pointer and the same document.
```cpp
--8<-- "examples/basic_json_view__at_json_pointer.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__at_json_pointer.output"
```
## See also
- [operator[]](operator[].md) - access specified element (returns a discarded view instead of throwing)
- [front](front.md), [back](back.md) - access the first or last element
- [`BasicJsonType::at`](../basic_json/at.md) - the corresponding function of `basic_json`
- [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3
## Version history
@@ -1,21 +1,30 @@
# <small>nlohmann::basic_json_view::</small>contains
```cpp
// (1)
bool contains(string_view_t key) const;
bool contains(const char* key) const;
bool contains(const string_t& key) const;
// (2)
bool contains(const json_pointer& ptr) const;
```
Checks whether the value is an object with a member with key `key`.
1. Checks whether the value is an object with a member with key `key`.
2. Checks whether a JSON pointer `ptr` can be resolved, starting at this value.
## Parameters
`key` (in)
: key value to check its existence
`ptr` (in)
: JSON pointer to check its existence
## Return value
`#!cpp true` if the value is an object and has a member with key `key`, `#!cpp false` otherwise.
1. `#!cpp true` if the value is an object and has a member with key `key`, `#!cpp false` otherwise
2. `#!cpp true` if `ptr` can be resolved to a value starting at this view, `#!cpp false` otherwise
## Exception safety
@@ -23,22 +32,34 @@ No-throw guarantee: this function never throws exceptions.
## Complexity
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
known from the index, without reading the key bytes -- before comparing its content.
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already
known from the index, without reading the key bytes -- before comparing its content.
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
[`at`](at.md#complexity) with a JSON pointer.
## Notes
This method always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md)
Overload 1 always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md)
view.
!!! info "Postconditions"
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md).
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md). If
`#!cpp v.contains(ptr)` returns `#!cpp true`, then `#!cpp v[ptr]` is not discarded and `#!cpp v.at(ptr)` does not
throw.
!!! info "Overload 2 never throws"
Unlike [`BasicJsonType::contains(const json_pointer&)`](../basic_json/contains.md), which can throw for certain
malformed pointers (for instance an empty array-index reference token), overload 2 never throws: a missing key,
an out-of-range or malformed array index, a `#!cpp "-"` index, or a reference token used on a primitive all
simply make it return `#!cpp false`.
## Examples
??? example
??? example "Example: (1) check with key"
The example below counts how many of a batch of records carry an optional `retry_of` field, using `contains()`
to check without ever materializing a single record of the batch.
@@ -53,10 +74,26 @@ view.
--8<-- "examples/basic_json_view__contains.output"
```
??? example "Example: (2) check with JSON pointer"
The example below checks an optional, nested field with a JSON pointer, and shows two pointers that
`#!cpp contains()` resolves to `#!cpp false` without throwing.
```cpp
--8<-- "examples/basic_json_view__contains_json_pointer.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__contains_json_pointer.output"
```
## See also
- [find](find.md) - find a value in an object
- [count](count.md) - returns the number of occurrences of a key
- [at](at.md), [operator[]](operator[].md) - resolve a JSON pointer and throw, or return a discarded view
- [`BasicJsonType::contains`](../basic_json/contains.md) - the corresponding function of `basic_json`
## Version history
+130
View File
@@ -0,0 +1,130 @@
# <small>nlohmann::basic_json_view::</small>get
```cpp
template<typename T>
T get() const;
```
Converts the value to `T`.
For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it:
- `#!cpp bool`
- arithmetic types other than `#!cpp bool` (from a number; from a boolean, as `#!cpp 0`/`#!cpp 1`, exactly as
[`BasicJsonType::get<T>()`](../basic_json/get.md) converts a boolean)
- `#!cpp std::nullptr_t`
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) -- a copy of the string
- [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's
[`source()`](../basic_json_document/source.md) text, or, for a string that contains escape sequences, into the
document's own buffer of decoded strings (see [`get_string()`](get_string.md))
- `BasicJsonType` -- equivalent to [`materialize()`](materialize.md)
- `basic_json_view` -- returns `#!cpp *this`
- `#!cpp std::vector<U, A>` -- element by element, each converted with `#!cpp get<U>()`; `#!cpp
std::vector<basic_json_view>` keeps a view of every element instead of a value
- `#!cpp std::map<K, V, C, A>` and `#!cpp std::unordered_map<K, V, H, E, A>`, if `K` is constructible from a `#!cpp
(const char*, std::size_t)` pair -- member by member, each value converted with `#!cpp get<V>()`; with a repeated
key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and
[`materialize()`](materialize.md)) does; `#!cpp std::map<std::string, basic_json_view>` keeps views of the members
instead of values
Every other `T` -- `#!cpp std::list`, `#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a
`from_json()`, ... -- is converted by `#!cpp materialize().get<T>()`: the subtree is built into a real `BasicJsonType`
value first (as [`BasicJsonType::parse()`](../basic_json/parse.md) would), and converted from there exactly as
[`BasicJsonType::get<T>()`](../basic_json/get.md) would convert it.
## Template parameters
`T`
: the type to convert the value to
## Return value
the value, converted to `T`
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
## Exceptions
- For the directly-converted types listed above (other than `BasicJsonType` and `basic_json_view`, which never
throw): throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value's type does not
match `T` -- the same exception, with the same message, that [`BasicJsonType::get<T>()`](../basic_json/get.md)
throws for the same JSON type and `T`.
- For `#!cpp std::vector<U, A>`: throws `type_error.302` if the value is not an array; otherwise, whatever converting
an element to `U` throws.
- For `#!cpp std::map`/`#!cpp std::unordered_map`: throws `type_error.302` if the value is not an object; otherwise,
whatever converting a member to the mapped type throws.
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws -- typically `type_error.302`,
or whatever a user-provided `from_json()` throws.
None of the exceptions thrown directly by this function (the first three bullets above) carry a
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
## Complexity
- `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `basic_json_view`:
constant.
- `#!cpp std::basic_string<char, Traits, Alloc>`: constant, plus one allocation and a copy of the string's bytes.
- `BasicJsonType`: linear in the size of the subtree, see [`materialize()`](materialize.md).
- `#!cpp std::vector<U, A>`: linear in the number of elements, times the complexity of converting one element to `U`.
- `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's
own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp
std::unordered_map`), times the complexity of converting one member to the mapped type.
- every other `T`: linear in the size of the subtree (building the `BasicJsonType` value), plus the complexity of
[`BasicJsonType::get<T>()`](../basic_json/get.md) on it.
## Notes
!!! info "Floating-point values"
A floating-point `T` is converted from the same digits the lexer would see during `#!cpp BasicJsonType::parse()`,
using the same conversion, so the result is bit-for-bit identical to `#!cpp BasicJsonType::parse(text).get<T>()`
for the same source text.
!!! info "Duplicate keys"
`#!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
of [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md), which resolve to the
*first* occurrence (see the [Notes on duplicate keys](operator[].md#notes)).
!!! info "No pointers, references, or implicit conversion"
Unlike `BasicJsonType`, `basic_json_view` has no stored value anywhere to hand out a pointer or a reference to, so
it provides neither `#!cpp get_ptr()`, `#!cpp get_ref()`, nor `#!cpp operator ValueType()`.
[`get_string()`](get_string.md) (equivalently, `#!cpp get<string_view_t>()`) is the zero-copy alternative for
strings.
## Examples
??? example
The example below reads typed fields straight into C++ variables, collects a view of every array element with
`#!cpp get<std::vector<basic_json_view>>()` instead of a value, and converts a nested object into a user type
through its `from_json()` -- which runs on a `BasicJsonType` value `materialize()` builds for just that one
member.
```cpp
--8<-- "examples/basic_json_view__get.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__get.output"
```
## See also
- [get_to](get_to.md) - convert and write into a passed value
- [get_string](get_string.md) - the string, without a copy
- [number_token](number_token.md) - a number's token text, without a copy
- [materialize](materialize.md) - build the `BasicJsonType` value of this subtree
- [`BasicJsonType::get`](../basic_json/get.md) - the corresponding function of `basic_json`
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,71 @@
# <small>nlohmann::basic_json_view::</small>get_string
```cpp
string_view_t get_string() const;
```
Returns the string value as a [`string_view_t`](index.md#member-types), without copying it.
## Return value
The string, as a [`string_view_t`](index.md#member-types) that points either into the document's
[`source()`](../basic_json_document/source.md) text (a string with no escape sequences), or into the document's own
buffer of decoded strings (a string that contains escape sequences, such as `#!json "\n"` or `#!json "\u00e9"`, which
had to be decoded once when the document was parsed).
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
## Exceptions
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
`"type must be string, but is array"`.
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
Constant.
## Notes
`basic_json_view` has no `BasicJsonType` value stored anywhere, so unlike `BasicJsonType`, it has no `get_ref()` to
hand out a reference to a stored `string_t`. `get_string()` (equivalently, [`get<string_view_t>()`](get.md)) is the
zero-copy alternative: [`BasicJsonType::get_ref<const string_t&>()`](../basic_json/get_ref.md) is its closest
counterpart, except that it returns a view instead of a reference to a value that must already exist.
The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the
[validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's
source text.
## Examples
??? example
The example below pulls one field out of a JSON text that stands in for a large API response, and shows that no
`#!cpp std::string` was allocated for it: the returned view still points inside the original buffer. A field that
contains an escape sequence cannot point into the original text -- it was decoded once into the document's own
buffer instead -- but still avoids a per-field allocation.
```cpp
--8<-- "examples/basic_json_view__get_string.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__get_string.output"
```
## See also
- [get](get.md) - convert the value to a given type (`#!cpp get<string_view_t>()` is equivalent to this function)
- [number_token](number_token.md) - a number's token text, without a copy
- [`BasicJsonType::get_ref`](../basic_json/get_ref.md) - the closest counterpart of `basic_json`
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,65 @@
# <small>nlohmann::basic_json_view::</small>get_to
```cpp
template<typename T>
T& get_to(T& v) const;
```
Converts the value to `T` and assigns it to `v`. Equivalent to
```cpp
v = get<T>();
return v;
```
## Template parameters
`T`
: the type to convert the value to
## Parameters
`v` (out)
: the variable to store the converted value in
## Return value
`v`, allowing calls to chain
## Exception safety
Strong exception safety: if an exception is thrown, `v` is not modified.
## Exceptions
Whatever [`get<T>()`](get.md) throws for the same value and `T`.
## Complexity
Whatever [`get<T>()`](get.md) has for the same `T`.
## Examples
??? example
The example below reads several fields of a service configuration directly into existing variables, then uses
the returned reference to fold the `#!cpp host`/`#!cpp port` pair into a single string in the same expression.
```cpp
--8<-- "examples/basic_json_view__get_to.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__get_to.output"
```
## See also
- [get](get.md) - convert the value to a given type
- [`BasicJsonType::get_to`](../basic_json/get_to.md) - the corresponding function of `basic_json`
## Version history
- Added in version 3.13.0.
+10 -2
View File
@@ -20,8 +20,11 @@ Moving the document itself does not invalidate its views: the index is heap-allo
`basic_json_document` object.
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element
access, lookup, iteration, and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on
demand. It does not (yet) provide `get<T>()`, JSON Pointer support, `dump()`, or comparison.
access, lookup, iteration, and conversion -- [`get<T>()`](get.md), [`get_string()`](get_string.md),
[`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a
subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and
[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). It does not (yet) provide `dump()` or
comparison.
## Template parameters
@@ -72,6 +75,7 @@ demand. It does not (yet) provide `get<T>()`, JSON Pointer support, `dump()`, or
- [**at**](at.md) - access specified element with bounds checking
- [**operator[]**](operator[].md) - access specified element
- [**value**](value.md) - access specified element with default value
- [**front**](front.md) - access the first element
- [**back**](back.md) - access the last element
@@ -96,6 +100,10 @@ demand. It does not (yet) provide `get<T>()`, JSON Pointer support, `dump()`, or
### Conversion
- [**get**](get.md) - get a value
- [**get_to**](get_to.md) - get a value and write it to a destination
- [**get_string**](get_string.md) - get a string value without a copy
- [**number_token**](number_token.md) - get a number's token text without a copy
- [**materialize**](materialize.md) - build the `BasicJsonType` value of this subtree
### Source access
@@ -0,0 +1,74 @@
# <small>nlohmann::basic_json_view::</small>number_token
```cpp
string_view_t number_token() const;
```
Returns the number exactly as it appears in the source text, without parsing or rounding it.
## Return value
The number's token text, as a [`string_view_t`](index.md#member-types) into the document's
[`source()`](../basic_json_document/source.md) text -- for example `#!cpp "1.50"`, `#!cpp "1E2"`, `#!cpp "-0"`, or an
integer literal with more digits than any number type holds (such as a 30-digit integer, which [`get<T>()`](get.md)
and [`materialize()`](materialize.md) can only represent approximately, as a `number_float_t`).
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
## Exceptions
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
`"type must be number, but is string"`.
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
Constant.
## Notes
`BasicJsonType` has no counterpart to this function: once a number is parsed into `number_integer_t`,
`number_unsigned_t`, or `number_float_t`, its original textual form (leading zeros aside, which are already rejected
by the grammar; trailing zeros in the fraction; the case and sign of the exponent; ...) is gone. `number_token()` is
useful precisely where that form must survive -- a price or an identifier that must be reproduced exactly, or a
number too large for any of `BasicJsonType`'s number types to hold without loss.
The returned [`string_view_t`](index.md#member-types) always points into the document's
[`source()`](../basic_json_document/source.md) text -- numbers are never decoded into the document's separate string
buffer -- and is valid exactly as long as that text is, see the [validity rules](index.md) of `basic_json_view`.
## Examples
??? example
The example below keeps a price and an order ID exactly as they were written in an incoming order, where
converting them with [`get<T>()`](get.md) would lose information: the price picks up floating-point rounding, and
the order ID -- more digits than a 64-bit integer holds -- can only be approximated as a `#!cpp double` once
`#!cpp materialize()`d.
```cpp
--8<-- "examples/basic_json_view__number_token.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__number_token.output"
```
## See also
- [get](get.md) - convert the value to a given type
- [get_string](get_string.md) - the string, without a copy
- [`BasicJsonType::number_integer_t`](../basic_json/number_integer_t.md),
[`number_unsigned_t`](../basic_json/number_unsigned_t.md), [`number_float_t`](../basic_json/number_float_t.md) - the
number types `#!cpp get<T>()` and `materialize()` convert into
## Version history
- Added in version 3.13.0.
@@ -9,12 +9,18 @@ basic_json_view operator[](const string_t& key) const;
// (2)
basic_json_view operator[](size_type idx) const;
basic_json_view operator[](int idx) const;
// (3)
basic_json_view operator[](const json_pointer& ptr) const;
```
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
the [Notes](#notes) below) -- or a [discarded](is_discarded.md) view if there is no such member.
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.)
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
[Return value](#return-value) and [Exceptions](#exceptions) below).
## Parameters
@@ -24,11 +30,18 @@ basic_json_view operator[](int idx) const;
`idx` (in)
: index of the element to access
`ptr` (in)
: JSON pointer to the element to access
## 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
member has this key
2. the element at index `idx`, or a discarded view if `#!cpp is_array()` is `#!cpp false` or `#!cpp idx >= size()`
3. the value `ptr` resolves to, starting at this value, or a discarded view for exactly the reference tokens where the
**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
range
## Exception safety
@@ -42,9 +55,19 @@ Strong exception safety: if an exception is thrown, there are no changes to the
2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an array --
the same exception, with the same message, that the **const** overload of
[`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a numeric argument on a non-array value.
3. Throws the same exceptions, with the same messages, that the **const** overload of
[`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for the same pointer and the same document:
- [`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402) if a reference token is `#!cpp "-"`
at an array.
- [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if a reference token cannot be
resolved because it is used on a primitive value.
- [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) if an array index in `ptr` begins
with `#!cpp '0'`.
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
number.
Neither exception carries a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType`
value to point at, so the exception is created without one, even if `BasicJsonType` was built with
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
@@ -55,6 +78,8 @@ value to point at, so the exception is created without one, even if `BasicJsonTy
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
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
that level (as 1.) or the index into the array (as 2.).
## Notes
@@ -74,9 +99,18 @@ document.
early. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
duplicates, in document order. See the example below and [`size()`](size.md#notes).
!!! info "JSON pointer resolution"
Overload 3 walks `ptr` one reference token at a time, starting at this value, the same way [`at`](at.md) and
[`contains`](contains.md) do. It only ever returns a discarded view where the **const** overload of
`BasicJsonType::operator[]` would be undefined behavior for the same pointer -- a missing object member or an
out-of-range array index -- and still throws for every other way `ptr` can fail to resolve. See
[`at`](at.md#exceptions) for the checked version, which throws in every case instead, and
[`contains`](contains.md) for a version that never throws.
## Examples
??? example
??? example "Example: (1)/(2) access specified element"
The example below reads a couple of fields out of a batch of user records without ever materializing a full
`BasicJsonType` value for the batch. `operator[]` is used both to look up an optional object member and to index
@@ -93,12 +127,29 @@ document.
--8<-- "examples/basic_json_view__operator[].output"
```
??? example "Example: (3) access specified element via JSON pointer"
The example below reaches straight into one deeply nested field of a large document with a single JSON pointer,
without ever building a tree for the rest of it, and shows the discarded-view and throwing outcomes of a pointer
that cannot be fully resolved.
```cpp
--8<-- "examples/basic_json_view__operator[]_json_pointer.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__operator[]_json_pointer.output"
```
## See also
- [at](at.md) - access specified element with bounds checking (throws instead of returning a discarded view)
- [front](front.md), [back](back.md) - access the first or last element
- [find](find.md), [contains](contains.md) - look up a member without throwing
- [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) - the corresponding function of `basic_json`
- [`json_pointer`](../json_pointer/index.md) - JSON pointer type used by overload 3
## Version history
@@ -0,0 +1,131 @@
# <small>nlohmann::basic_json_view::</small>value
```cpp
// (1)
template<typename T>
T value(string_view_t key, const T& default_value) const;
string_t value(string_view_t key, const char* default_value) const;
// (2)
template<typename T>
T value(const json_pointer& ptr, const T& default_value) const;
string_t value(const json_pointer& ptr, const char* default_value) const;
```
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
[Notes on duplicate keys](operator[].md#notes)) -- converted to `T`, or `default_value` if there is no such member.
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.
Both overloads have a dedicated `#!cpp const char*` overload, so `#!cpp v.value(key, "default")` (and the JSON pointer
equivalent) deduce `string_t`, not `const char*`, for their return type and for the comparison used to pick between
`key` and `default_value`.
## Template parameters
`T`
: the type to convert the found value to; also the type of `default_value`
## Parameters
`key` (in)
: object key of the element to access
`ptr` (in)
: JSON pointer to the element to access
`default_value` (in)
: the value to return if `key`/`ptr` resolves to no value
## Return value
1. the first member with key `key`, converted to `T`, or `default_value`
2. the value `ptr` resolves to, converted to `T`, or `default_value`
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
## Exceptions
1. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if the value is not an object --
the same exception, with the same message, that [`BasicJsonType::value`](../basic_json/value.md) throws for the
same call. If a member with key `key` is found, throws whatever converting it to `T` throws (typically
[`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302), with the same message
[`BasicJsonType::value`](../basic_json/value.md) throws for the same mismatch); a missing member never throws.
2. Throws [`type_error.306`](../../home/exceptions.md#jsonexceptiontype_error306) if this value -- not the value `ptr`
resolves to -- is neither an object nor an array. Throws [`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106)
or [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if `ptr` contains a malformed array
index. If `ptr` resolves to a value, throws whatever converting it to `T` throws. Every other way `ptr` can fail to
resolve -- a missing key, an out-of-range or "`-`" array index, an unresolvable token on a primitive -- yields
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
`out_of_range` and returns `default_value`.
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
`JSON_DIAGNOSTICS` enabled.
## Complexity
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
`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
that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and
[`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
to `T`.
## Notes
!!! info "Differences to `at` and `operator[]`"
Unlike [`at`](at.md), this function does not throw if `key`/`ptr` resolves to no value. Unlike
[`operator[]`](operator[].md), it never returns a [discarded](is_discarded.md) view -- it always returns a `T` --
and it is available on any view, since it never needs to insert a missing element the way the non-const
`BasicJsonType::operator[]` would.
!!! info "Which values can be asked"
As for [`BasicJsonType::value`](../basic_json/value.md), the key overload (1) requires an object, and the JSON
pointer overload (2) an object or an array.
## Examples
??? example "Example: (1) access specified object element with default value"
The example below reads a couple of optional configuration fields with a default, so that a missing key never
needs a `#!cpp try`/`#!cpp catch` of its own.
```cpp
--8<-- "examples/basic_json_view__value.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__value.output"
```
??? example "Example: (2) access specified element via JSON pointer with default value"
The example below reads an optional, nested configuration value with a default, given as a JSON pointer.
```cpp
--8<-- "examples/basic_json_view__value_json_pointer.cpp"
```
Output:
```json
--8<-- "examples/basic_json_view__value_json_pointer.output"
```
## See also
- [at](at.md) - access specified element with bounds checking (throws instead of returning a default value)
- [operator[]](operator[].md) - access specified element (returns a discarded view instead of a default value)
- [`BasicJsonType::value`](../basic_json/value.md) - the corresponding function of `basic_json`
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,34 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
using json_pointer = nlohmann::json::json_pointer;
int main()
{
json_document doc = json_document::parse(R"({"region": "eu", "servers": ["eu-1", "eu-2"]})");
const auto root = doc.root();
std::cout << root.at(json_pointer("/servers/1")).materialize().dump() << '\n';
// at() throws for every resolution failure -- with the very same
// message json::at(ptr) would throw for the same pointer and the same
// document
try
{
static_cast<void>(root.at(json_pointer("/servers/5")));
}
catch (const nlohmann::json::out_of_range& e)
{
std::cout << e.what() << '\n';
}
try
{
static_cast<void>(root.at(json_pointer("/missing")));
}
catch (const nlohmann::json::out_of_range& e)
{
std::cout << e.what() << '\n';
}
}
@@ -0,0 +1,3 @@
"eu-2"
[json.exception.out_of_range.401] array index 5 is out of range
[json.exception.out_of_range.403] key 'missing' not found
@@ -0,0 +1,39 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
using json_pointer = nlohmann::json::json_pointer;
int main()
{
// "retry_of" is only present on some records, nested a level down
// inside "meta"
json_document batch = json_document::parse(R"(
[
{"id": 1, "meta": {}},
{"id": 2, "meta": {"retry_of": 1}}
]
)");
const auto records = batch.root();
const json_pointer retry_of("/meta/retry_of");
for (std::size_t i = 0; i < records.size(); ++i)
{
const auto record = records[i];
if (record.contains(retry_of))
{
std::cout << "record " << i << " is a retry of " << record[retry_of].get<int>() << '\n';
}
else
{
std::cout << "record " << i << " is original\n";
}
}
// contains() with a JSON pointer never throws -- not even for a
// pointer that indexes into a primitive ("/0/id/x") or uses a
// malformed array index ("/01"), either of which would need a
// try/catch with json::contains(ptr)
std::cout << std::boolalpha << records.contains(json_pointer("/0/id/x")) << '\n';
std::cout << std::boolalpha << records.contains(json_pointer("/01")) << '\n';
}
@@ -0,0 +1,4 @@
record 0 is original
record 1 is a retry of 1
false
false
@@ -0,0 +1,55 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
#include <string>
#include <vector>
using json_document = nlohmann::json_document;
using json_view = nlohmann::json_view;
// address has no direct conversion in get<T>(), so get<address>() falls back
// to materialize().get<address>() -- a real nlohmann::json value is built
// for just this one member, and its own from_json() runs on that
struct address
{
std::string city;
int zip = 0;
};
void from_json(const nlohmann::json& j, address& a)
{
j.at("city").get_to(a.city);
j.at("zip").get_to(a.zip);
}
int main()
{
json_document doc = json_document::parse(R"(
{
"name": "Alice",
"active": true,
"orders": [1, 2, 3],
"address": {"city": "Berlin", "zip": 10115}
}
)");
const json_view customer = doc.root();
// read typed fields straight into C++ variables -- none of these build
// a nlohmann::json value
const std::string name = customer["name"].get<std::string>();
const bool active = customer["active"].get<bool>();
std::cout << name << (active ? " (active)" : " (inactive)") << '\n';
// std::vector<json_view> keeps views of the array elements instead of
// copies of their values
bool first = true;
for (const json_view order : customer["orders"].get<std::vector<json_view>>())
{
std::cout << (first ? "" : " ") << order.get<int>();
first = false;
}
std::cout << '\n';
// everything else goes through materialize()
const address a = customer["address"].get<address>();
std::cout << a.city << ' ' << a.zip << '\n';
}
@@ -0,0 +1,3 @@
Alice (active)
1 2 3
Berlin 10115
@@ -0,0 +1,31 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
#include <string>
using json_document = nlohmann::json_document;
using json_view = nlohmann::json_view;
int main()
{
// a "large response" stand-in: only the "id" field is ever read out of it
const std::string text =
R"({"id": "8f14e45f-ceea-467e-bb92-963f5e3c7a08", "note": "created via API\n", "payload": "..."})";
const json_document doc = json_document::parse(text);
const json_view response = doc.root();
const json_view::string_view_t id = response["id"].get_string();
std::cout << id << '\n';
// no std::string was allocated for "id": its bytes still live inside
// the original buffer, so id's data lies inside [text.data(),
// text.data() + text.size())
const bool id_in_source = id.data() >= text.data() && id.data() + id.size() <= text.data() + text.size();
std::cout << std::boolalpha << id_in_source << '\n';
// "note" contains an escape sequence ('\n'), so it was decoded once
// into the document's own buffer -- get_string() still avoids a copy
// into a new std::string, but the bytes no longer live inside "text"
const json_view::string_view_t note = response["note"].get_string();
const bool note_in_source = note.data() >= text.data() && note.data() + note.size() <= text.data() + text.size();
std::cout << std::boolalpha << note_in_source << '\n';
}
@@ -0,0 +1,3 @@
8f14e45f-ceea-467e-bb92-963f5e3c7a08
true
false
@@ -0,0 +1,28 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
#include <string>
using json_document = nlohmann::json_document;
using json_view = nlohmann::json_view;
int main()
{
json_document doc = json_document::parse(R"({"host": "db.example.com", "port": 5432, "ssl": true})");
const json_view config = doc.root();
// get_to() writes directly into existing variables -- handy for filling
// in the members of a struct one field at a time, without an
// intermediate value from get<T>() for each one
std::string host;
int port = 0;
bool ssl = false;
config["host"].get_to(host);
config["port"].get_to(port);
config["ssl"].get_to(ssl);
std::cout << host << ':' << port << (ssl ? " (tls)" : "") << '\n';
// the return value is a reference to the argument, so a call can be
// used directly in a larger expression
std::string other_host;
std::cout << config["host"].get_to(other_host).size() << '\n';
}
@@ -0,0 +1,2 @@
db.example.com:5432 (tls)
14
@@ -0,0 +1,29 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
using json_view = nlohmann::json_view;
int main()
{
// a price and an order id from an incoming order -- both need to be
// reproduced exactly, e.g. for an invoice or an audit log
json_document doc = json_document::parse(R"(
{"price": 19.90, "order_id": 1234567890123456789012345, "quantity": 3}
)");
const json_view order = doc.root();
// number_token() returns the number exactly as written in the source
std::cout << order["price"].number_token() << '\n';
std::cout << order["order_id"].number_token() << '\n';
// get<double>() converts it instead -- the exact source text is gone:
// "19.90" becomes the double closest to 19.9, printed without the
// trailing zero, and the 25-digit order id -- far beyond any 64-bit
// integer -- can only be approximated as a double
std::cout << order["price"].get<double>() << '\n';
std::cout << order.materialize()["order_id"].dump() << '\n';
// an ordinary quantity has nothing to lose either way
std::cout << order["quantity"].number_token() << " == " << order["quantity"].get<int>() << '\n';
}
@@ -0,0 +1,5 @@
19.90
1234567890123456789012345
19.9
1.2345678901234568e+24
3 == 3
@@ -0,0 +1,47 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
using json_pointer = nlohmann::json::json_pointer;
int main()
{
// a larger document; operator[] with a JSON pointer reaches straight to
// one deeply nested field, without ever building a tree for the rest
json_document doc = json_document::parse(R"(
{
"region": {
"servers": [
{"name": "eu-1", "metrics": {"cpu": 0.42}},
{"name": "eu-2", "metrics": {"cpu": 0.71}}
]
}
}
)");
const auto root = doc.root();
std::cout << root[json_pointer("/region/servers/1/metrics/cpu")].materialize().dump() << '\n';
// a missing key or an out-of-range index along the path gives a
// discarded view, exactly where const json::operator[] would be
// undefined behavior for the same pointer
if (const auto missing = root[json_pointer("/region/servers/5/metrics/cpu")])
{
std::cout << missing.materialize().dump() << '\n';
}
else
{
std::cout << "no such server\n";
}
// indexing into a primitive still throws, as basic_json::operator[]
// does for the same pointer
try
{
static_cast<void>(root[json_pointer("/region/servers/0/name/x")]);
}
catch (const nlohmann::json::out_of_range& e)
{
std::cout << e.what() << '\n';
}
}
@@ -0,0 +1,3 @@
0.71
no such server
[json.exception.out_of_range.404] unresolved reference token 'x'
@@ -0,0 +1,29 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
#include <string>
using json_document = nlohmann::json_document;
using json_view = nlohmann::json_view;
int main()
{
json_document doc = json_document::parse(R"({"server": {"host": "localhost"}})");
const json_view server = doc.root()["server"];
// "port" is missing -- value() returns the default instead of
// throwing, so optional configuration fields never need their own
// try/catch
std::cout << server.value("host", std::string("0.0.0.0")) << '\n';
std::cout << server.value("port", 8080) << '\n';
// a present but wrong-typed default still throws -- value() only
// replaces "not found", not "wrong type", exactly as basic_json::value
try
{
static_cast<void>(server.value("host", 0));
}
catch (const nlohmann::json::type_error& e)
{
std::cout << e.what() << '\n';
}
}
@@ -0,0 +1,3 @@
localhost
8080
[json.exception.type_error.302] type must be number, but is string
@@ -0,0 +1,21 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
using json_view = nlohmann::json_view;
using json_pointer = nlohmann::json::json_pointer;
int main()
{
json_document doc = json_document::parse(R"({"server": {"host": "localhost", "limits": {"connections": 100}}})");
const json_view config = doc.root();
// a nested, optional setting read with a default -- no exception, even
// though "timeout" is missing several levels down
std::cout << config.value(json_pointer("/server/limits/connections"), 10) << '\n';
std::cout << config.value(json_pointer("/server/limits/timeout"), 30) << '\n';
// an out-of-range array index also falls back to the default
json_document list_doc = json_document::parse(R"({"servers": ["a", "b"]})");
std::cout << list_doc.root().value(json_pointer("/servers/5"), std::string("none")) << '\n';
}
@@ -0,0 +1,3 @@
100
30
none
+25 -1
View File
@@ -139,9 +139,33 @@ whenever any of the other conditions above was not met.
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
`BasicJsonType` was built.
- **`get<T>()`, JSON Pointer, `dump()`, and comparison are not (yet) provided** by `basic_json_view`. For now,
- **`dump()` and comparison are not (yet) provided** by `basic_json_view`. For now,
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can do those things with.
## Getting values out without copying
[`get<T>()`](../api/basic_json_view/get.md) converts many `T` directly from the flat index, without ever building a
`basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`,
`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json`/`ordered_json` (via
[`materialize()`](../api/basic_json_view/materialize.md)), `basic_json_view` itself, `#!cpp std::vector<U>`, and
`#!cpp std::map`/`#!cpp std::unordered_map` with string-like keys. Every other type -- `#!cpp std::list`,
`#!cpp std::pair`, `#!cpp std::array`, enumerations, user types with a `from_json()` -- goes through
[`materialize()`](../api/basic_json_view/materialize.md)`.get<T>()` instead: the subtree is built into a real
`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there.
Two conversions never copy at all:
- [`get_string()`](../api/basic_json_view/get_string.md) (equivalently, `#!cpp get<string_view_t>()`) returns a
string as a `string_view_t` pointing into the document's [`source()`](../api/basic_json_document/source.md) text --
or, for a string that contains escape sequences, into the document's own buffer of decoded strings -- instead of
allocating a new `#!cpp std::string`.
- [`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written in the
source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of
rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get<T>()` (and
[`basic_json::parse()`](../api/basic_json/parse.md)) would.
Both results are only valid as long as the view -- and, for a string with no escapes, the borrowed source text -- is.
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) |
+5
View File
@@ -257,6 +257,9 @@ nav:
- 'end': api/basic_json_view/end.md
- 'find': api/basic_json_view/find.md
- 'front': api/basic_json_view/front.md
- 'get': api/basic_json_view/get.md
- 'get_string': api/basic_json_view/get_string.md
- 'get_to': api/basic_json_view/get_to.md
- 'is_array': api/basic_json_view/is_array.md
- 'is_binary': api/basic_json_view/is_binary.md
- 'is_boolean': api/basic_json_view/is_boolean.md
@@ -272,12 +275,14 @@ nav:
- 'is_structured': api/basic_json_view/is_structured.md
- 'items': api/basic_json_view/items.md
- 'materialize': api/basic_json_view/materialize.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[].md
- 'size': api/basic_json_view/size.md
- 'source_offset': api/basic_json_view/source_offset.md
- 'type': api/basic_json_view/type.md
- 'type_name': api/basic_json_view/type_name.md
- 'value': api/basic_json_view/value.md
- byte_container_with_subtype:
- 'Overview': api/byte_container_with_subtype/index.md
- '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md
+21
View File
@@ -30,6 +30,11 @@
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
struct json_pointer_access;
} // namespace detail
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
@@ -42,6 +47,8 @@ class json_pointer
template<typename>
friend class json_pointer;
friend struct detail::json_pointer_access;
template<typename T>
struct string_t_helper
{
@@ -1164,4 +1171,18 @@ inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
}
#endif
namespace detail
{
/// the reference tokens of a json_pointer, for code that resolves pointers
/// without a basic_json value (such as the zero-copy view)
struct json_pointer_access
{
template<typename RefStringType>
static const std::vector<typename json_pointer<RefStringType>::string_t>& reference_tokens(const json_pointer<RefStringType>& ptr) noexcept
{
return ptr.reference_tokens;
}
};
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+6
View File
@@ -40,6 +40,12 @@ namespace view
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
}
/// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
{
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
}
/*!
@brief throw the exception BasicJsonType::parse would throw for this input
+171
View File
@@ -0,0 +1,171 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef> // size_t
#include <cstdint> // uint64_t
#include <limits> // numeric_limits
#include <string> // string, to_string
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/errors.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
/// what resolving a JSON pointer does where it cannot continue
enum class pointer_mode
{
unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined
checked, ///< as basic_json::at(): out_of_range.401/403
value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used)
contains, ///< as basic_json::contains(): no exceptions at all
};
/// the outcome of reading an array index from a reference token
enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them
template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept
{
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))
{
return index_status::not_number;
}
if (s.empty())
{
return index_status::unresolved;
}
std::uint64_t v = 0;
for (std::size_t i = 0; i < s.size(); ++i)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
}
/// throws the exception json_pointer::array_index throws for this status
template<typename StringType>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
{
switch (status)
{
case index_status::leading_zero:
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_number:
throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::too_large:
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved:
case index_status::ok:
default:
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
}
}
/*!
@brief resolve the reference tokens of a JSON pointer, starting at a view
The exceptions are those basic_json throws for the same pointer; where
basic_json's behavior is undefined (a missing key or an index out of range
with const operator[]), the result is a discarded view.
*/
template<typename View, typename Tokens>
View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
using string_view_t = typename View::string_view_t;
const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked;
for (const auto& token : tokens)
{
if (cur.is_object())
{
const auto it = cur.find(string_view_t(token.data(), token.size()));
if (it == cur.end())
{
if (mode == pointer_mode::checked)
{
throw_out_of_range(403, concat("key '", token, "' not found"));
}
return View();
}
cur = *it;
}
else if (cur.is_array())
{
if (token.size() == 1 && token[0] == '-')
{
if (throwing)
{
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
}
return View();
}
std::size_t idx = 0;
const index_status status = array_index(token, idx);
if (status != index_status::ok)
{
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
if (throwing || (mode == pointer_mode::value && parse_error))
{
throw_array_index_error(status, token);
}
return View();
}
if (idx >= cur.size())
{
if (mode == pointer_mode::checked)
{
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
}
return View();
}
cur = cur[idx];
}
else
{
if (throwing)
{
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
}
return View();
}
}
return cur;
}
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+108
View File
@@ -0,0 +1,108 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstdint> // int64_t
#include <map> // map
#include <string> // basic_string
#include <type_traits> // enable_if, is_constructible
#include <unordered_map> // unordered_map
#include <vector> // vector
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/errors.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp>
#include <nlohmann/detail/view/number.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
/// selects a conversion by its target type
template<typename T>
struct value_tag {};
/*!
@brief the number or boolean of a node converted to an arithmetic type
As basic_json's get<T>() for arithmetic types: integers and floats are
converted with static_cast, booleans give 0 or 1, and other types throw
type_error.302.
*/
template<typename T, typename BasicJsonType>
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
{
switch (static_cast<value_t>(n.kind))
{
case value_t::number_unsigned:
return static_cast<T>(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(n)));
case value_t::number_integer:
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
case value_t::number_float:
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d.str(n), n));
case value_t::boolean:
return static_cast<T>((n.flags & node_flags::is_true) != 0);
case value_t::null:
case value_t::object:
case value_t::array:
case value_t::string:
case value_t::binary:
case value_t::discarded:
default:
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
}
}
/// std::vector from an array, element by element (type_error.302 otherwise)
template<typename View, typename U, typename A>
std::vector<U, A> vector_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{
throw_type_error(302, "type must be array, but is ", v.type_name());
}
std::vector<U, A> r;
r.reserve(v.size());
for (const View e : v)
{
r.push_back(e.template get<U>());
}
return r;
}
/// a map with string keys from an object; with duplicate keys, the last
/// value is kept, as parse() does (type_error.302 for other types)
template<typename Map, typename View>
Map map_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{
throw_type_error(302, "type must be object, but is ", v.type_name());
}
Map r;
for (auto it = v.begin(); it != v.end(); ++it)
{
const auto key = it.key();
r[typename Map::key_type(key.data(), key.size())] = it.value().template get<typename Map::mapped_type>();
}
return r;
}
/// whether a map type is read member by member (its keys are made from
/// characters and a length); other maps go through basic_json
template<typename Key>
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+187 -1
View File
@@ -27,11 +27,14 @@
#include <cstddef> // size_t
#include <cstring> // memcpy, strlen
#include <iterator> // distance, input_iterator_tag, iterator_traits
#include <map> // map
#include <memory> // unique_ptr
#include <string> // string
#include <tuple> // tuple_element, tuple_size
#include <type_traits> // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent
#include <unordered_map> // unordered_map
#include <utility> // forward, move
#include <vector> // vector
#include <nlohmann/json.hpp>
@@ -49,7 +52,9 @@
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/materialize.hpp>
#include <nlohmann/detail/view/node.hpp>
#include <nlohmann/detail/view/pointer.hpp>
#include <nlohmann/detail/view/string_ref.hpp>
#include <nlohmann/detail/view/value.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -268,6 +273,14 @@ class basic_json_view
return operator[](static_cast<size_type>(idx));
}
/// 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
/// basic_json::operator[] throws.
basic_json_view operator[](const json_pointer& ptr) const
{
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
/// 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.
@@ -315,6 +328,51 @@ class basic_json_view
return at(static_cast<size_type>(idx));
}
/// the value a JSON pointer refers to; throws what basic_json::at()
/// throws if it cannot be resolved
basic_json_view at(const json_pointer& ptr) const
{
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked);
}
/// 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
/// 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 >
T value(string_view_t key, const T& default_value) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = lookup(key);
return r ? r.template get<T>() : default_value;
}
string_t value(string_view_t key, const char* default_value) const
{
return value(key, string_t(default_value));
}
/// the value a JSON pointer refers to converted to T, or the default
/// value if the pointer cannot be resolved. Throws type_error.306 if this
/// is neither an object nor an array.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
T value(const json_pointer& ptr, const T& default_value) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{
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);
return r ? r.template get<T>() : default_value;
}
string_t value(const json_pointer& ptr, const char* default_value) const
{
return value(ptr, string_t(default_value));
}
/// the first element or member value; a primitive value itself. Throws
/// invalid_iterator.214 for null, discarded views, and empty containers.
basic_json_view front() const
@@ -381,6 +439,13 @@ class basic_json_view
return contains(string_view_t(key.data(), key.size()));
}
/// whether a JSON pointer can be resolved (never throws, as
/// basic_json::contains())
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));
}
/// 1 if this is an object with a member with this key, else 0 (duplicate
/// keys count once)
size_type count(string_view_t key) const
@@ -438,6 +503,50 @@ class basic_json_view
return detail::view::view_items<basic_json_view>(*this);
}
////////////////
// conversion //
////////////////
/// the value converted to T, as BasicJsonType::get<T>(): arithmetic types,
/// strings (string_view_t without a copy), std::nullptr_t, std::vector,
/// maps with string keys, and views are converted directly; other types
/// through materialize().get<T>()
template<typename T>
NLOHMANN_VIEW_ALWAYS_INLINE T get() const
{
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
}
template<typename T>
T& get_to(T& v) const
{
v = get<T>();
return v;
}
/// the string, without a copy; valid as long as the view is. Throws
/// type_error.302 for other types.
string_view_t get_string() const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
}
return {m_doc->str(*m_node), m_node->len};
}
/// the text of a number as it appears in the source (e.g. "1.50", "1E2",
/// or an integer with more digits than any number type holds). Throws
/// type_error.302 for other types.
string_view_t number_token() const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
}
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
}
/////////////////
// materialize //
/////////////////
@@ -478,6 +587,83 @@ class basic_json_view
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
}
// --- get() dispatch ---
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
}
return (m_node->flags & detail::view::node_flags::is_true) != 0;
}
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 >
NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
}
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
}
std::nullptr_t get_impl(detail::view::value_tag<std::nullptr_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
}
return nullptr;
}
string_view_t get_impl(detail::view::value_tag<string_view_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return get_string();
}
template<typename Traits, typename Alloc>
std::basic_string<char, Traits, Alloc> get_impl(detail::view::value_tag<std::basic_string<char, Traits, Alloc>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
const string_view_t s = get_string();
return std::basic_string<char, Traits, Alloc>(s.data(), s.size());
}
BasicJsonType get_impl(detail::view::value_tag<BasicJsonType> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return materialize();
}
basic_json_view get_impl(detail::view::value_tag<basic_json_view> /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept
{
return *this;
}
template<typename U, typename A>
std::vector<U, A> get_impl(detail::view::value_tag<std::vector<U, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return detail::view::vector_value<basic_json_view, U, A>(*this);
}
template<typename K, typename V, typename C, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
std::map<K, V, C, A> get_impl(detail::view::value_tag<std::map<K, V, C, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return detail::view::map_value<std::map<K, V, C, A>>(*this);
}
template<typename K, typename V, typename H, typename E, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
std::unordered_map<K, V, H, E, A> get_impl(detail::view::value_tag<std::unordered_map<K, V, H, E, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return detail::view::map_value<std::unordered_map<K, V, H, E, A>>(*this);
}
/// everything else through the BasicJsonType value (from_json included)
template<typename T>
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<0> /*unused*/) const
{
return materialize().template get<T>();
}
const document_data* m_doc = nullptr;
const node* m_node = nullptr;
};
+21
View File
@@ -19781,6 +19781,11 @@ NLOHMANN_JSON_NAMESPACE_END
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
struct json_pointer_access;
} // namespace detail
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
@@ -19793,6 +19798,8 @@ class json_pointer
template<typename>
friend class json_pointer;
friend struct detail::json_pointer_access;
template<typename T>
struct string_t_helper
{
@@ -20915,6 +20922,20 @@ inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
}
#endif
namespace detail
{
/// the reference tokens of a json_pointer, for code that resolves pointers
/// without a basic_json value (such as the zero-copy view)
struct json_pointer_access
{
template<typename RefStringType>
static const std::vector<typename json_pointer<RefStringType>::string_t>& reference_tokens(const json_pointer<RefStringType>& ptr) noexcept
{
return ptr.reference_tokens;
}
};
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/json_ref.hpp>
+481 -1
View File
@@ -27,11 +27,14 @@
#include <cstddef> // size_t
#include <cstring> // memcpy, strlen
#include <iterator> // distance, input_iterator_tag, iterator_traits
#include <map> // map
#include <memory> // unique_ptr
#include <string> // string
#include <tuple> // tuple_element, tuple_size
#include <type_traits> // enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_base_of, is_integral, is_same, remove_cv, remove_extent
#include <unordered_map> // unordered_map
#include <utility> // forward, move
#include <vector> // vector
#include <nlohmann/json.hpp>
@@ -1617,6 +1620,12 @@ namespace view
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
}
/// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
{
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
}
/*!
@brief throw the exception BasicJsonType::parse would throw for this input
@@ -2381,6 +2390,181 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/node.hpp>
// #include <nlohmann/detail/view/pointer.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include <cstddef> // size_t
#include <cstdint> // uint64_t
#include <limits> // numeric_limits
#include <string> // string, to_string
// #include <nlohmann/json.hpp>
// #include <nlohmann/detail/view/errors.hpp>
// #include <nlohmann/detail/view/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
/// what resolving a JSON pointer does where it cannot continue
enum class pointer_mode
{
unchecked, ///< as const basic_json::operator[]: a discarded view where basic_json's behavior is undefined
checked, ///< as basic_json::at(): out_of_range.401/403
value, ///< as basic_json::value(): no out_of_range exceptions (the default value is used)
contains, ///< as basic_json::contains(): no exceptions at all
};
/// the outcome of reading an array index from a reference token
enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them
template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept
{
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
if (s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))
{
return index_status::not_number;
}
if (s.empty())
{
return index_status::unresolved;
}
std::uint64_t v = 0;
for (std::size_t i = 0; i < s.size(); ++i)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
}
/// throws the exception json_pointer::array_index throws for this status
template<typename StringType>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
{
switch (status)
{
case index_status::leading_zero:
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_number:
throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::too_large:
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved:
case index_status::ok:
default:
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
}
}
/*!
@brief resolve the reference tokens of a JSON pointer, starting at a view
The exceptions are those basic_json throws for the same pointer; where
basic_json's behavior is undefined (a missing key or an index out of range
with const operator[]), the result is a discarded view.
*/
template<typename View, typename Tokens>
View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
using string_view_t = typename View::string_view_t;
const bool throwing = mode == pointer_mode::unchecked || mode == pointer_mode::checked;
for (const auto& token : tokens)
{
if (cur.is_object())
{
const auto it = cur.find(string_view_t(token.data(), token.size()));
if (it == cur.end())
{
if (mode == pointer_mode::checked)
{
throw_out_of_range(403, concat("key '", token, "' not found"));
}
return View();
}
cur = *it;
}
else if (cur.is_array())
{
if (token.size() == 1 && token[0] == '-')
{
if (throwing)
{
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
}
return View();
}
std::size_t idx = 0;
const index_status status = array_index(token, idx);
if (status != index_status::ok)
{
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
if (throwing || (mode == pointer_mode::value && parse_error))
{
throw_array_index_error(status, token);
}
return View();
}
if (idx >= cur.size())
{
if (mode == pointer_mode::checked)
{
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
}
return View();
}
cur = cur[idx];
}
else
{
if (throwing)
{
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
}
return View();
}
}
return cur;
}
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/string_ref.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
@@ -2495,6 +2679,121 @@ class string_ref
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/value.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include <cstdint> // int64_t
#include <map> // map
#include <string> // basic_string
#include <type_traits> // enable_if, is_constructible
#include <unordered_map> // unordered_map
#include <vector> // vector
// #include <nlohmann/json.hpp>
// #include <nlohmann/detail/view/document_data.hpp>
// #include <nlohmann/detail/view/errors.hpp>
// #include <nlohmann/detail/view/macro_scope.hpp>
// #include <nlohmann/detail/view/node.hpp>
// #include <nlohmann/detail/view/number.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
/// selects a conversion by its target type
template<typename T>
struct value_tag {};
/*!
@brief the number or boolean of a node converted to an arithmetic type
As basic_json's get<T>() for arithmetic types: integers and floats are
converted with static_cast, booleans give 0 or 1, and other types throw
type_error.302.
*/
template<typename T, typename BasicJsonType>
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
{
switch (static_cast<value_t>(n.kind))
{
case value_t::number_unsigned:
return static_cast<T>(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(n)));
case value_t::number_integer:
return static_cast<T>(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(n))));
case value_t::number_float:
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d.str(n), n));
case value_t::boolean:
return static_cast<T>((n.flags & node_flags::is_true) != 0);
case value_t::null:
case value_t::object:
case value_t::array:
case value_t::string:
case value_t::binary:
case value_t::discarded:
default:
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
}
}
/// std::vector from an array, element by element (type_error.302 otherwise)
template<typename View, typename U, typename A>
std::vector<U, A> vector_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{
throw_type_error(302, "type must be array, but is ", v.type_name());
}
std::vector<U, A> r;
r.reserve(v.size());
for (const View e : v)
{
r.push_back(e.template get<U>());
}
return r;
}
/// a map with string keys from an object; with duplicate keys, the last
/// value is kept, as parse() does (type_error.302 for other types)
template<typename Map, typename View>
Map map_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{
throw_type_error(302, "type must be object, but is ", v.type_name());
}
Map r;
for (auto it = v.begin(); it != v.end(); ++it)
{
const auto key = it.key();
r[typename Map::key_type(key.data(), key.size())] = it.value().template get<typename Map::mapped_type>();
}
return r;
}
/// whether a map type is read member by member (its keys are made from
/// characters and a length); other maps go through basic_json
template<typename Key>
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -2713,6 +3012,14 @@ class basic_json_view
return operator[](static_cast<size_type>(idx));
}
/// 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
/// basic_json::operator[] throws.
basic_json_view operator[](const json_pointer& ptr) const
{
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
/// 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.
@@ -2760,6 +3067,51 @@ class basic_json_view
return at(static_cast<size_type>(idx));
}
/// the value a JSON pointer refers to; throws what basic_json::at()
/// throws if it cannot be resolved
basic_json_view at(const json_pointer& ptr) const
{
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::checked);
}
/// 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
/// 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 >
T value(string_view_t key, const T& default_value) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = lookup(key);
return r ? r.template get<T>() : default_value;
}
string_t value(string_view_t key, const char* default_value) const
{
return value(key, string_t(default_value));
}
/// the value a JSON pointer refers to converted to T, or the default
/// value if the pointer cannot be resolved. Throws type_error.306 if this
/// is neither an object nor an array.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
T value(const json_pointer& ptr, const T& default_value) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{
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);
return r ? r.template get<T>() : default_value;
}
string_t value(const json_pointer& ptr, const char* default_value) const
{
return value(ptr, string_t(default_value));
}
/// the first element or member value; a primitive value itself. Throws
/// invalid_iterator.214 for null, discarded views, and empty containers.
basic_json_view front() const
@@ -2826,6 +3178,13 @@ class basic_json_view
return contains(string_view_t(key.data(), key.size()));
}
/// whether a JSON pointer can be resolved (never throws, as
/// basic_json::contains())
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));
}
/// 1 if this is an object with a member with this key, else 0 (duplicate
/// keys count once)
size_type count(string_view_t key) const
@@ -2883,6 +3242,50 @@ class basic_json_view
return detail::view::view_items<basic_json_view>(*this);
}
////////////////
// conversion //
////////////////
/// the value converted to T, as BasicJsonType::get<T>(): arithmetic types,
/// strings (string_view_t without a copy), std::nullptr_t, std::vector,
/// maps with string keys, and views are converted directly; other types
/// through materialize().get<T>()
template<typename T>
NLOHMANN_VIEW_ALWAYS_INLINE T get() const
{
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
}
template<typename T>
T& get_to(T& v) const
{
v = get<T>();
return v;
}
/// the string, without a copy; valid as long as the view is. Throws
/// type_error.302 for other types.
string_view_t get_string() const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
}
return {m_doc->str(*m_node), m_node->len};
}
/// the text of a number as it appears in the source (e.g. "1.50", "1E2",
/// or an integer with more digits than any number type holds). Throws
/// type_error.302 for other types.
string_view_t number_token() const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
}
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
}
/////////////////
// materialize //
/////////////////
@@ -2923,6 +3326,83 @@ class basic_json_view
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
}
// --- get() dispatch ---
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
}
return (m_node->flags & detail::view::node_flags::is_true) != 0;
}
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 >
NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
}
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
}
std::nullptr_t get_impl(detail::view::value_tag<std::nullptr_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
}
return nullptr;
}
string_view_t get_impl(detail::view::value_tag<string_view_t> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return get_string();
}
template<typename Traits, typename Alloc>
std::basic_string<char, Traits, Alloc> get_impl(detail::view::value_tag<std::basic_string<char, Traits, Alloc>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
const string_view_t s = get_string();
return std::basic_string<char, Traits, Alloc>(s.data(), s.size());
}
BasicJsonType get_impl(detail::view::value_tag<BasicJsonType> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return materialize();
}
basic_json_view get_impl(detail::view::value_tag<basic_json_view> /*unused*/, detail::priority_tag<2> /*unused*/) const noexcept
{
return *this;
}
template<typename U, typename A>
std::vector<U, A> get_impl(detail::view::value_tag<std::vector<U, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return detail::view::vector_value<basic_json_view, U, A>(*this);
}
template<typename K, typename V, typename C, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
std::map<K, V, C, A> get_impl(detail::view::value_tag<std::map<K, V, C, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return detail::view::map_value<std::map<K, V, C, A>>(*this);
}
template<typename K, typename V, typename H, typename E, typename A, typename std::enable_if<detail::view::is_string_key<K>::value, int>::type = 0>
std::unordered_map<K, V, H, E, A> get_impl(detail::view::value_tag<std::unordered_map<K, V, H, E, A>> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
return detail::view::map_value<std::unordered_map<K, V, H, E, A>>(*this);
}
/// everything else through the BasicJsonType value (from_json included)
template<typename T>
T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<0> /*unused*/) const
{
return materialize().template get<T>();
}
const document_data* m_doc = nullptr;
const node* m_node = nullptr;
};
+389
View File
@@ -17,12 +17,18 @@ using nlohmann::ordered_json_document;
using nlohmann::ordered_json_view;
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iterator>
#include <list>
#include <map>
#include <random>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -662,3 +668,386 @@ TEST_CASE("json_view element access and iteration")
#endif
}
}
namespace
{
#if !defined(JSON_NOEXCEPTION)
// an exception message without the context that basic_json adds with
// JSON_DIAGNOSTICS ("(/path) ") and JSON_DIAGNOSTIC_POSITIONS ("(bytes 1-2) ");
// the view's exceptions have no such context
std::string without_path(std::string msg)
{
for (const char* prefix :
{"] (/", "] (bytes "
})
{
const std::size_t open = msg.find(prefix);
if (open != std::string::npos)
{
msg.erase(open + 2, msg.find(") ", open) + 2 - (open + 2));
}
}
return msg;
}
#endif
// the bits of a float, to compare values bit for bit
std::uint64_t bits(double x)
{
std::uint64_t r = 0;
std::memcpy(&r, &x, sizeof(r));
return r;
}
std::uint32_t bits(float x)
{
std::uint32_t r = 0;
std::memcpy(&r, &x, sizeof(r));
return r;
}
bool has_duplicate_keys(const ordered_json_view& v)
{
if (v.is_object() && v.size() != v.materialize().size())
{
return true;
}
return std::any_of(v.begin(), v.end(), [](const ordered_json_view e)
{
return e.is_structured() && has_duplicate_keys(e);
});
}
// compares the conversions of a view with those of ordered_json
void check_values(const ordered_json_view& v, const ordered_json& j, const std::string& text)
{
CHECK(v.get<ordered_json>() == j);
switch (j.type())
{
case json::value_t::number_integer:
case json::value_t::number_unsigned:
case json::value_t::number_float:
{
// (converting a float out of range of the target type is undefined)
if (j.is_number_unsigned())
{
CHECK(v.get<std::uint64_t>() == j.get<std::uint64_t>());
}
else if (j.is_number_integer())
{
CHECK(v.get<std::int64_t>() == j.get<std::int64_t>());
}
CHECK(bits(v.get<double>()) == bits(j.get<double>()));
if (std::abs(j.get<double>()) < 1e9)
{
CHECK(v.get<int>() == j.get<int>());
}
const auto token = v.number_token();
CHECK(text.compare(v.source_offset(), token.size(), token.data(), token.size()) == 0);
break;
}
case json::value_t::string:
CHECK(v.get<std::string>() == j.get<std::string>());
CHECK(std::string(v.get_string().data(), v.get_string().size()) == j.get<std::string>());
break;
case json::value_t::boolean:
CHECK(v.get<bool>() == j.get<bool>());
CHECK(v.get<int>() == j.get<int>());
break;
case json::value_t::null:
CHECK(v.get<std::nullptr_t>() == nullptr);
break;
case json::value_t::array:
CHECK(v.get<std::vector<ordered_json>>() == j.get<std::vector<ordered_json>>());
break;
case json::value_t::object:
CHECK((v.get<std::map<std::string, ordered_json>>() == j.get<std::map<std::string, ordered_json>>()));
break;
case json::value_t::binary:
case json::value_t::discarded:
default:
break;
}
#if !defined(JSON_NOEXCEPTION)
// conversions to the wrong type throw what basic_json throws
if (!j.is_number())
{
CHECK(exception_of([&] { static_cast<void>(v.get<int>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<int>()); })));
}
CHECK(exception_of([&] { static_cast<void>(v.get<bool>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<bool>()); })));
CHECK(exception_of([&] { static_cast<void>(v.get<std::string>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::string>()); })));
CHECK(exception_of([&] { static_cast<void>(v.get<std::nullptr_t>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::nullptr_t>()); })));
if (!j.is_array())
{
CHECK(exception_of([&] { static_cast<void>(v.get<std::vector<int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::vector<int>>()); })));
}
if (!j.is_object())
{
CHECK(exception_of([&] { static_cast<void>(v.get<std::map<std::string, int>>()); }) == without_path(exception_of([&] { static_cast<void>(j.get<std::map<std::string, int>>()); })));
}
#endif
if (v.is_array())
{
std::size_t i = 0;
for (const ordered_json_view e : v)
{
check_values(e, j[i++], text);
}
}
else if (v.is_object())
{
for (auto it = v.begin(); it != v.end(); ++it)
{
const std::string key(it.key().data(), it.key().size());
if (v.size() == j.size()) // (no duplicate keys)
{
check_values(it.value(), j[key], text);
}
}
}
}
struct record
{
std::string name{}; // NOLINT(readability-redundant-member-init)
int count = 0;
};
void from_json(const json& j, record& r)
{
j.at("name").get_to(r.name);
j.at("count").get_to(r.count);
}
} // namespace
TEST_CASE("json_view values")
{
SECTION("generated documents")
{
generator g;
for (int i = 0; i < 2000; ++i)
{
std::string text;
g.value(text, 0);
CAPTURE(text);
const ordered_json_document d = ordered_json_document::parse(text);
check_values(d.root(), ordered_json::parse(text), text);
}
}
SECTION("floats are converted as parse() converts them")
{
std::mt19937_64 rng(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
std::vector<std::string> tokens = {"0.1", "-0.0", "1e308", "1.7976931348623157e308", "2.2250738585072011e-308", "4.9e-324", "5e-324",
"0.1000000000000000055511151231257827021181583404541015625", "123456789012345678901234567890",
"9007199254740993", "1.00000000000000011102230246251565404236316680908203125", "7.2057594037927933e16"
};
for (int i = 0; i < 20000; ++i)
{
const std::uint64_t bits = rng();
double d = 0;
std::memcpy(&d, &bits, sizeof(d));
if (!std::isfinite(d))
{
continue;
}
std::array<char, 400> buf{};
switch (i % 5) // NOLINT(hicpp-multiway-paths-covered)
{
case 0:
std::snprintf(buf.data(), buf.size(), "%.17g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
break;
case 1:
std::snprintf(buf.data(), buf.size(), "%.15g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
break;
case 2:
std::snprintf(buf.data(), buf.size(), "%.3e", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
break;
case 3:
std::snprintf(buf.data(), buf.size(), "%.25g", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
break;
default:
std::snprintf(buf.data(), buf.size(), "%.0f", d); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
break;
}
tokens.emplace_back(buf.data());
}
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
for (const auto& token : tokens)
{
CAPTURE(token);
const std::string text = "[" + token + "]";
const double b = json::parse(text)[0].get<double>();
CHECK(bits(json_document::parse(text).root()[0].get<double>()) == bits(b));
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>()));
}
}
}
SECTION("number tokens")
{
const json_document d = json_document::parse(R"([1.50, 1E2, -0, 123456789012345678901234567890, -12, 7, "x"])");
const json_view v = d.root();
CHECK(v[0].number_token() == "1.50");
CHECK(v[1].number_token() == "1E2");
CHECK(v[2].number_token() == "-0");
CHECK(v[3].number_token() == "123456789012345678901234567890");
CHECK(v[4].number_token() == "-12");
CHECK(v[5].number_token() == "7");
CHECK_THROWS_WITH_AS(v[6].number_token(), "[json.exception.type_error.302] type must be number, but is string", json::type_error&);
CHECK_THROWS_WITH_AS(v.get_string(), "[json.exception.type_error.302] type must be string, but is array", json::type_error&);
}
SECTION("conversions")
{
const std::string text = R"({"name": "widget", "count": 3, "tags": ["a", "b\n"], "sizes": {"s": 1, "m": 2}, "pair": [1, "x"]})";
const json_document d = json_document::parse(text);
const json_view v = d.root();
const json j = json::parse(text);
// user types with from_json, and other types, through basic_json
const record r = v.get<record>();
CHECK(r.name == "widget");
CHECK(r.count == 3);
CHECK((v["pair"].get<std::pair<int, std::string>>() == j["pair"].get<std::pair<int, std::string>>()));
CHECK(v["tags"].get<std::list<std::string>>() == j["tags"].get<std::list<std::string>>());
CHECK((v["sizes"].get<std::unordered_map<std::string, int>>() == j["sizes"].get<std::unordered_map<std::string, int>>()));
CHECK(v["tags"].get<std::vector<std::string>>() == std::vector<std::string> {"a", "b\n"});
// views of the elements
const auto views = v["tags"].get<std::vector<json_view>>();
CHECK(views.size() == 2);
CHECK(views[1].get_string() == "b\n");
const auto members = v.get<std::map<std::string, json_view>>();
CHECK(members.at("count").get<int>() == 3);
CHECK(v.get<json_view>()["name"].get_string() == "widget");
// strings without a copy point into the source text
CHECK(v["name"].get_string().data() == text.data() + text.find("widget"));
#ifdef JSON_HAS_CPP_17
CHECK(v["name"].get<std::string_view>() == "widget");
#endif
std::string name;
int count = 0;
CHECK(&v["name"].get_to(name) == &name);
v["count"].get_to(count);
CHECK(name == "widget");
CHECK(count == 3);
// 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_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(invalid.get<json>().is_discarded());
}
SECTION("value")
{
const json_document d = json_document::parse(R"({"n": 1, "s": "text", "o": {"x": [10, 20]}})");
const json_view v = d.root();
const json j = v.materialize();
CHECK(v.value("n", 0) == j.value("n", 0));
CHECK(v.value("missing", 42) == j.value("missing", 42));
CHECK(v.value("s", "default") == j.value("s", "default"));
CHECK(v.value("missing", "default") == j.value("missing", "default"));
CHECK(v.value(std::string("n"), 2.5) == j.value(std::string("n"), 2.5));
CHECK(v.value(json::json_pointer("/o/x/1"), 0) == j.value(json::json_pointer("/o/x/1"), 0));
CHECK(v.value(json::json_pointer("/o/x/5"), 0) == j.value(json::json_pointer("/o/x/5"), 0));
CHECK(v.value(json::json_pointer("/o/y"), "none") == j.value(json::json_pointer("/o/y"), "none"));
// with a JSON pointer, arrays can be asked as well
CHECK(v["o"]["x"].value(json::json_pointer("/1"), 0) == j["o"]["x"].value(json::json_pointer("/1"), 0));
CHECK(v["o"]["x"].value(json::json_pointer("/7"), 3) == j["o"]["x"].value(json::json_pointer("/7"), 3));
#if !defined(JSON_NOEXCEPTION)
CHECK(exception_of([&] { static_cast<void>(v["o"]["x"].value("k", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["o"]["x"].value("k", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
#endif
}
}
TEST_CASE("json_view JSON pointers")
{
SECTION("every value of generated documents")
{
generator g;
for (int i = 0; i < 1000; ++i)
{
std::string text;
g.value(text, 0);
const ordered_json_document d = ordered_json_document::parse(text);
if (has_duplicate_keys(d.root()))
{
continue;
}
CAPTURE(text);
const ordered_json j = ordered_json::parse(text);
const ordered_json flat = j.flatten();
for (const auto& leaf : flat.items())
{
// the leaf and each of its parents
for (ordered_json::json_pointer p(leaf.key());; p = p.parent_pointer())
{
CAPTURE(p.to_string());
CHECK(d.root()[p].materialize() == j[p]);
CHECK(d.root().at(p).materialize() == j.at(p));
CHECK(d.root().contains(p));
if (p.empty())
{
break;
}
}
}
}
}
#if !defined(JSON_NOEXCEPTION)
SECTION("errors are those of basic_json")
{
const std::string text = R"({"a": [1, {"b": null}], "c": "s", "": {"": 0}, "a~b": 1, "c/d": 2})";
const json_document d = json_document::parse(text);
const json_view v = d.root();
const json j = v.materialize();
for (const char* pointer :
{"", "/", "//", "/a", "/a/0", "/a/1/b", "/a/-", "/a/01", "/a/00", "/a/1a", "/a/a", "/a/", "/a/2", "/a/99", "/a/99999999999999999999",
"/a/18446744073709551615", "/a/-1", "/a/+1", "/a/ 1", "/x", "/c/x", "/a/0/x", "/a/1/b/c", "/a~0b", "/c~1d", "/c~1d/x", "/a/1/-"
})
{
CAPTURE(pointer);
const json::json_pointer p(pointer);
const std::string at_error = without_path(exception_of([&] { static_cast<void>(j.at(p)); }));
CHECK(exception_of([&] { static_cast<void>(v.at(p)); }) == at_error);
if (at_error.empty())
{
CHECK(v.at(p).materialize() == j.at(p));
CHECK(v[p].materialize() == j[p]);
}
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[]
CHECK(!v[p]);
}
else
{
CHECK(exception_of([&] { static_cast<void>(v[p]); }) == without_path(exception_of([&] { static_cast<void>(j[p]); })));
}
// (basic_json::contains() throws out_of_range.404 for an empty
// array index token, although it is not meant to throw; the view
// answers false)
const std::string contains_error = exception_of([&]
{
const bool found = j.contains(p);
static_cast<void>(found);
});
CHECK(v.contains(p) == (contains_error.empty() && j.contains(p)));
CHECK(exception_of([&] { static_cast<void>(v.value(p, 5)); }) == without_path(exception_of([&] { static_cast<void>(j.value(p, 5)); })));
}
}
#endif
}