Make view lookups last-wins and chained access safe

* Lookups (operator[], at, find, contains, count, value, JSON pointers)
  return the last member of a duplicate key, as materialize() and
  parse() keep it.
* operator[] on a discarded view returns a discarded view instead of
  throwing, so v["a"]["b"] is safe for a missing "a".
* operator[] and at() take any integer type (not only int and size_t),
  fixing ambiguous calls with unsigned, long, std::int64_t, ...
* Fix the operator[] documentation, which claimed a discarded view for
  a type mismatch where type_error.305 is thrown.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-11 10:27:59 +02:00
1 parent 041a561e56
commit 4ebac0263e
16 files changed
+306 -84

No files matched your search

+9 -6
View File
@@ -8,15 +8,18 @@ basic_json_view at(const string_t& key) const;
// (2) // (2)
basic_json_view at(size_type idx) const; basic_json_view at(size_type idx) const;
basic_json_view at(int idx) const; template<typename IntegerType>
basic_json_view at(IntegerType idx) const;
// (3) // (3)
basic_json_view at(const json_pointer& ptr) const; basic_json_view at(const json_pointer& ptr) const;
``` ```
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see 1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
[Notes on duplicate keys](operator[].md#notes)). [Notes on duplicate keys](operator[].md#notes)).
2. Returns the array element at index `idx`. 2. Returns the array element at index `idx`. The template accepts every integer type except `#!cpp bool` and
`#!cpp std::size_t` and forwards to the `size_type` overload, as for [`operator[]`](operator[].md); a negative
`idx` is out of range.
3. Returns the value a JSON pointer `ptr` refers to, starting at this value. 3. Returns the value a JSON pointer `ptr` refers to, starting at this value.
## Parameters ## Parameters
@@ -32,7 +35,7 @@ basic_json_view at(const json_pointer& ptr) const;
## Return value ## Return value
1. the value of the first member with key `key` 1. the value of the last member with key `key`
2. the element at index `idx` 2. the element at index `idx`
3. the value `ptr` resolves to, starting at this value 3. the value `ptr` resolves to, starting at this value
@@ -72,8 +75,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity ## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, stopping at the first match. Each comparison first checks the key's length -- another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
already known from the index, without reading the key bytes -- before comparing its content. key's length -- already known from the index, without reading the key bytes -- before comparing its content.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). index (unlike `BasicJsonType`'s array, which is random-access).
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
@@ -24,7 +24,7 @@ Constant.
For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike
[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md), and [`count`](count.md), [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md), and [`count`](count.md),
which all resolve to the *first* member with a given key. See the which all resolve to the *last* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`. [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
Because objects are iterated in document order rather than sorted by key, the order seen here can differ from what Because objects are iterated in document order rather than sorted by key, the order seen here can differ from what
@@ -33,8 +33,8 @@ No-throw guarantee: this function never throws exceptions.
## Complexity ## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
known from the index, without reading the key bytes -- before comparing its content. key's length -- already known from the index, without reading the key bytes -- before comparing its content.
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and
[`at`](at.md#complexity) with a JSON pointer. [`at`](at.md#complexity) with a JSON pointer.
@@ -23,9 +23,9 @@ No-throw guarantee: this function never throws exceptions.
## Complexity ## Complexity
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already in document order, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
known from the index, without reading the key bytes -- before comparing its content. -- already known from the index, without reading the key bytes -- before comparing its content.
## Notes ## Notes
@@ -36,7 +36,7 @@ Unlike [`BasicJsonType::count()`](../basic_json/count.md), whose return value ca
an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly
[`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the [`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the
[Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require [Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require
counting every member with a matching key, not just finding the first one. counting every member with a matching key (the lookup functions resolve to the *last* one).
## Examples ## Examples
+4 -4
View File
@@ -6,7 +6,7 @@ iterator find(const char* key) const;
iterator find(const string_t& key) const; iterator find(const string_t& key) const;
``` ```
Finds a member with key `key` -- the first one, should the key occur more than once (see Finds a member with key `key` -- the last one, should the key occur more than once (see
[Notes on duplicate keys](operator[].md#notes)). If the value is not an object, or no member has this key, [Notes on duplicate keys](operator[].md#notes)). If the value is not an object, or no member has this key,
[`end()`](end.md) is returned. [`end()`](end.md) is returned.
@@ -25,9 +25,9 @@ No-throw guarantee: this function never throws exceptions.
## Complexity ## Complexity
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
another, in document order, stopping at the first match. Each comparison first checks the key's length -- already in document order, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
known from the index, without reading the key bytes -- before comparing its content. -- already known from the index, without reading the key bytes -- before comparing its content.
## Notes ## Notes
+3 -3
View File
@@ -87,9 +87,9 @@ exception thrown while converting through `materialize()` (the last bullet) is d
!!! info "Duplicate keys" !!! info "Duplicate keys"
`#!cpp std::map`/`#!cpp std::unordered_map` conversions keep the *last* value of a repeated key, like `#!cpp std::map`/`#!cpp std::unordered_map` conversions keep the *last* value of a repeated key, like
[`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the opposite [`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the member
of [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md), which resolve to the [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md) resolve to, too (see the
*first* occurrence (see the [Notes on duplicate keys](operator[].md#notes)). [Notes on duplicate keys](operator[].md#notes)).
!!! info "No pointers, references, or implicit conversion" !!! info "No pointers, references, or implicit conversion"
@@ -9,6 +9,10 @@ view (see [(constructor)](basic_json_view.md)), and for [`root()`](../basic_json
that is itself [discarded](../basic_json_document/is_discarded.md) -- in particular, the root of a failed that is itself [discarded](../basic_json_document/is_discarded.md) -- in particular, the root of a failed
[`parse()`](../basic_json_document/parse.md) with `allow_exceptions` set to `#!cpp false`. [`parse()`](../basic_json_document/parse.md) with `allow_exceptions` set to `#!cpp false`.
A discarded view is also what [`operator[]`](operator[].md) returns for a missing key, an index out of range, or a
JSON pointer that cannot be resolved, and for any access on a view that is itself discarded (so a chain such as
`#!cpp v["a"]["b"]` is safe). [`at`](at.md) throws instead.
## Return value ## Return value
`#!cpp true` if the view is discarded, `#!cpp false` otherwise. `#!cpp true` if the view is discarded, `#!cpp false` otherwise.
@@ -46,6 +50,7 @@ A `basic_json_view` is not convertible to `#!cpp bool`: such a conversion would
## See also ## See also
- [operator[]](operator[].md) - access specified element; yields a discarded view where an element is missing
- [(constructor)](basic_json_view.md) - the default constructor creates a discarded view - [(constructor)](basic_json_view.md) - the default constructor creates a discarded view
- [is_discarded (basic_json_document)](../basic_json_document/is_discarded.md) - return whether the last parse failed - [is_discarded (basic_json_document)](../basic_json_document/is_discarded.md) - return whether the last parse failed
- [`BasicJsonType::is_discarded`](../basic_json/is_discarded.md) - the corresponding function of `basic_json` - [`BasicJsonType::is_discarded`](../basic_json/is_discarded.md) - the corresponding function of `basic_json`
@@ -48,7 +48,7 @@ Constant.
As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all
occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md),
[`contains`](contains.md), and [`count`](count.md), which resolve to the *first* member with a given key. See the [`contains`](contains.md), and [`count`](count.md), which resolve to the *last* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`. [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
!!! danger "Lifetime issues" !!! danger "Lifetime issues"
@@ -63,8 +63,8 @@ occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](a
The example below shows a settings object whose source text records every update to a key as a duplicate The example below shows a settings object whose source text records every update to a key as a duplicate
member, in the order they happened. `items()` walks all of them, so the update history is visible, while member, in the order they happened. `items()` walks all of them, so the update history is visible, while
[`operator[]`](operator[].md) only ever sees the *first* one and [`materialize()`](materialize.md) -- like [`operator[]`](operator[].md) sees the *last* one, and so does [`materialize()`](materialize.md) -- like
[`BasicJsonType::parse()`](../basic_json/parse.md) -- keeps only the *last*. [`BasicJsonType::parse()`](../basic_json/parse.md).
```cpp ```cpp
--8<-- "examples/basic_json_view__items.cpp" --8<-- "examples/basic_json_view__items.cpp"
@@ -8,16 +8,19 @@ basic_json_view operator[](const string_t& key) const;
// (2) // (2)
basic_json_view operator[](size_type idx) const; basic_json_view operator[](size_type idx) const;
basic_json_view operator[](int idx) const; template<typename IntegerType>
basic_json_view operator[](IntegerType idx) const;
// (3) // (3)
basic_json_view operator[](const json_pointer& ptr) const; basic_json_view operator[](const json_pointer& ptr) const;
``` ```
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see 1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
the [Notes](#notes) below) -- or a [discarded](is_discarded.md) view if there is no such member. the [Notes](#notes) below) -- or a [discarded](is_discarded.md) view if there is no such member.
2. Returns the array element at index `idx`, or a [discarded](is_discarded.md) view if `idx` is out of range. (The 2. Returns the array element at index `idx`, or a [discarded](is_discarded.md) view if `idx` is out of range. The
`#!cpp int` overload only exists so that an integer literal is not ambiguous between this overload and 1.) template accepts every integer type except `#!cpp bool` and `#!cpp std::size_t` (`#!cpp int`, `#!cpp unsigned`,
`#!cpp long`, `#!cpp std::int64_t`, ...) and forwards to the `size_type` overload, so that an integer argument is
not ambiguous between that overload and 1; a negative `idx` is out of range.
3. Returns the value a JSON pointer `ptr` refers to, starting at this value, or a [discarded](is_discarded.md) view 3. Returns the value a JSON pointer `ptr` refers to, starting at this value, or a [discarded](is_discarded.md) view
wherever resolving it further is not possible without inserting into or extending the document (see wherever resolving it further is not possible without inserting into or extending the document (see
[Return value](#return-value) and [Exceptions](#exceptions) below). [Return value](#return-value) and [Exceptions](#exceptions) below).
@@ -35,10 +38,12 @@ basic_json_view operator[](const json_pointer& ptr) const;
## Return value ## Return value
1. the value of the first member with key `key`, or a discarded view if `#!cpp is_object()` is `#!cpp false` or no 1. the value of the last member with key `key`, or a discarded view if no member has this key (or if this view is
member has this key [discarded](is_discarded.md))
2. the element at index `idx`, or a discarded view if `#!cpp is_array()` is `#!cpp false` or `#!cpp idx >= size()` 2. the element at index `idx`, or a discarded view if `#!cpp idx >= size()` or `idx` is negative (or if this view is
3. the value `ptr` resolves to, starting at this value, or a discarded view for exactly the reference tokens where the [discarded](is_discarded.md))
3. the value `ptr` resolves to, starting at this value, or a discarded view (also if this view is
[discarded](is_discarded.md)) for exactly the reference tokens where the
**const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) invokes undefined behavior for **const** overload of [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) invokes undefined behavior for
the same pointer and the same document: an object member that does not exist, or an array index that is out of the same pointer and the same document: an object member that does not exist, or an array index that is out of
range range
@@ -49,10 +54,12 @@ Strong exception safety: if an exception is thrown, there are no changes to the
## Exceptions ## Exceptions
1. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an object -- 1. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an object and
not [discarded](is_discarded.md) --
the same exception, with the same message, that the **const** overload of the same exception, with the same message, that the **const** overload of
[`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a string argument on a non-object value. [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a string argument on a non-object value.
2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an array -- 2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if the value is not an array and
not [discarded](is_discarded.md) --
the same exception, with the same message, that the **const** overload of the same exception, with the same message, that the **const** overload of
[`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a numeric argument on a non-array value. [`BasicJsonType::operator[]`](../basic_json/operator%5B%5D.md) throws for a numeric argument on a non-array value.
3. Throws the same exceptions, with the same messages, that the **const** overload of 3. Throws the same exceptions, with the same messages, that the **const** overload of
@@ -73,9 +80,9 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity ## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, stopping at the first match. Each comparison first checks the key's length -- another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
already known from the index, without reading the key bytes -- before comparing its content, so a key of a key's length -- already known from the index, without reading the key bytes -- before comparing its content, so a
different length than `key` is rejected without touching the source text. key of a different length than `key` is rejected without touching the source text.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). index (unlike `BasicJsonType`'s array, which is random-access).
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
@@ -84,20 +91,29 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Notes ## Notes
Unlike `BasicJsonType::operator[]`, which is undefined behavior (guarded by a Unlike `BasicJsonType::operator[]`, which is undefined behavior (guarded by a
[runtime assertion](../../features/assertions.md)) for a missing key on a **const** value, this operator always [runtime assertion](../../features/assertions.md)) for a missing key on a **const** value, this operator returns a
returns a safe, testable result: a [discarded](is_discarded.md) view, which is `#!cpp false` in a boolean context. safe, testable result for a missing key or an index out of range: a [discarded](is_discarded.md) view, which is
`#!cpp false` in a boolean context.
There is also no non-const overload that inserts a missing key or extends an array -- a view never modifies the There is also no non-const overload that inserts a missing key or extends an array -- a view never modifies the
document. document.
!!! info "Chained access"
`#!cpp operator[]` on a [discarded](is_discarded.md) view returns a discarded view and does not throw, so a chain
like `#!cpp v["a"]["b"][0]` is safe even if `"a"` or `"b"` is missing: the first missing step makes the whole
result discarded, which is tested once at the end. Type errors on values that are *not* discarded still throw: a
key on an array or a primitive, or an index on an object or a primitive, is `type_error.305` as for
`BasicJsonType`. [`at`](at.md) still throws for a discarded view, as it does for a missing key.
!!! info "Duplicate keys" !!! info "Duplicate keys"
If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md), If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md),
[`contains`](contains.md), [`count`](count.md)) all resolve to the *first* member with that key, because a [`contains`](contains.md), [`count`](count.md), [`value`](value.md), and JSON pointer resolution) all resolve to
lookup can stop as soon as it finds a match. This is different from the *last* member with that key. This is the member [`materialize()`](materialize.md) (and
[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every [`BasicJsonType::parse()`](../basic_json/parse.md)) keeps, so a lookup in the view and in the materialized value
member in order and so end up keeping the *last* value for a repeated key -- there is no reason for them to stop agree. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
early. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including duplicates, in document order. A lookup scans all members for this: it cannot stop at the first match. See the
duplicates, in document order. See the example below and [`size()`](size.md#notes). example below and [`size()`](size.md#notes).
!!! info "JSON pointer resolution" !!! info "JSON pointer resolution"
@@ -12,7 +12,7 @@ T value(const json_pointer& ptr, const T& default_value) const;
string_t value(const json_pointer& ptr, const char* default_value) const; string_t value(const json_pointer& ptr, const char* default_value) const;
``` ```
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see 1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
[Notes on duplicate keys](operator[].md#notes)) -- converted to `T`, or `default_value` if there is no such member. [Notes on duplicate keys](operator[].md#notes)) -- converted to `T`, or `default_value` if there is no such member.
2. Returns the value a JSON pointer `ptr` refers to, starting at this value, converted to `T`, or `default_value` if 2. Returns the value a JSON pointer `ptr` refers to, starting at this value, converted to `T`, or `default_value` if
`ptr` cannot be resolved. `ptr` cannot be resolved.
@@ -39,7 +39,7 @@ equivalent) deduce `string_t`, not `const char*`, for their return type and for
## Return value ## Return value
1. the first member with key `key`, converted to `T`, or `default_value` 1. the last member with key `key`, converted to `T`, or `default_value`
2. the value `ptr` resolves to, converted to `T`, or `default_value` 2. the value `ptr` resolves to, converted to `T`, or `default_value`
## Exception safety ## Exception safety
@@ -68,8 +68,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity ## Complexity
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after 1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
another, in document order, stopping at the first match. Plus the complexity of converting the found member to another, in document order, scanning all of them, since the last match is wanted. Plus the complexity of converting
`T` (see [`get`](get.md)). the found member to `T` (see [`get`](get.md)).
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and
[`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value [`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
@@ -7,8 +7,8 @@ int main()
{ {
// a settings object whose source text records every update to a key as // a settings object whose source text records every update to a key as
// a duplicate member. items() visits all of them, in document order, so // a duplicate member. items() visits all of them, in document order, so
// the update history is visible; operator[] only ever sees the first // the update history is visible; operator[] and materialize() -- like
// one, and materialize() -- like basic_json::parse() -- keeps the last // basic_json::parse() -- see the last one
json_document updates = json_document::parse(R"({"retries": 1, "timeout": 30, "retries": 5})"); json_document updates = json_document::parse(R"({"retries": 1, "timeout": 30, "retries": 5})");
const auto settings = updates.root(); const auto settings = updates.root();
@@ -17,6 +17,6 @@ int main()
std::cout << item.key() << '=' << item.value().materialize().dump() << '\n'; std::cout << item.key() << '=' << item.value().materialize().dump() << '\n';
} }
std::cout << "first \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n'; std::cout << "last \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n';
std::cout << "last \"retries\" kept by materialize(): " << settings.materialize()["retries"].dump() << '\n'; std::cout << "last \"retries\" kept by materialize(): " << settings.materialize()["retries"].dump() << '\n';
} }
@@ -1,5 +1,5 @@
retries=1 retries=1
timeout=30 timeout=30
retries=5 retries=5
first "retries" seen by operator[]: 1 last "retries" seen by operator[]: 5
last "retries" kept by materialize(): 5 last "retries" kept by materialize(): 5
+8 -2
View File
@@ -129,14 +129,20 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
sorts by key, so iterating a [`materialize()`](../api/basic_json_view/materialize.md)d value can print members in sorts by key, so iterating a [`materialize()`](../api/basic_json_view/materialize.md)d value can print members in
a different order than iterating the view they came from. a different order than iterating the view they came from.
- **Chained access is safe.** [`operator[]`](../api/basic_json_view/operator%5B%5D.md) with a missing key, an index
out of range, or an unresolvable JSON pointer returns a [discarded](../api/basic_json_view/is_discarded.md) view, and
`operator[]` on a discarded view returns a discarded view without throwing: `#!cpp v["a"]["b"][0]` can be tested
once at the end. Type errors on values that exist (a key on an array, an index on an object) still throw, and
[`at`](../api/basic_json_view/at.md) throws for every missing value.
- **Duplicate keys are visible.** If an object in the source text repeats a key, - **Duplicate keys are visible.** If an object in the source text repeats a key,
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and [`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence (and [`size()`](../api/basic_json_view/size.md) [`items()`](../api/basic_json_view/items.md) visit *every* occurrence (and [`size()`](../api/basic_json_view/size.md)
counts all of them), while [`operator[]`](../api/basic_json_view/operator%5B%5D.md), counts all of them), while [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md), [`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
[`contains`](../api/basic_json_view/contains.md), and [`count`](../api/basic_json_view/count.md) resolve to the [`contains`](../api/basic_json_view/contains.md), and [`count`](../api/basic_json_view/count.md) resolve to the
*first* occurrence, since a lookup can stop as soon as it finds a match. `basic_json::parse()` (and so *last* occurrence -- the one `basic_json::parse()` (and so
[`materialize()`](../api/basic_json_view/materialize.md)) instead keeps only the *last* value for a repeated key. [`materialize()`](../api/basic_json_view/materialize.md)) keeps for a repeated key -- which makes a lookup scan all
members instead of stopping at a match.
See the [Notes on duplicate keys](../api/basic_json_view/operator%5B%5D.md#notes) of `operator[]`. See the [Notes on duplicate keys](../api/basic_json_view/operator%5B%5D.md#notes) of `operator[]`.
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own - **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
+28 -6
View File
@@ -11,6 +11,8 @@
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint16_t, uint32_t, uint64_t #include <cstdint> // uint16_t, uint32_t, uint64_t
#include <cstring> // memcmp, memcpy #include <cstring> // memcmp, memcpy
#include <limits> // numeric_limits
#include <type_traits> // integral_constant, is_integral, is_same
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp> #include <nlohmann/detail/view/document_data.hpp>
@@ -81,12 +83,14 @@ class short_key
std::uint64_t m_b = 0; std::uint64_t m_b = 0;
}; };
/// the key node of the first member of an object with the given key, or /// the key node of the last member of an object with the given key, or
/// nullptr; most keys are rejected by their length, from the index alone /// nullptr (the last one, as materialize() and parse() keep it); most keys are
/// rejected by their length, from the index alone
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
{ {
const node* const end = document_data::child_end(object); const node* const end = document_data::child_end(object);
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const node* last = nullptr;
if (NLOHMANN_VIEW_LIKELY(n <= 16)) if (NLOHMANN_VIEW_LIKELY(n <= 16))
{ {
const short_key probe(k, n); const short_key probe(k, n);
@@ -94,19 +98,37 @@ inline const node* find_member(const document_data& d, const node* object, const
{ {
if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{ {
return m; last = m;
} }
} }
return nullptr; return last;
} }
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1)) for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
{ {
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
{ {
return m; last = m;
} }
} }
return nullptr; return last;
}
/// whether an integer type is accepted as an array index by the view's
/// operator[] and at(): every integer type but bool and size_t, which has its
/// own overload
template<typename T>
struct is_index_type : std::integral_constant < bool,
std::is_integral<T>::value && !std::is_same<T, bool>::value && !std::is_same<T, std::size_t>::value >
{};
/// an integer as an index: negative values, and values that do not fit a
/// size_t, map to the largest size_t (out of range for every array)
template<typename SizeType, typename IntegerType>
SizeType to_index(IntegerType idx) noexcept
{
const IntegerType zero = 0;
const auto result = static_cast<SizeType>(idx);
return (idx < zero || static_cast<IntegerType>(result) != idx) ? (std::numeric_limits<SizeType>::max)() : result;
} }
/// the element of an array at an index below its size /// the element of an array at an index below its size
+35 -15
View File
@@ -240,13 +240,18 @@ class basic_json_view
// element access // // element access //
//////////////////// ////////////////////
/// the value of the member with this key (the first one, should the key /// the value of the member with this key (the last one, should the key
/// occur more than once); a discarded view if there is none. Throws /// occur more than once); a discarded view if there is none, or if this
/// type_error.305 if this is not an object. /// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
/// if this is any other value but an object.
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view operator[](string_view_t key) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{ {
if (is_discarded())
{
return basic_json_view();
}
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
return lookup(key); return lookup(key);
@@ -263,31 +268,43 @@ class basic_json_view
} }
/// the element at this index; a discarded view if the index is out of /// the element at this index; a discarded view if the index is out of
/// range. Throws type_error.305 if this is not an array. /// range, or if this is a discarded view. Throws type_error.305 if this is
/// any other value but an array.
basic_json_view operator[](size_type idx) const basic_json_view operator[](size_type idx) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_array())) if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{ {
if (is_discarded())
{
return basic_json_view();
}
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view();
} }
/// (an int argument would be ambiguous between size_type and const char*) /// any other integer type (int, unsigned, long, std::int64_t, ...; a
basic_json_view operator[](int idx) const /// single overload for size_type alone would be ambiguous for all of them
/// and for const char*); negative values are out of range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view operator[](IntegerType idx) const
{ {
return operator[](static_cast<size_type>(idx)); return operator[](detail::view::to_index<size_type>(idx));
} }
/// the value a JSON pointer refers to; a discarded view if a key is /// the value a JSON pointer refers to; a discarded view if a key is
/// missing or an index is out of range. Other errors throw what const /// missing or an index is out of range, or if this is a discarded view.
/// basic_json::operator[] throws. /// Other errors throw what const basic_json::operator[] throws.
basic_json_view operator[](const json_pointer& ptr) const basic_json_view operator[](const json_pointer& ptr) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
{
return basic_json_view();
}
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked); return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
} }
/// the value of the member with this key (the first one, should the key /// the value of the member with this key (the last one, should the key
/// occur more than once). Throws type_error.304 if this is not an object, /// occur more than once). Throws type_error.304 if this is not an object,
/// and out_of_range.403 if there is no such member. /// and out_of_range.403 if there is no such member.
basic_json_view at(string_view_t key) const basic_json_view at(string_view_t key) const
@@ -329,9 +346,12 @@ class basic_json_view
return basic_json_view(m_doc, detail::view::element_at(m_node, idx)); return basic_json_view(m_doc, detail::view::element_at(m_node, idx));
} }
basic_json_view at(int idx) const /// any other integer type, see operator[]; negative values are out of
/// range
template < typename IntegerType, typename std::enable_if < detail::view::is_index_type<IntegerType>::value, int >::type = 0 >
basic_json_view at(IntegerType idx) const
{ {
return at(static_cast<size_type>(idx)); return at(detail::view::to_index<size_type>(idx));
} }
/// the value a JSON pointer refers to; throws what basic_json::at() /// the value a JSON pointer refers to; throws what basic_json::at()
@@ -342,7 +362,7 @@ class basic_json_view
} }
/// the member with this key converted to T, or the default value if there /// the member with this key converted to T, or the default value if there
/// is no such member (the first one, should the key occur more than /// is no such member (the last one, should the key occur more than
/// once). Throws type_error.306 if this is not an object. /// once). Throws type_error.306 if this is not an object.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 > template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
T value(string_view_t key, const T& default_value) const T value(string_view_t key, const T& default_value) const
@@ -407,7 +427,7 @@ class basic_json_view
// lookup // // lookup //
//////////// ////////////
/// an iterator to the member with this key (the first one, should the /// an iterator to the member with this key (the last one, should the
/// key occur more than once), or end(); end() also for non-objects /// key occur more than once), or end(); end() also for non-objects
iterator find(string_view_t key) const iterator find(string_view_t key) const
{ {
@@ -585,7 +605,7 @@ class basic_json_view
: m_doc(d), m_node(n) : m_doc(d), m_node(n)
{} {}
/// the value of the first member with this key, or a discarded view /// the value of the last member with this key, or a discarded view
/// (object required) /// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{ {
+159 -9
View File
@@ -24,6 +24,7 @@ using nlohmann::ordered_json_view;
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <iterator> #include <iterator>
#include <limits>
#include <list> #include <list>
#include <map> #include <map>
#include <random> #include <random>
@@ -582,7 +583,7 @@ std::string exception_of(F f)
// compares a view with the ordered_json value materialize() gives for it: // compares a view with the ordered_json value materialize() gives for it:
// types, sizes, elements and members (by index, key, and iteration), in // types, sizes, elements and members (by index, key, and iteration), in
// document order; duplicate keys are found as their first occurrence // document order; duplicate keys are found as their last occurrence
void check_access(const ordered_json_view& v, const ordered_json& j) void check_access(const ordered_json_view& v, const ordered_json& j)
{ {
REQUIRE(v.type() == j.type()); REQUIRE(v.type() == j.type());
@@ -623,11 +624,23 @@ void check_access(const ordered_json_view& v, const ordered_json& j)
const std::string key(it.key().data(), it.key().size()); const std::string key(it.key().data(), it.key().size());
CHECK(v.contains(key)); CHECK(v.contains(key));
CHECK(v.count(key) == 1); CHECK(v.count(key) == 1);
if (std::find(keys.begin(), keys.end(), key) != keys.end()) if (std::find(keys.begin(), keys.end(), key) == keys.end())
{ {
continue; // a duplicate: lookups find the first one keys.push_back(key);
}
// lookups find the last member with the key, which is this one if
// there is no later one
auto next = it;
++next;
bool is_last = true;
for (; next != v.end(); ++next)
{
is_last = is_last && next.key() != it.key();
}
if (!is_last)
{
continue;
} }
keys.push_back(key);
CHECK(v.find(key) == it); CHECK(v.find(key) == it);
CHECK(v[key].materialize() == it->materialize()); CHECK(v[key].materialize() == it->materialize());
CHECK(v.at(key).materialize() == it.value().materialize()); CHECK(v.at(key).materialize() == it.value().materialize());
@@ -718,15 +731,35 @@ TEST_CASE("json_view element access and iteration")
#endif #endif
} }
SECTION("duplicate keys: lookups find the first member, iteration all") SECTION("duplicate keys: lookups find the last member, iteration all")
{ {
const json_document d = json_document::parse(R"({"a":1,"b":2,"a":3})"); const json_document d = json_document::parse(R"({"a":1,"b":2,"a":3})");
const json_view v = d.root(); const json_view v = d.root();
CHECK(v.size() == 3); CHECK(v.size() == 3);
CHECK(v["a"].materialize() == 1); CHECK(v["a"].materialize() == 3);
CHECK(v.at("a").materialize() == 1); CHECK(v.at("a").materialize() == 3);
CHECK(v.find("a") == v.begin()); CHECK(v.find("a") == std::next(v.begin(), 2));
CHECK(v.find("a").value().materialize() == 3);
CHECK(v.find("b") == std::next(v.begin()));
CHECK(v.count("a") == 1); CHECK(v.count("a") == 1);
CHECK(v.contains("a"));
CHECK(v.value("a", 0) == 3);
CHECK(v["a"].materialize() == v.materialize()["a"]); // as materialize()
// keys of every length class (the 16-byte short compare and memcmp)
for (const std::size_t n :
{
0u, 1u, 3u, 7u, 8u, 15u, 16u, 17u, 40u
})
{
const std::string key(n, 'k');
const json_document dk = json_document::parse("{\"" + key + "\":1,\"" + key + "x\":2,\"" + key + "\":3,\"" + key + "\":4}");
CAPTURE(n)
CHECK(dk.root()[key].materialize() == 4);
CHECK(dk.root().at(key).materialize() == 4);
CHECK(dk.root().find(key) == std::next(dk.root().begin(), 3));
CHECK(dk.root().value(key, 0) == 4);
CHECK(dk.root()[key + "x"].materialize() == 2);
}
std::string order; std::string order;
for (auto it = v.begin(); it != v.end(); ++it) for (auto it = v.begin(); it != v.end(); ++it)
{ {
@@ -784,7 +817,124 @@ TEST_CASE("json_view element access and iteration")
const json_view invalid{}; const json_view invalid{};
CHECK(invalid.begin() == invalid.end()); CHECK(invalid.begin() == invalid.end());
CHECK(std::string(invalid.type_name()) == "discarded"); CHECK(std::string(invalid.type_name()) == "discarded");
CHECK_THROWS_WITH_AS(invalid["a"], "[json.exception.type_error.305] cannot use operator[] with a string argument with discarded", json::type_error&); }
SECTION("chained access is safe: operator[] of a discarded view is discarded")
{
const json_document d = json_document::parse(R"({"a":{"b":[10,20]},"s":"str"})");
const json_view v = d.root();
// missing keys and indexes
CHECK(!v["x"]);
CHECK(!v["x"]["y"]);
CHECK(!v["x"]["y"]["z"]);
CHECK(!v["x"][0]);
CHECK(!v["x"][0u][1L]);
CHECK(!v["a"]["b"][2]);
CHECK(!v["a"]["b"][2]["c"]);
CHECK(!v["a"]["b"][2][json_view::json_pointer("/c")]);
CHECK(!v["x"][json_view::json_pointer("/a/b")]);
CHECK(!v["x"][json_view::json_pointer("")]);
CHECK(v["x"]["y"].is_discarded());
CHECK(!v["x"][std::string("y")]);
// a resolvable path still resolves
CHECK(v["a"]["b"][1].materialize() == 20);
CHECK(v[json_view::json_pointer("/a/b/1")].materialize() == 20);
// the discarded view of an unresolved pointer is discarded too
CHECK(!v[json_view::json_pointer("/x/y")]["z"]);
CHECK(!v[json_view::json_pointer("/a/b/5")][0]);
#if !defined(JSON_NOEXCEPTION)
// type errors on values that are not discarded stay
CHECK_THROWS_WITH_AS(v[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
CHECK_THROWS_WITH_AS(v["a"]["b"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&);
CHECK_THROWS_WITH_AS(v["s"]["c"], "[json.exception.type_error.305] cannot use operator[] with a string argument with string", json::type_error&);
CHECK_THROWS_WITH_AS(v["s"][0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with string", json::type_error&);
CHECK_THROWS_AS(v["a"]["b"][0]["c"], json::type_error&);
CHECK_THROWS_AS(v["s"][json_view::json_pointer("/x")], json::out_of_range&);
// at() keeps throwing on a discarded view
const json_view invalid{};
CHECK(!invalid["a"]);
CHECK(!invalid[0]);
CHECK(!invalid[json_view::json_pointer("/a")]);
CHECK_THROWS_WITH_AS(invalid.at("a"), "[json.exception.type_error.304] cannot use at() with discarded", json::type_error&);
CHECK_THROWS_WITH_AS(invalid.at(0), "[json.exception.type_error.304] cannot use at() with discarded", json::type_error&);
CHECK_THROWS_AS(v.at("x").at("y"), json::out_of_range&);
CHECK_THROWS_AS(v["x"].at("y"), json::type_error&);
CHECK_THROWS_AS(v.at(json_view::json_pointer("/x/y")), json::out_of_range&);
CHECK_THROWS_AS(v["x"].at(json_view::json_pointer("/y")), json::out_of_range&);
#endif
}
SECTION("integer types as array indexes")
{
const json_document d = json_document::parse("[10,20,30]");
const json_view v = d.root();
const json j = v.materialize();
// (compile-time: no overload is ambiguous)
CHECK(v[0].materialize() == 10);
CHECK(v[1].materialize() == 20);
CHECK(v[0u].materialize() == 10);
CHECK(v[1u].materialize() == 20);
CHECK(v[1L].materialize() == 20);
CHECK(v[2UL].materialize() == 30);
CHECK(v[1LL].materialize() == 20);
CHECK(v[2ULL].materialize() == 30);
CHECK(v[static_cast<short>(1)].materialize() == 20);
CHECK(v[static_cast<unsigned short>(2)].materialize() == 30);
CHECK(v[static_cast<signed char>(1)].materialize() == 20);
CHECK(v[static_cast<unsigned char>(2)].materialize() == 30);
CHECK(v[std::int8_t(1)].materialize() == 20);
CHECK(v[std::int16_t(2)].materialize() == 30);
CHECK(v[std::int32_t(1)].materialize() == 20);
CHECK(v[std::int64_t(2)].materialize() == 30);
CHECK(v[std::uint32_t(0)].materialize() == 10);
CHECK(v[std::uint64_t(1)].materialize() == 20);
CHECK(v[std::size_t(2)].materialize() == 30);
CHECK(v[std::ptrdiff_t(1)].materialize() == 20);
CHECK(j[0u] == 10); // as basic_json
CHECK(v.at(0).materialize() == 10);
CHECK(v.at(1u).materialize() == 20);
CHECK(v.at(1L).materialize() == 20);
CHECK(v.at(2LL).materialize() == 30);
CHECK(v.at(2ULL).materialize() == 30);
CHECK(v.at(static_cast<short>(1)).materialize() == 20);
CHECK(v.at(static_cast<unsigned short>(2)).materialize() == 30);
CHECK(v.at(std::int32_t(0)).materialize() == 10);
CHECK(v.at(std::uint32_t(0)).materialize() == 10);
CHECK(v.at(std::int64_t(0)).materialize() == 10);
CHECK(v.at(std::uint64_t(1)).materialize() == 20);
CHECK(v.at(std::size_t(2)).materialize() == 30);
CHECK(j.at(std::uint32_t(0)) == 10); // as basic_json
// out of range, including negative values (no wrap-around)
CHECK(!v[3]);
CHECK(!v[3u]);
CHECK(!v[3L]);
CHECK(!v[-1]);
CHECK(!v[-1L]);
CHECK(!v[-1LL]);
CHECK(!v[static_cast<short>(-1)]);
CHECK(!v[std::int64_t(-3)]);
CHECK(!v[(std::numeric_limits<std::int64_t>::min)()]);
CHECK(!v[(std::numeric_limits<std::uint64_t>::max)()]);
CHECK(!v[(std::numeric_limits<std::size_t>::max)()]);
CHECK(!v[std::numeric_limits<int>::max()]);
#if !defined(JSON_NOEXCEPTION)
CHECK_THROWS_WITH_AS(v.at(3), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at(3u), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
CHECK_THROWS_WITH_AS(v.at(std::int64_t(3)), "[json.exception.out_of_range.401] array index 3 is out of range", json::out_of_range&);
CHECK_THROWS_AS(v.at(-1), json::out_of_range&);
CHECK_THROWS_AS(v.at(-1L), json::out_of_range&);
CHECK_THROWS_AS(v.at(std::int64_t(-1)), json::out_of_range&);
CHECK_THROWS_AS(v.at((std::numeric_limits<std::int64_t>::min)()), json::out_of_range&);
CHECK_THROWS_AS(v.at((std::numeric_limits<std::uint64_t>::max)()), json::out_of_range&);
// not an array
const json_document o = json_document::parse("{}");
CHECK_THROWS_AS(o.root()[0u], json::type_error&);
CHECK_THROWS_AS(o.root()[1L], json::type_error&);
CHECK_THROWS_AS(o.root().at(std::uint32_t(0)), json::type_error&);
CHECK_THROWS_AS(o.root().at(std::int64_t(0)), json::type_error&);
#endif
} }
SECTION("iterators") SECTION("iterators")