* Keep the value unchanged when operator[] with a JSON pointer throws
The non-const operator[](json_pointer) is documented with strong exception
safety, but get_unchecked created intermediate values while walking the
pointer and kept them when a later reference token failed, e.g.
json j; j["/a/99999999999999999999999"_json_pointer] threw 404 and left
{"a":[]}; padding an existing array or appending with "-" behaved alike,
and so did std::length_error/std::bad_alloc from a huge index.
get_unchecked now only walks existing values up to the last reference
token. The last token's key insertion or array growth is strongly
exception safe by itself. Any other change goes through
get_unchecked_create, which records the one change it makes to an existing
value (null converted, key inserted, array grown) in a RAII guard and
reverts it if a later step throws; everything else it creates lies below
that change. The guard needs no try/catch, so JSON_NOEXCEPTION builds are
unaffected. Successful calls create the same values as before.
Docs: state the guarantee in the exceptions list and version history of
operator[], fix the complexity of the pointer overloads of operator[] and
at, and warn that padding arrays up to an untrusted index allocates memory
proportional to the index (operator[].md, untrusted_input.md).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Make the strongly exception-safe pointer operator[] faster than before
The previous commit made get_unchecked strongly exception safe but slower
with GCC 16 when it creates values (up to +6 % instructions for appending
with "-"). This restructures it so that no benchmark row is slower than
before the strong guarantee, with GCC 16, Clang 22 and Apple Clang.
- get_unchecked walks existing values in one loop that inserts nothing.
The last reference token is resolved in that walk: "-" appends with
emplace_back(), an index grows the array with operator[], and a missing
key is inserted with emplace_hint() at the position the walk's
lower_bound() found, so the key is searched once.
- With std::less<string_t> or std::less<> as comparator, the walk checks
the key at the lower bound for equality instead of equivalence, which is
the same for these comparators and cheaper.
- get_unchecked_create (not inlined) handles all other changes. Its guard
swaps a null value with a fresh null value instead of copying it and
reverts the change in a non-inlined function, so a successful call pays
one compare for it. As every null value converted afterwards is a fresh
one, it is converted in place instead of assigning a new array or object.
- Keys are added to new objects with emplace(), which also replaces the
basic_json::operator[] call for ordered_json.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix rollback of pointer operator[] for unusual object and string types
Follow-up to the strong exception guarantee of the non-const
operator[](json_pointer):
- get_unchecked_create assumed that emplace() always inserts the key that
the walk did not find. For object types whose lookup and emplace()
disagree (e.g. a copied fifo_map), nothing was inserted, the rollback
erased the existing member, and the walk continued with a value of the
wrong type (assertion, type_error.305, or a crash). It now checks the
result of emplace(), arms the rollback only if a key was inserted, and
otherwise continues the walk with the member found, as basic_json's
operator[] does: such pointers throw out_of_range.404 and leave the value
unchanged, as before the strong guarantee.
- The rollback erased the inserted key by key, which converts string_t to
object_t::key_type and may allocate inside a noexcept function
(std::terminate on bad_alloc with custom key types). It now erases by the
iterator emplace() returned, and shrinks a grown array with erase() at
the end instead of resize(). The documentation states the remaining
requirement for custom template parameters.
- Creating values no longer calls string_t::begin(), so the non-const
operator[](json_pointer) compiles with string types that lack it (as the
alternative string type of the tests).
- Correct the @throw lists of get_unchecked and get_unchecked_create.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Address review comments on operator[](json_pointer) rollback
- undo() shrinks the array with resize() instead of erase()
- comments say what "will be" done in get_unchecked_create
- tests: avoid a clang-tidy performance-unnecessary-copy-initialization
hit (unit-alt-string) and the word "access" in section names, which
Flawfinder flags as race/access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Let the pointer rollback guard call undo() unconditionally
The check in the destructor and JSON_HEDLEY_NEVER_INLINE on undo() made no
measurable difference: instruction counts of j[ptr] are identical with
GCC 13 -O3 and within 0.4 % with clang, timings are within noise.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
14 KiB
nlohmann::basic_json::operator[]
// (1)
reference operator[](size_type idx);
const_reference operator[](size_type idx) const;
// (2)
reference operator[](typename object_t::key_type key);
const_reference operator[](const typename object_t::key_type& key) const;
// (3)
template<typename KeyType>
reference operator[](KeyType&& key);
template<typename KeyType>
const_reference operator[](KeyType&& key) const;
// (4)
reference operator[](const json_pointer& ptr);
const_reference operator[](const json_pointer& ptr) const;
- Returns a reference to the array element at specified location
idx. - Returns a reference to the object element with specified key
key. The non-const qualified overload takes the key by value. - See 2. This overload is only available if
KeyTypeis comparable with#!cpp typename object_t::key_typeand#!cpp typename object_comparator_t::is_transparentdenotes a type. - Returns a reference to the element with specified JSON pointer
ptr.
Template parameters
KeyType- A type for an object key other than
json_pointerthat is comparable withstring_tusingobject_comparator_t. This can also be a string view (C++17).
Iterator invalidation
For the non-const versions 1. and 4., when passing an array index that does not exist, it is created and filled with
a #!json null value before a reference to it is returned. For this, a reallocation can happen, in which case all
iterators (including the end() iterator) and all references to the elements are invalidated.
For ordered_json, also passing an object key to the non-const versions 2., 3., and 4., a
reallocation can happen which again invalidates all iterators and all references.
Parameters
idx(in)- index of the element to access
key(in)- object key of the element to access
ptr(in)- JSON pointer to the desired element
Return value
- (const) reference to the element at index
idx - (const) reference to the element at key
key - (const) reference to the element at key
key - (const) reference to the element pointed to by
ptr
Exception safety
Strong exception safety: if an exception occurs, the original value stays intact.
Exceptions
-
The function can throw the following exceptions:
- Throws
type_error.305if the JSON value is not an array or null; in that case, using the[]operator with an index makes no sense. - Throws
#!cpp std::length_errorifidxequals the maximum value ofsize_type; the array is left unchanged. (This is the one index for which growing the array to hold it cannot be expressed as asize_typesize, the same way an oversizedresizethrows.)
- Throws
-
The function can throw the following exceptions:
- Throws
type_error.305if the JSON value is not an object or null; in that case, using the[]operator with a key makes no sense.
- Throws
-
See 2.
-
The function can throw the following exceptions:
- Throws
parse_error.106if an array index in the passed JSON pointerptrbegins with '0'. - Throws
parse_error.109if an array index in the passed JSON pointerptris not a number. - Throws
out_of_range.402if the array index '-' is used in the passed JSON pointerptrfor the const version. - Throws
out_of_range.404if the JSON pointerptrcan not be resolved. - Throws
out_of_range.410if an array index in the passed JSON pointerptrexceeds the range ofsize_type(e.g., on 32-bit platforms).
For the const version, an object key or array index in
ptrthat does not exist is not reported by an exception, but is undefined behavior (see the notes below). Useatfor checked access.If the non-const version throws, the value is left unchanged: values it created for earlier reference tokens of
ptrare removed again. This also holds for exceptions such as#!cpp std::bad_allocor#!cpp std::length_errorthrown while padding an array up to a very large index. With custom template parameters, this requires thaterase()of the object and array types and the move operations of the custom base class do not throw. - Throws
Complexity
- Constant if
idxis in the range of the array. Otherwise, linear inidx - size(). - Logarithmic in the size of the container.
- Logarithmic in the size of the container.
- Linear in the number of reference tokens of
ptr; for each of them, logarithmic in the size of the object, or constant for an array. When the non-const version pads an array with#!json nullvalues, it is additionally linear inidx - size()for that array.
Notes
!!! danger "Undefined behavior and runtime assertions"
The following cases apply to the **const** overloads; the non-const overloads instead insert the missing element
(see the notes below).
1. If the element at index `idx` does not exist, the behavior is undefined and is **guarded by a
[runtime assertion](../../features/assertions.md)**!
2. If the element with key `key` does not exist, the behavior is undefined and is **guarded by a
[runtime assertion](../../features/assertions.md)**!
3. If the JSON pointer `ptr` refers to an object key or an array index that does not exist, the behavior is
undefined and is **guarded by a [runtime assertion](../../features/assertions.md)**!
-
The non-const version may add values: If
idxis beyond the range of the array (i.e.,idx >= size()), then the array is silently filled up with#!json nullvalues to makeidxa valid reference to the last stored element. In case the value was#!json nullbefore, it is converted to an array. -
If
keyis not found in the object, then it is silently added to the object and filled with a#!json nullvalue to makekeya valid reference. In case the value was#!json nullbefore, it is converted to an object. -
See 2.
-
nullvalues are created in arrays and objects if necessary.In particular:
- If the JSON pointer points to an object key that does not exist, it is created and filled with a
#!json nullvalue before a reference to it is returned. - If the JSON pointer points to an array index that does not exist, it is created and filled with a
#!json nullvalue before a reference to it is returned. All indices between the current maximum and the given index are also filled with#!json null. - The special value
-is treated as a synonym for the index past the end.
!!! note "Creating intermediate levels that don't exist yet"
When the JSON pointer traverses intermediate levels that don't exist at all yet (not just a missing leaf), each missing level is created as an array or an object depending on whether the corresponding pointer token is a valid array index: the token `0`, a sequence of digits that does not begin with `0`, or the token `-` creates an array, and every other token creates an object. For example, on an initially `#!json null` value, `/foo/0/0/0` creates nested arrays, while `/foo/one/one/one` creates nested objects. Tokens such as `01` or the empty token cannot be array indices (cf. RFC 6901, Sect. 4) and therefore create objects, just as they would if the level already existed as an object. This is not specified by the JSON Pointer RFC; it is this library's own, intentional disambiguation rule. See also [JSON Pointer](../../features/json_pointer.md).!!! warning "Large array indices"
As the array is filled up with `#!json null` values to the given index, the memory used grows with the index and not with the size of the pointer: on a 64-bit platform, the index `10000000` allocates about 160 MB. The library does not limit the index, so check the array indices of a JSON pointer from untrusted input before using it with the non-const version. See [Parsing untrusted input](../../features/parsing/untrusted_input.md#json-pointers-and-flattened-keys). - If the JSON pointer points to an object key that does not exist, it is created and filled with a
!!! warning "Deprecation"
Overload (4) also accepts a [`json_pointer`](../json_pointer/index.md) whose template argument is a `basic_json`
specialization (e.g., `nlohmann::json_pointer<nlohmann::json>`) instead of a string type. This is deprecated since
version 3.11.0 and will be removed in a future major version; use `basic_json::json_pointer` (for `json`,
`nlohmann::json_pointer<std::string>`) instead.
You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated
function.
See the [migration guide](../../integration/migration_guide.md#json-pointers) for how to update existing code.
Examples
??? example "Example: (1) access specified array element"
The example below shows how array elements can be read and written using `[]` operator. Note the addition of
`#!json null` values.
```cpp
--8<-- "examples/operator_array__size_type.cpp"
```
Output:
```json
--8<-- "examples/operator_array__size_type.output"
```
??? example "Example: (1) access specified array element (const)"
The example below shows how array elements can be read using the `[]` operator.
```cpp
--8<-- "examples/operator_array__size_type_const.cpp"
```
Output:
```json
--8<-- "examples/operator_array__size_type_const.output"
```
??? example "Example: (2) access specified object element"
The example below shows how object elements can be read and written using the `[]` operator.
```cpp
--8<-- "examples/operator_array__object_t_key_type.cpp"
```
Output:
```json
--8<-- "examples/operator_array__object_t_key_type.output"
```
??? example "Example: (2) access specified object element (const)"
The example below shows how object elements can be read using the `[]` operator.
```cpp
--8<-- "examples/operator_array__object_t_key_type_const.cpp"
```
Output:
```json
--8<-- "examples/operator_array__object_t_key_type_const.output"
```
??? example "Example: (3) access specified object element using string_view"
The example below shows how object elements can be read using the `[]` operator.
```cpp
--8<-- "examples/operator_array__keytype.c++17.cpp"
```
Output:
```json
--8<-- "examples/operator_array__keytype.c++17.output"
```
??? example "Example: (3) access specified object element using string_view (const)"
The example below shows how object elements can be read using the `[]` operator.
```cpp
--8<-- "examples/operator_array__keytype_const.c++17.cpp"
```
Output:
```json
--8<-- "examples/operator_array__keytype_const.c++17.output"
```
??? example "Example: (4) access specified element via JSON Pointer"
The example below shows how values can be read and written using JSON Pointers.
```cpp
--8<-- "examples/operator_array__json_pointer.cpp"
```
Output:
```json
--8<-- "examples/operator_array__json_pointer.output"
```
??? example "Example: (4) access specified element via JSON Pointer (const)"
The example below shows how values can be read using JSON Pointers.
```cpp
--8<-- "examples/operator_array__json_pointer_const.cpp"
```
Output:
```json
--8<-- "examples/operator_array__json_pointer_const.output"
```
See also
- Unchecked access - element access without range checking
- Runtime assertions - assertions guarding against undefined behavior
at- access by reference with range checkingvalue- access with default valuebasic_json_view::operator[]- the same access on a zero-copy view (a duplicate key resolves to the first member, whereasparse()keeps the last; a missing key gives a discarded view instead of undefined behavior)- Element Access - overview of all ways to access elements
- JSON Pointer - access nested values with a pointer
Version history
- Added in version 1.0.0. Fixed in version 3.13.0 to throw
#!cpp std::length_errorinstead of emptying the array and accessing it out of bounds whenidxequals the maximum value ofsize_type. A missing index in the const version is guarded by a runtime assertion since version 3.13.0. - Added in version 1.0.0. Added overloads for
T* keyin version 1.1.0. TheT* keyoverloads forward to overload 3 since version 3.11.0. - Added in version 3.11.0. Support for keys convertible to
std::string_viewadded in version 3.13.0, consistently across the lookup functions. - Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since
version 3.13.0. Throws
parse_error.109instead ofout_of_range.404for a one-character array index that is not a digit (e.g.,/x) in version 3.13.0, as it already did for longer ones. Fixed in version 3.13.0 to leave the value unchanged if the non-const version throws (strong exception safety, as documented); before, values created for earlier reference tokens ofptrwere kept.