Files
json/docs/mkdocs/docs/api/basic_json/operator[].md
T
Niels Lohmann 78a0687147 Strong exception guarantee for operator[](json_pointer) (#5940)
* 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>
2026-10-27 17:15:55 +01:00

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;
  1. Returns a reference to the array element at specified location idx.
  2. Returns a reference to the object element with specified key key. The non-const qualified overload takes the key by value.
  3. See 2. This overload is only available if KeyType is comparable with #!cpp typename object_t::key_type and #!cpp typename object_comparator_t::is_transparent denotes a type.
  4. Returns a reference to the element with specified JSON pointer ptr.

Template parameters

KeyType
A type for an object key other than json_pointer that is comparable with string_t using object_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

  1. (const) reference to the element at index idx
  2. (const) reference to the element at key key
  3. (const) reference to the element at key key
  4. (const) reference to the element pointed to by ptr

Exception safety

Strong exception safety: if an exception occurs, the original value stays intact.

Exceptions

  1. The function can throw the following exceptions:

    • Throws type_error.305 if 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_error if idx equals the maximum value of size_type; the array is left unchanged. (This is the one index for which growing the array to hold it cannot be expressed as a size_type size, the same way an oversized resize throws.)
  2. The function can throw the following exceptions:

    • Throws type_error.305 if the JSON value is not an object or null; in that case, using the [] operator with a key makes no sense.
  3. See 2.

  4. The function can throw the following exceptions:

    • Throws parse_error.106 if an array index in the passed JSON pointer ptr begins with '0'.
    • Throws parse_error.109 if an array index in the passed JSON pointer ptr is not a number.
    • Throws out_of_range.402 if the array index '-' is used in the passed JSON pointer ptr for the const version.
    • Throws out_of_range.404 if the JSON pointer ptr can not be resolved.
    • Throws out_of_range.410 if an array index in the passed JSON pointer ptr exceeds the range of size_type (e.g., on 32-bit platforms).

    For the const version, an object key or array index in ptr that does not exist is not reported by an exception, but is undefined behavior (see the notes below). Use at for checked access.

    If the non-const version throws, the value is left unchanged: values it created for earlier reference tokens of ptr are removed again. This also holds for exceptions such as #!cpp std::bad_alloc or #!cpp std::length_error thrown while padding an array up to a very large index. With custom template parameters, this requires that erase() of the object and array types and the move operations of the custom base class do not throw.

Complexity

  1. Constant if idx is in the range of the array. Otherwise, linear in idx - size().
  2. Logarithmic in the size of the container.
  3. Logarithmic in the size of the container.
  4. Linear in the number of reference tokens of ptr; for each of them, logarithmic in the size of the object, or constant for an array. When the non-const version pads an array with #!json null values, it is additionally linear in idx - size() for that array.

Notes

!!! 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)**!
  1. The non-const version may add values: If idx is beyond the range of the array (i.e., idx >= size()), then the array is silently filled up with #!json null values to make idx a valid reference to the last stored element. In case the value was #!json null before, it is converted to an array.

  2. If key is not found in the object, then it is silently added to the object and filled with a #!json null value to make key a valid reference. In case the value was #!json null before, it is converted to an object.

  3. See 2.

  4. null values 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 null value 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 null value 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).
    

!!! 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 checking
  • value - access with default value
  • basic_json_view::operator[] - the same access on a zero-copy view (a duplicate key resolves to the first member, whereas parse() keeps the last; a missing key gives a discarded view instead of undefined behavior)
  • Element Access - overview of all ways to access elements
  • JSON Pointer - access nested values with a pointer

Version history

  1. Added in version 1.0.0. Fixed in version 3.13.0 to throw #!cpp std::length_error instead of emptying the array and accessing it out of bounds when idx equals the maximum value of size_type. A missing index in the const version is guarded by a runtime assertion since version 3.13.0.
  2. Added in version 1.0.0. Added overloads for T* key in version 1.1.0. The T* key overloads forward to overload 3 since version 3.11.0.
  3. Added in version 3.11.0. Support for keys convertible to std::string_view added in version 3.13.0, consistently across the lookup functions.
  4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since version 3.13.0. Throws parse_error.109 instead of out_of_range.404 for a one-character array index that is not a digit (e.g., /x) in version 3.13.0, as it already did for longer ones. Fixed in version 3.13.0 to leave the value unchanged if the non-const version throws (strong exception safety, as documented); before, values created for earlier reference tokens of ptr were kept.