mirror of
https://github.com/nlohmann/json.git
synced 2026-10-11 17:07:15 +00:00
Merge branch 'json-view/22-view-dump-fast' into json-view/15-view-bench
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
25 files changed
+378
-285
No files matched your search
@@ -86,8 +86,8 @@ view of a *different* document (overloads 1-2 only; overload 3 always starts fro
|
|||||||
|
|
||||||
!!! info "Duplicate keys"
|
!!! info "Duplicate keys"
|
||||||
|
|
||||||
Overload 1. removes *every* member with `key`, not just the last one that lookups find -- unlike [`set`](set.md),
|
Overload 1. removes *every* member with `key`, not just the first -- unlike [`set`](set.md), which assigns the
|
||||||
which assigns that member and drops the rest. This is why it returns a count rather than a single view: there may be
|
first occurrence and drops the rest. This is why it returns a count rather than a single view: there may be
|
||||||
more than one member removed, or none.
|
more than one member removed, or none.
|
||||||
|
|
||||||
Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
|
Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ has `set`; calling it on a read-only `basic_json_document` fails to compile (`#!
|
|||||||
|
|
||||||
1. Replaces the value `target` refers to with `value`.
|
1. Replaces the value `target` refers to with `value`.
|
||||||
2. Sets the member `key` of the object `object` to `value`: assigns it if `object` already has a member with this
|
2. Sets the member `key` of the object `object` to `value`: assigns it if `object` already has a member with this
|
||||||
key -- the last one, should the key occur more than once (the member
|
key -- the first one, should the key occur more than once, and the later duplicates are then dropped (see the
|
||||||
[`operator[]`](../basic_json_view/operator%5B%5D.md) returns), and the other duplicates are then dropped (see the
|
|
||||||
[Notes](#notes) below) -- or appends a new member at the end otherwise. A [null](../basic_json_view/is_null.md)
|
[Notes](#notes) below) -- or appends a new member at the end otherwise. A [null](../basic_json_view/is_null.md)
|
||||||
`object` first becomes an empty object.
|
`object` first becomes an empty object.
|
||||||
3. Assigns `value` to the element at index `idx` of the array `array`, which must already exist (`#!cpp idx <
|
3. Assigns `value` to the element at index `idx` of the array `array`, which must already exist (`#!cpp idx <
|
||||||
@@ -147,13 +146,12 @@ document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_d
|
|||||||
|
|
||||||
!!! info "Duplicate keys"
|
!!! info "Duplicate keys"
|
||||||
|
|
||||||
If `object` already has more than one member with `key` (2.), `value` is assigned to the *last* one -- the member
|
If `object` already has more than one member with `key` (2.), the *first* one is assigned `value` and every
|
||||||
[`operator[]`](../basic_json_view/operator%5B%5D.md), [`at`](../basic_json_view/at.md), and
|
later member with the same key is removed -- so that a lookup, an iteration, and
|
||||||
[`find`](../basic_json_view/find.md) return for reading, so that a view taken from `object["key"]` before the call
|
[`materialize()`](../basic_json_view/materialize.md) of `object` afterward all agree on a single value for
|
||||||
shows `value` afterward -- and every other member with the same key is removed. The key stays at the
|
`key`, the same way [`operator[]`](../basic_json_view/operator%5B%5D.md) already picks the first occurrence of a
|
||||||
position of its *first* occurrence, where [`materialize()`](../basic_json_view/materialize.md) puts it as well. A
|
duplicate key for reading. See the [Notes on duplicate keys](../basic_json_view/operator%5B%5D.md#notes) of
|
||||||
lookup, an iteration, and `materialize()` of `object` afterward therefore all agree on a single member for `key`. See the
|
`operator[]`.
|
||||||
[Notes on duplicate keys](../basic_json_view/operator%5B%5D.md#notes) of `operator[]`.
|
|
||||||
|
|
||||||
Setting a member (2.) or an element (3., through 4.) of an array or object whose elements have not been edited
|
Setting a member (2.) or an element (3., through 4.) of an array or object whose elements have not been edited
|
||||||
before switches it from its parsed layout to a growable block holding links to its elements; a later
|
before switches it from its parsed layout to a growable block holding links to its elements; a later
|
||||||
|
|||||||
@@ -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.
|
||||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||||
on average.
|
on average.
|
||||||
@@ -86,6 +86,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.
|
||||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||||
on average.
|
on average.
|
||||||
@@ -43,6 +43,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,13 +24,20 @@ 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.
|
||||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||||
average.
|
average.
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||
@@ -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
|
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,13 +26,20 @@ 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.
|
||||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||||
average.
|
average.
|
||||||
|
|
||||||
## 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.
|
||||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
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
|
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 in an object without a hash index scans all members for this: it cannot
|
`#!cpp v.materialize()["a"]` can differ. To get the value `parse()` would give, use
|
||||||
stop at the first match. The hash index of a larger object (128 members or more) leads to the last member of a key
|
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
|
||||||
as well. See the example below and [`size()`](size.md#notes).
|
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"
|
!!! 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
|
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
|
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.
|
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
|
## Parameters
|
||||||
|
|
||||||
|
|||||||
@@ -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)).
|
||||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||||
on average.
|
on average.
|
||||||
@@ -79,6 +79,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,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
|
`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 (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[]`.
|
|
||||||
- **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
|
||||||
@@ -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,
|
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.
|
[`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
|
## 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
|
||||||
@@ -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
|
[`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
|
`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
|
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
|
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see [Duplicate keys](#duplicate-keys).
|
||||||
a different result in both respects for a `json_view`.
|
`#!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.
|
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
|
[`number_format::source`](../api/basic_json_view/number_format.md) instead copies every number exactly as it was
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ packet-beta
|
|||||||
- **Large objects** (128 members or more) get a hash index after parsing
|
- **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)):
|
([`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
|
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.
|
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
|
For example, `#!json {"a": [1, 2.5]}` becomes five nodes. Each node's elements follow it, and `next` leads from an
|
||||||
|
|||||||
@@ -152,23 +152,25 @@ class editor
|
|||||||
{
|
{
|
||||||
become_empty(o, value_t::object);
|
become_empty(o, value_t::object);
|
||||||
}
|
}
|
||||||
// an existing member: assign the one that lookups find (the last
|
// an existing member: assign it (and drop later duplicates, so that
|
||||||
// one, should the key occur more than once), and drop the others, so
|
// lookups, iteration, and materialize() agree)
|
||||||
// that lookups, iteration, and materialize() agree. The key stays at
|
|
||||||
// the position of its first occurrence, as materialize() puts it.
|
|
||||||
node* slot = nullptr;
|
node* slot = nullptr;
|
||||||
std::size_t matches = 0;
|
bool duplicates = false;
|
||||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||||
{
|
{
|
||||||
if (key_equals(*k, key))
|
if (key_equals(*k, key))
|
||||||
{
|
{
|
||||||
|
if (slot != nullptr)
|
||||||
|
{
|
||||||
|
duplicates = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
slot = const_cast<node*>(nav::value(k + 1)); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
slot = const_cast<node*>(nav::value(k + 1)); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||||
++matches;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (slot != nullptr)
|
if (slot != nullptr)
|
||||||
{
|
{
|
||||||
if (matches > 1)
|
if (duplicates)
|
||||||
{
|
{
|
||||||
erase_members(o, key, true);
|
erase_members(o, key, true);
|
||||||
}
|
}
|
||||||
@@ -314,38 +316,27 @@ class editor
|
|||||||
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the members with this key from an object: all of them, or all
|
/// remove the members with this key (all, or all but the first) from an object
|
||||||
/// but one. That one stays where the first occurrence is, but holds the
|
std::size_t erase_members(node* o, string_view_t key, bool keep_first)
|
||||||
/// value of the last (the one that lookups find, which views may refer to).
|
|
||||||
std::size_t erase_members(node* o, string_view_t key, bool keep_one)
|
|
||||||
{
|
{
|
||||||
node* const h = block_of(m_doc, o, 0);
|
node* const h = block_of(m_doc, o, 0);
|
||||||
node last_value{}; // the entry of the value of the last member
|
|
||||||
node* const end = h + h->next;
|
|
||||||
if (keep_one)
|
|
||||||
{
|
|
||||||
for (node* r = h + 1; r != end; r += 2)
|
|
||||||
{
|
|
||||||
if (key_equals(*r, key))
|
|
||||||
{
|
|
||||||
last_value = r[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node* w = h + 1;
|
node* w = h + 1;
|
||||||
std::size_t erased = 0;
|
std::size_t erased = 0;
|
||||||
bool kept = false;
|
bool kept = false;
|
||||||
for (node* r = h + 1; r != end; r += 2)
|
for (node* r = h + 1, *end = h + h->next; r != end; r += 2)
|
||||||
{
|
{
|
||||||
const bool match = key_equals(*r, key);
|
const bool match = key_equals(*r, key);
|
||||||
if (match && (kept || !keep_one))
|
if (match && (kept || !keep_first))
|
||||||
{
|
{
|
||||||
++erased;
|
++erased;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
w[0] = r[0];
|
|
||||||
w[1] = match ? last_value : r[1];
|
|
||||||
kept = kept || match;
|
kept = kept || match;
|
||||||
|
if (w != r)
|
||||||
|
{
|
||||||
|
w[0] = r[0];
|
||||||
|
w[1] = r[1];
|
||||||
|
}
|
||||||
w += 2;
|
w += 2;
|
||||||
}
|
}
|
||||||
h->next = static_cast<std::uint32_t>(w - h);
|
h->next = static_cast<std::uint32_t>(w - h);
|
||||||
|
|||||||
@@ -84,9 +84,10 @@ 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
|
||||||
template<bool Editable>
|
template<bool Editable>
|
||||||
const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
@@ -97,7 +98,6 @@ const node* find_member(const document_data& d, const node* object, const char*
|
|||||||
}
|
}
|
||||||
const node* const end = nav::end(d, object);
|
const node* const end = nav::end(d, 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);
|
||||||
@@ -105,19 +105,19 @@ const node* find_member(const document_data& d, const node* object, const char*
|
|||||||
{
|
{
|
||||||
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 = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
for (const node* m = nav::first(d, 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
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
// large objects). An object with document_data::index_min_members members or
|
// large objects). An object with document_data::index_min_members members or
|
||||||
// more gets an open-addressing table after parsing; its node stores the
|
// more gets an open-addressing table after parsing; its node stores the
|
||||||
// number of the table (1-based) in `extra`. A slot holds the offset of a key
|
// number of the table (1-based) in `extra`. A slot holds the offset of a key
|
||||||
// node from its object node (0: empty). Of duplicate keys, the last is kept,
|
// node from its object node (0: empty). Of duplicate keys, the first is kept,
|
||||||
// as for the linear search, and as basic_json::parse() does.
|
// as for the linear search.
|
||||||
//
|
//
|
||||||
// The hash is not seeded, so keys chosen to collide could make the build
|
// The hash is not seeded, so keys chosen to collide could make the build
|
||||||
// quadratic. A key therefore sits at most index_max_displacement slots away
|
// quadratic. A key therefore sits at most index_max_displacement slots away
|
||||||
@@ -92,8 +92,7 @@ inline void build_object_index(document_data& d, node* obj)
|
|||||||
const node* const other = obj + slots[i];
|
const node* const other = obj + slots[i];
|
||||||
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
||||||
{
|
{
|
||||||
duplicate = true; // keep the last: the key's slot now leads to this member
|
duplicate = true; // keep the first
|
||||||
slots[i] = static_cast<std::uint32_t>(k - obj);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (++distance > index_max_displacement)
|
if (++distance > index_max_displacement)
|
||||||
@@ -130,7 +129,7 @@ inline void build_object_indexes(document_data& d)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the key node of the last member with this key of an indexed object, or
|
/// the key node of the first member with this key of an indexed object, or
|
||||||
/// nullptr
|
/// nullptr
|
||||||
inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept
|
inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,11 +50,6 @@ class output_buffer
|
|||||||
{
|
{
|
||||||
const auto size = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto size = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
m_out.resize(size);
|
m_out.resize(size);
|
||||||
// do not keep a buffer that was sized for a much larger output
|
|
||||||
if (m_out.capacity() > 1024 && m_out.capacity() / 2 > size)
|
|
||||||
{
|
|
||||||
m_out.shrink_to_fit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE void reserve(std::size_t n)
|
NLOHMANN_VIEW_ALWAYS_INLINE void reserve(std::size_t n)
|
||||||
|
|||||||
@@ -243,7 +243,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.
|
||||||
@@ -307,7 +307,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
|
||||||
@@ -365,7 +365,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
|
||||||
@@ -430,7 +430,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
|
||||||
{
|
{
|
||||||
@@ -765,7 +765,7 @@ class basic_json_view
|
|||||||
return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16);
|
return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2939,8 +2939,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
// large objects). An object with document_data::index_min_members members or
|
// large objects). An object with document_data::index_min_members members or
|
||||||
// more gets an open-addressing table after parsing; its node stores the
|
// more gets an open-addressing table after parsing; its node stores the
|
||||||
// number of the table (1-based) in `extra`. A slot holds the offset of a key
|
// number of the table (1-based) in `extra`. A slot holds the offset of a key
|
||||||
// node from its object node (0: empty). Of duplicate keys, the last is kept,
|
// node from its object node (0: empty). Of duplicate keys, the first is kept,
|
||||||
// as for the linear search, and as basic_json::parse() does.
|
// as for the linear search.
|
||||||
//
|
//
|
||||||
// The hash is not seeded, so keys chosen to collide could make the build
|
// The hash is not seeded, so keys chosen to collide could make the build
|
||||||
// quadratic. A key therefore sits at most index_max_displacement slots away
|
// quadratic. A key therefore sits at most index_max_displacement slots away
|
||||||
@@ -3009,8 +3009,7 @@ inline void build_object_index(document_data& d, node* obj)
|
|||||||
const node* const other = obj + slots[i];
|
const node* const other = obj + slots[i];
|
||||||
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
||||||
{
|
{
|
||||||
duplicate = true; // keep the last: the key's slot now leads to this member
|
duplicate = true; // keep the first
|
||||||
slots[i] = static_cast<std::uint32_t>(k - obj);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (++distance > index_max_displacement)
|
if (++distance > index_max_displacement)
|
||||||
@@ -3047,7 +3046,7 @@ inline void build_object_indexes(document_data& d)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the key node of the last member with this key of an indexed object, or
|
/// the key node of the first member with this key of an indexed object, or
|
||||||
/// nullptr
|
/// nullptr
|
||||||
inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept
|
inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
@@ -3141,9 +3140,10 @@ 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
|
||||||
template<bool Editable>
|
template<bool Editable>
|
||||||
const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
@@ -3154,7 +3154,6 @@ const node* find_member(const document_data& d, const node* object, const char*
|
|||||||
}
|
}
|
||||||
const node* const end = nav::end(d, object);
|
const node* const end = nav::end(d, 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);
|
||||||
@@ -3162,19 +3161,19 @@ const node* find_member(const document_data& d, const node* object, const char*
|
|||||||
{
|
{
|
||||||
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 = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
for (const node* m = nav::first(d, 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
|
||||||
@@ -3361,23 +3360,25 @@ class editor
|
|||||||
{
|
{
|
||||||
become_empty(o, value_t::object);
|
become_empty(o, value_t::object);
|
||||||
}
|
}
|
||||||
// an existing member: assign the one that lookups find (the last
|
// an existing member: assign it (and drop later duplicates, so that
|
||||||
// one, should the key occur more than once), and drop the others, so
|
// lookups, iteration, and materialize() agree)
|
||||||
// that lookups, iteration, and materialize() agree. The key stays at
|
|
||||||
// the position of its first occurrence, as materialize() puts it.
|
|
||||||
node* slot = nullptr;
|
node* slot = nullptr;
|
||||||
std::size_t matches = 0;
|
bool duplicates = false;
|
||||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||||
{
|
{
|
||||||
if (key_equals(*k, key))
|
if (key_equals(*k, key))
|
||||||
{
|
{
|
||||||
|
if (slot != nullptr)
|
||||||
|
{
|
||||||
|
duplicates = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
slot = const_cast<node*>(nav::value(k + 1)); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
slot = const_cast<node*>(nav::value(k + 1)); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||||
++matches;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (slot != nullptr)
|
if (slot != nullptr)
|
||||||
{
|
{
|
||||||
if (matches > 1)
|
if (duplicates)
|
||||||
{
|
{
|
||||||
erase_members(o, key, true);
|
erase_members(o, key, true);
|
||||||
}
|
}
|
||||||
@@ -3523,38 +3524,27 @@ class editor
|
|||||||
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the members with this key from an object: all of them, or all
|
/// remove the members with this key (all, or all but the first) from an object
|
||||||
/// but one. That one stays where the first occurrence is, but holds the
|
std::size_t erase_members(node* o, string_view_t key, bool keep_first)
|
||||||
/// value of the last (the one that lookups find, which views may refer to).
|
|
||||||
std::size_t erase_members(node* o, string_view_t key, bool keep_one)
|
|
||||||
{
|
{
|
||||||
node* const h = block_of(m_doc, o, 0);
|
node* const h = block_of(m_doc, o, 0);
|
||||||
node last_value{}; // the entry of the value of the last member
|
|
||||||
node* const end = h + h->next;
|
|
||||||
if (keep_one)
|
|
||||||
{
|
|
||||||
for (node* r = h + 1; r != end; r += 2)
|
|
||||||
{
|
|
||||||
if (key_equals(*r, key))
|
|
||||||
{
|
|
||||||
last_value = r[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node* w = h + 1;
|
node* w = h + 1;
|
||||||
std::size_t erased = 0;
|
std::size_t erased = 0;
|
||||||
bool kept = false;
|
bool kept = false;
|
||||||
for (node* r = h + 1; r != end; r += 2)
|
for (node* r = h + 1, *end = h + h->next; r != end; r += 2)
|
||||||
{
|
{
|
||||||
const bool match = key_equals(*r, key);
|
const bool match = key_equals(*r, key);
|
||||||
if (match && (kept || !keep_one))
|
if (match && (kept || !keep_first))
|
||||||
{
|
{
|
||||||
++erased;
|
++erased;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
w[0] = r[0];
|
|
||||||
w[1] = match ? last_value : r[1];
|
|
||||||
kept = kept || match;
|
kept = kept || match;
|
||||||
|
if (w != r)
|
||||||
|
{
|
||||||
|
w[0] = r[0];
|
||||||
|
w[1] = r[1];
|
||||||
|
}
|
||||||
w += 2;
|
w += 2;
|
||||||
}
|
}
|
||||||
h->next = static_cast<std::uint32_t>(w - h);
|
h->next = static_cast<std::uint32_t>(w - h);
|
||||||
@@ -5835,11 +5825,6 @@ class output_buffer
|
|||||||
{
|
{
|
||||||
const auto size = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto size = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
m_out.resize(size);
|
m_out.resize(size);
|
||||||
// do not keep a buffer that was sized for a much larger output
|
|
||||||
if (m_out.capacity() > 1024 && m_out.capacity() / 2 > size)
|
|
||||||
{
|
|
||||||
m_out.shrink_to_fit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE void reserve(std::size_t n)
|
NLOHMANN_VIEW_ALWAYS_INLINE void reserve(std::size_t n)
|
||||||
@@ -7151,7 +7136,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.
|
||||||
@@ -7215,7 +7200,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
|
||||||
@@ -7273,7 +7258,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
|
||||||
@@ -7338,7 +7323,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
|
||||||
{
|
{
|
||||||
@@ -7673,7 +7658,7 @@ class basic_json_view
|
|||||||
return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16);
|
return (std::min)(m_doc->size - m_node->off, static_cast<std::size_t>(1024) + nodes * 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -577,7 +577,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());
|
||||||
@@ -618,23 +618,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());
|
||||||
@@ -725,20 +715,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 :
|
||||||
{
|
{
|
||||||
@@ -748,10 +746,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;
|
||||||
@@ -1515,6 +1513,8 @@ TEST_CASE("json_view dump")
|
|||||||
const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})");
|
const json_document d = json_document::parse(R"({"b": 1, "a": 2, "b": 3})");
|
||||||
CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})");
|
CHECK(d.root().dump() == R"({"b":1,"a":2,"b":3})");
|
||||||
CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}");
|
CHECK(d.root().dump(1) == "{\n \"b\": 1,\n \"a\": 2,\n \"b\": 3\n}");
|
||||||
|
// dump() writes all members, though the lookup finds the first
|
||||||
|
CHECK(d.root()["b"].dump() == "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("deep nesting")
|
SECTION("deep nesting")
|
||||||
@@ -1637,6 +1637,9 @@ TEST_CASE("json_view comparison")
|
|||||||
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
const ordered_json_document dup = ordered_json_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
const ordered_json_document last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
||||||
CHECK(dup.root() == last.root());
|
CHECK(dup.root() == last.root());
|
||||||
|
// (== compares as parse() resolves duplicates, a lookup finds the first)
|
||||||
|
CHECK(dup.root()["a"].materialize() == 1);
|
||||||
|
CHECK(dup.root()["a"] != last.root()["a"]);
|
||||||
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
const ordered_json_document ab = ordered_json_document::parse(R"({"a": 1, "b": 2})");
|
||||||
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
const ordered_json_document ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
||||||
CHECK(ab.root() != ba.root());
|
CHECK(ab.root() != ba.root());
|
||||||
@@ -1684,31 +1687,26 @@ TEST_CASE("json_view large objects")
|
|||||||
for (std::size_t i = 0; i < members; ++i)
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
{
|
{
|
||||||
const std::string key = std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\n" : "");
|
const std::string key = std::string(i % 23, 'k') + std::to_string(i) + (i % 7 == 0 ? "\n" : "");
|
||||||
|
CHECK(v[key].get<std::size_t>() == i);
|
||||||
CHECK(v.contains(key));
|
CHECK(v.contains(key));
|
||||||
CHECK(v.find(key).key() == key);
|
CHECK(v.find(key).key() == key);
|
||||||
CHECK(!v.contains(key + "x"));
|
|
||||||
if (key == "k1")
|
|
||||||
{
|
|
||||||
continue; // repeated below: the last member wins
|
|
||||||
}
|
|
||||||
CHECK(v[key].get<std::size_t>() == i);
|
|
||||||
CHECK(v.at(key).get<std::size_t>() == i);
|
CHECK(v.at(key).get<std::size_t>() == i);
|
||||||
|
CHECK(!v.contains(key + "x"));
|
||||||
}
|
}
|
||||||
CHECK(v[""].get_string() == "empty key");
|
CHECK(v[""].get_string() == "empty key");
|
||||||
CHECK(v["k1"].get_string() == "a duplicate of an earlier key"); // the last of duplicate keys, as for small objects
|
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
|
||||||
CHECK(v.at("k1").get_string() == "a duplicate of an earlier key");
|
|
||||||
CHECK(!v.contains("missing"));
|
CHECK(!v.contains("missing"));
|
||||||
CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
|
CHECK_THROWS_WITH_AS(v.at("missing"), "[json.exception.out_of_range.403] key 'missing' not found", json::out_of_range&);
|
||||||
CHECK(v == j);
|
CHECK(v == j);
|
||||||
CHECK(v.materialize() == j);
|
CHECK(v.materialize() == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("duplicate keys: the last member wins, with and without a table")
|
SECTION("duplicate keys: the first member wins, with and without a table")
|
||||||
{
|
{
|
||||||
// an object of `total` members: the keys "k0".."k<n-1>" in order, then
|
// an object of `total` members: the keys "k0".."k<n-1>" in order, then
|
||||||
// three keys repeated twice more (one copy in the middle, one at the
|
// three keys repeated twice more (one copy in the middle, one at the
|
||||||
// end), and two keys repeated once; the value of a member is its
|
// end), and two keys repeated once; the value of a member is its
|
||||||
// position, so that the last member of a key can be told apart
|
// position, so that the first member of a key can be told apart
|
||||||
struct member
|
struct member
|
||||||
{
|
{
|
||||||
std::string key;
|
std::string key;
|
||||||
@@ -1756,20 +1754,23 @@ TEST_CASE("json_view large objects")
|
|||||||
REQUIRE(members.size() >= total);
|
REQUIRE(members.size() >= total);
|
||||||
REQUIRE(members.size() >= 100);
|
REQUIRE(members.size() >= 100);
|
||||||
std::string text = "{";
|
std::string text = "{";
|
||||||
|
std::map<std::string, std::size_t> first;
|
||||||
std::map<std::string, std::size_t> last;
|
std::map<std::string, std::size_t> last;
|
||||||
for (const member& m : members)
|
for (const member& m : members)
|
||||||
{
|
{
|
||||||
text += (text.size() > 1 ? ",\"" : "\"") + m.key + "\":" + std::to_string(m.position);
|
text += (text.size() > 1 ? ",\"" : "\"") + m.key + "\":" + std::to_string(m.position);
|
||||||
|
first.insert({m.key, m.position});
|
||||||
last[m.key] = m.position;
|
last[m.key] = m.position;
|
||||||
}
|
}
|
||||||
text += '}';
|
text += '}';
|
||||||
REQUIRE(last.size() < members.size());
|
REQUIRE(first.size() < members.size());
|
||||||
|
|
||||||
const json_document d = json_document::parse(text);
|
const json_document d = json_document::parse(text);
|
||||||
const json_view v = d.root();
|
const json_view v = d.root();
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
CHECK(v.size() == members.size()); // every occurrence is visited
|
CHECK(v.size() == members.size()); // every occurrence is visited
|
||||||
for (const auto& entry : last)
|
const json m = v.materialize();
|
||||||
|
for (const auto& entry : first)
|
||||||
{
|
{
|
||||||
CAPTURE(entry.first)
|
CAPTURE(entry.first)
|
||||||
const std::size_t expected = entry.second;
|
const std::size_t expected = entry.second;
|
||||||
@@ -1784,7 +1785,9 @@ TEST_CASE("json_view large objects")
|
|||||||
CHECK(v.at(json::json_pointer(pointer)).get<std::size_t>() == expected);
|
CHECK(v.at(json::json_pointer(pointer)).get<std::size_t>() == expected);
|
||||||
CHECK(v.value(json::json_pointer(pointer), std::size_t{0}) == expected);
|
CHECK(v.value(json::json_pointer(pointer), std::size_t{0}) == expected);
|
||||||
CHECK(v.contains(json::json_pointer(pointer)));
|
CHECK(v.contains(json::json_pointer(pointer)));
|
||||||
CHECK(j[entry.first].get<std::size_t>() == expected); // as materialize() and parse() keep it
|
// materialize() and parse() keep the last value instead
|
||||||
|
CHECK(j[entry.first].get<std::size_t>() == last.at(entry.first));
|
||||||
|
CHECK(m[entry.first].get<std::size_t>() == last.at(entry.first));
|
||||||
}
|
}
|
||||||
CHECK(!v.contains("k"));
|
CHECK(!v.contains("k"));
|
||||||
CHECK(v["missing"].is_discarded());
|
CHECK(v["missing"].is_discarded());
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ using ptr_t = ordered_json::json_pointer;
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -181,8 +182,10 @@ void compare(const ordered_json_editable_view& v, const ordered_json& j)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the text of j, in which some objects repeat a key of theirs: before their
|
// the text of j, in which some objects repeat a key of theirs after their
|
||||||
// members (the real member is then the last), or after (the repeat is)
|
// members: lookups of the edited document find the first member of a key, so
|
||||||
|
// j (the first members) is what they must agree with, while parse() would
|
||||||
|
// keep the last value
|
||||||
std::string text_with_duplicates(const ordered_json& j)
|
std::string text_with_duplicates(const ordered_json& j)
|
||||||
{
|
{
|
||||||
if (j.is_object())
|
if (j.is_object())
|
||||||
@@ -197,10 +200,6 @@ std::string text_with_duplicates(const ordered_json& j)
|
|||||||
return ordered_json(keys[static_cast<std::size_t>(r(static_cast<int>(keys.size())))]).dump() + ":" + random_value(2).dump();
|
return ordered_json(keys[static_cast<std::size_t>(r(static_cast<int>(keys.size())))]).dump() + ":" + random_value(2).dump();
|
||||||
};
|
};
|
||||||
std::string text = "{";
|
std::string text = "{";
|
||||||
if (!keys.empty() && r(4) == 0)
|
|
||||||
{
|
|
||||||
text += repeated() + ",";
|
|
||||||
}
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const auto& kv : j.items())
|
for (const auto& kv : j.items())
|
||||||
{
|
{
|
||||||
@@ -225,10 +224,58 @@ std::string text_with_duplicates(const ordered_json& j)
|
|||||||
return j.dump();
|
return j.dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
// every lookup of the edited view finds what j holds, although the view may
|
// every lookup of the edited view finds the first member of its key, which
|
||||||
// have several members for a key (j has the last value, at the position of the
|
// the iteration of the view shows
|
||||||
// first member: what parse() and materialize() make of it)
|
void check_lookups(const ordered_json_editable_view& v)
|
||||||
void check_lookups(const ordered_json_editable_view& v, const ordered_json& j)
|
{
|
||||||
|
if (v.is_object())
|
||||||
|
{
|
||||||
|
std::set<std::string> seen;
|
||||||
|
for (const auto item : v.items())
|
||||||
|
{
|
||||||
|
const std::string key(item.key().data(), item.key().size());
|
||||||
|
if (!seen.insert(key).second)
|
||||||
|
{
|
||||||
|
continue; // (not the first member of the key)
|
||||||
|
}
|
||||||
|
const ptr_t ptr = ptr_t() / key;
|
||||||
|
CAPTURE(key)
|
||||||
|
const ordered_json expected = item.value().materialize();
|
||||||
|
const ordered_json_editable_view m = v[key];
|
||||||
|
REQUIRE(!m.is_discarded());
|
||||||
|
CHECK(m.materialize() == expected);
|
||||||
|
CHECK(v.at(key).materialize() == expected);
|
||||||
|
CHECK(v[ptr].materialize() == expected);
|
||||||
|
CHECK(v.at(ptr).materialize() == expected);
|
||||||
|
CHECK(v.contains(key));
|
||||||
|
CHECK(v.contains(ptr));
|
||||||
|
CHECK(v.count(key) == 1);
|
||||||
|
const auto it = v.find(key);
|
||||||
|
REQUIRE(it != v.end());
|
||||||
|
CHECK((*it).materialize() == expected);
|
||||||
|
CHECK(v.value(ptr, ordered_json(nullptr)) == expected);
|
||||||
|
if (expected.is_string())
|
||||||
|
{
|
||||||
|
CHECK(v.value(key, std::string("-")) == expected.get<std::string>());
|
||||||
|
}
|
||||||
|
check_lookups(m);
|
||||||
|
}
|
||||||
|
CHECK(v["missing#key"].is_discarded());
|
||||||
|
CHECK(!v.contains("missing#key"));
|
||||||
|
CHECK(v.find("missing#key") == v.end());
|
||||||
|
}
|
||||||
|
else if (v.is_array())
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < v.size(); ++i)
|
||||||
|
{
|
||||||
|
check_lookups(v[i]);
|
||||||
|
check_lookups(v.at(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// every lookup of the edited view finds what the first members of j hold
|
||||||
|
void check_first_members(const ordered_json_editable_view& v, const ordered_json& j)
|
||||||
{
|
{
|
||||||
REQUIRE(v.type() == j.type());
|
REQUIRE(v.type() == j.type());
|
||||||
if (j.is_object())
|
if (j.is_object())
|
||||||
@@ -238,45 +285,39 @@ void check_lookups(const ordered_json_editable_view& v, const ordered_json& j)
|
|||||||
const std::string& key = kv.key();
|
const std::string& key = kv.key();
|
||||||
const ptr_t ptr = ptr_t() / key;
|
const ptr_t ptr = ptr_t() / key;
|
||||||
CAPTURE(key)
|
CAPTURE(key)
|
||||||
const ordered_json_editable_view m = v[key];
|
REQUIRE(v.contains(key));
|
||||||
REQUIRE(!m.is_discarded());
|
|
||||||
CHECK(m.materialize() == kv.value());
|
|
||||||
CHECK(v.at(key).materialize() == kv.value());
|
|
||||||
CHECK(v[ptr].materialize() == kv.value());
|
|
||||||
CHECK(v.at(ptr).materialize() == kv.value());
|
|
||||||
CHECK(v.contains(key));
|
|
||||||
CHECK(v.contains(ptr));
|
CHECK(v.contains(ptr));
|
||||||
CHECK(v.count(key) == 1);
|
CHECK(v.count(key) == 1);
|
||||||
const auto it = v.find(key);
|
CHECK(v.find(key) != v.end());
|
||||||
REQUIRE(it != v.end());
|
CHECK(v.at(ptr).type() == kv.value().type());
|
||||||
CHECK((*it).materialize() == kv.value());
|
check_first_members(v[key], kv.value());
|
||||||
CHECK(v.value(ptr, ordered_json(nullptr)) == kv.value());
|
check_first_members(v.at(key), kv.value());
|
||||||
if (kv.value().is_string())
|
check_first_members(v[ptr], kv.value());
|
||||||
{
|
|
||||||
CHECK(v.value(key, std::string("-")) == kv.value().get<std::string>());
|
|
||||||
}
|
|
||||||
check_lookups(m, kv.value());
|
|
||||||
}
|
}
|
||||||
CHECK(v["missing#key"].is_discarded());
|
|
||||||
CHECK(!v.contains("missing#key"));
|
CHECK(!v.contains("missing#key"));
|
||||||
CHECK(v.find("missing#key") == v.end());
|
|
||||||
}
|
}
|
||||||
else if (j.is_array())
|
else if (j.is_array())
|
||||||
{
|
{
|
||||||
|
REQUIRE(v.size() == j.size());
|
||||||
for (std::size_t i = 0; i < j.size(); ++i)
|
for (std::size_t i = 0; i < j.size(); ++i)
|
||||||
{
|
{
|
||||||
check_lookups(v[i], j[i]);
|
check_first_members(v[i], j[i]);
|
||||||
check_lookups(v.at(i), j[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK(v.materialize() == j);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (for documents whose objects repeat keys: dump(), size(), and iteration
|
// (for documents whose objects repeat keys: j holds the first members, the
|
||||||
// list all members, so only the lookups and materialize() are compared)
|
// lookups must find them; dump(), size(), and iteration list all members, but
|
||||||
|
// materialize() keeps the last value of a key, as parse() does of the dump)
|
||||||
void check_duplicates(const ordered_json_editable_document& d, const ordered_json& j)
|
void check_duplicates(const ordered_json_editable_document& d, const ordered_json& j)
|
||||||
{
|
{
|
||||||
CHECK(d.root().materialize() == j);
|
check_first_members(d.root(), j);
|
||||||
check_lookups(d.root(), j);
|
check_lookups(d.root());
|
||||||
|
CHECK(d.root().materialize() == ordered_json::parse(d.root().dump()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_all(const ordered_json_editable_document& d, const ordered_json& j, bool deep)
|
void check_all(const ordered_json_editable_document& d, const ordered_json& j, bool deep)
|
||||||
@@ -304,17 +345,25 @@ TEST_CASE("json_view edits: differential")
|
|||||||
// materialize, and read back the same
|
// materialize, and read back the same
|
||||||
for (int n = 0; n < 250; ++n)
|
for (int n = 0; n < 250; ++n)
|
||||||
{
|
{
|
||||||
// the last 100 documents repeat keys in some of their objects
|
// the last 100 documents repeat keys in some of their objects; j then
|
||||||
|
// holds the first member of each key, which the edits address
|
||||||
const bool duplicates = n >= 150;
|
const bool duplicates = n >= 150;
|
||||||
ordered_json j = random_value(0);
|
ordered_json j = random_value(0);
|
||||||
if (r(4) == 0)
|
if (r(4) == 0)
|
||||||
{
|
{
|
||||||
j = ordered_json::object({{"a", random_value(1)}, {"b", random_value(1)}});
|
j = ordered_json::object({{"a", random_value(1)}, {"b", random_value(1)}});
|
||||||
}
|
}
|
||||||
|
if (duplicates)
|
||||||
|
{
|
||||||
|
j = ordered_json::parse(j.dump()); // (normalized as the document reads it)
|
||||||
|
}
|
||||||
const std::string text = duplicates ? text_with_duplicates(j) : j.dump(r(2) == 0 ? -1 : 2);
|
const std::string text = duplicates ? text_with_duplicates(j) : j.dump(r(2) == 0 ? -1 : 2);
|
||||||
CAPTURE(text)
|
CAPTURE(text)
|
||||||
ordered_json_editable_document d = ordered_json_editable_document::parse(text);
|
ordered_json_editable_document d = ordered_json_editable_document::parse(text);
|
||||||
j = ordered_json::parse(text);
|
if (!duplicates)
|
||||||
|
{
|
||||||
|
j = ordered_json::parse(text);
|
||||||
|
}
|
||||||
const ordered_json_document other = ordered_json_document::parse(random_value(0).dump());
|
const ordered_json_document other = ordered_json_document::parse(random_value(0).dump());
|
||||||
const int edits = 30;
|
const int edits = 30;
|
||||||
for (int e = 0; e < edits; ++e)
|
for (int e = 0; e < edits; ++e)
|
||||||
@@ -624,7 +673,7 @@ TEST_CASE("json_view edits: views and values")
|
|||||||
SECTION("duplicate keys")
|
SECTION("duplicate keys")
|
||||||
{
|
{
|
||||||
json_editable_document d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
json_editable_document d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
d.set(d.root(), "a", 4); // the last member is assigned (at the position of the first), the others dropped
|
d.set(d.root(), "a", 4); // the first member is assigned, the others dropped
|
||||||
CHECK(d.root().dump() == R"({"a":4,"b":2})");
|
CHECK(d.root().dump() == R"({"a":4,"b":2})");
|
||||||
d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
CHECK(d.erase(d.root(), "a") == 2);
|
CHECK(d.erase(d.root(), "a") == 2);
|
||||||
@@ -911,10 +960,10 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// reads of "a" in an object that repeats it: the last member wins, however the
|
// reads of "a" in an object that repeats it: the first member wins, however the
|
||||||
// object is stored
|
// object is stored
|
||||||
template<typename View>
|
template<typename View>
|
||||||
void check_last_wins(const View& o, int expected)
|
void check_first_wins(const View& o, int expected)
|
||||||
{
|
{
|
||||||
CAPTURE(expected)
|
CAPTURE(expected)
|
||||||
CHECK(o["a"].template get<int>() == expected);
|
CHECK(o["a"].template get<int>() == expected);
|
||||||
@@ -935,35 +984,36 @@ TEST_CASE("json_view edits: duplicate keys")
|
|||||||
SECTION("an object in its parsed layout, then moved by edits")
|
SECTION("an object in its parsed layout, then moved by edits")
|
||||||
{
|
{
|
||||||
json_editable_document d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
json_editable_document d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
check_last_wins(d.root(), 3);
|
check_first_wins(d.root(), 1);
|
||||||
CHECK(d.root().size() == 3);
|
CHECK(d.root().size() == 3);
|
||||||
CHECK(d.root().dump() == R"({"a":1,"b":2,"a":3})");
|
CHECK(d.root().dump() == R"({"a":1,"b":2,"a":3})");
|
||||||
|
CHECK(d.root().materialize()["a"] == 3); // (parse() keeps the last value)
|
||||||
|
|
||||||
// an edit of a value does not move the object
|
// an edit of a value does not move the object
|
||||||
d.set(d.root()["b"], 5);
|
d.set(d.root()["b"], 5);
|
||||||
check_last_wins(d.root(), 3);
|
check_first_wins(d.root(), 1);
|
||||||
d.set(d.root()["a"], 4); // the member that reads find
|
d.set(d.root()["a"], 4); // the member that lookups find
|
||||||
check_last_wins(d.root(), 4);
|
check_first_wins(d.root(), 4);
|
||||||
CHECK(d.root().dump() == R"({"a":1,"b":5,"a":4})");
|
CHECK(d.root().dump() == R"({"a":4,"b":5,"a":3})");
|
||||||
|
|
||||||
// an appended member moves it
|
// an appended member moves it
|
||||||
d.set(d.root(), "c", true);
|
d.set(d.root(), "c", true);
|
||||||
check_last_wins(d.root(), 4);
|
check_first_wins(d.root(), 4);
|
||||||
CHECK(d.root().size() == 4);
|
CHECK(d.root().size() == 4);
|
||||||
CHECK(d.root().dump() == R"({"a":1,"b":5,"a":4,"c":true})");
|
CHECK(d.root().dump() == R"({"a":4,"b":5,"a":3,"c":true})");
|
||||||
d.set(d.root()["a"], 6);
|
d.set(d.root()["a"], 6);
|
||||||
check_last_wins(d.root(), 6);
|
check_first_wins(d.root(), 6);
|
||||||
CHECK(d.root().dump() == R"({"a":1,"b":5,"a":6,"c":true})");
|
CHECK(d.root().dump() == R"({"a":6,"b":5,"a":3,"c":true})");
|
||||||
d.set(json::json_pointer("/a"), 7);
|
d.set(json::json_pointer("/a"), 7);
|
||||||
check_last_wins(d.root(), 7);
|
check_first_wins(d.root(), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("set assigns the member that reads find")
|
SECTION("set assigns the member that lookups find")
|
||||||
{
|
{
|
||||||
// the key keeps the position of its first occurrence (as in materialize()), the later members are dropped
|
// the key stays where its first occurrence is, the later members are dropped
|
||||||
ordered_json_editable_document d = ordered_json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3, "c": 4, "a": 5})");
|
ordered_json_editable_document d = ordered_json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3, "c": 4, "a": 5})");
|
||||||
const ordered_json_editable_view held = d.root()["a"];
|
const ordered_json_editable_view held = d.root()["a"];
|
||||||
CHECK(held.get<int>() == 5);
|
CHECK(held.get<int>() == 1);
|
||||||
const ordered_json_editable_view assigned = d.set(d.root(), "a", "x");
|
const ordered_json_editable_view assigned = d.set(d.root(), "a", "x");
|
||||||
CHECK(held.get<std::string>() == "x");
|
CHECK(held.get<std::string>() == "x");
|
||||||
CHECK(assigned.get<std::string>() == "x");
|
CHECK(assigned.get<std::string>() == "x");
|
||||||
@@ -979,7 +1029,7 @@ TEST_CASE("json_view edits: duplicate keys")
|
|||||||
// through a pointer, below a duplicate
|
// through a pointer, below a duplicate
|
||||||
d = ordered_json_editable_document::parse(R"({"a": {"x": 1}, "b": 2, "a": {"x": 3}})");
|
d = ordered_json_editable_document::parse(R"({"a": {"x": 1}, "b": 2, "a": {"x": 3}})");
|
||||||
d.set(ptr_t("/a/x"), 4);
|
d.set(ptr_t("/a/x"), 4);
|
||||||
CHECK(d.root().dump() == R"({"a":{"x":1},"b":2,"a":{"x":4}})");
|
CHECK(d.root().dump() == R"({"a":{"x":4},"b":2,"a":{"x":3}})");
|
||||||
d.set(ptr_t("/a"), 0);
|
d.set(ptr_t("/a"), 0);
|
||||||
CHECK(d.root().dump() == R"({"a":0,"b":2})");
|
CHECK(d.root().dump() == R"({"a":0,"b":2})");
|
||||||
}
|
}
|
||||||
@@ -988,7 +1038,7 @@ TEST_CASE("json_view edits: duplicate keys")
|
|||||||
{
|
{
|
||||||
json_editable_document d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
json_editable_document d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||||
d.set(d.root(), "c", 4);
|
d.set(d.root(), "c", 4);
|
||||||
check_last_wins(d.root(), 3);
|
check_first_wins(d.root(), 1);
|
||||||
CHECK(d.erase(d.root(), "a") == 2);
|
CHECK(d.erase(d.root(), "a") == 2);
|
||||||
CHECK(d.root().dump() == R"({"b":2,"c":4})");
|
CHECK(d.root().dump() == R"({"b":2,"c":4})");
|
||||||
CHECK(!d.root().contains("a"));
|
CHECK(!d.root().contains("a"));
|
||||||
@@ -1008,20 +1058,20 @@ TEST_CASE("json_view edits: duplicate keys")
|
|||||||
}
|
}
|
||||||
text += R"(,"a":1,"k7":"last","a":2})";
|
text += R"(,"a":1,"k7":"last","a":2})";
|
||||||
json_editable_document d = json_editable_document::parse(text);
|
json_editable_document d = json_editable_document::parse(text);
|
||||||
check_last_wins(d.root(), 2);
|
check_first_wins(d.root(), 0);
|
||||||
CHECK(d.root()["k7"].get_string() == "last");
|
CHECK(d.root()["k7"].get_string() == "first");
|
||||||
CHECK(d.root()["k199"].get<int>() == 199);
|
CHECK(d.root()["k199"].get<int>() == 199);
|
||||||
|
|
||||||
// a value assigned in place: the index stays in use
|
// a value assigned in place: the index stays in use
|
||||||
d.set(d.root()["a"], 3);
|
d.set(d.root()["a"], 3);
|
||||||
check_last_wins(d.root(), 3);
|
check_first_wins(d.root(), 3);
|
||||||
d.set(d.root()["k7"], "changed");
|
d.set(d.root()["k7"], "changed");
|
||||||
CHECK(d.root()["k7"].get_string() == "changed");
|
CHECK(d.root()["k7"].get_string() == "changed");
|
||||||
CHECK(d.root()["k199"].get<int>() == 199);
|
CHECK(d.root()["k199"].get<int>() == 199);
|
||||||
|
|
||||||
// an appended member moves the members: the lookup scans them
|
// an appended member moves the members: the lookup scans them
|
||||||
d.set(d.root(), "new", 1);
|
d.set(d.root(), "new", 1);
|
||||||
check_last_wins(d.root(), 3);
|
check_first_wins(d.root(), 3);
|
||||||
CHECK(d.root()["k7"].get_string() == "changed");
|
CHECK(d.root()["k7"].get_string() == "changed");
|
||||||
CHECK(d.root()["k7"].get_string() == d.root().at("k7").get_string());
|
CHECK(d.root()["k7"].get_string() == d.root().at("k7").get_string());
|
||||||
CHECK(d.root()["k199"].get<int>() == 199);
|
CHECK(d.root()["k199"].get<int>() == 199);
|
||||||
@@ -1031,7 +1081,7 @@ TEST_CASE("json_view edits: duplicate keys")
|
|||||||
d = json_editable_document::parse(text);
|
d = json_editable_document::parse(text);
|
||||||
d.set(d.root()["a"], json{{"x", 1}});
|
d.set(d.root()["a"], json{{"x", 1}});
|
||||||
CHECK(d.root()["a"]["x"].get<int>() == 1);
|
CHECK(d.root()["a"]["x"].get<int>() == 1);
|
||||||
CHECK(d.root()["k7"].get_string() == "last");
|
CHECK(d.root()["k7"].get_string() == "first");
|
||||||
CHECK(d.erase(d.root(), "k7") == 3); // (the key occurs three times)
|
CHECK(d.erase(d.root(), "k7") == 3); // (the key occurs three times)
|
||||||
CHECK(d.root()["k7"].is_discarded());
|
CHECK(d.root()["k7"].is_discarded());
|
||||||
CHECK(d.root()["a"]["x"].get<int>() == 1);
|
CHECK(d.root()["a"]["x"].get<int>() == 1);
|
||||||
@@ -1040,18 +1090,18 @@ TEST_CASE("json_view edits: duplicate keys")
|
|||||||
SECTION("objects in moved arrays and in new values")
|
SECTION("objects in moved arrays and in new values")
|
||||||
{
|
{
|
||||||
json_editable_document d = json_editable_document::parse(R"([{"a": 1, "a": 2}, {"a": 3, "b": 0, "a": 4}])");
|
json_editable_document d = json_editable_document::parse(R"([{"a": 1, "a": 2}, {"a": 3, "b": 0, "a": 4}])");
|
||||||
check_last_wins(d.root()[0], 2);
|
check_first_wins(d.root()[0], 1);
|
||||||
check_last_wins(d.root()[1], 4);
|
check_first_wins(d.root()[1], 3);
|
||||||
d.insert(d.root(), 0, json::parse(R"({"a": 0})"));
|
d.insert(d.root(), 0, json::parse(R"({"a": 0})"));
|
||||||
d.push_back(d.root(), json::parse(R"({"a": 5, "a": 6})")); // (a basic_json value has one member)
|
d.push_back(d.root(), json::parse(R"({"a": 5, "a": 6})")); // (a basic_json value has one member)
|
||||||
check_last_wins(d.root()[1], 2);
|
check_first_wins(d.root()[1], 1);
|
||||||
check_last_wins(d.root()[2], 4);
|
check_first_wins(d.root()[2], 3);
|
||||||
CHECK(d.root()[3]["a"].get<int>() == 6);
|
CHECK(d.root()[3]["a"].get<int>() == 6);
|
||||||
d.set(d.root()[1], "a", 8);
|
d.set(d.root()[1], "a", 8);
|
||||||
check_last_wins(d.root()[1], 8);
|
check_first_wins(d.root()[1], 8);
|
||||||
CHECK(d.root()[1].dump() == R"({"a":8})");
|
CHECK(d.root()[1].dump() == R"({"a":8})");
|
||||||
d.erase(d.root(), 0);
|
d.erase(d.root(), 0);
|
||||||
check_last_wins(d.root()[1], 4);
|
check_first_wins(d.root()[1], 3);
|
||||||
CHECK(d.root().dump() == R"([{"a":8},{"a":3,"b":0,"a":4},{"a":6}])");
|
CHECK(d.root().dump() == R"([{"a":8},{"a":3,"b":0,"a":4},{"a":6}])");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1063,14 +1113,14 @@ TEST_CASE("json_view edits: duplicate keys")
|
|||||||
d.set(d.root(), "o", source.root()["o"]);
|
d.set(d.root(), "o", source.root()["o"]);
|
||||||
// (the copies keep the repeated members)
|
// (the copies keep the repeated members)
|
||||||
CHECK(d.root().dump() == R"({"copy":[{"a":1,"a":2}],"o":{"b":{"a":3,"a":4},"a":5,"a":6}})");
|
CHECK(d.root().dump() == R"({"copy":[{"a":1,"a":2}],"o":{"b":{"a":3,"a":4},"a":5,"a":6}})");
|
||||||
check_last_wins(d.root()["copy"][0], 2);
|
check_first_wins(d.root()["copy"][0], 1);
|
||||||
check_last_wins(d.root()["o"]["b"], 4);
|
check_first_wins(d.root()["o"]["b"], 3);
|
||||||
check_last_wins(d.root()["o"], 6);
|
check_first_wins(d.root()["o"], 5);
|
||||||
d.set(d.root()["o"]["b"], "c", 0);
|
d.set(d.root()["o"]["b"], "c", 0);
|
||||||
check_last_wins(d.root()["o"]["b"], 4);
|
check_first_wins(d.root()["o"]["b"], 3);
|
||||||
d.set(d.root()["o"], "d", 0);
|
d.set(d.root()["o"], "d", 0);
|
||||||
check_last_wins(d.root()["o"], 6);
|
check_first_wins(d.root()["o"], 5);
|
||||||
CHECK(d.root()["o"]["b"]["a"].get<int>() == 4);
|
CHECK(d.root()["o"]["b"]["a"].get<int>() == 3);
|
||||||
CHECK(d.root()["o"]["d"].get<int>() == 0);
|
CHECK(d.root()["o"]["d"].get<int>() == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ TEST_CASE("json_view images: round trips")
|
|||||||
{
|
{
|
||||||
CHECK(l.root()["k" + std::to_string(i)] == d.root()["k" + std::to_string(i)]);
|
CHECK(l.root()["k" + std::to_string(i)] == d.root()["k" + std::to_string(i)]);
|
||||||
}
|
}
|
||||||
CHECK(l.root()["k7"] == "a duplicate"); // the last of duplicate keys
|
CHECK(l.root()["k7"].get<int>() == 7); // the first of duplicate keys
|
||||||
CHECK(l.root()["inner"]["m199"].get<int>() == -199);
|
CHECK(l.root()["inner"]["m199"].get<int>() == -199);
|
||||||
CHECK(!l.root().contains("k1000"));
|
CHECK(!l.root().contains("k1000"));
|
||||||
// the index is not part of the image
|
// the index is not part of the image
|
||||||
@@ -251,18 +251,18 @@ TEST_CASE("json_view images: round trips")
|
|||||||
CHECK(node_at(image, 0).extra == 0);
|
CHECK(node_at(image, 0).extra == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("duplicate keys of a large object: lookups return the last member")
|
SECTION("duplicate keys of a large object: lookups return the first member")
|
||||||
{
|
{
|
||||||
std::string text = "{";
|
std::string text = "{";
|
||||||
for (int i = 0; i < 200; ++i)
|
for (int i = 0; i < 200; ++i)
|
||||||
{
|
{
|
||||||
text += (i != 0 ? ",\"k" : "\"k") + std::to_string(i) + "\":" + std::to_string(i);
|
text += (i != 0 ? ",\"k" : "\"k") + std::to_string(i) + "\":" + std::to_string(i);
|
||||||
}
|
}
|
||||||
// three members for k5 (the third is the last), and duplicates of k0 and k199
|
// three members for k5 (the first is a number), and duplicates of k0 and k199
|
||||||
text += R"(,"k5":"two","k0":null,"k199":[],"k5":"three"})";
|
text += R"(,"k5":"two","k0":null,"k199":[],"k5":"three"})";
|
||||||
const json_document d = json_document::parse(text);
|
const json_document d = json_document::parse(text);
|
||||||
REQUIRE(d.root().size() == 204);
|
REQUIRE(d.root().size() == 204);
|
||||||
CHECK(d.root()["k5"] == "three");
|
CHECK(d.root()["k5"] == 5);
|
||||||
const std::vector<std::uint8_t> image = d.save();
|
const std::vector<std::uint8_t> image = d.save();
|
||||||
for (const image_check check :
|
for (const image_check check :
|
||||||
{
|
{
|
||||||
@@ -271,13 +271,15 @@ TEST_CASE("json_view images: round trips")
|
|||||||
{
|
{
|
||||||
const json_document l = json_document::load(image, check);
|
const json_document l = json_document::load(image, check);
|
||||||
CHECK(l.root().size() == 204);
|
CHECK(l.root().size() == 204);
|
||||||
CHECK(l.root()["k5"] == "three");
|
CHECK(l.root()["k5"] == 5);
|
||||||
CHECK(l.root().at("k5") == "three");
|
CHECK(l.root().at("k5") == 5);
|
||||||
CHECK(l.root().find("k5").value() == "three");
|
CHECK(l.root().find("k5").value() == 5);
|
||||||
CHECK(l.root()["k0"].is_null());
|
CHECK(l.root()["k0"] == 0);
|
||||||
CHECK(l.root()["k199"] == json::array());
|
CHECK(l.root()["k199"] == 199);
|
||||||
CHECK(l.root()["k100"] == 100);
|
CHECK(l.root()["k100"] == 100);
|
||||||
|
// materialize() keeps the semantics of parse(): the last value
|
||||||
CHECK(l.root().materialize() == d.root().materialize());
|
CHECK(l.root().materialize() == d.root().materialize());
|
||||||
|
CHECK(l.root().materialize()["k5"] == "three");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,15 +357,8 @@ TEST_CASE("json_view images: round trips")
|
|||||||
for (std::size_t i = 0; i < members; ++i)
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
{
|
{
|
||||||
CAPTURE(i)
|
CAPTURE(i)
|
||||||
const bool duplicate = std::find(duplicated.begin(), duplicated.end(), keys[i]) != duplicated.end();
|
// lookups find the first member, also of the duplicated keys
|
||||||
if (duplicate)
|
CHECK(l.root()[keys[i]] == i);
|
||||||
{
|
|
||||||
CHECK(l.root()[keys[i]] == "last");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CHECK(l.root()[keys[i]] == i);
|
|
||||||
}
|
|
||||||
CHECK(l.root().at(keys[i]) == l.root()[keys[i]]);
|
CHECK(l.root().at(keys[i]) == l.root()[keys[i]]);
|
||||||
CHECK(l.root().find(keys[i]).key() == keys[i]);
|
CHECK(l.root().find(keys[i]).key() == keys[i]);
|
||||||
CHECK(!l.root().contains(keys[i] + "x"));
|
CHECK(!l.root().contains(keys[i] + "x"));
|
||||||
|
|||||||
Reference in new issue
Block a user