mirror of
https://github.com/nlohmann/json.git
synced 2026-10-10 16:37:14 +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"
|
||||
|
||||
Overload 1. removes *every* member with `key`, not just the last one that lookups find -- unlike [`set`](set.md),
|
||||
which assigns that member and drops the rest. This is why it returns a count rather than a single view: there may be
|
||||
Overload 1. removes *every* member with `key`, not just the first -- unlike [`set`](set.md), which assigns the
|
||||
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.
|
||||
|
||||
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`.
|
||||
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
|
||||
[`operator[]`](../basic_json_view/operator%5B%5D.md) returns), and the other duplicates are then dropped (see the
|
||||
key -- the first one, should the key occur more than once, and the later 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)
|
||||
`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 <
|
||||
@@ -147,13 +146,12 @@ document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_d
|
||||
|
||||
!!! info "Duplicate keys"
|
||||
|
||||
If `object` already has more than one member with `key` (2.), `value` is assigned to the *last* one -- the member
|
||||
[`operator[]`](../basic_json_view/operator%5B%5D.md), [`at`](../basic_json_view/at.md), and
|
||||
[`find`](../basic_json_view/find.md) return for reading, so that a view taken from `object["key"]` before the call
|
||||
shows `value` afterward -- and every other member with the same key is removed. The key stays at the
|
||||
position of its *first* occurrence, where [`materialize()`](../basic_json_view/materialize.md) puts it as well. A
|
||||
lookup, an iteration, and `materialize()` of `object` afterward therefore all agree on a single member for `key`. See the
|
||||
[Notes on duplicate keys](../basic_json_view/operator%5B%5D.md#notes) of `operator[]`.
|
||||
If `object` already has more than one member with `key` (2.), the *first* one is assigned `value` and every
|
||||
later member with the same key is removed -- so that a lookup, an iteration, and
|
||||
[`materialize()`](../basic_json_view/materialize.md) of `object` afterward all agree on a single value for
|
||||
`key`, the same way [`operator[]`](../basic_json_view/operator%5B%5D.md) already picks the first occurrence of a
|
||||
duplicate key for reading. See the [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
|
||||
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;
|
||||
```
|
||||
|
||||
1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
|
||||
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
|
||||
[Notes on duplicate keys](operator[].md#notes)).
|
||||
2. Returns the array element at index `idx`. The template accepts every integer type except `#!cpp bool` and
|
||||
`#!cpp std::size_t` and forwards to the `size_type` overload, as for [`operator[]`](operator[].md); a negative
|
||||
@@ -35,7 +35,7 @@ basic_json_view at(const json_pointer& ptr) const;
|
||||
|
||||
## Return value
|
||||
|
||||
1. the value of the last member with key `key`
|
||||
1. the value of the first member with key `key`
|
||||
2. the element at index `idx`
|
||||
3. the value `ptr` resolves to, starting at this value
|
||||
|
||||
@@ -75,7 +75,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
## Complexity
|
||||
|
||||
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||
another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
|
||||
another, in document order, stopping at the first match. Each comparison first checks the
|
||||
key's length -- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
on average.
|
||||
@@ -86,6 +86,13 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
|
||||
## Notes
|
||||
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
||||
the last value.
|
||||
|
||||
Unlike [`operator[]`](operator[].md), which returns a [discarded](is_discarded.md) view for a missing key or an
|
||||
out-of-range index, `at` always throws -- exactly as `BasicJsonType::at` does, and with the same messages, so
|
||||
existing error handling written against `BasicJsonType::at` keeps working unchanged when switched to a view. This
|
||||
|
||||
@@ -24,7 +24,7 @@ Constant.
|
||||
|
||||
For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike
|
||||
[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md), and [`count`](count.md),
|
||||
which all resolve to the *last* member with a given key. See the
|
||||
which all resolve to the *first* member with a given key. See the
|
||||
[Notes on duplicate keys](operator[].md#notes) of `operator[]`.
|
||||
|
||||
Because objects are iterated in document order rather than sorted by key, the order seen here can differ from what
|
||||
|
||||
@@ -33,7 +33,7 @@ No-throw guarantee: this function never throws exceptions.
|
||||
## Complexity
|
||||
|
||||
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||
another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
|
||||
another, in document order, stopping at the first match. Each comparison first checks the
|
||||
key's length -- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
on average.
|
||||
@@ -43,6 +43,13 @@ No-throw guarantee: this function never throws exceptions.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
||||
the last value.
|
||||
|
||||
Overload 1 always returns `#!cpp false` when the value is not an object -- including a [discarded](is_discarded.md)
|
||||
view.
|
||||
|
||||
|
||||
@@ -24,13 +24,20 @@ No-throw guarantee: this function never throws exceptions.
|
||||
## Complexity
|
||||
|
||||
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
|
||||
in document order, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
|
||||
in document order, stopping at the first match. Each comparison first checks the key's length
|
||||
-- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||
average.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
||||
the last value.
|
||||
|
||||
This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md)
|
||||
view.
|
||||
|
||||
@@ -38,7 +45,7 @@ Unlike [`BasicJsonType::count()`](../basic_json/count.md), whose return value ca
|
||||
an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly
|
||||
[`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the
|
||||
[Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require
|
||||
counting every member with a matching key (the lookup functions resolve to the *last* one).
|
||||
counting every member with a matching key (the lookup functions resolve to the *first* one).
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ iterator find(const char* key) const;
|
||||
iterator find(const string_t& key) const;
|
||||
```
|
||||
|
||||
Finds a member with key `key` -- the last one, should the key occur more than once (see
|
||||
Finds a member with key `key` -- the first one, should the key occur more than once (see
|
||||
[Notes on duplicate keys](operator[].md#notes)). If the value is not an object, or no member has this key,
|
||||
[`end()`](end.md) is returned.
|
||||
|
||||
@@ -26,13 +26,20 @@ No-throw guarantee: this function never throws exceptions.
|
||||
## Complexity
|
||||
|
||||
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
|
||||
in document order, scanning all of them, since the last match is wanted. Each comparison first checks the key's length
|
||||
in document order, stopping at the first match. Each comparison first checks the key's length
|
||||
-- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||
average.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
||||
the last value.
|
||||
|
||||
Unlike [`BasicJsonType::find`](../basic_json/find.md), which always returns `#!cpp end()` for a non-object type, this
|
||||
also does so for a [discarded](is_discarded.md) view -- there is no separate "invalid" iterator to return.
|
||||
|
||||
|
||||
@@ -87,9 +87,9 @@ exception thrown while converting through `materialize()` (the last bullet) is d
|
||||
!!! info "Duplicate keys"
|
||||
|
||||
`#!cpp std::map`/`#!cpp std::unordered_map` conversions keep the *last* value of a repeated key, like
|
||||
[`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the member
|
||||
[`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md) resolve to, too (see the
|
||||
[Notes on duplicate keys](operator[].md#notes)).
|
||||
[`materialize()`](materialize.md) and [`BasicJsonType::parse()`](../basic_json/parse.md) do. This is the opposite
|
||||
of [`operator[]`](operator[].md)/[`at`](at.md)/[`find`](find.md)/[`contains`](contains.md), which resolve to the
|
||||
*first* occurrence (see the [Notes on duplicate keys](operator[].md#notes)).
|
||||
|
||||
!!! info "No pointers, references, or implicit conversion"
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Constant.
|
||||
|
||||
As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all
|
||||
occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md),
|
||||
[`contains`](contains.md), and [`count`](count.md), which resolve to the *last* member with a given key. See the
|
||||
[`contains`](contains.md), and [`count`](count.md), which resolve to the *first* member with a given key. See the
|
||||
[Notes on duplicate keys](operator[].md#notes) of `operator[]`.
|
||||
|
||||
!!! danger "Lifetime issues"
|
||||
@@ -63,8 +63,8 @@ occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](a
|
||||
|
||||
The example below shows a settings object whose source text records every update to a key as a duplicate
|
||||
member, in the order they happened. `items()` walks all of them, so the update history is visible, while
|
||||
[`operator[]`](operator[].md) sees the *last* one, and so does [`materialize()`](materialize.md) -- like
|
||||
[`BasicJsonType::parse()`](../basic_json/parse.md).
|
||||
[`operator[]`](operator[].md) only ever sees the *first* one and [`materialize()`](materialize.md) -- like
|
||||
[`BasicJsonType::parse()`](../basic_json/parse.md) -- keeps only the *last*.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__items.cpp"
|
||||
|
||||
@@ -15,7 +15,7 @@ basic_json_view operator[](IntegerType idx) const;
|
||||
basic_json_view operator[](const json_pointer& ptr) const;
|
||||
```
|
||||
|
||||
1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
|
||||
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
|
||||
the [Notes](#notes) below) -- or a [discarded](is_discarded.md) view if there is no such member.
|
||||
2. Returns the array element at index `idx`, or a [discarded](is_discarded.md) view if `idx` is out of range. The
|
||||
template accepts every integer type except `#!cpp bool` and `#!cpp std::size_t` (`#!cpp int`, `#!cpp unsigned`,
|
||||
@@ -38,7 +38,7 @@ basic_json_view operator[](const json_pointer& ptr) const;
|
||||
|
||||
## Return value
|
||||
|
||||
1. the value of the last member with key `key`, or a discarded view if no member has this key (or if this view is
|
||||
1. the value of the first member with key `key`, or a discarded view if no member has this key (or if this view is
|
||||
[discarded](is_discarded.md))
|
||||
2. the element at index `idx`, or a discarded view if `#!cpp idx >= size()` or `idx` is negative (or if this view is
|
||||
[discarded](is_discarded.md))
|
||||
@@ -80,7 +80,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
## Complexity
|
||||
|
||||
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||
another, in document order, scanning all of them, since the last match is wanted. Each comparison first checks the
|
||||
another, in document order, stopping at the first match. Each comparison first checks the
|
||||
key's length -- already known from the index, without reading the key bytes -- before comparing its content, so a
|
||||
key of a different length than `key` is rejected without touching the source text.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
@@ -107,16 +107,19 @@ document.
|
||||
key on an array or a primitive, or an index on an object or a primitive, is `type_error.305` as for
|
||||
`BasicJsonType`. [`at`](at.md) still throws for a discarded view, as it does for a missing key.
|
||||
|
||||
!!! info "Duplicate keys"
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md),
|
||||
[`contains`](contains.md), [`count`](count.md), [`value`](value.md), and JSON pointer resolution) all resolve to
|
||||
the *last* member with that key. This is the member [`materialize()`](materialize.md) (and
|
||||
[`BasicJsonType::parse()`](../basic_json/parse.md)) keeps, so a lookup in the view and in the materialized value
|
||||
agree. [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
|
||||
duplicates, in document order. A lookup in an object without a hash index scans all members for this: it cannot
|
||||
stop at the first match. The hash index of a larger object (128 members or more) leads to the last member of a key
|
||||
as well. See the example below and [`size()`](size.md#notes).
|
||||
the *first* member with that key, because a lookup can stop as soon as it finds a match. This is different from
|
||||
[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every
|
||||
member in order and so keep the *last* value for a repeated key, so `#!cpp v["a"]` and
|
||||
`#!cpp v.materialize()["a"]` can differ. To get the value `parse()` would give, use
|
||||
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
|
||||
The hash index of a larger object (128 members or more) leads to the first member of a key as well.
|
||||
[`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
|
||||
duplicates, in document order. See [Duplicate keys](../../features/json_view.md#duplicate-keys) and
|
||||
[`size()`](size.md#notes).
|
||||
|
||||
!!! info "JSON pointer resolution"
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ bool operator==(const BasicJsonType& lhs, const basic_json_view& rhs);
|
||||
Neither overload builds a `BasicJsonType` value for a view to do the comparison (see [Notes](#notes) below). Numbers
|
||||
compare by value across their types (`#!cpp 1 == 1.0`), and an object compares by its members, with duplicate keys
|
||||
resolved exactly as `parse()` resolves them -- the last value, at the position of the first occurrence of the key.
|
||||
This is not what [`operator[]`](operator[].md) and the other lookups return for a duplicate key: they find the *first*
|
||||
member (see [Duplicate keys](../../features/json_view.md#duplicate-keys)).
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ T value(const json_pointer& ptr, const T& default_value) const;
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const;
|
||||
```
|
||||
|
||||
1. Returns the value of the object member with key `key` -- the last one, should the key occur more than once (see
|
||||
1. Returns the value of the object member with key `key` -- the first one, should the key occur more than once (see
|
||||
[Notes on duplicate keys](operator[].md#notes)) -- converted to `T`, or `default_value` if there is no such member.
|
||||
2. Returns the value a JSON pointer `ptr` refers to, starting at this value, converted to `T`, or `default_value` if
|
||||
`ptr` cannot be resolved.
|
||||
@@ -39,7 +39,7 @@ equivalent) deduce `string_t`, not `const char*`, for their return type and for
|
||||
|
||||
## Return value
|
||||
|
||||
1. the last member with key `key`, converted to `T`, or `default_value`
|
||||
1. the first member with key `key`, converted to `T`, or `default_value`
|
||||
2. the value `ptr` resolves to, converted to `T`, or `default_value`
|
||||
|
||||
## Exception safety
|
||||
@@ -68,7 +68,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
## Complexity
|
||||
|
||||
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
|
||||
another, in document order, scanning all of them, since the last match is wanted. Plus the complexity of converting
|
||||
another, in document order, stopping at the first match. Plus the complexity of converting
|
||||
the found member to `T` (see [`get`](get.md)).
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
on average.
|
||||
@@ -79,6 +79,13 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
|
||||
## Notes
|
||||
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
||||
the last value.
|
||||
|
||||
!!! info "Differences to `at` and `operator[]`"
|
||||
|
||||
Unlike [`at`](at.md), this function does not throw if `key`/`ptr` resolves to no value. Unlike
|
||||
|
||||
@@ -7,8 +7,8 @@ int main()
|
||||
{
|
||||
// a settings object whose source text records every update to a key as
|
||||
// a duplicate member. items() visits all of them, in document order, so
|
||||
// the update history is visible; operator[] and materialize() -- like
|
||||
// basic_json::parse() -- see the last one
|
||||
// the update history is visible; operator[] only ever sees the first
|
||||
// one, and materialize() -- like basic_json::parse() -- keeps the last
|
||||
json_document updates = json_document::parse(R"({"retries": 1, "timeout": 30, "retries": 5})");
|
||||
const auto settings = updates.root();
|
||||
|
||||
@@ -17,6 +17,6 @@ int main()
|
||||
std::cout << item.key() << '=' << item.value().materialize().dump() << '\n';
|
||||
}
|
||||
|
||||
std::cout << "last \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n';
|
||||
std::cout << "first \"retries\" seen by operator[]: " << settings["retries"].materialize().dump() << '\n';
|
||||
std::cout << "last \"retries\" kept by materialize(): " << settings.materialize()["retries"].dump() << '\n';
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
retries=1
|
||||
timeout=30
|
||||
retries=5
|
||||
last "retries" seen by operator[]: 5
|
||||
first "retries" seen by operator[]: 1
|
||||
last "retries" kept by materialize(): 5
|
||||
@@ -134,17 +134,8 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
`operator[]` on a discarded view returns a discarded view without throwing: `#!cpp v["a"]["b"][0]` can be tested
|
||||
once at the end. Type errors on values that exist (a key on an array, an index on an object) still throw, and
|
||||
[`at`](../api/basic_json_view/at.md) throws for every missing value.
|
||||
- **Duplicate keys are visible.** If an object in the source text repeats a key,
|
||||
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
|
||||
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence (and [`size()`](../api/basic_json_view/size.md)
|
||||
counts all of them), while [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
|
||||
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
|
||||
[`contains`](../api/basic_json_view/contains.md), and [`count`](../api/basic_json_view/count.md) resolve to the
|
||||
*last* occurrence -- the one `basic_json::parse()` (and so
|
||||
[`materialize()`](../api/basic_json_view/materialize.md)) keeps for a repeated key -- which makes a lookup scan all
|
||||
members instead of stopping at a match (objects with 128 members or more get a hash index that leads to the last
|
||||
occurrence directly).
|
||||
See the [Notes on duplicate keys](../api/basic_json_view/operator%5B%5D.md#notes) of `operator[]`.
|
||||
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
|
||||
below.
|
||||
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own
|
||||
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
|
||||
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
||||
@@ -156,6 +147,52 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
equal values for them, without ever building a tree to do it. For ordering, too,
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can compare.
|
||||
|
||||
## Duplicate keys
|
||||
|
||||
!!! warning "A lookup in a view and in the parsed value can give different answers"
|
||||
|
||||
If an object in the source text repeats a key, [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
|
||||
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
|
||||
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md),
|
||||
[`count`](../api/basic_json_view/count.md), and JSON pointer resolution all return the **first** member with that
|
||||
key. `basic_json::parse()` instead keeps the **last** value of a repeated key, so for `#!cpp {"a":1,"a":2}`,
|
||||
`#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is `#!cpp 2`.
|
||||
|
||||
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
|
||||
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence, in document order, and
|
||||
[`size()`](../api/basic_json_view/size.md) counts all of them, so `#!cpp view.size()` can be larger than
|
||||
`#!cpp view.materialize().size()`.
|
||||
|
||||
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
|
||||
to scan all members of the object, even when the key is found at the very first one: this made lookups in small
|
||||
objects 1.6 to 3.4 times slower. The hash index of objects with 128 members or more leads to the first member of a
|
||||
key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return the first
|
||||
member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees
|
||||
duplicates is unpredictable, so neither choice is wrong.
|
||||
|
||||
**What stays the same as `parse()`?** [`materialize()`](../api/basic_json_view/materialize.md) and
|
||||
[`get<std::map<...>>()`](../api/basic_json_view/get.md) replay every member in order, so they keep the *last* value
|
||||
exactly like `#!cpp basic_json::parse()` (at the position of the first occurrence of the key, for an
|
||||
[`ordered_json`](../api/ordered_json.md)).
|
||||
|
||||
**How do I get the value `parse()` would give?** Either call `#!cpp view.materialize()` and look the key up in the
|
||||
result, or iterate the members and keep the last match:
|
||||
|
||||
```cpp
|
||||
// the last member with the key "a", as parse() would keep it
|
||||
json_view last;
|
||||
for (const auto item : view.items())
|
||||
{
|
||||
if (item.key() == "a")
|
||||
{
|
||||
last = item.value();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you cannot trust the source text to have unique keys, check [`size()`](../api/basic_json_view/size.md) against the
|
||||
number of distinct keys, or reject duplicates when iterating.
|
||||
|
||||
## Getting values out without copying
|
||||
|
||||
[`get<T>()`](../api/basic_json_view/get.md) converts many `T` directly from the flat index, without ever building a
|
||||
@@ -185,8 +222,8 @@ Both results are only valid as long as the view -- and, for a string with no esc
|
||||
[`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a
|
||||
`basic_json` value. An object's members are written in document order, not sorted by key, and *every* occurrence of a
|
||||
repeated key is written, not only the last one -- the same two ways [iteration](#what-is-different) already differs
|
||||
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see above. `#!cpp materialize().dump()` gives
|
||||
a different result in both respects for a `json_view`.
|
||||
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see [Duplicate keys](#duplicate-keys).
|
||||
`#!cpp materialize().dump()` gives a different result in both respects for a `json_view`.
|
||||
|
||||
By default, numbers are written the way [`basic_json::dump()`](../api/basic_json/dump.md) would.
|
||||
[`number_format::source`](../api/basic_json_view/number_format.md) instead copies every number exactly as it was
|
||||
|
||||
@@ -215,7 +215,7 @@ packet-beta
|
||||
- **Large objects** (128 members or more) get a hash index after parsing
|
||||
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
|
||||
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
|
||||
not compare every key. Of duplicate keys the table leads to the last, as a linear search does. The object's `extra`
|
||||
not compare every key. Of duplicate keys the table leads to the first, as a linear search does. The object's `extra`
|
||||
holds the number of its table. Only 65,535 tables fit into `extra`; objects beyond them are searched linearly.
|
||||
|
||||
For example, `#!json {"a": [1, 2.5]}` becomes five nodes. Each node's elements follow it, and `next` leads from an
|
||||
|
||||
@@ -152,23 +152,25 @@ class editor
|
||||
{
|
||||
become_empty(o, value_t::object);
|
||||
}
|
||||
// an existing member: assign the one that lookups find (the last
|
||||
// one, should the key occur more than once), and drop the others, so
|
||||
// that lookups, iteration, and materialize() agree. The key stays at
|
||||
// the position of its first occurrence, as materialize() puts it.
|
||||
// an existing member: assign it (and drop later duplicates, so that
|
||||
// lookups, iteration, and materialize() agree)
|
||||
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))
|
||||
{
|
||||
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
|
||||
++matches;
|
||||
}
|
||||
}
|
||||
if (slot != nullptr)
|
||||
{
|
||||
if (matches > 1)
|
||||
if (duplicates)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// Remove the members with this key from an object: all of them, or all
|
||||
/// but one. That one stays where the first occurrence is, but holds the
|
||||
/// 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)
|
||||
/// remove the members with this key (all, or all but the first) from an object
|
||||
std::size_t erase_members(node* o, string_view_t key, bool keep_first)
|
||||
{
|
||||
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;
|
||||
std::size_t erased = 0;
|
||||
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);
|
||||
if (match && (kept || !keep_one))
|
||||
if (match && (kept || !keep_first))
|
||||
{
|
||||
++erased;
|
||||
continue;
|
||||
}
|
||||
w[0] = r[0];
|
||||
w[1] = match ? last_value : r[1];
|
||||
kept = kept || match;
|
||||
if (w != r)
|
||||
{
|
||||
w[0] = r[0];
|
||||
w[1] = r[1];
|
||||
}
|
||||
w += 2;
|
||||
}
|
||||
h->next = static_cast<std::uint32_t>(w - h);
|
||||
|
||||
@@ -84,9 +84,10 @@ class short_key
|
||||
std::uint64_t m_b = 0;
|
||||
};
|
||||
|
||||
/// the key node of the last member of an object with the given key, or
|
||||
/// nullptr (the last one, as materialize() and parse() keep it); most keys are
|
||||
/// rejected by their length, from the index alone
|
||||
/// the key node of the first member of an object with the given key, or
|
||||
/// nullptr (the search stops at the first match; materialize() and parse()
|
||||
/// keep the last value of a duplicate key instead); most keys are rejected by
|
||||
/// their length, from the index alone
|
||||
template<bool Editable>
|
||||
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 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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
last = m;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
return nullptr;
|
||||
}
|
||||
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)
|
||||
{
|
||||
last = m;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// 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
|
||||
// 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
|
||||
// node from its object node (0: empty). Of duplicate keys, the last is kept,
|
||||
// as for the linear search, and as basic_json::parse() does.
|
||||
// node from its object node (0: empty). Of duplicate keys, the first is kept,
|
||||
// as for the linear search.
|
||||
//
|
||||
// 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
|
||||
@@ -92,8 +92,7 @@ inline void build_object_index(document_data& d, node* obj)
|
||||
const node* const other = obj + slots[i];
|
||||
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
|
||||
slots[i] = static_cast<std::uint32_t>(k - obj);
|
||||
duplicate = true; // keep the first
|
||||
break;
|
||||
}
|
||||
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
|
||||
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());
|
||||
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)
|
||||
|
||||
@@ -243,7 +243,7 @@ class basic_json_view
|
||||
// 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
|
||||
/// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// and out_of_range.403 if there is no such member.
|
||||
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
|
||||
/// 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.
|
||||
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
|
||||
@@ -430,7 +430,7 @@ class basic_json_view
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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)
|
||||
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
|
||||
// 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
|
||||
// node from its object node (0: empty). Of duplicate keys, the last is kept,
|
||||
// as for the linear search, and as basic_json::parse() does.
|
||||
// node from its object node (0: empty). Of duplicate keys, the first is kept,
|
||||
// as for the linear search.
|
||||
//
|
||||
// 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
|
||||
@@ -3009,8 +3009,7 @@ inline void build_object_index(document_data& d, node* obj)
|
||||
const node* const other = obj + slots[i];
|
||||
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
|
||||
slots[i] = static_cast<std::uint32_t>(k - obj);
|
||||
duplicate = true; // keep the first
|
||||
break;
|
||||
}
|
||||
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
|
||||
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;
|
||||
};
|
||||
|
||||
/// the key node of the last member of an object with the given key, or
|
||||
/// nullptr (the last one, as materialize() and parse() keep it); most keys are
|
||||
/// rejected by their length, from the index alone
|
||||
/// the key node of the first member of an object with the given key, or
|
||||
/// nullptr (the search stops at the first match; materialize() and parse()
|
||||
/// keep the last value of a duplicate key instead); most keys are rejected by
|
||||
/// their length, from the index alone
|
||||
template<bool Editable>
|
||||
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 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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
last = m;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
return nullptr;
|
||||
}
|
||||
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)
|
||||
{
|
||||
last = m;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
// an existing member: assign the one that lookups find (the last
|
||||
// one, should the key occur more than once), and drop the others, so
|
||||
// that lookups, iteration, and materialize() agree. The key stays at
|
||||
// the position of its first occurrence, as materialize() puts it.
|
||||
// an existing member: assign it (and drop later duplicates, so that
|
||||
// lookups, iteration, and materialize() agree)
|
||||
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))
|
||||
{
|
||||
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
|
||||
++matches;
|
||||
}
|
||||
}
|
||||
if (slot != nullptr)
|
||||
{
|
||||
if (matches > 1)
|
||||
if (duplicates)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// Remove the members with this key from an object: all of them, or all
|
||||
/// but one. That one stays where the first occurrence is, but holds the
|
||||
/// 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)
|
||||
/// remove the members with this key (all, or all but the first) from an object
|
||||
std::size_t erase_members(node* o, string_view_t key, bool keep_first)
|
||||
{
|
||||
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;
|
||||
std::size_t erased = 0;
|
||||
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);
|
||||
if (match && (kept || !keep_one))
|
||||
if (match && (kept || !keep_first))
|
||||
{
|
||||
++erased;
|
||||
continue;
|
||||
}
|
||||
w[0] = r[0];
|
||||
w[1] = match ? last_value : r[1];
|
||||
kept = kept || match;
|
||||
if (w != r)
|
||||
{
|
||||
w[0] = r[0];
|
||||
w[1] = r[1];
|
||||
}
|
||||
w += 2;
|
||||
}
|
||||
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());
|
||||
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)
|
||||
@@ -7151,7 +7136,7 @@ class basic_json_view
|
||||
// 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
|
||||
/// is a discarded view (so that v["a"]["b"] is safe). Throws type_error.305
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// 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,
|
||||
/// and out_of_range.403 if there is no such member.
|
||||
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
|
||||
/// 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.
|
||||
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
|
||||
@@ -7338,7 +7323,7 @@ class basic_json_view
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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)
|
||||
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:
|
||||
// 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)
|
||||
{
|
||||
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());
|
||||
CHECK(v.contains(key));
|
||||
CHECK(v.count(key) == 1);
|
||||
if (std::find(keys.begin(), keys.end(), key) == keys.end())
|
||||
{
|
||||
keys.push_back(key);
|
||||
}
|
||||
// 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)
|
||||
// lookups find the first member with the key, which is this one if
|
||||
// there is no earlier one
|
||||
if (std::find(keys.begin(), keys.end(), key) != keys.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
keys.push_back(key);
|
||||
CHECK(v.find(key) == it);
|
||||
CHECK(v[key].materialize() == it->materialize());
|
||||
CHECK(v.at(key).materialize() == it.value().materialize());
|
||||
@@ -725,20 +715,28 @@ TEST_CASE("json_view element access and iteration")
|
||||
#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_view v = d.root();
|
||||
CHECK(v.size() == 3);
|
||||
CHECK(v["a"].materialize() == 3);
|
||||
CHECK(v.at("a").materialize() == 3);
|
||||
CHECK(v.find("a") == std::next(v.begin(), 2));
|
||||
CHECK(v.find("a").value().materialize() == 3);
|
||||
CHECK(v["a"].materialize() == 1);
|
||||
CHECK(v.at("a").materialize() == 1);
|
||||
CHECK(v.find("a") == v.begin());
|
||||
CHECK(v.find("a").value().materialize() == 1);
|
||||
CHECK(v.find("b") == std::next(v.begin()));
|
||||
CHECK(v.count("a") == 1);
|
||||
CHECK(v.contains("a"));
|
||||
CHECK(v.value("a", 0) == 3);
|
||||
CHECK(v["a"].materialize() == v.materialize()["a"]); // as materialize()
|
||||
CHECK(v.value("a", 0) == 1);
|
||||
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)
|
||||
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 json_document dk = json_document::parse("{\"" + key + "\":1,\"" + key + "x\":2,\"" + key + "\":3,\"" + key + "\":4}");
|
||||
CAPTURE(n)
|
||||
CHECK(dk.root()[key].materialize() == 4);
|
||||
CHECK(dk.root().at(key).materialize() == 4);
|
||||
CHECK(dk.root().find(key) == std::next(dk.root().begin(), 3));
|
||||
CHECK(dk.root().value(key, 0) == 4);
|
||||
CHECK(dk.root()[key].materialize() == 1);
|
||||
CHECK(dk.root().at(key).materialize() == 1);
|
||||
CHECK(dk.root().find(key) == dk.root().begin());
|
||||
CHECK(dk.root().value(key, 0) == 1);
|
||||
CHECK(dk.root()[key + "x"].materialize() == 2);
|
||||
}
|
||||
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})");
|
||||
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}");
|
||||
// dump() writes all members, though the lookup finds the first
|
||||
CHECK(d.root()["b"].dump() == "1");
|
||||
}
|
||||
|
||||
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 last = ordered_json_document::parse(R"({"a": 3, "b": 2})");
|
||||
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 ba = ordered_json_document::parse(R"({"b": 2, "a": 1})");
|
||||
CHECK(ab.root() != ba.root());
|
||||
@@ -1684,31 +1687,26 @@ TEST_CASE("json_view large objects")
|
||||
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" : "");
|
||||
CHECK(v[key].get<std::size_t>() == i);
|
||||
CHECK(v.contains(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.contains(key + "x"));
|
||||
}
|
||||
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.at("k1").get_string() == "a duplicate of an earlier key");
|
||||
CHECK(v["k1"].get<int>() == 1); // the first of duplicate keys, as for small objects
|
||||
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(v == 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
|
||||
// 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
|
||||
// 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
|
||||
{
|
||||
std::string key;
|
||||
@@ -1756,20 +1754,23 @@ TEST_CASE("json_view large objects")
|
||||
REQUIRE(members.size() >= total);
|
||||
REQUIRE(members.size() >= 100);
|
||||
std::string text = "{";
|
||||
std::map<std::string, std::size_t> first;
|
||||
std::map<std::string, std::size_t> last;
|
||||
for (const member& m : members)
|
||||
{
|
||||
text += (text.size() > 1 ? ",\"" : "\"") + m.key + "\":" + std::to_string(m.position);
|
||||
first.insert({m.key, m.position});
|
||||
last[m.key] = m.position;
|
||||
}
|
||||
text += '}';
|
||||
REQUIRE(last.size() < members.size());
|
||||
REQUIRE(first.size() < members.size());
|
||||
|
||||
const json_document d = json_document::parse(text);
|
||||
const json_view v = d.root();
|
||||
const json j = json::parse(text);
|
||||
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)
|
||||
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.value(json::json_pointer(pointer), std::size_t{0}) == expected);
|
||||
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["missing"].is_discarded());
|
||||
|
||||
@@ -28,6 +28,7 @@ using ptr_t = ordered_json::json_pointer;
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#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
|
||||
// members (the real member is then the last), or after (the repeat is)
|
||||
// the text of j, in which some objects repeat a key of theirs after their
|
||||
// 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)
|
||||
{
|
||||
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();
|
||||
};
|
||||
std::string text = "{";
|
||||
if (!keys.empty() && r(4) == 0)
|
||||
{
|
||||
text += repeated() + ",";
|
||||
}
|
||||
bool first = true;
|
||||
for (const auto& kv : j.items())
|
||||
{
|
||||
@@ -225,10 +224,58 @@ std::string text_with_duplicates(const ordered_json& j)
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
// every lookup of the edited view finds what j holds, although the view may
|
||||
// have several members for a key (j has the last value, at the position of the
|
||||
// first member: what parse() and materialize() make of it)
|
||||
void check_lookups(const ordered_json_editable_view& v, const ordered_json& j)
|
||||
// every lookup of the edited view finds the first member of its key, which
|
||||
// the iteration of the view shows
|
||||
void check_lookups(const ordered_json_editable_view& v)
|
||||
{
|
||||
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());
|
||||
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 ptr_t ptr = ptr_t() / key;
|
||||
CAPTURE(key)
|
||||
const ordered_json_editable_view m = v[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));
|
||||
REQUIRE(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() == kv.value());
|
||||
CHECK(v.value(ptr, ordered_json(nullptr)) == kv.value());
|
||||
if (kv.value().is_string())
|
||||
{
|
||||
CHECK(v.value(key, std::string("-")) == kv.value().get<std::string>());
|
||||
}
|
||||
check_lookups(m, kv.value());
|
||||
CHECK(v.find(key) != v.end());
|
||||
CHECK(v.at(ptr).type() == kv.value().type());
|
||||
check_first_members(v[key], kv.value());
|
||||
check_first_members(v.at(key), kv.value());
|
||||
check_first_members(v[ptr], kv.value());
|
||||
}
|
||||
CHECK(v["missing#key"].is_discarded());
|
||||
CHECK(!v.contains("missing#key"));
|
||||
CHECK(v.find("missing#key") == v.end());
|
||||
}
|
||||
else if (j.is_array())
|
||||
{
|
||||
REQUIRE(v.size() == j.size());
|
||||
for (std::size_t i = 0; i < j.size(); ++i)
|
||||
{
|
||||
check_lookups(v[i], j[i]);
|
||||
check_lookups(v.at(i), j[i]);
|
||||
check_first_members(v[i], j[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(v.materialize() == j);
|
||||
}
|
||||
}
|
||||
|
||||
// (for documents whose objects repeat keys: dump(), size(), and iteration
|
||||
// list all members, so only the lookups and materialize() are compared)
|
||||
// (for documents whose objects repeat keys: j holds the first members, the
|
||||
// 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)
|
||||
{
|
||||
CHECK(d.root().materialize() == j);
|
||||
check_lookups(d.root(), j);
|
||||
check_first_members(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)
|
||||
@@ -304,17 +345,25 @@ TEST_CASE("json_view edits: differential")
|
||||
// materialize, and read back the same
|
||||
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;
|
||||
ordered_json j = random_value(0);
|
||||
if (r(4) == 0)
|
||||
{
|
||||
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);
|
||||
CAPTURE(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 int edits = 30;
|
||||
for (int e = 0; e < edits; ++e)
|
||||
@@ -624,7 +673,7 @@ TEST_CASE("json_view edits: views and values")
|
||||
SECTION("duplicate keys")
|
||||
{
|
||||
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})");
|
||||
d = json_editable_document::parse(R"({"a": 1, "b": 2, "a": 3})");
|
||||
CHECK(d.erase(d.root(), "a") == 2);
|
||||
@@ -911,10 +960,10 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
|
||||
|
||||
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
|
||||
template<typename View>
|
||||
void check_last_wins(const View& o, int expected)
|
||||
void check_first_wins(const View& o, int expected)
|
||||
{
|
||||
CAPTURE(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")
|
||||
{
|
||||
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().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
|
||||
d.set(d.root()["b"], 5);
|
||||
check_last_wins(d.root(), 3);
|
||||
d.set(d.root()["a"], 4); // the member that reads find
|
||||
check_last_wins(d.root(), 4);
|
||||
CHECK(d.root().dump() == R"({"a":1,"b":5,"a":4})");
|
||||
check_first_wins(d.root(), 1);
|
||||
d.set(d.root()["a"], 4); // the member that lookups find
|
||||
check_first_wins(d.root(), 4);
|
||||
CHECK(d.root().dump() == R"({"a":4,"b":5,"a":3})");
|
||||
|
||||
// an appended member moves it
|
||||
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().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);
|
||||
check_last_wins(d.root(), 6);
|
||||
CHECK(d.root().dump() == R"({"a":1,"b":5,"a":6,"c":true})");
|
||||
check_first_wins(d.root(), 6);
|
||||
CHECK(d.root().dump() == R"({"a":6,"b":5,"a":3,"c":true})");
|
||||
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})");
|
||||
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");
|
||||
CHECK(held.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
|
||||
d = ordered_json_editable_document::parse(R"({"a": {"x": 1}, "b": 2, "a": {"x": 3}})");
|
||||
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);
|
||||
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})");
|
||||
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.root().dump() == R"({"b":2,"c":4})");
|
||||
CHECK(!d.root().contains("a"));
|
||||
@@ -1008,20 +1058,20 @@ TEST_CASE("json_view edits: duplicate keys")
|
||||
}
|
||||
text += R"(,"a":1,"k7":"last","a":2})";
|
||||
json_editable_document d = json_editable_document::parse(text);
|
||||
check_last_wins(d.root(), 2);
|
||||
CHECK(d.root()["k7"].get_string() == "last");
|
||||
check_first_wins(d.root(), 0);
|
||||
CHECK(d.root()["k7"].get_string() == "first");
|
||||
CHECK(d.root()["k199"].get<int>() == 199);
|
||||
|
||||
// a value assigned in place: the index stays in use
|
||||
d.set(d.root()["a"], 3);
|
||||
check_last_wins(d.root(), 3);
|
||||
check_first_wins(d.root(), 3);
|
||||
d.set(d.root()["k7"], "changed");
|
||||
CHECK(d.root()["k7"].get_string() == "changed");
|
||||
CHECK(d.root()["k199"].get<int>() == 199);
|
||||
|
||||
// an appended member moves the members: the lookup scans them
|
||||
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() == d.root().at("k7").get_string());
|
||||
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.set(d.root()["a"], json{{"x", 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.root()["k7"].is_discarded());
|
||||
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")
|
||||
{
|
||||
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_last_wins(d.root()[1], 4);
|
||||
check_first_wins(d.root()[0], 1);
|
||||
check_first_wins(d.root()[1], 3);
|
||||
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)
|
||||
check_last_wins(d.root()[1], 2);
|
||||
check_last_wins(d.root()[2], 4);
|
||||
check_first_wins(d.root()[1], 1);
|
||||
check_first_wins(d.root()[2], 3);
|
||||
CHECK(d.root()[3]["a"].get<int>() == 6);
|
||||
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})");
|
||||
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}])");
|
||||
}
|
||||
|
||||
@@ -1063,14 +1113,14 @@ TEST_CASE("json_view edits: duplicate keys")
|
||||
d.set(d.root(), "o", source.root()["o"]);
|
||||
// (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_last_wins(d.root()["copy"][0], 2);
|
||||
check_last_wins(d.root()["o"]["b"], 4);
|
||||
check_last_wins(d.root()["o"], 6);
|
||||
check_first_wins(d.root()["copy"][0], 1);
|
||||
check_first_wins(d.root()["o"]["b"], 3);
|
||||
check_first_wins(d.root()["o"], 5);
|
||||
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);
|
||||
check_last_wins(d.root()["o"], 6);
|
||||
CHECK(d.root()["o"]["b"]["a"].get<int>() == 4);
|
||||
check_first_wins(d.root()["o"], 5);
|
||||
CHECK(d.root()["o"]["b"]["a"].get<int>() == 3);
|
||||
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()["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().contains("k1000"));
|
||||
// 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);
|
||||
}
|
||||
|
||||
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 = "{";
|
||||
for (int i = 0; i < 200; ++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"})";
|
||||
const json_document d = json_document::parse(text);
|
||||
REQUIRE(d.root().size() == 204);
|
||||
CHECK(d.root()["k5"] == "three");
|
||||
CHECK(d.root()["k5"] == 5);
|
||||
const std::vector<std::uint8_t> image = d.save();
|
||||
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);
|
||||
CHECK(l.root().size() == 204);
|
||||
CHECK(l.root()["k5"] == "three");
|
||||
CHECK(l.root().at("k5") == "three");
|
||||
CHECK(l.root().find("k5").value() == "three");
|
||||
CHECK(l.root()["k0"].is_null());
|
||||
CHECK(l.root()["k199"] == json::array());
|
||||
CHECK(l.root()["k5"] == 5);
|
||||
CHECK(l.root().at("k5") == 5);
|
||||
CHECK(l.root().find("k5").value() == 5);
|
||||
CHECK(l.root()["k0"] == 0);
|
||||
CHECK(l.root()["k199"] == 199);
|
||||
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()["k5"] == "three");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,15 +357,8 @@ TEST_CASE("json_view images: round trips")
|
||||
for (std::size_t i = 0; i < members; ++i)
|
||||
{
|
||||
CAPTURE(i)
|
||||
const bool duplicate = std::find(duplicated.begin(), duplicated.end(), keys[i]) != duplicated.end();
|
||||
if (duplicate)
|
||||
{
|
||||
CHECK(l.root()[keys[i]] == "last");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(l.root()[keys[i]] == i);
|
||||
}
|
||||
// lookups find the first member, also of the duplicated keys
|
||||
CHECK(l.root()[keys[i]] == i);
|
||||
CHECK(l.root().at(keys[i]) == l.root()[keys[i]]);
|
||||
CHECK(l.root().find(keys[i]).key() == keys[i]);
|
||||
CHECK(!l.root().contains(keys[i] + "x"));
|
||||
|
||||
Reference in new issue
Block a user