Merge branch 'json-view/16-view-simd' into json-view/19-edit-set

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-09 23:33:56 +02:00
commit ddd0423b14
20 files changed
+199 -131

No files matched your search

+10 -3
View File
@@ -15,7 +15,7 @@ basic_json_view at(IntegerType idx) const;
basic_json_view at(const json_pointer& ptr) const;
```
1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
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`. 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
@@ -35,7 +35,7 @@ basic_json_view at(const json_pointer& ptr) const;
## Return value
1. the value of the last member with key `key`
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
@@ -75,7 +75,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
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.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
on average.
@@ -86,6 +86,13 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Notes
!!! warning "Duplicate keys: the first member wins"
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
the last value.
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. This
@@ -24,7 +24,7 @@ Constant.
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),
which all resolve to the *last* member with a given key. See the
which all resolve to the *first* member with a given key. See the
[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
@@ -33,7 +33,7 @@ No-throw guarantee: this function never throws exceptions.
## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
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.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
on average.
@@ -43,6 +43,13 @@ No-throw guarantee: this function never throws exceptions.
## Notes
!!! warning "Duplicate keys: the first member wins"
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
the last value.
Overload 1 always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md)
view.
@@ -24,13 +24,20 @@ 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, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
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.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
average.
## Notes
!!! warning "Duplicate keys: the first member wins"
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
the last value.
This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md)
view.
@@ -38,7 +45,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
[`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
counting every member with a matching key (the lookup functions resolve to the *last* one).
counting every member with a matching key (the lookup functions resolve to the *first* one).
## Examples
+9 -2
View File
@@ -6,7 +6,7 @@ iterator find(const char* key) const;
iterator find(const string_t& key) const;
```
Finds a member with key `key` -- the last one, should the key occur more than once (see
Finds a member with key `key` -- the first 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,
[`end()`](end.md) is returned.
@@ -26,13 +26,20 @@ 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, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
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.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
average.
## Notes
!!! warning "Duplicate keys: the first member wins"
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
the last value.
Unlike [`BasicJsonType::find`](../basic_json/find.md), which always returns `#!cpp end()` for a non-object type, this
also does so for a [discarded](is_discarded.md) view -- there is no separate "invalid" iterator to return.
+3 -3
View File
@@ -87,9 +87,9 @@ exception thrown while converting through `materialize()` (the last bullet) is d
!!! 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 member
[`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md) resolve to, too (see the
[Notes on duplicate keys](operator[].md#notes)).
[`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"
@@ -48,7 +48,7 @@ Constant.
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),
[`contains`](contains.md), and [`count`](count.md), which resolve to the *last* member with a given key. See the
[`contains`](contains.md), and [`count`](count.md), which resolve to the *first* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`.
!!! 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
member, in the order they happened. `items()` walks all of them, so the update history is visible, while
[`operator[]`](operator[].md) sees the *last* one, and so does [`materialize()`](materialize.md) -- like
[`BasicJsonType::parse()`](../basic_json/parse.md).
[`operator[]`](operator[].md) only ever sees the *first* one and [`materialize()`](materialize.md) -- like
[`BasicJsonType::parse()`](../basic_json/parse.md) -- keeps only the *last*.
```cpp
--8<-- "examples/basic_json_view__items.cpp"
@@ -15,7 +15,7 @@ basic_json_view operator[](IntegerType idx) const;
basic_json_view operator[](const json_pointer& ptr) const;
```
1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
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
template accepts every integer type except `#!cpp bool` and `#!cpp std::size_t` (`#!cpp int`, `#!cpp unsigned`,
@@ -38,7 +38,7 @@ basic_json_view operator[](const json_pointer& ptr) const;
## Return value
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
1. the value of the first member with key `key`, or a discarded view if no member has this key (or if this view is
[discarded](is_discarded.md))
2. the element at index `idx`, or a discarded view if `#!cpp idx >= size()` or `idx` is negative (or if this view is
[discarded](is_discarded.md))
@@ -80,7 +80,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
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, so a
key of a different length than `key` is rejected without touching the source text.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
@@ -107,16 +107,19 @@ document.
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"
!!! warning "Duplicate keys: the first member wins"
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), [`value`](value.md), and JSON pointer resolution) all resolve to
the *last* member with that key. This is the member [`materialize()`](materialize.md) (and
[`BasicJsonType::parse()`](../basic_json/parse.md)) keeps, so a lookup in the view and in the materialized value
agree. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
duplicates, in document order. A lookup in an object without a hash index scans all members for this: it cannot
stop at the first match. The hash index of a larger object (128 members or more) leads to the last member of a key
as well. See the example below and [`size()`](size.md#notes).
the *first* member with that key, because a lookup can stop as soon as it finds a match. This is different from
[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every
member in order and so keep the *last* value for a repeated key, so `#!cpp v["a"]` and
`#!cpp v.materialize()["a"]` can differ. To get the value `parse()` would give, use
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
The hash index of a larger object (128 members or more) leads to the first member of a key as well.
[`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
duplicates, in document order. See [Duplicate keys](../../features/json_view.md#duplicate-keys) and
[`size()`](size.md#notes).
!!! info "JSON pointer resolution"
@@ -18,6 +18,8 @@ bool operator==(const BasicJsonType& lhs, const basic_json_view& rhs);
Neither overload builds a `BasicJsonType` value for a view to do the comparison (see [Notes](#notes) below). Numbers
compare by value across their types (`#!cpp 1 == 1.0`), and an object compares by its members, with duplicate keys
resolved exactly as `parse()` resolves them -- the last value, at the position of the first occurrence of the key.
This is not what [`operator[]`](operator[].md) and the other lookups return for a duplicate key: they find the *first*
member (see [Duplicate keys](../../features/json_view.md#duplicate-keys)).
## Parameters
+10 -3
View File
@@ -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;
```
1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
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.
@@ -39,7 +39,7 @@ equivalent) deduce `string_t`, not `const char*`, for their return type and for
## Return value
1. the last member with key `key`, converted to `T`, or `default_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
@@ -68,7 +68,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Complexity
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
another, in document order, scanning all of them, since the last match is wanted. Plus the complexity of converting
another, in document order, stopping at the first match. Plus the complexity of converting
the found member to `T` (see [`get`](get.md)).
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
on average.
@@ -79,6 +79,13 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
## Notes
!!! warning "Duplicate keys: the first member wins"
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
the last value.
!!! info "Differences to `at` and `operator[]`"
Unlike [`at`](at.md), this function does not throw if `key`/`ptr` resolves to no value. Unlike
@@ -7,8 +7,8 @@ int main()
{
// 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
// the update history is visible; operator[] and materialize() -- like
// basic_json::parse() -- see the last one
// the update history is visible; operator[] only ever sees the first
// one, and materialize() -- like basic_json::parse() -- keeps the last
json_document updates = json_document::parse(R"({"retries": 1, "timeout": 30, "retries": 5})");
const auto settings = updates.root();
@@ -17,6 +17,6 @@ int main()
std::cout << item.key() << '=' << item.value().materialize().dump() << '\n';
}
std::cout << "last \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n';
std::cout << "first \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n';
std::cout << "last \"retries\" kept by materialize(): " << settings.materialize()["retries"].dump() << '\n';
}
@@ -1,5 +1,5 @@
retries=1
timeout=30
retries=5
last "retries" seen by operator[]: 5
first "retries" seen by operator[]: 1
last "retries" kept by materialize(): 5
+50 -13
View File
@@ -134,17 +134,8 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
`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,
[`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)
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),
[`contains`](../api/basic_json_view/contains.md), and [`count`](../api/basic_json_view/count.md) resolve to the
*last* occurrence -- the one `basic_json::parse()` (and so
[`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 (objects with 128 members or more get a hash index that leads to the last
occurrence directly).
See the [Notes on duplicate keys](../api/basic_json_view/operator%5B%5D.md#notes) of `operator[]`.
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
below.
- **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
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
@@ -156,6 +147,52 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
equal values for them, without ever building a tree to do it. For ordering, too,
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can compare.
## Duplicate keys
!!! warning "A lookup in a view and in the parsed value can give different answers"
If an object in the source text repeats a key, [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md),
[`count`](../api/basic_json_view/count.md), and JSON pointer resolution all return the **first** member with that
key. `basic_json::parse()` instead keeps the **last** value of a repeated key, so for `#!cpp {"a":1,"a":2}`,
`#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is `#!cpp 2`.
[`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, in document order, and
[`size()`](../api/basic_json_view/size.md) counts all of them, so `#!cpp view.size()` can be larger than
`#!cpp view.materialize().size()`.
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
to scan all members of the object, even when the key is found at the very first one: this made lookups in small
objects 1.6 to 3.4 times slower. The hash index of objects with 128 members or more leads to the first member of a
key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return the first
member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees
duplicates is unpredictable, so neither choice is wrong.
**What stays the same as `parse()`?** [`materialize()`](../api/basic_json_view/materialize.md) and
[`get<std::map<...>>()`](../api/basic_json_view/get.md) replay every member in order, so they keep the *last* value
exactly like `#!cpp basic_json::parse()` (at the position of the first occurrence of the key, for an
[`ordered_json`](../api/ordered_json.md)).
**How do I get the value `parse()` would give?** Either call `#!cpp view.materialize()` and look the key up in the
result, or iterate the members and keep the last match:
```cpp
// the last member with the key "a", as parse() would keep it
json_view last;
for (const auto item : view.items())
{
if (item.key() == "a")
{
last = item.value();
}
}
```
If you cannot trust the source text to have unique keys, check [`size()`](../api/basic_json_view/size.md) against the
number of distinct keys, or reject duplicates when iterating.
## 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
@@ -185,8 +222,8 @@ Both results are only valid as long as the view -- and, for a string with no esc
[`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a
`basic_json` value. An object's members are written in document order, not sorted by key, and *every* occurrence of a
repeated key is written, not only the last one -- the same two ways [iteration](#what-is-different) already differs
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see above. `#!cpp materialize().dump()` gives
a different result in both respects for a `json_view`.
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see [Duplicate keys](#duplicate-keys).
`#!cpp materialize().dump()` gives a different result in both respects for a `json_view`.
By default, numbers are written the way [`basic_json::dump()`](../api/basic_json/dump.md) would.
[`number_format::source`](../api/basic_json_view/number_format.md) instead copies every number exactly as it was
+1 -1
View File
@@ -215,7 +215,7 @@ packet-beta
- **Large objects** (128 members or more) get a hash index after parsing
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
not compare every key. Of duplicate keys the table leads to the last, as a linear search does. The object's `extra`
not compare every key. Of duplicate keys the table leads to the first, as a linear search does. The object's `extra`
holds the number of its table. Only 65,535 tables fit into `extra`; objects beyond them are searched linearly.
For example, `#!json {"a": [1, 2.5]}` becomes five nodes. Each node's elements follow it, and `next` leads from an