Address the review of json_view element access

From gregmarr's review of #5622:

- count() counts every member with the key, so count(key) > 1 detects
  duplicate keys; it was contains() as 0/1 before.
- get_to() passes types that get<T>() does not convert directly to
  from_json with the existing value, as basic_json::get_to() does, so that
  a from_json which sets only the fields present keeps the others.
- array_index() checks the empty token and "-" first; the results are
  unchanged.
- Docs: no "if an exception is thrown" where a view never changes
  anything; the borrowed source must also stay unmodified; items() on a
  temporary view is safe; back()/front() notes shortened; the basic_json
  see-also entries say how the view's results can differ.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-18 10:05:51 +02:00
1 parent f2582daa62
commit 7b755737b4
27 files changed
+239 -114

No files matched your search

+2 -1
View File
@@ -93,7 +93,8 @@ Logarithmic in the size of the JSON object.
- [count](count.md) returns the number of occurrences of a key
- [contains](contains.md) checks whether a key exists
- [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view
- [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view (a duplicate key resolves
to the first member, whereas `parse()` keeps the last)
## Version history
+2 -1
View File
@@ -51,7 +51,8 @@ Constant.
## See also
- [back](back.md) to access the last element
- [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view
- [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view (document order, not
sorted; duplicate keys are visited)
## Version history
+1 -1
View File
@@ -100,7 +100,7 @@ When iterating over an array, `key()` will return the index of the element as st
- [begin](begin.md) returns an iterator to the first element
- [end](end.md) returns an iterator to one past the last element
- [basic_json_view::items](../basic_json_view/items.md) - the same range on a zero-copy view (`#!cpp const auto`,
not `#!cpp const auto&`: items are produced on the fly)
not `#!cpp const auto&`: items are produced on the fly; document order, not sorted; duplicate keys are visited)
## Version history
@@ -275,8 +275,9 @@ Strong exception safety: if an exception occurs, the original value stays intact
- documentation on [runtime assertions](../../features/assertions.md)
- see [`at`](at.md) for access by reference with range checking
- see [`value`](value.md) for access with default value
- [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (always
returns a discarded view instead of assuming undefined behavior)
- [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (a duplicate
key resolves to the first member, whereas `parse()` keeps the last; a missing key gives a discarded view instead of
undefined behavior)
## Version history
+1 -1
View File
@@ -41,7 +41,7 @@ basic_json_view at(const json_pointer& ptr) const;
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
+2 -3
View File
@@ -13,7 +13,7 @@ The last element or member value. For a primitive value (number, string, boolean
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
@@ -33,8 +33,7 @@ index.
## Notes
Unlike [`BasicJsonType::back()`](../basic_json/back.md), which has undefined behavior for an empty array or object,
`back()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and for a
discarded view, where `BasicJsonType::back()` also throws.
`back()` throws `invalid_iterator.214` for an empty array, an empty object, a null value, or a discarded view.
## Examples
@@ -23,7 +23,7 @@ Constant.
## Notes
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), and [`contains`](contains.md),
which all resolve to the *first* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`.
@@ -31,6 +31,10 @@ Because objects are iterated in document order rather than sorted by key, the or
iterating the [`materialize()`](materialize.md)d `BasicJsonType` value would produce: a `#!cpp basic_json` object
(`std::map`-backed by default) sorts its keys, while a view does not.
An `ordered_json` value also keeps document order, but it keeps only one entry per key: as `parse()` does, it keeps the
*last* value, at the position of the key's *first* occurrence. The view, in contrast, visits every member, including
the duplicates.
## Examples
??? example
@@ -53,7 +53,8 @@ view.
!!! info "Postconditions"
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md). If
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md) and
`#!cpp v.at(key)` does not throw. If
`#!cpp v.contains(ptr)` returns `#!cpp true`, then `#!cpp v[ptr]` is not discarded and `#!cpp v.at(ptr)` does not
throw.
+16 -19
View File
@@ -6,7 +6,8 @@ size_type count(const char* key) const;
size_type count(const string_t& key) const;
```
Returns `#!cpp 1` if the value is an object with a member with key `key`, `#!cpp 0` otherwise.
Returns the number of members with key `key` if the value is an object, `#!cpp 0` otherwise. Members with a repeated
key are all counted.
## Parameters
@@ -15,7 +16,8 @@ Returns `#!cpp 1` if the value is an object with a member with key `key`, `#!cpp
## Return value
`#!cpp 1` if the value is an object and has a member with key `key`, `#!cpp 0` otherwise.
the number of members of the object with key `key` (duplicates included), or `#!cpp 0` if the value is not an object
or has no such member.
## Exception safety
@@ -23,34 +25,29 @@ 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, 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.
Linear in the number of members: unlike the lookup functions, `count()` does not stop at the first match, because it
has to count every member with the key. Members are compared one after another, in document order. Each comparison
first checks the key's length -- already known from the index, without reading the key bytes -- before comparing its
content.
## 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.
Unlike [`BasicJsonType::count()`](../basic_json/count.md), whose return value can in principle exceed `#!cpp 1` for
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 *first* one).
If the source text repeats a key, `count()` returns more than `#!cpp 1` for it, even though the
[materialized](materialize.md) value (like the result of `parse()`) would have only one member with that key. Hence
`#!cpp count(key) > 1` detects duplicate keys. This is unlike the lookup functions
([`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md)), which resolve to the
*first* member; see the [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
## Examples
??? example
The example below validates that every transaction of a batch carries a mandatory `amount` field, using
`count()` before deciding whether to materialize a transaction at all.
The example below validates that every transaction of a batch carries exactly one `amount` field, using
`count()` before deciding whether to materialize a transaction at all. It reports a transaction with no `amount`
field and one with a duplicated `amount` field.
```cpp
--8<-- "examples/basic_json_view__count.cpp"
@@ -13,7 +13,7 @@ The first element or member value. For a primitive value (number, string, boolea
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
@@ -31,8 +31,8 @@ Constant.
## Notes
Unlike [`BasicJsonType::front()`](../basic_json/front.md), which has undefined behavior for an empty array or
object, `front()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and
for a discarded view, where `BasicJsonType::front()` also throws.
object, `front()` throws `invalid_iterator.214` for an empty array, an empty object, a null value, or a discarded
view.
## Examples
+10 -7
View File
@@ -10,8 +10,8 @@ Converts the value to `T`.
For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it:
- `#!cpp bool`
- arithmetic types other than `#!cpp bool` (from a number; from a boolean, as `#!cpp 0`/`#!cpp 1`, exactly as
[`BasicJsonType::get<T>()`](../basic_json/get.md) converts a boolean)
- arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
[`BasicJsonType::get<T>()`](../basic_json/get.md))
- `#!cpp std::nullptr_t`
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) -- a copy of the string
- [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's
@@ -19,10 +19,10 @@ For the types below, the conversion works directly on the flat index -- no `Basi
document's own buffer of decoded strings (see [`get_string()`](get_string.md))
- `BasicJsonType` -- equivalent to [`materialize()`](materialize.md)
- `basic_json_view` -- returns `#!cpp *this`
- `#!cpp std::vector<U, A>` -- element by element, each converted with `#!cpp get<U>()`; `#!cpp
- `#!cpp std::vector<U, A>` -- from an array, element by element, each converted with `#!cpp get<U>()`; `#!cpp
std::vector<basic_json_view>` keeps a view of every element instead of a value
- `#!cpp std::map<K, V, C, A>` and `#!cpp std::unordered_map<K, V, H, E, A>`, if `K` is constructible from a `#!cpp
(const char*, std::size_t)` pair -- member by member, each value converted with `#!cpp get<V>()`; with a repeated
(const char*, std::size_t)` pair -- from an object, member by member, each value converted with `#!cpp get<V>()`; with a repeated
key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and
[`materialize()`](materialize.md)) does; `#!cpp std::map<std::string, basic_json_view>` keeps views of the members
instead of values
@@ -43,7 +43,7 @@ the value, converted to `T`
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
@@ -62,6 +62,9 @@ None of the exceptions thrown directly by this function (the first three bullets
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
document.
## Complexity
@@ -73,8 +76,8 @@ exception thrown while converting through `materialize()` (the last bullet) is d
- `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's
own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp
std::unordered_map`), times the complexity of converting one member to the mapped type.
- every other `T`: linear in the size of the subtree (building the `BasicJsonType` value), plus the complexity of
[`BasicJsonType::get<T>()`](../basic_json/get.md) on it.
- every other `T`: the cost of [`materialize()`](materialize.md) and of destroying the `BasicJsonType` value
afterwards, plus that of [`BasicJsonType::get<T>()`](../basic_json/get.md).
## Notes
@@ -15,7 +15,7 @@ had to be decoded once when the document was parsed).
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
@@ -37,9 +37,14 @@ hand out a reference to a stored `string_t`. `get_string()` (equivalently, [`get
zero-copy alternative: [`BasicJsonType::get_ref<const string_t&>()`](../basic_json/get_ref.md) is its closest
counterpart, except that it returns a view instead of a reference to a value that must already exist.
`get_string()` borrows the string: nothing is copied, whereas [`get<std::string>()`](get.md) copies it. For a borrowed
document, the returned [`string_view_t`](index.md#member-types) is valid as long as the caller's buffer; for an owned
one, as long as the document.
The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the
[validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's
source text.
source text, which for a borrowed document must remain valid **and unmodified** for as long as the document exists,
since floating-point values and unescaped strings are read from the source lazily.
## Examples
+23 -8
View File
@@ -5,12 +5,15 @@ template<typename T>
T& get_to(T& v) const;
```
Converts the value to `T` and assigns it to `v`. Equivalent to
Converts the value to `T` and stores it in `v`, in one of two ways:
```cpp
v = get<T>();
return v;
```
- For the types that [`get<T>()`](get.md) converts directly -- arithmetic types, `#!cpp bool`,
`#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `#!cpp std::basic_string`, `BasicJsonType`,
`basic_json_view`, `#!cpp std::vector`, and `#!cpp std::map`/`#!cpp std::unordered_map` with string keys -- `v` is
assigned: `#!cpp v = get<T>()`.
- For every other type, the subtree is built into a `BasicJsonType` value and `#!cpp materialize().get_to(v)` is
called, i.e. `from_json()` is invoked with the existing `v`, exactly as
[`BasicJsonType::get_to()`](../basic_json/get_to.md) does.
## Template parameters
@@ -28,15 +31,27 @@ return v;
## Exception safety
Strong exception safety: if an exception is thrown, `v` is not modified.
- For the directly converted types: strong exception safety. If an exception is thrown, `v` is not modified.
- For every other type: as for [`BasicJsonType::get_to()`](../basic_json/get_to.md). `materialize()` throws before `v`
is touched, but a `from_json()` that throws may leave `v` partly modified.
## Exceptions
Whatever [`get<T>()`](get.md) throws for the same value and `T`.
- For the directly converted types: whatever [`get<T>()`](get.md) throws for the same value and `T`.
- For every other type: whatever [`materialize()`](materialize.md) and
[`BasicJsonType::get_to()`](../basic_json/get_to.md) throw.
## Complexity
Whatever [`get<T>()`](get.md) has for the same `T`.
- For the directly converted types: whatever [`get<T>()`](get.md) has for the same `T`.
- For every other type: that of [`materialize()`](materialize.md), plus that of
[`BasicJsonType::get_to()`](../basic_json/get_to.md).
## Notes
The difference between the two ways matters for a `from_json()` that sets only the fields present in the JSON: since
it runs on the existing `v`, the other fields of `v` keep their values, whereas assigning `get<T>()` would replace
them with those of a default-constructed `T`.
## Examples
+15 -8
View File
@@ -14,17 +14,25 @@ to the document and a pointer into its index), trivially copyable. A view is val
- the document has not been re-parsed with [`read()`](../basic_json_document/read.md) (or
[`parse()`](../basic_json_document/parse.md) into it) or shrunk with
[`shrink_to_fit()`](../basic_json_document/shrink_to_fit.md) since the view was taken, and
- if the document borrows its source text, that text is still alive.
- if the document borrows its source text, that text is still alive and unmodified (floating-point values and
unescaped strings are read from it lazily).
Moving the document itself does not invalidate its views: the index is heap-allocated independently of the
`basic_json_document` object.
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element
access, lookup, iteration, and conversion -- [`get<T>()`](get.md), [`get_string()`](get_string.md),
[`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a
subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and
[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). It does not (yet) provide `dump()` or
comparison.
`basic_json_view` provides the read-only part of the `BasicJsonType` interface:
- the type-inspection functions,
- element access,
- lookup,
- iteration, and
- conversion: [`get<T>()`](get.md), [`get_string()`](get_string.md), [`number_token()`](number_token.md), and
[`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand.
[`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and [`value`](value.md) also accept a
[`json_pointer`](../json_pointer/index.md). It does not (yet) provide `dump()` or comparison. Unlike `basic_json`,
there are no implicit conversion operators (no `#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or
`#!cpp get_ref()`; see [`get`](get.md).
## Template parameters
@@ -46,7 +54,6 @@ comparison.
- **string_view_t** - `#!cpp std::string_view` on C++17 and newer, a minimal internal substitute otherwise
- **iterator**, **const_iterator** - a forward iterator over the elements of an array or the member values of an
object, in document order; both names refer to the same type, since a view is always read-only
- **item** - a (key, value) pair produced by [`items()`](items.md)
## Member functions
@@ -4,9 +4,9 @@
/* unspecified */ items() const noexcept;
```
Returns a range of [`item`](index.md#member-types) values -- (key, value) pairs -- for use in range-based for loops.
The key of an array element is its index, converted to a string, as for
[`BasicJsonType::items()`](../basic_json/items.md).
Returns a range of items -- (key, value) pairs -- for use in range-based for loops. An item's `#!cpp key()` is a
`#!cpp string_view_t` (for an array element, its index converted to a string, as for
[`BasicJsonType::items()`](../basic_json/items.md)), and its `#!cpp value()` is a `basic_json_view` of the element.
The returned type is not part of the public API and may change between versions; use a range-based for loop (see the
example), or `#!cpp decltype(v.items())` if you need to name it.
@@ -33,8 +33,8 @@ is nothing for a reference to bind to.
## Return value
A range whose iterators dereference to [`item`](index.md#member-types) and whose `#!cpp begin()`/`#!cpp end()` are
equivalent to [`basic_json_view::begin()`](begin.md)/[`end()`](end.md), in document order.
A range whose iterators dereference to an item (with `#!cpp key()` and `#!cpp value()`, see above) and whose
`#!cpp begin()`/`#!cpp end()` are equivalent to [`basic_json_view::begin()`](begin.md)/[`end()`](end.md), in document order.
## Exception safety
@@ -48,14 +48,14 @@ 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 *first* member with a given key. See the
and [`contains`](contains.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"
As for `BasicJsonType::items()`, calling `items()` on a temporary view (or a temporary document) is dangerous:
the range refers back to the document, so the document must outlive the loop. See
[#2040](https://github.com/nlohmann/json/issues/2040) for the `BasicJsonType` background.
Unlike `BasicJsonType::items()` (see [#2040](https://github.com/nlohmann/json/issues/2040)), calling `items()` on
a temporary view is safe, for instance `#!cpp for (auto item : v["a"].items())`: the returned range holds a copy
of the view. What must outlive the loop is the document and, for a borrowed document, the source buffer.
## Examples
@@ -15,7 +15,7 @@ and [`materialize()`](materialize.md) can only represent approximately, as a `nu
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
@@ -50,7 +50,7 @@ basic_json_view operator[](const json_pointer& ptr) const;
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
@@ -108,7 +108,7 @@ document.
!!! 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
[`contains`](contains.md), [`value`](value.md), and JSON pointer resolution) all resolve to
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
@@ -14,14 +14,14 @@ same string for a discarded `BasicJsonType` value).
a string representation of the type ([`value_t`](../basic_json/value_t.md)):
| Value type | return value |
|-----------------------------------------------------|---------------|
| `#!json null` | `"null"` |
| boolean | `"boolean"` |
| string | `"string"` |
| number (integer, unsigned integer, floating-point) | `"number"` |
| object | `"object"` |
| array | `"array"` |
| discarded | `"discarded"` |
|----------------------------------------------------|---------------|
| `#!json null` | `"null"` |
| boolean | `"boolean"` |
| string | `"string"` |
| number (integer, unsigned integer, floating-point) | `"number"` |
| object | `"object"` |
| array | `"array"` |
| discarded | `"discarded"` |
`type_name()` never returns `#!cpp "binary"`, since a JSON text has no binary values (see
[`is_binary()`](is_binary.md)); it also never returns `#!cpp "invalid"`, since a view's `#!cpp kind` always comes
@@ -44,7 +44,7 @@ equivalent) deduce `string_t`, not `const char*`, for their return type and for
## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions
@@ -5,13 +5,14 @@ using json_document = nlohmann::json_document;
int main()
{
// validate that every transaction of a batch carries a mandatory
// validate that every transaction of a batch carries exactly one
// "amount" field before materializing any of them into a nlohmann::json
// value -- count() returns 0 or 1 for an object
// value -- count() counts every member with the key
json_document batch = json_document::parse(R"(
[
{"id": 1, "amount": 9.99},
{"id": 2}
{"id": 2},
{"id": 3, "amount": 5.00, "amount": 500.00}
]
)");
@@ -19,9 +20,10 @@ int main()
for (std::size_t i = 0; i < transactions.size(); ++i)
{
const auto transaction = transactions[i];
if (transaction.count("amount") == 0)
const auto amounts = transaction.count("amount");
if (amounts != 1)
{
std::cout << "transaction " << i << " is missing \"amount\"\n";
std::cout << "transaction " << i << " has " << amounts << " \"amount\" fields\n";
continue;
}
std::cout << "transaction " << i << ": " << transaction["amount"].materialize().dump() << '\n';
@@ -1,2 +1,3 @@
transaction 0: 9.99
transaction 1 is missing "amount"
transaction 1 has 0 "amount" fields
transaction 2 has 2 "amount" fields
+9 -8
View File
@@ -67,9 +67,10 @@ table). [`parse_copy()`](../api/basic_json_document/parse_copy.md) always owns a
!!! warning "A borrowed document depends on your buffer"
If a document borrows its text, that text **must outlive the document** (and every view taken from it). Reading
or writing through a view after the underlying buffer is gone is undefined behavior, exactly as it would be for
a dangling `#!cpp std::string_view`.
If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
exactly as it would be for a dangling `#!cpp std::string_view`.
A view is valid only while all of the following hold:
@@ -77,7 +78,7 @@ A view is valid only while all of the following hold:
- the document has not been re-parsed since the view was taken (with [`read()`](../api/basic_json_document/read.md)
or [`parse()`](../api/basic_json_document/parse.md) into it),
- the document has not had [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and
- if the document borrows its source text, that text is still alive.
- if the document borrows its source text, that text is still alive and unmodified.
Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap
allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md)
@@ -163,10 +164,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
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`.
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.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`. [`count`](../api/basic_json_view/count.md) counts every member with the key, so it is `#!cpp 2` here.
[`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
+15
View File
@@ -113,6 +113,21 @@ inline const node* find_member(const document_data& d, const node* object, const
return nullptr;
}
/// the number of members of an object with this key (duplicates included)
inline std::size_t count_members(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
{
std::size_t result = 0;
const node* const end = document_data::child_end(object);
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
{
if (m->len == n && (n == 0 || std::memcmp(d.str(*m), key, n) == 0))
{
++result;
}
}
return result;
}
/// whether an integer type is accepted as an array index by the view's
/// operator[] and at(): every integer type but bool and size_t, which has its
/// own overload
+12 -12
View File
@@ -47,24 +47,24 @@ enum class index_status
template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept
{
// as basic_json: the empty token and "-" (past the last element) are
// unresolved rather than not a number
if (s.empty() || (s.size() == 1 && s[0] == '-'))
{
return index_status::unresolved;
}
if (s[0] < '0' || s[0] > '9')
{
return index_status::not_number;
}
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
// as basic_json: also a single character, but "-" and the empty token are
// left to the conversion below and reported as unresolved
if (!s.empty() && !(s.size() == 1 && s[0] == '-') && !(s[0] >= '0' && s[0] <= '9'))
{
return index_status::not_number;
}
if (s.empty())
{
return index_status::unresolved;
}
std::uint64_t v = 0;
for (std::size_t i = 0; i < s.size(); ++i)
for (const auto c : s)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
+22
View File
@@ -8,6 +8,7 @@
#pragma once
#include <cstddef> // nullptr_t, size_t
#include <cstdint> // int64_t
#include <map> // map
#include <string> // basic_string
@@ -103,6 +104,27 @@ Map map_value(const View& v)
template<typename Key>
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
/// whether View::get<T>() converts to T without materialize(); get_to()
/// assigns these, and passes the others to from_json
template<typename T, typename BasicJsonType, typename View>
struct is_direct_value : std::integral_constant < bool,
std::is_arithmetic<T>::value || std::is_same<T, std::nullptr_t>::value
|| std::is_same<T, typename View::string_view_t>::value
|| std::is_same<T, BasicJsonType>::value || std::is_same<T, View>::value >
{};
template<typename Traits, typename Alloc, typename BasicJsonType, typename View>
struct is_direct_value<std::basic_string<char, Traits, Alloc>, BasicJsonType, View> : std::true_type {};
template<typename U, typename A, typename BasicJsonType, typename View>
struct is_direct_value<std::vector<U, A>, BasicJsonType, View> : std::true_type {};
template<typename K, typename V, typename C, typename A, typename BasicJsonType, typename View>
struct is_direct_value<std::map<K, V, C, A>, BasicJsonType, View> : is_string_key<K> {};
template<typename K, typename V, typename H, typename E, typename A, typename BasicJsonType, typename View>
struct is_direct_value<std::unordered_map<K, V, H, E, A>, BasicJsonType, View> : is_string_key<K> {};
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+24 -7
View File
@@ -251,7 +251,7 @@ class basic_json_view
{
if (is_discarded())
{
return basic_json_view();
return {};
}
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
}
@@ -277,7 +277,7 @@ class basic_json_view
{
if (is_discarded())
{
return basic_json_view();
return {};
}
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
}
@@ -300,7 +300,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
{
return basic_json_view();
return {};
}
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
}
@@ -473,11 +473,11 @@ class basic_json_view
return !detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains).is_discarded();
}
/// 1 if this is an object with a member with this key, else 0 (duplicate
/// keys count once)
/// the number of members with this key (more than 1 if the key occurs
/// more than once); 0 if this is not an object
size_type count(string_view_t key) const
{
return contains(key) ? 1 : 0;
return is_object() ? detail::view::count_members(*m_doc, m_node, key.data(), key.size()) : 0;
}
size_type count(const char* key) const
@@ -544,10 +544,12 @@ class basic_json_view
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
}
/// as BasicJsonType::get_to(): the types get<T>() converts directly are
/// assigned, all others are passed to from_json with v as it is
template<typename T>
T& get_to(T& v) const
{
v = get<T>();
get_to_impl(v, detail::view::is_direct_value<T, BasicJsonType, basic_json_view> {});
return v;
}
@@ -691,6 +693,21 @@ class basic_json_view
return materialize().template get<T>();
}
// --- get_to() dispatch ---
template<typename T>
void get_to_impl(T& v, std::true_type /*direct*/) const
{
v = get<T>();
}
/// from_json may keep parts of v (members a JSON object lacks, say)
template<typename T>
void get_to_impl(T& v, std::false_type /*direct*/) const
{
materialize().get_to(v);
}
const document_data* m_doc = nullptr;
const node* m_node = nullptr;
};
+35 -2
View File
@@ -731,7 +731,12 @@ void check_access(const ordered_json_view& v, const ordered_json& j)
++members;
const std::string key(it.key().data(), it.key().size());
CHECK(v.contains(key));
CHECK(v.count(key) == 1);
std::size_t occurrences = 0;
for (auto other = v.begin(); other != v.end(); ++other)
{
occurrences += other.key() == it.key() ? 1 : 0;
}
CHECK(v.count(key) == occurrences);
// 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())
@@ -839,7 +844,8 @@ TEST_CASE("json_view element access and iteration")
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.count("a") == 2); // every member, unlike materialize()
CHECK(v.count("b") == 1);
CHECK(v.contains("a"));
CHECK(v.value("a", 0) == 1);
CHECK(v.materialize()["a"] == 3); // materialize() keeps the last value, unlike the lookups
@@ -1248,6 +1254,25 @@ void from_json(const json& j, record& r)
j.at("name").get_to(r.name);
j.at("count").get_to(r.count);
}
struct defaults
{
int a = 1;
int b = 2;
};
// sets only the members the JSON value has
void from_json(const json& j, defaults& d)
{
if (j.contains("a"))
{
j.at("a").get_to(d.a);
}
if (j.contains("b"))
{
j.at("b").get_to(d.b);
}
}
} // namespace
TEST_CASE("json_view values")
@@ -1369,6 +1394,14 @@ TEST_CASE("json_view values")
CHECK(name == "widget");
CHECK(count == 3);
// other types go to from_json with the existing value, as basic_json::get_to()
defaults def;
def.b = 5;
const json_document partial = json_document::parse(R"({"a":4})");
partial.root().get_to(def);
CHECK(def.a == 4);
CHECK(def.b == 5);
// a duplicate key: the last value, as parse()
const json_document dup = json_document::parse(R"({"a":1,"a":2})");
CHECK((dup.root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}}));