mirror of
https://github.com/nlohmann/json.git
synced 2026-10-11 08:57:15 +00:00
Return the first member of duplicate keys from lookups again
Looking up the last member of a duplicate key cannot stop at a match, so every lookup scanned the whole object (1.6 to 3.4 times slower for small objects). operator[](key), at, find, value, contains, count, and JSON pointer resolution return the first member again, as yyjson and simdjson do; materialize() and get<map>() keep the last value, as parse(). The documentation says so in the feature page and on each lookup page, and explains how to get the value parse() would give. The integer index templates and the discarded chaining of operator[] stay. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
15 files changed
+153
-80
No files matched your search
@@ -15,7 +15,7 @@ basic_json_view at(IntegerType idx) const;
|
|||||||
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 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)).
|
[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
|
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
|
`#!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
|
## 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`
|
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
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ 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, 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.
|
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).
|
||||||
@@ -84,6 +84,13 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
|
|
||||||
## Notes
|
## 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
|
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
|
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
|
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
|
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 *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[]`.
|
[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,7 +33,7 @@ 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, 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.
|
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
|
||||||
@@ -41,6 +41,13 @@ No-throw guarantee: this function never throws exceptions.
|
|||||||
|
|
||||||
## Notes
|
## 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)
|
Overload 1 always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md)
|
||||||
view.
|
view.
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,18 @@ 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 another,
|
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.
|
-- already known from the index, without reading the key bytes -- before comparing its content.
|
||||||
|
|
||||||
## Notes
|
## 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)
|
This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md)
|
||||||
view.
|
view.
|
||||||
|
|
||||||
@@ -36,7 +43,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 (the lookup functions resolve to the *last* one).
|
counting every member with a matching key (the lookup functions resolve to the *first* one).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -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 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,
|
[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.
|
||||||
|
|
||||||
@@ -26,11 +26,18 @@ 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 another,
|
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.
|
-- already known from the index, without reading the key bytes -- before comparing its content.
|
||||||
|
|
||||||
## Notes
|
## 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
|
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.
|
also does so for a [discarded](is_discarded.md) view -- there is no separate "invalid" iterator to return.
|
||||||
|
|
||||||
|
|||||||
@@ -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 member
|
[`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the opposite
|
||||||
[`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md) resolve to, too (see the
|
of [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md), which resolve to the
|
||||||
[Notes on duplicate keys](operator[].md#notes)).
|
*first* occurrence (see the [Notes on duplicate keys](operator[].md#notes)).
|
||||||
|
|
||||||
!!! info "No pointers, references, or implicit conversion"
|
!!! 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
|
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 *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[]`.
|
[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) sees the *last* one, and so does [`materialize()`](materialize.md) -- like
|
[`operator[]`](operator[].md) only ever sees the *first* one and [`materialize()`](materialize.md) -- like
|
||||||
[`BasicJsonType::parse()`](../basic_json/parse.md).
|
[`BasicJsonType::parse()`](../basic_json/parse.md) -- keeps only the *last*.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
--8<-- "examples/basic_json_view__items.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;
|
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.
|
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
|
||||||
template accepts every integer type except `#!cpp bool` and `#!cpp std::size_t` (`#!cpp int`, `#!cpp unsigned`,
|
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
|
## 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))
|
[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
|
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))
|
[discarded](is_discarded.md))
|
||||||
@@ -80,7 +80,7 @@ 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, 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'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.
|
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
|
||||||
@@ -105,15 +105,18 @@ document.
|
|||||||
key on an array or a primitive, or an index on an object or a primitive, is `type_error.305` as for
|
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.
|
`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),
|
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
|
[`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
|
the *first* member with that key, because a lookup can stop as soon as it finds a match. This is different from
|
||||||
[`BasicJsonType::parse()`](../basic_json/parse.md)) keeps, so a lookup in the view and in the materialized value
|
[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every
|
||||||
agree. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
|
member in order and so keep the *last* value for a repeated key, so `#!cpp v["a"]` and
|
||||||
duplicates, in document order. A lookup scans all members for this: it cannot stop at the first match. See the
|
`#!cpp v.materialize()["a"]` can differ. To get the value `parse()` would give, use
|
||||||
example below and [`size()`](size.md#notes).
|
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
|
||||||
|
[`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"
|
!!! 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 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.
|
[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 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`
|
2. the value `ptr` resolves to, converted to `T`, or `default_value`
|
||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
@@ -68,7 +68,7 @@ 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, 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)).
|
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
|
||||||
@@ -77,6 +77,13 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
|
|
||||||
## Notes
|
## 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[]`"
|
!!! info "Differences to `at` and `operator[]`"
|
||||||
|
|
||||||
Unlike [`at`](at.md), this function does not throw if `key`/`ptr` resolves to no value. Unlike
|
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 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[] and materialize() -- like
|
// the update history is visible; operator[] only ever sees the first
|
||||||
// basic_json::parse() -- see the last one
|
// one, and materialize() -- like basic_json::parse() -- keeps the last
|
||||||
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 << "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';
|
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
|
||||||
last "retries" seen by operator[]: 5
|
first "retries" seen by operator[]: 1
|
||||||
last "retries" kept by materialize(): 5
|
last "retries" kept by materialize(): 5
|
||||||
@@ -134,16 +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
|
`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
|
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.
|
[`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: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
|
||||||
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
|
below.
|
||||||
[`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.
|
|
||||||
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
|
||||||
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
||||||
@@ -151,6 +143,51 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
|||||||
- **`dump()` and comparison are not (yet) provided** by `basic_json_view`. For now,
|
- **`dump()` and comparison are not (yet) provided** by `basic_json_view`. For now,
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can do those things with.
|
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can do those things with.
|
||||||
|
|
||||||
|
## 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. 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
|
## 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
|
[`get<T>()`](../api/basic_json_view/get.md) converts many `T` directly from the flat index, without ever building a
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ class short_key
|
|||||||
std::uint64_t m_b = 0;
|
std::uint64_t m_b = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// the key node of the last member of an object with the given key, or
|
/// the key node of the first member of an object with the given key, or
|
||||||
/// nullptr (the last one, as materialize() and parse() keep it); most keys are
|
/// nullptr (the search stops at the first match; materialize() and parse()
|
||||||
/// rejected by their length, from the index alone
|
/// keep the last value of a duplicate key instead); 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);
|
||||||
@@ -98,19 +98,19 @@ 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)
|
||||||
{
|
{
|
||||||
last = m;
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return last;
|
return nullptr;
|
||||||
}
|
}
|
||||||
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)
|
||||||
{
|
{
|
||||||
last = m;
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return last;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether an integer type is accepted as an array index by the view's
|
/// whether an integer type is accepted as an array index by the view's
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class basic_json_view
|
|||||||
// element access //
|
// element access //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
/// the value of the member with this key (the last one, should the key
|
/// the value of the member with this key (the first one, should the key
|
||||||
/// occur more than once); a discarded view if there is none, or if this
|
/// occur more than once); a discarded view if there is none, or if this
|
||||||
/// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
|
/// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
|
||||||
/// if this is any other value but an object.
|
/// if this is any other value but an object.
|
||||||
@@ -304,7 +304,7 @@ class 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 last one, should the key
|
/// the value of the member with this key (the first one, should the key
|
||||||
/// occur more than once). Throws type_error.304 if this is not an object,
|
/// 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
|
||||||
@@ -362,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 last one, should the key occur more than
|
/// is no such member (the first 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
|
||||||
@@ -427,7 +427,7 @@ class basic_json_view
|
|||||||
// lookup //
|
// lookup //
|
||||||
////////////
|
////////////
|
||||||
|
|
||||||
/// an iterator to the member with this key (the last one, should the
|
/// an iterator to the member with this key (the first 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
|
||||||
{
|
{
|
||||||
@@ -605,7 +605,7 @@ class basic_json_view
|
|||||||
: m_doc(d), m_node(n)
|
: m_doc(d), m_node(n)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// the value of the last member with this key, or a discarded view
|
/// the value of the first 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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -583,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 last occurrence
|
// document order; duplicate keys are found as their first 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());
|
||||||
@@ -624,23 +624,13 @@ 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())
|
// lookups find the first member with the key, which is this one if
|
||||||
{
|
// there is no earlier one
|
||||||
keys.push_back(key);
|
if (std::find(keys.begin(), keys.end(), key) != keys.end())
|
||||||
}
|
|
||||||
// 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;
|
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());
|
||||||
@@ -731,20 +721,28 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("duplicate keys: lookups find the last member, iteration all")
|
SECTION("duplicate keys: lookups find the first 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() == 3);
|
CHECK(v["a"].materialize() == 1);
|
||||||
CHECK(v.at("a").materialize() == 3);
|
CHECK(v.at("a").materialize() == 1);
|
||||||
CHECK(v.find("a") == std::next(v.begin(), 2));
|
CHECK(v.find("a") == v.begin());
|
||||||
CHECK(v.find("a").value().materialize() == 3);
|
CHECK(v.find("a").value().materialize() == 1);
|
||||||
CHECK(v.find("b") == std::next(v.begin()));
|
CHECK(v.find("b") == std::next(v.begin()));
|
||||||
CHECK(v.count("a") == 1);
|
CHECK(v.count("a") == 1);
|
||||||
CHECK(v.contains("a"));
|
CHECK(v.contains("a"));
|
||||||
CHECK(v.value("a", 0) == 3);
|
CHECK(v.value("a", 0) == 1);
|
||||||
CHECK(v["a"].materialize() == v.materialize()["a"]); // as materialize()
|
CHECK(v.materialize()["a"] == 3); // materialize() keeps the last value, unlike the lookups
|
||||||
|
// JSON pointers resolve to the first member at every level
|
||||||
|
const json_document dp = json_document::parse(R"({"a":{"b":1},"a":{"b":2,"b":3}})");
|
||||||
|
CHECK(dp.root()[json::json_pointer("/a/b")].materialize() == 1);
|
||||||
|
CHECK(dp.root().at(json::json_pointer("/a/b")).materialize() == 1);
|
||||||
|
CHECK(dp.root().value(json::json_pointer("/a/b"), 0) == 1);
|
||||||
|
CHECK(dp.root().materialize()[json::json_pointer("/a/b")] == 3);
|
||||||
|
// get<map> keeps the last value, as materialize()
|
||||||
|
CHECK((v.get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 3}, {"b", 2}}));
|
||||||
// keys of every length class (the 16-byte short compare and memcmp)
|
// keys of every length class (the 16-byte short compare and memcmp)
|
||||||
for (const std::size_t n :
|
for (const std::size_t n :
|
||||||
{
|
{
|
||||||
@@ -754,10 +752,10 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
const std::string key(n, 'k');
|
const std::string key(n, 'k');
|
||||||
const json_document dk = json_document::parse("{\"" + key + "\":1,\"" + key + "x\":2,\"" + key + "\":3,\"" + key + "\":4}");
|
const json_document dk = json_document::parse("{\"" + key + "\":1,\"" + key + "x\":2,\"" + key + "\":3,\"" + key + "\":4}");
|
||||||
CAPTURE(n)
|
CAPTURE(n)
|
||||||
CHECK(dk.root()[key].materialize() == 4);
|
CHECK(dk.root()[key].materialize() == 1);
|
||||||
CHECK(dk.root().at(key).materialize() == 4);
|
CHECK(dk.root().at(key).materialize() == 1);
|
||||||
CHECK(dk.root().find(key) == std::next(dk.root().begin(), 3));
|
CHECK(dk.root().find(key) == dk.root().begin());
|
||||||
CHECK(dk.root().value(key, 0) == 4);
|
CHECK(dk.root().value(key, 0) == 1);
|
||||||
CHECK(dk.root()[key + "x"].materialize() == 2);
|
CHECK(dk.root()[key + "x"].materialize() == 2);
|
||||||
}
|
}
|
||||||
std::string order;
|
std::string order;
|
||||||
|
|||||||
Reference in new issue
Block a user