mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 11:40:30 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
711ddc461f | ||
|
|
f72fe8d2f7 | ||
|
|
5cbb9095ee | ||
|
|
ada8f17230 | ||
|
|
266c2249fe | ||
|
|
2e5419eebc | ||
|
|
7e577fb2d4 | ||
|
|
baf4ccfda8 | ||
|
|
76897a9b9b | ||
|
|
2b1a720fc0 |
+2
-2
@@ -56,8 +56,8 @@ labels:
|
||||
- "tools/amalgamate/config_json_view\\.json"
|
||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
||||
- "docs/mkdocs/docs/api/(ordered_)?json_(document|view)\\.md"
|
||||
- "docs/mkdocs/docs/examples/(basic_json_(document|view)__|(ordered_)?json_(document|view)).*"
|
||||
- "docs/mkdocs/docs/api/(ordered_)?json_(editable_)?(document|view)\\.md"
|
||||
- "docs/mkdocs/docs/examples/(basic_json_(document|view)__|(ordered_)?json_(editable_)?(document|view)).*"
|
||||
- "tests/benchmarks/src/benchmarks_view\\.cpp"
|
||||
|
||||
- label: "aspect: json_view"
|
||||
|
||||
@@ -69,6 +69,8 @@ cc_library(
|
||||
"include/nlohmann/detail/view/builder.hpp",
|
||||
"include/nlohmann/detail/view/compare.hpp",
|
||||
"include/nlohmann/detail/view/document_data.hpp",
|
||||
"include/nlohmann/detail/view/edit.hpp",
|
||||
"include/nlohmann/detail/view/edit_storage.hpp",
|
||||
"include/nlohmann/detail/view/errors.hpp",
|
||||
"include/nlohmann/detail/view/input.hpp",
|
||||
"include/nlohmann/detail/view/iterator.hpp",
|
||||
|
||||
@@ -137,8 +137,10 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::node_cou
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::owns_source', 'Method', 'api/basic_json_document/owns_source/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::parse', 'Function', 'api/basic_json_document/parse/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::parse_copy', 'Function', 'api/basic_json_document/parse_copy/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::push_back', 'Method', 'api/basic_json_document/push_back/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::read', 'Method', 'api/basic_json_document/read/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::root', 'Method', 'api/basic_json_document/root/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::set', 'Method', 'api/basic_json_document/set/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::shrink_to_fit', 'Method', 'api/basic_json_document/shrink_to_fit/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::source', 'Method', 'api/basic_json_document/source/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view', 'Class', 'api/basic_json_view/index.html');
|
||||
@@ -187,6 +189,8 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type_name',
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::value', 'Method', 'api/basic_json_view/value/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_document', 'Class', 'api/json_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_editable_document', 'Class', 'api/json_editable_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_editable_view', 'Class', 'api/json_editable_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_view', 'Class', 'api/json_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer', 'Class', 'api/json_pointer/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::back', 'Method', 'api/json_pointer/back/index.html');
|
||||
@@ -222,6 +226,8 @@ INSERT INTO searchIndex(name, type, path) VALUES ('operator<<', 'Operator', 'api
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('operator>>', 'Operator', 'api/operator_gtgt/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json', 'Class', 'api/ordered_json/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_document', 'Class', 'api/ordered_json_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_editable_document', 'Class', 'api/ordered_json_editable_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_editable_view', 'Class', 'api/ordered_json_editable_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_view', 'Class', 'api/ordered_json_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_map', 'Class', 'api/ordered_map/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('std::hash<basic_json>', 'Class', 'api/basic_json/std_hash/index.html');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_document;
|
||||
```
|
||||
|
||||
@@ -19,6 +19,11 @@ it (a copy, or an rvalue `#!cpp std::string` that was moved in); see [`owns_sour
|
||||
is move-only: copying a document would either duplicate a potentially large index and text, or leave two documents
|
||||
claiming to borrow the same buffer, so it is disabled.
|
||||
|
||||
With `#!cpp Editable == true`, the document also offers [`set`](set.md) and [`push_back`](push_back.md) to change
|
||||
values in place, see [Edits](#edits) below. The source text itself is never written; a read-only document
|
||||
(`#!cpp Editable == false`, the default) does not carry any of the bookkeeping edits need, and calling `set` or
|
||||
`push_back` on one fails to compile (`#!cpp static_assert`).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`BasicJsonType`
|
||||
@@ -26,14 +31,22 @@ claiming to borrow the same buffer, so it is disabled.
|
||||
[`ordered_json`](../ordered_json.md). Only 64-bit `number_integer_t`/`number_unsigned_t` types are supported; this
|
||||
is checked with a `static_assert`.
|
||||
|
||||
`Editable`
|
||||
: whether the document supports [`set`](set.md) and [`push_back`](push_back.md) (optional, `#!cpp false` by
|
||||
default). See [Edits](#edits) below.
|
||||
|
||||
## Specializations
|
||||
|
||||
- [**json_document**](../json_document.md) - documents of the default specialization [`json`](../json.md)
|
||||
- [**ordered_json_document**](../ordered_json_document.md) - documents of [`ordered_json`](../ordered_json.md)
|
||||
- [**json_document**](../json_document.md) - read-only documents of the default specialization [`json`](../json.md)
|
||||
- [**ordered_json_document**](../ordered_json_document.md) - read-only documents of
|
||||
[`ordered_json`](../ordered_json.md)
|
||||
- [**json_editable_document**](../json_editable_document.md) - editable documents of [`json`](../json.md)
|
||||
- [**ordered_json_editable_document**](../ordered_json_editable_document.md) - editable documents of
|
||||
[`ordered_json`](../ordered_json.md)
|
||||
|
||||
## Member types
|
||||
|
||||
- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view<BasicJsonType>`)
|
||||
- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view<BasicJsonType, Editable>`)
|
||||
- **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md)
|
||||
|
||||
## Member functions
|
||||
@@ -50,6 +63,37 @@ claiming to borrow the same buffer, so it is disabled.
|
||||
- [**node_count**](node_count.md) - the number of index entries (values plus object keys)
|
||||
- [**memory_usage**](memory_usage.md) - the number of bytes held by the document
|
||||
- [**shrink_to_fit**](shrink_to_fit.md) - release unused index capacity
|
||||
- [**set**](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers
|
||||
to (`#!cpp Editable` documents only)
|
||||
- [**push_back**](push_back.md) - append to an array (`#!cpp Editable` documents only)
|
||||
|
||||
## Edits
|
||||
|
||||
An editable document (`#!cpp Editable == true`) can be changed after parsing, with [`set`](set.md) and
|
||||
[`push_back`](push_back.md); [`json_editable_document`](../json_editable_document.md) and
|
||||
[`ordered_json_editable_document`](../ordered_json_editable_document.md) are the corresponding specializations. A few
|
||||
points apply to every edit:
|
||||
|
||||
- **The source text is never written**, and the parsed index never moves: every value keeps the node it was parsed
|
||||
into, so [views](../basic_json_view/index.md) taken before an edit stay valid, including
|
||||
[`root()`](root.md). New values (and the element sequences of an edited array/object) go to storage owned by the
|
||||
document, allocated on demand.
|
||||
- **A view keeps referring to the same value.** After [`set`](set.md) replaces the value a view refers to, that view
|
||||
sees the new value; a view of a value that a later edit replaces or drops keeps showing what it last held. An edit
|
||||
of an array or object, however, **invalidates the iterators taken over it** (its members may now live in a
|
||||
different sequence), and a string obtained with [`get_string()`](../basic_json_view/get_string.md) stays valid even
|
||||
as further edits happen (earlier buffers of edited text are kept alive, not overwritten).
|
||||
- **Values are accepted three ways:** a [`basic_json_view`](../basic_json_view/index.md) of *any* document
|
||||
(read-only or editable; it is copied, nothing is shared with the source document), a `BasicJsonType` value, or
|
||||
anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
||||
- [`dump()`](../basic_json_view/dump.md) writes an edited document with members in document order, new members at
|
||||
the end, and, with [`number_format::source`](../basic_json_view/number_format.md), keeps the spelling of every
|
||||
number that was not itself edited -- see [Editing a document](../../features/json_view.md#editing-a-document) for
|
||||
why this matters.
|
||||
- [`read()`](read.md) discards all edits, [`shrink_to_fit()`](shrink_to_fit.md) does not move the node index once
|
||||
there are edits, and [`memory_usage()`](memory_usage.md) includes the memory edits use.
|
||||
[`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is
|
||||
`#!cpp static_cast<std::size_t>(-1)`, the same value it reports for a decoded string.
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# <small>nlohmann::basic_json_document::</small>push_back
|
||||
|
||||
```cpp
|
||||
template<typename V>
|
||||
view_type push_back(view_type array, V&& value);
|
||||
```
|
||||
|
||||
Appends `value` as a new last element of `array`. A [null](../basic_json_view/is_null.md) `array` first becomes an
|
||||
empty array, the same way [`set`](set.md) turns a null `object` into an empty object.
|
||||
|
||||
`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document -- read-only or
|
||||
editable, and it does not have to be `array`'s own document -- which is copied so that nothing is shared with the
|
||||
source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers,
|
||||
strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
||||
|
||||
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md))
|
||||
has `push_back`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`V`
|
||||
: the type of `value`, deduced; see above for what is accepted.
|
||||
|
||||
## Parameters
|
||||
|
||||
`array` (in)
|
||||
: the array (or null value) to append to
|
||||
|
||||
`value` (in)
|
||||
: the value to append
|
||||
|
||||
## Return value
|
||||
|
||||
a view of the new last element of `array`, now holding `value`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
|
||||
before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
|
||||
invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
|
||||
for the encoding that is not reclaimed. A failure of a later allocation -- while `array` switches from its parsed
|
||||
layout to a growable block, or while that block grows, see [Notes](#notes) -- can still leave a partial effect, such
|
||||
as a null `array` argument already turned into an empty array even though `value` itself was not appended.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) if `array` is neither an array nor
|
||||
null -- the same message [`BasicJsonType::push_back`](../basic_json/push_back.md) throws for the same type. Throws
|
||||
[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
|
||||
document") if `array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a *different* document.
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
|
||||
[discarded](../basic_json_view/is_discarded.md) view or a [discarded](../basic_json/is_discarded.md) `BasicJsonType`
|
||||
value, and [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or contains) a
|
||||
binary value -- `BasicJsonType` can hold one, but a `json_document` cannot. Throws
|
||||
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `value` is (or contains) a string that is
|
||||
not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
|
||||
|
||||
## Complexity
|
||||
|
||||
Amortized constant, plus time linear in the size of `value` to encode it into the document's storage (constant for
|
||||
a scalar, linear in the number of nested values for an array or object): the elements of `array` move to a growable
|
||||
block of links the first time it is appended to (or [`set`](set.md) on), and that block itself grows -- doubling its
|
||||
capacity, so the cost of growing it amortizes to constant per element -- only once it runs out of room. See
|
||||
[Notes](#notes).
|
||||
|
||||
## Notes
|
||||
|
||||
Like [`set`](set.md) on a member or an element, `push_back` never moves an existing element itself -- only where
|
||||
`array`'s *links* to its elements live -- so a view of an existing element of `array` stays valid across a
|
||||
`push_back`, but any iterator already taken over `array` is invalidated, since it was walking the old layout. See
|
||||
[Edits](index.md#edits) for what stays valid across an edit in general.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below appends records to an array one at a time, as they might arrive from a stream of events,
|
||||
without ever building a `BasicJsonType` value for the array or for the records already in it, and shows that a
|
||||
view taken from an earlier `push_back` still refers to the same element once later ones have run.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__push_back.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__push_back.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to
|
||||
- [root](root.md) - the view of the root value
|
||||
- [`BasicJsonType::push_back`](../basic_json/push_back.md) - the corresponding function of `basic_json`
|
||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,192 @@
|
||||
# <small>nlohmann::basic_json_document::</small>set
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
template<typename V>
|
||||
view_type set(view_type target, V&& value);
|
||||
|
||||
// (2)
|
||||
template<typename V>
|
||||
view_type set(view_type object, string_view_t key, V&& value);
|
||||
|
||||
// (3)
|
||||
template<typename I, typename V>
|
||||
view_type set(view_type array, I idx, V&& value);
|
||||
|
||||
// (4)
|
||||
template<typename V>
|
||||
view_type set(const json_pointer& ptr, V&& value);
|
||||
```
|
||||
|
||||
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md))
|
||||
has `set`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
|
||||
|
||||
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 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 <
|
||||
array.size()`).
|
||||
4. Sets the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), to `value`. The *parent* of the
|
||||
target must already exist: an object member is set as in 2. (added if it does not exist yet), an array element is
|
||||
assigned as in 3., and a last reference token of `#!cpp "-"`, or equal to the size of the array, appends `value`
|
||||
instead, exactly as [`push_back`](push_back.md) would. An empty `ptr` sets [`root()`](root.md) itself, as in 1.
|
||||
|
||||
In every overload, `value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any*
|
||||
document -- read-only or editable, and it does not have to be `target`'s/`object`'s/`array`'s own document -- which
|
||||
is copied so that nothing is shared with the source document afterward; a `BasicJsonType` value; or anything
|
||||
`BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`V`
|
||||
: the type of `value`, deduced; see above for what is accepted.
|
||||
|
||||
`I`
|
||||
: an integral type other than `#!cpp bool`, deduced (overloads taking a `#!cpp bool` or a non-integral type for
|
||||
`idx` do not participate in overload resolution).
|
||||
|
||||
## Parameters
|
||||
|
||||
`target` (in)
|
||||
: the value to replace
|
||||
|
||||
`object` (in)
|
||||
: the object (or null value) whose member to set
|
||||
|
||||
`array` (in)
|
||||
: the array whose element to assign
|
||||
|
||||
`key` (in)
|
||||
: the key of the member to set
|
||||
|
||||
`idx` (in)
|
||||
: the index of the element to assign; a negative value throws (see [Exceptions](#exceptions))
|
||||
|
||||
`ptr` (in)
|
||||
: a JSON pointer to the value to set, relative to `root()`
|
||||
|
||||
`value` (in)
|
||||
: the new value
|
||||
|
||||
## Return value
|
||||
|
||||
1. a view of `target`, now holding `value`
|
||||
2. a view of the member `key` of `object`, now holding `value`
|
||||
3. a view of the element `idx` of `array`, now holding `value`
|
||||
4. a view of the value `ptr` refers to, now holding `value`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
|
||||
before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
|
||||
invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
|
||||
for the encoding that is not reclaimed. A failure of a later allocation -- while an edited array or object switches
|
||||
from its parsed layout to a growable block, see [Notes](#notes) -- can still leave a partial effect, such as a
|
||||
[null](../basic_json_view/is_null.md) `object`/`array` argument already turned into an empty object/array even
|
||||
though `value` itself was not linked in.
|
||||
|
||||
## Exceptions
|
||||
|
||||
1. Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
|
||||
[discarded](../basic_json_view/is_discarded.md) view, or a [discarded](../basic_json/is_discarded.md)
|
||||
`BasicJsonType` value (e.g. `#!cpp BasicJsonType(value_t::discarded)`) -- an object or array `value`, of either
|
||||
kind, is fine and is encoded as a whole subtree.
|
||||
2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if `object` is neither an object
|
||||
nor null -- the same message [`operator[]`](../basic_json_view/operator%5B%5D.md) throws for a string argument on
|
||||
such a value. Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `key` is not
|
||||
valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
|
||||
Also throws what 1. throws for `value`.
|
||||
3. Throws `type_error.305` if `array` is not an array -- the same message `operator[]` throws for a numeric argument
|
||||
on such a value. Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is
|
||||
negative, or if `#!cpp idx >= array.size()`. Also throws what 1. throws for `value`.
|
||||
4. Throws what [`at`](../basic_json_view/at.md) throws (overload 3) for resolving `ptr`'s parent, except that a
|
||||
missing object member or an array index equal to the array's size at the very last reference token is not an
|
||||
error there (it becomes a new member or an appended element) instead of
|
||||
[`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403)/[`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402).
|
||||
For the last reference token itself: if the parent is an object (or a primitive value, where it throws
|
||||
`type_error.305`), throws what 2. throws; if the parent is an array, throws what 3. throws for an index that is
|
||||
out of range, or, for a token that is not a valid array index,
|
||||
[`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) (a leading `#!cpp '0'`),
|
||||
[`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) (not a number),
|
||||
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) (too large for `size_type`), or
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token). Also throws what 1.
|
||||
throws for `value`.
|
||||
|
||||
Every overload also throws [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or
|
||||
contains) a binary value -- `BasicJsonType` can hold one, but a `json_document` cannot -- and
|
||||
[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
|
||||
document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a
|
||||
*different* document (overloads 1-3 only; overload 4 always starts from this document's own [`root()`](root.md)).
|
||||
|
||||
## Complexity
|
||||
|
||||
1. Linear in the size of `value` (encoding it into the document's storage): constant for a scalar, linear in the
|
||||
number of nested values for an array or object. If `target` is itself an array or object that spans more than one
|
||||
node in its parent's original, unedited layout, and `value` is a scalar, replacing it additionally costs time
|
||||
linear in the number of elements of that parent, the *first* time -- see [Notes](#notes).
|
||||
2. Linear in the number of members of `object`, to find an existing member with `key`, plus the complexity of 1. for
|
||||
`value`.
|
||||
3. Constant, plus the complexity of 1. for `value`.
|
||||
4. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level or the index into the array (as [`at`](../basic_json_view/at.md)), plus the complexity of 2. or 3. for
|
||||
the last token.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! info "Duplicate keys"
|
||||
|
||||
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
|
||||
[`push_back`](push_back.md) or `set` on the same container reuses that block, growing it (amortized constant time)
|
||||
only once it runs out of room. This never moves an element itself -- only where the container's *links* to its
|
||||
elements live -- so a view of an element stays valid, but any iterator already taken over the container is
|
||||
invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across an edit in
|
||||
general.
|
||||
|
||||
The same switch happens, for the same reason, when overload 1. replaces a multi-node array/object value with a
|
||||
scalar: the *parent's* element sequence is what has to switch to links, not `target` itself, because the parent
|
||||
originally stepped over `target`'s whole subtree by its node count, which no longer applies once `target` is a
|
||||
one-node scalar.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: (1)/(2)/(3)/(4) replace a value, set a member, assign an element, set via a JSON pointer"
|
||||
|
||||
The example below edits a small configuration document -- replacing a value, adding an object member, assigning
|
||||
an array element, and reaching a field through a JSON pointer -- and shows what
|
||||
[`dump()`](../basic_json_view/dump.md) preserves that is lost once the same edits are made on a `BasicJsonType`
|
||||
value instead: the order object members were written in, and the exact spelling of a number that was never
|
||||
touched.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__set.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__set.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [push_back](push_back.md) - append to an array
|
||||
- [root](root.md) - the view of the root value, the starting point of overload 4
|
||||
- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document, keeping an untouched number's
|
||||
spelling with `#!cpp number_format::source`
|
||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||
- [Editing a document](../../features/json_view.md#editing-a-document) - why editable documents keep the source
|
||||
text's order and number spelling
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -3,7 +3,7 @@
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_view;
|
||||
```
|
||||
|
||||
@@ -27,16 +27,30 @@ subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`
|
||||
[`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a
|
||||
`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided.
|
||||
|
||||
`basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an
|
||||
**editable** document (`#!cpp Editable == true`) sees every edit made through
|
||||
[`basic_json_document::set`](../basic_json_document/set.md) and
|
||||
[`basic_json_document::push_back`](../basic_json_document/push_back.md): once a value is changed, every view that
|
||||
still refers to it -- including ones taken before the change -- reads the new value. See
|
||||
[Edits](../basic_json_document/index.md#edits).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`BasicJsonType`
|
||||
: a specialization of [`basic_json`](../basic_json/index.md), matching the
|
||||
[`basic_json_document`](../basic_json_document/index.md) the view was taken from.
|
||||
|
||||
`Editable`
|
||||
: whether the view is of an editable document, matching the [`basic_json_document`](../basic_json_document/index.md)
|
||||
it was taken from (optional, `#!cpp false` by default). See [Edits](../basic_json_document/index.md#edits).
|
||||
|
||||
## Specializations
|
||||
|
||||
- [**json_view**](../json_view.md) - views of a [`json_document`](../json_document.md)
|
||||
- [**ordered_json_view**](../ordered_json_view.md) - views of an [`ordered_json_document`](../ordered_json_document.md)
|
||||
- [**json_editable_view**](../json_editable_view.md) - views of a [`json_editable_document`](../json_editable_document.md)
|
||||
- [**ordered_json_editable_view**](../ordered_json_editable_view.md) - views of an
|
||||
[`ordered_json_editable_document`](../ordered_json_editable_document.md)
|
||||
|
||||
## Member types
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# <small>nlohmann::</small>json_editable_document
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using json_editable_document = basic_json_document<json, true>;
|
||||
```
|
||||
|
||||
This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default
|
||||
[`json`](json.md) specialization: in addition to everything [`json_document`](json_document.md) offers,
|
||||
[`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) change values after
|
||||
parsing, without ever rewriting the source text -- see [Edits](basic_json_document/index.md#edits) and
|
||||
[Editing a document](../features/json_view.md#editing-a-document).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below patches two fields of a small configuration document -- changing one and adding another --
|
||||
and dumps it back out with the member order and the spelling of the untouched number preserved, something a
|
||||
plain [`json`](json.md) value cannot do.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [json_editable_view](json_editable_view.md) - a view of a value of a `json_editable_document`
|
||||
- [json_document](json_document.md) - the read-only document this type adds edits to
|
||||
- [ordered_json_editable_document](ordered_json_editable_document.md) - the corresponding editable document for
|
||||
`ordered_json`
|
||||
- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
|
||||
|
||||
## Version history
|
||||
|
||||
Since version 3.13.0.
|
||||
@@ -0,0 +1,41 @@
|
||||
# <small>nlohmann::</small>json_editable_view
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using json_editable_view = basic_json_view<json, true>;
|
||||
```
|
||||
|
||||
This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a
|
||||
[`json_editable_document`](json_editable_document.md). It offers the same read-only interface as
|
||||
[`json_view`](json_view.md); what is different is what it can be a view *of* -- a value that
|
||||
[`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) can change, with every view
|
||||
still referring to it seeing the change, see [Edits](basic_json_document/index.md#edits).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below is the same as [`json_editable_document`'s](json_editable_document.md): every view read back
|
||||
out of the document -- `#!cpp doc.root()` and the views nested under it -- sees the edits made through `set`.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [json_editable_document](json_editable_document.md) - the document type this view refers into
|
||||
- [json_view](json_view.md) - the corresponding read-only view
|
||||
- [ordered_json_editable_view](ordered_json_editable_view.md) - the corresponding view for
|
||||
`ordered_json_editable_document`
|
||||
|
||||
## Version history
|
||||
|
||||
Since version 3.13.0.
|
||||
@@ -0,0 +1,47 @@
|
||||
# <small>nlohmann::</small>ordered_json_editable_document
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using ordered_json_editable_document = basic_json_document<ordered_json, true>;
|
||||
```
|
||||
|
||||
This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the
|
||||
[`ordered_json`](ordered_json.md) specialization: [`set`](basic_json_document/set.md) and
|
||||
[`push_back`](basic_json_document/push_back.md) change values after parsing, as for
|
||||
[`json_editable_document`](json_editable_document.md), and
|
||||
[`materialize()`](basic_json_view/materialize.md) preserves the document order of object members -- including
|
||||
members [`set`](basic_json_document/set.md) added -- instead of sorting them like
|
||||
[`json_editable_document`](json_editable_document.md) does.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below edits a document with `set`, then shows that `materialize()` keeps the member order of the
|
||||
source text (with the new member at the end) for `ordered_json_editable_document`, where it would sort the
|
||||
members alphabetically for [`json_editable_document`](json_editable_document.md).
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/ordered_json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/ordered_json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [ordered_json_editable_view](ordered_json_editable_view.md) - a view of a value of an
|
||||
`ordered_json_editable_document`
|
||||
- [ordered_json_document](ordered_json_document.md) - the read-only document this type adds edits to
|
||||
- [json_editable_document](json_editable_document.md) - the corresponding editable document for the default `json`
|
||||
specialization
|
||||
- [Object Order](../features/object_order.md)
|
||||
- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
|
||||
|
||||
## Version history
|
||||
|
||||
Since version 3.13.0.
|
||||
@@ -0,0 +1,40 @@
|
||||
# <small>nlohmann::</small>ordered_json_editable_view
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using ordered_json_editable_view = basic_json_view<ordered_json, true>;
|
||||
```
|
||||
|
||||
This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an
|
||||
[`ordered_json_editable_document`](ordered_json_editable_document.md), the corresponding view for
|
||||
[`ordered_json_view`](ordered_json_view.md) the way [`json_editable_view`](json_editable_view.md) is for
|
||||
[`json_view`](json_view.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below is the same as [`ordered_json_editable_document`'s](ordered_json_editable_document.md): the
|
||||
views `set` returns see the document's member order preserved on `materialize()`, unlike for a
|
||||
[`json_editable_document`](json_editable_document.md).
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/ordered_json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/ordered_json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [ordered_json_editable_document](ordered_json_editable_document.md) - the document type this view refers into
|
||||
- [ordered_json_view](ordered_json_view.md) - the corresponding read-only view
|
||||
- [json_editable_view](json_editable_view.md) - the corresponding view for `json_editable_document`
|
||||
|
||||
## Version history
|
||||
|
||||
Since version 3.13.0.
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
|
||||
int main()
|
||||
{
|
||||
// "events" starts out null -- the first push_back() turns it into an
|
||||
// array, exactly like set() turns a null object member into an object
|
||||
json_editable_document doc = json_editable_document::parse(R"({"source": "sensor-1", "events": null})");
|
||||
|
||||
const auto first = doc.push_back(doc.root()["events"], json{{"type", "start"}, {"t", 0}});
|
||||
for (int t = 1; t <= 3; ++t)
|
||||
{
|
||||
doc.push_back(doc.root()["events"], json{{"type", "tick"}, {"t", t}});
|
||||
}
|
||||
|
||||
// push_back() never moves an existing element: a view taken from an
|
||||
// earlier call still refers to the same element after later ones
|
||||
std::cout << first.dump() << '\n';
|
||||
std::cout << doc.root()["events"].size() << '\n';
|
||||
std::cout << doc.root().dump(2) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{"t":0,"type":"start"}
|
||||
4
|
||||
{
|
||||
"source": "sensor-1",
|
||||
"events": [
|
||||
{
|
||||
"t": 0,
|
||||
"type": "start"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"type": "tick"
|
||||
},
|
||||
{
|
||||
"t": 2,
|
||||
"type": "tick"
|
||||
},
|
||||
{
|
||||
"t": 3,
|
||||
"type": "tick"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
using json_editable_view = nlohmann::json_editable_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a configuration file, as it might be read from disk -- "price" is
|
||||
// written with a trailing zero that has no effect on its value
|
||||
const std::string text = R"({
|
||||
"name": "cache",
|
||||
"host": "db1",
|
||||
"port": 6379,
|
||||
"price": 19.90,
|
||||
"replicas": ["db2", "db3"],
|
||||
"timeout": 30
|
||||
})";
|
||||
|
||||
json_editable_document doc = json_editable_document::parse(text);
|
||||
|
||||
doc.set(doc.root()["port"], 6380); // (1) replace a value
|
||||
doc.set(doc.root(), "region", "us-east"); // (2) add a member
|
||||
doc.set(doc.root()["replicas"], 0, "db4"); // (3) assign an element
|
||||
doc.set(json::json_pointer("/timeout"), 45); // (4) via a JSON pointer
|
||||
|
||||
// members stay in document order (the new one at the end), and a number
|
||||
// that was not itself edited keeps its exact spelling
|
||||
std::cout << doc.root().dump(2, ' ', false, json_editable_view::number_format::source) << "\n\n";
|
||||
|
||||
// the same edits on a plain json value: object_t is a std::map, so
|
||||
// parsing already sorted the keys, and dump() rewrites every number to
|
||||
// its shortest form, even "price", which was never touched
|
||||
json plain = json::parse(text);
|
||||
plain["port"] = 6380;
|
||||
plain["region"] = "us-east";
|
||||
plain["replicas"][0] = "db4";
|
||||
plain[json::json_pointer("/timeout")] = 45;
|
||||
std::cout << plain.dump(2) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "cache",
|
||||
"host": "db1",
|
||||
"port": 6380,
|
||||
"price": 19.90,
|
||||
"replicas": [
|
||||
"db4",
|
||||
"db3"
|
||||
],
|
||||
"timeout": 45,
|
||||
"region": "us-east"
|
||||
}
|
||||
|
||||
{
|
||||
"host": "db1",
|
||||
"name": "cache",
|
||||
"port": 6380,
|
||||
"price": 19.9,
|
||||
"region": "us-east",
|
||||
"replicas": [
|
||||
"db4",
|
||||
"db3"
|
||||
],
|
||||
"timeout": 45
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
using json_editable_view = nlohmann::json_editable_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a configuration file, as it might be read from disk
|
||||
const std::string text = R"({"name": "cache", "host": "db1", "port": 6379, "price": 19.90})";
|
||||
std::cout << text << "\n\n";
|
||||
|
||||
// patch two fields -- "price" is never touched
|
||||
json_editable_document doc = json_editable_document::parse(text);
|
||||
doc.set(doc.root(), "host", "db2");
|
||||
doc.set(doc.root(), "retries", 3);
|
||||
|
||||
// member order (the new member at the end) and the untouched number's
|
||||
// exact spelling survive
|
||||
std::cout << doc.root().dump(-1, ' ', false, json_editable_view::number_format::source) << '\n';
|
||||
|
||||
// the same patch on a plain json value: keys are sorted (object_t is a
|
||||
// std::map), and "price" is rewritten even though the patch never
|
||||
// touched it
|
||||
json plain = json::parse(text);
|
||||
plain["host"] = "db2";
|
||||
plain["retries"] = 3;
|
||||
std::cout << plain.dump() << '\n';
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"name": "cache", "host": "db1", "port": 6379, "price": 19.90}
|
||||
|
||||
{"name":"cache","host":"db2","port":6379,"price":19.90,"retries":3}
|
||||
{"host":"db2","name":"cache","port":6379,"price":19.9,"retries":3}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using ordered_json_editable_document = nlohmann::ordered_json_editable_document;
|
||||
|
||||
int main()
|
||||
{
|
||||
// ordered_json_editable_document is basic_json_document<nlohmann::ordered_json, true>
|
||||
ordered_json_editable_document doc = ordered_json_editable_document::parse(R"({"z": 1, "a": 2, "m": 3})");
|
||||
doc.set(doc.root(), "b", 4); // set() always appends a new member at the end
|
||||
|
||||
// materialize() preserves the document order (with "b" at the end),
|
||||
// instead of sorting the keys the way json_editable_document does
|
||||
std::cout << doc.root().materialize().dump() << '\n';
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"z":1,"a":2,"m":3,"b":4}
|
||||
@@ -13,6 +13,7 @@ C++ types, and finally serialize it again.
|
||||
[SAX interface](parsing/sax_interface.md), and [error handling](parsing/parse_exceptions.md).
|
||||
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
|
||||
strings and numbers stay in the input and are only decoded when needed.
|
||||
[Editable documents](json_view.md#editing-a-document) can also be modified.
|
||||
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
|
||||
|
||||
## Accessing and modifying values
|
||||
|
||||
@@ -187,14 +187,73 @@ a number to its parsed `#!cpp double`/`#!cpp int64_t` value.
|
||||
[`operator<<`](../api/basic_json_view/operator_ltlt.md) writes a view to a stream the way `basic_json`'s does, using
|
||||
the stream's `width`/`fill` for indentation.
|
||||
|
||||
## Editing a document
|
||||
|
||||
Everything above is read-only: a `json_document`/`json_view` lets you look at a parsed text without copying it, but
|
||||
not change it. [`basic_json_document<BasicJsonType, true>`](../api/basic_json_document/index.md) -- more conveniently
|
||||
spelled [`json_editable_document`](../api/json_editable_document.md) or
|
||||
[`ordered_json_editable_document`](../api/ordered_json_editable_document.md) -- also lets you
|
||||
[`set`](../api/basic_json_document/set.md) a value and [`push_back`](../api/basic_json_document/push_back.md) onto
|
||||
an array, still without ever building a `basic_json` tree for parts you do not touch.
|
||||
|
||||
`#!cpp Editable` defaults to `#!cpp false`, so `json_document`/`ordered_json_document` are unaffected -- they carry
|
||||
none of the bookkeeping edits need, and calling `set`/`push_back` on one is a compile error, not a runtime one.
|
||||
|
||||
### Why: editing without reformatting
|
||||
|
||||
The `#!cpp 19.90` price from [above](#writing-a-view-back) is exactly the kind of value that makes editing a `json`
|
||||
or `ordered_json` value in place lossy. Say you parse a configuration file, patch one field, and write it back:
|
||||
|
||||
- **`json`** re-sorts every key on the way in (`object_t` is a `#!cpp std::map`) and rewrites every number to its
|
||||
shortest round-trip form on the way out -- a one-field patch turns into a diff that reorders the whole file and
|
||||
rewrites `#!cpp 19.90` to `#!cpp 19.9`.
|
||||
- **`ordered_json`** keeps the key order, but still rewrites every number the same way: parsing has already reduced
|
||||
it to a `#!cpp double`/`#!cpp int64_t`, and there is no way back to how it was spelled in the source text.
|
||||
|
||||
An editable document keeps both. [`dump()`](../api/basic_json_view/dump.md) of an edited document writes members in
|
||||
document order -- a member [`set`](../api/basic_json_document/set.md) added goes at the end, exactly where it was
|
||||
inserted -- and [`number_format::source`](../api/basic_json_view/number_format.md) keeps the exact spelling of
|
||||
every number an edit did not itself touch; a number an edit *did* touch is written the way
|
||||
[`BasicJsonType::dump()`](../api/basic_json/dump.md) would write it, since there is no source spelling for a brand
|
||||
new value.
|
||||
|
||||
??? example "Example: patch a configuration, keeping member order and number spellings"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/json_editable_document.output"
|
||||
```
|
||||
|
||||
### What stays valid, and what an edit costs
|
||||
|
||||
The source text itself is **never written**, and the parsed index never moves -- a value keeps the node it was
|
||||
parsed into for as long as it is not itself replaced. So every [view](../api/basic_json_view/index.md) taken before
|
||||
an edit, including a previously obtained [`root()`](../api/basic_json_document/root.md), stays valid and, if it
|
||||
still refers to the edited value, sees the edit; a view of a value a later edit drops or replaces just keeps showing
|
||||
what it last held. New values go to storage the document allocates and owns on demand. The one thing an edit does
|
||||
invalidate is the **iterators** taken over an edited array or object: the first time one of its elements is set or
|
||||
appended to, its elements move from the parsed, fixed layout to a growable block of links so that
|
||||
[`push_back`](../api/basic_json_document/push_back.md) can later grow it in amortized constant time -- existing
|
||||
elements are not touched, but an iterator that was walking the old layout no longer matches. A string obtained with
|
||||
[`get_string()`](../api/basic_json_view/get_string.md) is unaffected either way and stays valid across further
|
||||
edits. See [`basic_json_document`'s Edits](../api/basic_json_document/index.md#edits) for the details, and
|
||||
[`set`'s Exception safety](../api/basic_json_document/set.md#exception-safety) for what an edit guarantees if it
|
||||
throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in
|
||||
the index is described in the [architecture overview](../home/architecture.md#node-index-of-json-views).
|
||||
|
||||
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
|
||||
|
||||
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) |
|
||||
|---|---|---|---|
|
||||
| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document |
|
||||
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only |
|
||||
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use |
|
||||
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives |
|
||||
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) | [`json_editable_document`](../api/json_editable_document.md) / [`json_editable_view`](../api/json_editable_view.md) |
|
||||
|---|---|---|---|---|
|
||||
| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document | same as `json_document`; edits go to storage the document owns |
|
||||
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../api/basic_json_document/set.md)/[`push_back`](../api/basic_json_document/push_back.md) edit in place; the source text is never rewritten |
|
||||
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../api/basic_json_view/number_format.md), the spelling of every untouched number |
|
||||
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back -- a configuration file, for instance -- where the rest of it should come back exactly as it was |
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -194,8 +194,8 @@ packet-beta
|
||||
|
||||
| Bytes | Field | Type | Contents |
|
||||
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes are (0: the source text, 1: the buffer of decoded strings, for strings with escapes); bit 2: the value of a boolean |
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below) |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bits 3 and 4: moved and new (see below) |
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their hash index (1-based), or 0; otherwise 0 |
|
||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||
@@ -244,6 +244,21 @@ flowchart LR
|
||||
All `flags` are 0. The integer's bytes 8-15 hold its value, 1; its `extra` says it has one digit. The float's `extra`
|
||||
says it has one integer and one fraction digit, and its `len` is that of the token `2.5`.
|
||||
|
||||
Editable documents ([`json_editable_document`](../api/json_editable_document.md),
|
||||
[`detail/view/edit.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/edit.hpp) and
|
||||
[`detail/view/edit_storage.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/edit_storage.hpp))
|
||||
never write the source text and never move or resize the parsed index, so views stay valid while the document is
|
||||
edited:
|
||||
|
||||
- A new scalar is written over its node. Its text (a string, or the token of a number as `dump()` writes it) goes to
|
||||
the edit buffer, which `flags` bits 0-1 then name.
|
||||
- An array or object whose elements change gets the flag *moved* (bit 3): its elements then live in a separate
|
||||
sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes
|
||||
8-15 hold the address of the value's node, so values never move.
|
||||
- A node written by an edit gets the flag *new* (bit 4): it has no position in the source text.
|
||||
- Views of read-only documents compile without any of this: how views walk the index is a template parameter
|
||||
(`navigation<Editable>`).
|
||||
|
||||
## Input adapters
|
||||
|
||||
Input is read via **input adapters** that abstract a source. Every input adapter provides this interface:
|
||||
|
||||
@@ -782,6 +782,24 @@ The dynamic type of the object cannot be represented in the requested serializat
|
||||
|
||||
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
|
||||
|
||||
### json.exception.type_error.319
|
||||
|
||||
[`basic_json_document::set`](../api/basic_json_document/set.md) and
|
||||
[`basic_json_document::push_back`](../api/basic_json_document/push_back.md) can store any `basic_json` value except
|
||||
a binary one: a `json_document` has no representation for [binary values](../features/binary_values.md), which only
|
||||
ever arise from parsing a binary format or from an explicit [`json::binary`](../api/basic_json/binary.md) value, not
|
||||
from JSON text.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
[json.exception.type_error.319] cannot store a binary value in a json_document
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0, together with editable [`json_document`s](../features/json_view.md).
|
||||
|
||||
## Out of range
|
||||
|
||||
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
||||
@@ -1006,13 +1024,19 @@ MessagePack's ext type and BSON's binary subtype are each stored in a single byt
|
||||
|
||||
[`basic_json_document::parse()`](../api/basic_json_document/parse.md) and the other parsing functions of
|
||||
[`basic_json_document`](../api/basic_json_document/index.md) index a value's position in the source text in 32 bits,
|
||||
so they do not support an input of 4 GiB or more.
|
||||
so they do not support an input of 4 GiB or more. The same 32-bit limit applies to an **editable** document's own
|
||||
storage: [`set`](../api/basic_json_document/set.md) and [`push_back`](../api/basic_json_document/push_back.md) throw
|
||||
this exception once the strings and number tokens written by edits reach 4 GiB in total, or once more than
|
||||
4294967295 arrays/objects have had an element set or appended to them.
|
||||
|
||||
!!! failure "Example message"
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.out_of_range.416] input of 4 GiB or more is not supported by json_document
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.416] edits of 4 GiB or more are not supported by json_document
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
|
||||
@@ -239,8 +239,10 @@ nav:
|
||||
- 'owns_source': api/basic_json_document/owns_source.md
|
||||
- 'parse': api/basic_json_document/parse.md
|
||||
- 'parse_copy': api/basic_json_document/parse_copy.md
|
||||
- 'push_back': api/basic_json_document/push_back.md
|
||||
- 'read': api/basic_json_document/read.md
|
||||
- 'root': api/basic_json_document/root.md
|
||||
- 'set': api/basic_json_document/set.md
|
||||
- 'shrink_to_fit': api/basic_json_document/shrink_to_fit.md
|
||||
- 'source': api/basic_json_document/source.md
|
||||
- basic_json_view:
|
||||
@@ -301,6 +303,8 @@ nav:
|
||||
- 'to_json': api/adl_serializer/to_json.md
|
||||
- 'json': api/json.md
|
||||
- 'json_document': api/json_document.md
|
||||
- 'json_editable_document': api/json_editable_document.md
|
||||
- 'json_editable_view': api/json_editable_view.md
|
||||
- json_pointer:
|
||||
- 'Overview': api/json_pointer/index.md
|
||||
- '(Constructor)': api/json_pointer/json_pointer.md
|
||||
@@ -341,6 +345,8 @@ nav:
|
||||
- 'operator""_json_pointer': api/operator_literal_json_pointer.md
|
||||
- 'ordered_json': api/ordered_json.md
|
||||
- 'ordered_json_document': api/ordered_json_document.md
|
||||
- 'ordered_json_editable_document': api/ordered_json_editable_document.md
|
||||
- 'ordered_json_editable_view': api/ordered_json_editable_view.md
|
||||
- 'ordered_json_view': api/ordered_json_view.md
|
||||
- 'ordered_map': api/ordered_map.md
|
||||
- macros:
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <functional> // less
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <new> // operator new, placement new
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
@@ -50,9 +53,30 @@ struct document_data
|
||||
std::vector<object_index> indexes{}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::uint32_t> index_slots{}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::uint32_t> large_objects{}; ///< positions of the objects to index (noted while parsing) // NOLINT(readability-redundant-member-init)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena (indexed by flags & node_flags::storage)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage)
|
||||
bool discarded = true;
|
||||
|
||||
/// The storage of edits (editable documents only; see edit_storage.hpp).
|
||||
/// Edits never move or resize the parsed index, so views stay valid: an
|
||||
/// array/object whose elements change gets node_flags::moved, and its
|
||||
/// elements then live in a separate sequence (a header node, then the
|
||||
/// entries), whose entries link to the values.
|
||||
struct edit_state
|
||||
{
|
||||
std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::size_t> moved_cap{}; ///< capacity in nodes of a growable block; 0: a fixed sequence (a new value) // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::unique_ptr<node[]>> chunks{}; ///< storage of new values and blocks; never moved // NOLINT(readability-redundant-member-init,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
std::map<const node*, node*, std::less<const node*>> regions{}; ///< new arrays/objects: root -> container that uses it as its element sequence (nullptr: linked from a block) // NOLINT(readability-redundant-member-init)
|
||||
node* chunk_cur = nullptr;
|
||||
node* chunk_end = nullptr;
|
||||
std::size_t chunk_next = 64;
|
||||
std::vector<std::unique_ptr<char[]>> texts{}; ///< edit arena, the current buffer last; earlier ones stay alive for string views // NOLINT(readability-redundant-member-init,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
std::size_t text_used = 0;
|
||||
std::size_t text_cap = 0;
|
||||
std::size_t bytes = 0; ///< memory held by edits
|
||||
};
|
||||
std::unique_ptr<edit_state> edits{}; ///< created by the first edit // NOLINT(readability-redundant-member-init)
|
||||
|
||||
/// one allocation for the header and room for `nodes` nodes; large
|
||||
/// documents get a separate node array instead (so it can be trimmed)
|
||||
static document_data* create(std::size_t nodes)
|
||||
@@ -136,6 +160,78 @@ struct document_data
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
|
||||
/// (editable documents) first element or key, also of a moved container
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* first_child_edited(const node* n) const noexcept
|
||||
{
|
||||
return NLOHMANN_VIEW_LIKELY((n->flags & node_flags::moved) == 0) ? n + 1 : edits->moved[n->off] + 1;
|
||||
}
|
||||
|
||||
/// (editable documents) end of the elements, also of a moved container
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* child_end_edited(const node* n) const noexcept
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY((n->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
const node* const h = edits->moved[n->off];
|
||||
return h + h->next;
|
||||
}
|
||||
|
||||
/// (editable documents) the value at an element position: entries of
|
||||
/// moved sequences are links. The link case is out of line, so that this
|
||||
/// compiles to a predicted branch rather than a select that delays the
|
||||
/// following loads.
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept
|
||||
{
|
||||
return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept
|
||||
{
|
||||
return link_target(*n);
|
||||
}
|
||||
};
|
||||
|
||||
/// How the index is walked: views of read-only documents follow the node
|
||||
/// array alone and compile without any of the edit handling; views of
|
||||
/// editable documents also follow moved element sequences and links.
|
||||
template<bool Editable>
|
||||
struct navigation
|
||||
{
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first(const document_data& /*d*/, const node* n) noexcept
|
||||
{
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* end(const document_data& /*d*/, const node* n) noexcept
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* value(const node* n) noexcept
|
||||
{
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct navigation<true>
|
||||
{
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.first_child_edited(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* end(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.child_end_edited(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* value(const node* n) noexcept
|
||||
{
|
||||
return document_data::deref(n);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
|
||||
@@ -0,0 +1,711 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cmath> // isinf, isnan
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memmove
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed
|
||||
#include <utility> // forward
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/edit_storage.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/lookup.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
class basic_json_view;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// the index of the first true condition (the number of conditions if none is)
|
||||
template<bool... Conditions>
|
||||
struct first_true : std::integral_constant<int, 0> {};
|
||||
|
||||
template<bool... Conditions>
|
||||
struct first_true<false, Conditions...> : std::integral_constant < int, 1 + first_true<Conditions...>::value > {};
|
||||
|
||||
/// Checks a string the way basic_json's serializer does when it writes it
|
||||
/// (type_error.316 with the same message), so that an editable document
|
||||
/// only holds valid UTF-8: the error is at the first byte that no
|
||||
/// well-formed sequence can continue with (Unicode, Table 3-7).
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
{
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const auto hex = [](unsigned char c)
|
||||
{
|
||||
constexpr const char* digits = "0123456789ABCDEF";
|
||||
return std::string{digits[c >> 4u], digits[c & 0xFu]};
|
||||
};
|
||||
for (std::size_t i = 0; i < n;)
|
||||
{
|
||||
const unsigned char c = p[i];
|
||||
if (c < 0x80)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
std::size_t len = 0;
|
||||
unsigned char lo = 0x80;
|
||||
unsigned char hi = 0xBF;
|
||||
if (c >= 0xC2 && c <= 0xDF)
|
||||
{
|
||||
len = 2;
|
||||
}
|
||||
else if (c >= 0xE0 && c <= 0xEF)
|
||||
{
|
||||
len = 3;
|
||||
lo = c == 0xE0 ? 0xA0 : 0x80;
|
||||
hi = c == 0xED ? 0x9F : 0xBF;
|
||||
}
|
||||
else if (c >= 0xF0 && c <= 0xF4)
|
||||
{
|
||||
len = 4;
|
||||
lo = c == 0xF0 ? 0x90 : 0x80;
|
||||
hi = c == 0xF4 ? 0x8F : 0xBF;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
|
||||
}
|
||||
for (std::size_t k = 1; k < len; ++k)
|
||||
{
|
||||
if (i + k == n)
|
||||
{
|
||||
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
|
||||
}
|
||||
const unsigned char b = p[i + k];
|
||||
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
|
||||
}
|
||||
}
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the edits of an editable basic_json_document
|
||||
|
||||
Values are accepted as views (of any document), BasicJsonType values, and
|
||||
everything BasicJsonType can be constructed from. The source text is never
|
||||
written: new values go to storage owned by the document (see
|
||||
edit_storage.hpp).
|
||||
*/
|
||||
template<typename BasicJsonType, typename View>
|
||||
class editor
|
||||
{
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using string_view_t = typename View::string_view_t;
|
||||
using nav = navigation<true>;
|
||||
|
||||
public:
|
||||
explicit editor(document_data& d) noexcept
|
||||
: m_doc(d)
|
||||
{}
|
||||
|
||||
/// replace a value; returns its view
|
||||
template<typename V>
|
||||
View set(const View& target, V&& value)
|
||||
{
|
||||
node* const slot = own(target);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
assign(slot, e, nullptr, false);
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// set a member (appended if missing; a null value becomes an object);
|
||||
/// returns a view of the member value
|
||||
template<typename V>
|
||||
View set(const View& object, string_view_t key, V&& value)
|
||||
{
|
||||
node* const o = own(object);
|
||||
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
}
|
||||
check_utf8(key.data(), key.size());
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (o->kind == static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
become_empty(o, value_t::object);
|
||||
}
|
||||
// an existing member: assign it (and drop later duplicates, so that
|
||||
// lookups, iteration, and materialize() agree)
|
||||
node* slot = nullptr;
|
||||
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
|
||||
}
|
||||
}
|
||||
if (slot != nullptr)
|
||||
{
|
||||
if (duplicates)
|
||||
{
|
||||
erase_members(o, key, true);
|
||||
}
|
||||
assign(slot, e, o, true);
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
const node k = string_node(key.data(), key.size());
|
||||
slot = new_slot(e);
|
||||
node* const h = block_of(m_doc, o, 2);
|
||||
h[h->next] = k;
|
||||
make_link(h[h->next + 1], slot);
|
||||
h->next += 2;
|
||||
++h->len;
|
||||
++o->len;
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// assign an existing array element; returns a view of it
|
||||
template<typename V>
|
||||
View set(const View& array, std::size_t idx, V&& value)
|
||||
{
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
||||
assign(slot, e, a, true);
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// append to an array (a null value becomes an array); returns a view of
|
||||
/// the new element
|
||||
template<typename V>
|
||||
View push_back(const View& array, V&& value)
|
||||
{
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(308, "cannot use push_back() with ", array.type_name());
|
||||
}
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
become_empty(a, value_t::array);
|
||||
}
|
||||
node* const slot = new_slot(e);
|
||||
node* const h = block_of(m_doc, a, 1);
|
||||
make_link(h[h->next], slot);
|
||||
++h->next;
|
||||
++h->len;
|
||||
++a->len;
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
private:
|
||||
/// an encoded value: a scalar node, or the root of a new array/object
|
||||
struct encoded
|
||||
{
|
||||
node scalar{};
|
||||
node* region = nullptr;
|
||||
};
|
||||
|
||||
/// the node of a view of this document
|
||||
node* own(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
||||
{
|
||||
throw_invalid_iterator(202, "view does not belong to this document");
|
||||
}
|
||||
edit_state_of(m_doc);
|
||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||
}
|
||||
|
||||
static void check_index(std::size_t idx, std::size_t limit)
|
||||
{
|
||||
if (idx >= limit)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
}
|
||||
|
||||
bool key_equals(const node& k, string_view_t key) const noexcept
|
||||
{
|
||||
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 (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* w = h + 1;
|
||||
std::size_t erased = 0;
|
||||
bool kept = false;
|
||||
for (node* r = h + 1, *end = h + h->next; r != end; r += 2)
|
||||
{
|
||||
const bool match = key_equals(*r, key);
|
||||
if (match && (kept || !keep_first))
|
||||
{
|
||||
++erased;
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
h->len -= static_cast<std::uint32_t>(erased);
|
||||
o->len -= static_cast<std::uint32_t>(erased);
|
||||
return erased;
|
||||
}
|
||||
|
||||
/// turn a null into an empty array/object in place
|
||||
static void become_empty(node* n, value_t k) noexcept
|
||||
{
|
||||
*n = node{};
|
||||
n->kind = static_cast<std::uint8_t>(k);
|
||||
n->flags = node_flags::is_new;
|
||||
n->next = 1;
|
||||
}
|
||||
|
||||
/// Replace the value at slot; `parent` is the container whose elements
|
||||
/// include slot (if known).
|
||||
void assign(node* slot, const encoded& e, node* parent, bool parent_known)
|
||||
{
|
||||
if (e.region == nullptr)
|
||||
{
|
||||
if (is_container(*slot) && slot->next > 1 && slot != m_doc.tape)
|
||||
{
|
||||
// The slot spans its old elements in the enclosing sequence, but
|
||||
// a scalar is one node: the enclosing container first switches to
|
||||
// links (then the extent of the slot no longer matters).
|
||||
node* const p = parent_known ? parent : find_parent(m_doc, slot);
|
||||
if (p != nullptr && ((p->flags & node_flags::moved) == 0 || moved_capacity(m_doc, p) == 0))
|
||||
{
|
||||
block_of(m_doc, p, 0);
|
||||
}
|
||||
}
|
||||
*slot = e.scalar;
|
||||
return;
|
||||
}
|
||||
// an array/object: the slot keeps its extent (so that the enclosing
|
||||
// sequence still steps over it), and the elements come from the new
|
||||
// sequence
|
||||
const node* const r = e.region;
|
||||
const std::uint32_t extent = is_container(*slot) ? slot->next : 1;
|
||||
const bool was_moved = (slot->flags & node_flags::moved) != 0;
|
||||
slot->kind = r->kind;
|
||||
slot->extra = 0;
|
||||
slot->len = r->len;
|
||||
slot->next = extent;
|
||||
slot->flags = was_moved ? static_cast<std::uint8_t>(node_flags::moved | node_flags::is_new) : std::uint8_t{0};
|
||||
set_moved(m_doc, slot, e.region, 0);
|
||||
edit_state_of(m_doc).regions[e.region] = slot;
|
||||
}
|
||||
|
||||
/// a node for a new element (links point to it; it never moves)
|
||||
node* new_slot(const encoded& e)
|
||||
{
|
||||
if (e.region != nullptr)
|
||||
{
|
||||
return e.region;
|
||||
}
|
||||
node* const s = alloc_nodes(m_doc, 1);
|
||||
*s = e.scalar;
|
||||
return s;
|
||||
}
|
||||
|
||||
//////////////
|
||||
// encoding //
|
||||
//////////////
|
||||
|
||||
template<int N>
|
||||
using encode_tag = std::integral_constant<int, N>;
|
||||
|
||||
template<typename T>
|
||||
struct is_view : std::false_type {};
|
||||
|
||||
template<typename J, bool E>
|
||||
struct is_view<basic_json_view<J, E>> : std::true_type {};
|
||||
|
||||
template<typename V>
|
||||
encoded encode(V&& v)
|
||||
{
|
||||
using D = typename std::decay<V>::type;
|
||||
return encode_impl(std::forward<V>(v), encode_tag<first_true<is_view<D>::value,
|
||||
std::is_same<D, BasicJsonType>::value,
|
||||
std::is_same<D, std::nullptr_t>::value,
|
||||
std::is_same<D, bool>::value,
|
||||
std::is_arithmetic<D>::value,
|
||||
std::is_convertible<const D&, string_view_t>::value>::value> {});
|
||||
}
|
||||
|
||||
/// a view of any document (copied; nothing is shared with it)
|
||||
template<typename J, bool E>
|
||||
encoded encode_impl(const basic_json_view<J, E>& v, encode_tag<0> /*view*/)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
||||
{
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
encoded r;
|
||||
if (!is_container(*v.m_node))
|
||||
{
|
||||
r.scalar = copy_scalar(*v.m_doc, *v.m_node);
|
||||
return r;
|
||||
}
|
||||
r.region = alloc_nodes(m_doc, count_nodes<E>(*v.m_doc, v.m_node));
|
||||
fill_nodes<E>(*v.m_doc, v.m_node, r.region);
|
||||
edit_state_of(m_doc).regions.emplace(r.region, nullptr);
|
||||
return r;
|
||||
}
|
||||
|
||||
encoded encode_impl(const BasicJsonType& j, encode_tag<1> /*json*/)
|
||||
{
|
||||
encoded r;
|
||||
if (!j.is_structured())
|
||||
{
|
||||
r.scalar = json_scalar(j);
|
||||
return r;
|
||||
}
|
||||
r.region = alloc_nodes(m_doc, count_nodes(j));
|
||||
fill_nodes(j, r.region);
|
||||
edit_state_of(m_doc).regions.emplace(r.region, nullptr);
|
||||
return r;
|
||||
}
|
||||
|
||||
encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/)
|
||||
{
|
||||
encoded r;
|
||||
r.scalar = plain_node(value_t::null);
|
||||
return r;
|
||||
}
|
||||
|
||||
encoded encode_impl(bool b, encode_tag<3> /*boolean*/)
|
||||
{
|
||||
encoded r;
|
||||
r.scalar = plain_node(value_t::boolean);
|
||||
r.scalar.flags = static_cast<std::uint8_t>(r.scalar.flags | (b ? node_flags::is_true : 0));
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
encoded encode_impl(T x, encode_tag<4> /*number*/)
|
||||
{
|
||||
encoded r;
|
||||
r.scalar = number_node(x, std::integral_constant<int, first_true<std::is_floating_point<T>::value, std::is_signed<T>::value>::value> {});
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
encoded encode_impl(const T& s, encode_tag<5> /*string*/)
|
||||
{
|
||||
const string_view_t sv(s);
|
||||
check_utf8(sv.data(), sv.size());
|
||||
encoded r;
|
||||
r.scalar = string_node(sv.data(), sv.size());
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
encoded encode_impl(T&& x, encode_tag<6> /*other*/)
|
||||
{
|
||||
return encode_impl(BasicJsonType(std::forward<T>(x)), encode_tag<1> {});
|
||||
}
|
||||
|
||||
static node plain_node(value_t k) noexcept
|
||||
{
|
||||
node n{};
|
||||
n.kind = static_cast<std::uint8_t>(k);
|
||||
n.flags = node_flags::is_new;
|
||||
return n;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
node number_node(T x, std::integral_constant<int, 0> /*floating-point*/)
|
||||
{
|
||||
return float_node(static_cast<number_float_t>(x));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
node number_node(T x, std::integral_constant<int, 1> /*signed*/)
|
||||
{
|
||||
return integer_node(static_cast<std::uint64_t>(static_cast<std::int64_t>(x)), value_t::number_integer);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
node number_node(T x, std::integral_constant<int, 2> /*unsigned*/)
|
||||
{
|
||||
return integer_node(static_cast<std::uint64_t>(x), value_t::number_unsigned);
|
||||
}
|
||||
|
||||
/// an integer with its canonical token in the edit arena
|
||||
node integer_node(std::uint64_t bits, value_t k)
|
||||
{
|
||||
const bool negative = k == value_t::number_integer && static_cast<std::int64_t>(bits) < 0;
|
||||
std::uint64_t magnitude = negative ? 0 - bits : bits;
|
||||
std::array<char, 24> buf{};
|
||||
char* p = buf.data() + buf.size();
|
||||
do
|
||||
{
|
||||
*--p = static_cast<char>('0' + (magnitude % 10));
|
||||
magnitude /= 10;
|
||||
}
|
||||
while (magnitude != 0);
|
||||
if (negative)
|
||||
{
|
||||
*--p = '-';
|
||||
}
|
||||
const auto len = static_cast<std::size_t>(buf.data() + buf.size() - p);
|
||||
node n = plain_node(k);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
n.off = append_text(m_doc, p, len);
|
||||
// number_length() adds one for the sign of number_integer nodes
|
||||
n.extra = static_cast<std::uint16_t>(k == value_t::number_integer ? len - 1 : len);
|
||||
set_integer_bits(n, bits);
|
||||
return n;
|
||||
}
|
||||
|
||||
/// a float with its shortest round-trip token (as basic_json::dump()
|
||||
/// writes it), or nan, inf, -inf, in the edit arena
|
||||
node float_node(number_float_t x)
|
||||
{
|
||||
string_t text;
|
||||
if (std::isnan(x))
|
||||
{
|
||||
text = "nan";
|
||||
}
|
||||
else if (std::isinf(x))
|
||||
{
|
||||
text = x > 0 ? "inf" : "-inf";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = BasicJsonType(x).dump();
|
||||
}
|
||||
node n = plain_node(value_t::number_float);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
n.extra = 0xFFFFu; // (the digit layout is not recorded)
|
||||
n.off = append_text(m_doc, text.data(), text.size());
|
||||
n.len = static_cast<std::uint32_t>(text.size());
|
||||
return n;
|
||||
}
|
||||
|
||||
/// a string (or key) in the edit arena
|
||||
node string_node(const char* s, std::size_t len)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
||||
{
|
||||
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
node n = plain_node(value_t::string);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
n.off = append_text(m_doc, s, len);
|
||||
n.len = static_cast<std::uint32_t>(len);
|
||||
return n;
|
||||
}
|
||||
|
||||
/// a scalar of a view (of any document) as a node of this document
|
||||
node copy_scalar(const document_data& from, const node& n)
|
||||
{
|
||||
if (&from == &m_doc)
|
||||
{
|
||||
return n; // the same storage
|
||||
}
|
||||
switch (static_cast<value_t>(n.kind))
|
||||
{
|
||||
case value_t::string:
|
||||
return string_node(from.str(n), n.len);
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
return integer_node(integer_bits(n), static_cast<value_t>(n.kind));
|
||||
case value_t::number_float:
|
||||
{
|
||||
node r = plain_node(value_t::number_float);
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | node_flags::edited);
|
||||
r.off = append_text(m_doc, from.str(n), n.len);
|
||||
r.len = n.len;
|
||||
r.extra = n.extra;
|
||||
return r;
|
||||
}
|
||||
case value_t::boolean:
|
||||
{
|
||||
node r = plain_node(value_t::boolean);
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (n.flags & node_flags::is_true));
|
||||
return r;
|
||||
}
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return plain_node(value_t::null);
|
||||
}
|
||||
}
|
||||
|
||||
node json_scalar(const BasicJsonType& j)
|
||||
{
|
||||
switch (j.type())
|
||||
{
|
||||
case value_t::null:
|
||||
return plain_node(value_t::null);
|
||||
case value_t::boolean:
|
||||
{
|
||||
node r = plain_node(value_t::boolean);
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (j.template get<bool>() ? node_flags::is_true : 0));
|
||||
return r;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
return integer_node(static_cast<std::uint64_t>(static_cast<std::int64_t>(j.template get<number_integer_t>())), value_t::number_integer);
|
||||
case value_t::number_unsigned:
|
||||
return integer_node(static_cast<std::uint64_t>(j.template get<number_unsigned_t>()), value_t::number_unsigned);
|
||||
case value_t::number_float:
|
||||
return float_node(j.template get<number_float_t>());
|
||||
case value_t::string:
|
||||
{
|
||||
const auto& s = j.template get_ref<const string_t&>();
|
||||
check_utf8(s.data(), s.size());
|
||||
return string_node(s.data(), s.size());
|
||||
}
|
||||
case value_t::binary:
|
||||
throw_type_error(319, "cannot store a binary value in a json_document", "");
|
||||
case value_t::discarded:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
default:
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
}
|
||||
|
||||
/// number of nodes of a subtree (containers, keys, scalars)
|
||||
template<bool E>
|
||||
static std::size_t count_nodes(const document_data& d, const node* n)
|
||||
{
|
||||
if (!is_container(*n))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
std::size_t r = 1;
|
||||
for (const node* c = navigation<E>::first(d, n), *end = navigation<E>::end(d, n); c != end;)
|
||||
{
|
||||
const node* const v = object ? c + 1 : c;
|
||||
r += (object ? 1 : 0) + count_nodes<E>(d, navigation<E>::value(v));
|
||||
c = document_data::after(v);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// copy a subtree (of any document) as a contiguous sequence; returns its end
|
||||
template<bool E>
|
||||
node* fill_nodes(const document_data& d, const node* n, node* out)
|
||||
{
|
||||
if (!is_container(*n))
|
||||
{
|
||||
*out = copy_scalar(d, *n);
|
||||
return out + 1;
|
||||
}
|
||||
node* const self = out++;
|
||||
*self = plain_node(static_cast<value_t>(n->kind));
|
||||
self->len = n->len;
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
for (const node* c = navigation<E>::first(d, n), *end = navigation<E>::end(d, n); c != end;)
|
||||
{
|
||||
if (object)
|
||||
{
|
||||
*out++ = copy_scalar(d, *c);
|
||||
++c;
|
||||
}
|
||||
out = fill_nodes<E>(d, navigation<E>::value(c), out);
|
||||
c = document_data::after(c);
|
||||
}
|
||||
self->next = static_cast<std::uint32_t>(out - self);
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::size_t count_nodes(const BasicJsonType& j)
|
||||
{
|
||||
std::size_t r = 1;
|
||||
if (j.is_object())
|
||||
{
|
||||
for (const auto& member : j.items())
|
||||
{
|
||||
r += 1 + count_nodes(member.value());
|
||||
}
|
||||
}
|
||||
else if (j.is_array())
|
||||
{
|
||||
for (const auto& e : j)
|
||||
{
|
||||
r += count_nodes(e);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
node* fill_nodes(const BasicJsonType& j, node* out)
|
||||
{
|
||||
if (!j.is_structured())
|
||||
{
|
||||
*out = json_scalar(j);
|
||||
return out + 1;
|
||||
}
|
||||
node* const self = out++;
|
||||
*self = plain_node(j.type());
|
||||
self->len = static_cast<std::uint32_t>(j.size());
|
||||
if (j.is_object())
|
||||
{
|
||||
for (const auto& member : j.items())
|
||||
{
|
||||
check_utf8(member.key().data(), member.key().size());
|
||||
*out++ = string_node(member.key().data(), member.key().size());
|
||||
out = fill_nodes(member.value(), out);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& e : j)
|
||||
{
|
||||
out = fill_nodes(e, out);
|
||||
}
|
||||
}
|
||||
self->next = static_cast<std::uint32_t>(out - self);
|
||||
return out;
|
||||
}
|
||||
|
||||
document_data& m_doc;
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -0,0 +1,242 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max, min
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <functional> // less
|
||||
#include <memory> // unique_ptr
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
// The storage of edits. Edits never move or resize the parsed index: every
|
||||
// value keeps its node, so views stay valid. New values and element sequences
|
||||
// live in chunks that never move; strings and number tokens written by edits
|
||||
// live in the edit arena. An array/object whose elements change gets
|
||||
// node_flags::moved: its elements then live in a separate sequence (a header
|
||||
// node, then the entries), whose entries link to the values (kind_link).
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
inline document_data::edit_state& edit_state_of(document_data& d)
|
||||
{
|
||||
if (!d.edits)
|
||||
{
|
||||
d.edits.reset(new document_data::edit_state()); // NOLINT(cppcoreguidelines-owning-memory): owned by the unique_ptr
|
||||
}
|
||||
return *d.edits;
|
||||
}
|
||||
|
||||
/// k consecutive nodes that never move (new values and blocks)
|
||||
inline node* alloc_nodes(document_data& d, std::size_t k)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(e.chunk_end - e.chunk_cur) < k))
|
||||
{
|
||||
const std::size_t count = (std::max)(k, e.chunk_next);
|
||||
std::unique_ptr<node[]> fresh(new node[count]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(fresh));
|
||||
e.chunk_cur = e.chunks.back().get();
|
||||
e.chunk_end = e.chunk_cur + count;
|
||||
e.chunk_next = (std::min)(e.chunk_next * 2, std::size_t{65536});
|
||||
e.bytes += count * sizeof(node);
|
||||
}
|
||||
node* const r = e.chunk_cur;
|
||||
e.chunk_cur += k;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// copy n bytes into the edit arena and return their offset; a new buffer
|
||||
/// leaves the old one alive, so that string views into it remain valid
|
||||
inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(e.text_cap - e.text_used < n))
|
||||
{
|
||||
const std::size_t cap = (std::max)(e.text_cap * 2, e.text_used + n + 256);
|
||||
if (cap > 0xFFFFFFFFu)
|
||||
{
|
||||
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE (4 GiB)
|
||||
}
|
||||
std::unique_ptr<char[]> fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (e.text_used != 0)
|
||||
{
|
||||
std::memcpy(fresh.get(), e.texts.back().get(), e.text_used);
|
||||
}
|
||||
e.texts.push_back(std::move(fresh));
|
||||
e.text_cap = cap;
|
||||
e.bytes += cap;
|
||||
d.base[2] = e.texts.back().get();
|
||||
}
|
||||
const auto off = static_cast<std::uint32_t>(e.text_used);
|
||||
if (n != 0)
|
||||
{
|
||||
std::memcpy(e.texts.back().get() + e.text_used, s, n);
|
||||
}
|
||||
e.text_used += n;
|
||||
return off;
|
||||
}
|
||||
|
||||
/// the capacity in nodes of the block of a moved container (0: a fixed
|
||||
/// sequence, the elements of a new value)
|
||||
inline std::size_t moved_capacity(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.edits->moved_cap[n->off];
|
||||
}
|
||||
|
||||
/// let container n take its elements from `seq` (header node first)
|
||||
inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if ((n->flags & node_flags::moved) != 0)
|
||||
{
|
||||
e.moved[n->off] = seq;
|
||||
e.moved_cap[n->off] = cap;
|
||||
return;
|
||||
}
|
||||
if (e.moved.size() >= 0xFFFFFFFFu)
|
||||
{
|
||||
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||
{
|
||||
// both grow before either changes, so that the push_backs cannot throw
|
||||
e.moved.reserve((2 * e.moved.size()) + 16);
|
||||
e.moved_cap.reserve((2 * e.moved.size()) + 16);
|
||||
}
|
||||
e.moved.push_back(seq);
|
||||
e.moved_cap.push_back(cap);
|
||||
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
||||
n->flags = static_cast<std::uint8_t>(n->flags | node_flags::moved | node_flags::is_new);
|
||||
}
|
||||
|
||||
/// Make the elements of container n a growable block with room for `extra`
|
||||
/// more nodes, and return its header. The entries link to the existing
|
||||
/// values, which stay where they are. A block that grows is copied (its old
|
||||
/// space is not reused).
|
||||
inline node* block_of(document_data& d, node* n, std::size_t extra)
|
||||
{
|
||||
if ((n->flags & node_flags::moved) != 0 && moved_capacity(d, n) != 0)
|
||||
{
|
||||
node* const h = d.edits->moved[n->off];
|
||||
if (h->next + extra <= moved_capacity(d, n))
|
||||
{
|
||||
return h;
|
||||
}
|
||||
const std::size_t cap = (std::max)(2 * moved_capacity(d, n), h->next + extra);
|
||||
node* const nh = alloc_nodes(d, cap);
|
||||
std::memcpy(nh, h, h->next * sizeof(node));
|
||||
set_moved(d, n, nh, cap);
|
||||
return nh;
|
||||
}
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
const std::size_t used = 1 + (static_cast<std::size_t>(n->len) * (object ? 2 : 1));
|
||||
const std::size_t cap = used + extra;
|
||||
node* const h = alloc_nodes(d, cap);
|
||||
*h = node{};
|
||||
h->kind = n->kind;
|
||||
h->len = n->len;
|
||||
h->next = static_cast<std::uint32_t>(used);
|
||||
node* o = h + 1;
|
||||
for (const node* c = d.first_child_edited(n), *e = d.child_end_edited(n); c != e;)
|
||||
{
|
||||
if (object)
|
||||
{
|
||||
*o++ = *c++; // the key
|
||||
}
|
||||
make_link(*o, document_data::deref(c));
|
||||
++o;
|
||||
c = document_data::after(c);
|
||||
}
|
||||
set_moved(d, n, h, cap);
|
||||
return h;
|
||||
}
|
||||
|
||||
/// The container whose elements include `target`; nullptr for the root, for
|
||||
/// a value that is no longer part of the document, and for a value that is
|
||||
/// only reached through a link. Values never move between allocations, so
|
||||
/// the path to `target` stays inside the allocation that holds it (the parsed
|
||||
/// index, or one new value), where the extent of each container (`next`)
|
||||
/// still covers its original subtree.
|
||||
inline node* find_parent(const document_data& d, const node* target)
|
||||
{
|
||||
const std::less<const node*> lt;
|
||||
const node* lo = d.tape;
|
||||
const node* hi = d.tape + d.tape_size;
|
||||
const node* c = d.tape;
|
||||
if (lt(target, lo) || !lt(target, hi))
|
||||
{
|
||||
if (!d.edits)
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (nodes outside the index exist only after edits)
|
||||
}
|
||||
auto it = d.edits->regions.upper_bound(target);
|
||||
if (it == d.edits->regions.begin())
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (an array/object with elements is in the index or a new value)
|
||||
}
|
||||
--it;
|
||||
lo = it->first;
|
||||
hi = lo + lo->next;
|
||||
if (!lt(target, hi))
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (a single-node value, reached through a link)
|
||||
}
|
||||
// the root of a new value is the element sequence of its owner, or a linked value
|
||||
c = it->second != nullptr ? it->second : lo;
|
||||
}
|
||||
if (target == lo)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
if (!is_container(*c))
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (the value is inside c)
|
||||
}
|
||||
const bool object = c->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
const node* down = nullptr;
|
||||
for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;)
|
||||
{
|
||||
const node* const at = object ? p + 1 : p;
|
||||
const node* const v = document_data::deref(at);
|
||||
if (v == target)
|
||||
{
|
||||
return const_cast<node*>(c); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to the document
|
||||
}
|
||||
if (is_container(*v) && !lt(v, lo) && lt(v, target) && lt(target, v + v->next))
|
||||
{
|
||||
down = v;
|
||||
break;
|
||||
}
|
||||
p = document_data::after(at);
|
||||
}
|
||||
if (down == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
c = down;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -30,6 +30,11 @@ namespace view
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
||||
|
||||
@@ -73,7 +73,7 @@ class view_iterator
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE View operator*() const noexcept
|
||||
{
|
||||
return View(m_doc, m_pos + m_value_offset);
|
||||
return View(m_doc, View::navigation::value(m_pos + m_value_offset));
|
||||
}
|
||||
|
||||
pointer operator->() const noexcept
|
||||
|
||||
@@ -84,18 +84,20 @@ class short_key
|
||||
|
||||
/// the key node of the first member of an object with the given key, or
|
||||
/// nullptr; most keys are rejected by their length, from the index alone
|
||||
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||
template<bool Editable>
|
||||
const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(object->extra != 0))
|
||||
using nav = navigation<Editable>;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(object->extra != 0) && (!Editable || (object->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return find_indexed(d, object, key, n); // a large object
|
||||
return find_indexed(d, object, key, n); // a large object (whose members have not been edited)
|
||||
}
|
||||
const node* const end = document_data::child_end(object);
|
||||
const node* const end = nav::end(d, object);
|
||||
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
if (NLOHMANN_VIEW_LIKELY(n <= 16))
|
||||
{
|
||||
const short_key probe(k, n);
|
||||
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
{
|
||||
@@ -104,7 +106,7 @@ inline const node* find_member(const document_data& d, const node* object, const
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
|
||||
{
|
||||
@@ -114,10 +116,16 @@ inline const node* find_member(const document_data& d, const node* object, const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// the element of an array at an index below its size
|
||||
inline const node* element_at(const node* array, std::size_t idx) noexcept
|
||||
/// the entry of the element of an array at an index below its size (a link
|
||||
/// in the moved sequences of editable documents)
|
||||
template<bool Editable>
|
||||
const node* element_at(const document_data& d, const node* array, std::size_t idx) noexcept
|
||||
{
|
||||
const node* e = document_data::first_child(array);
|
||||
const node* e = navigation<Editable>::first(d, array);
|
||||
if (Editable && (array->flags & node_flags::moved) != 0 && d.edits->moved_cap[array->off] != 0)
|
||||
{
|
||||
return e + idx; // a growable block: one link per element
|
||||
}
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
e = document_data::after(e);
|
||||
@@ -125,13 +133,14 @@ inline const node* element_at(const node* array, std::size_t idx) noexcept
|
||||
return e;
|
||||
}
|
||||
|
||||
/// the last element of a non-empty array, or the key of the last member of a
|
||||
/// non-empty object
|
||||
inline const node* last_child(const node* container) noexcept
|
||||
/// the entry of the last element of a non-empty array, or the key of the
|
||||
/// last member of a non-empty object
|
||||
template<bool Editable>
|
||||
const node* last_child(const document_data& d, const node* container) noexcept
|
||||
{
|
||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
||||
const node* const end = document_data::child_end(container);
|
||||
const node* last = document_data::first_child(container);
|
||||
const node* const end = navigation<Editable>::end(d, container);
|
||||
const node* last = navigation<Editable>::first(d, container);
|
||||
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||
{
|
||||
last = c;
|
||||
|
||||
@@ -34,17 +34,24 @@ iterative, so the nesting depth is limited by memory only, as for parse().
|
||||
Without a lexer the handler records no source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS).
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
struct frame
|
||||
{
|
||||
const node* pos; ///< next element, or key of the next member
|
||||
const node* end;
|
||||
bool object;
|
||||
};
|
||||
|
||||
BasicJsonType result;
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
// the ends of the open containers, and whether they are objects
|
||||
std::vector<std::pair<const node*, bool>> open;
|
||||
std::vector<frame> open;
|
||||
for (;;)
|
||||
{
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
@@ -61,67 +68,66 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
sax.start_array(n->len);
|
||||
}
|
||||
open.emplace_back(document_data::child_end(n), object);
|
||||
n = document_data::first_child(n);
|
||||
open.push_back(frame{nav::first(d, n), nav::end(d, n), object});
|
||||
break;
|
||||
}
|
||||
case value_t::string:
|
||||
{
|
||||
string_t s(d.str(*n), n->len);
|
||||
sax.string(s);
|
||||
++n;
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
sax.number_integer(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
|
||||
++n;
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
sax.number_unsigned(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
|
||||
++n;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
sax.number_float(float_value<typename BasicJsonType::number_float_t>(d, *n), no_token);
|
||||
++n;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
sax.boolean((n->flags & node_flags::is_true) != 0);
|
||||
++n;
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
sax.null();
|
||||
++n;
|
||||
break;
|
||||
}
|
||||
|
||||
// the next value: close finished containers, then read the key
|
||||
for (;;)
|
||||
{
|
||||
if (open.empty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (n != open.back().first)
|
||||
frame& f = open.back();
|
||||
if (f.pos == f.end)
|
||||
{
|
||||
break;
|
||||
if (f.object)
|
||||
{
|
||||
sax.end_object();
|
||||
}
|
||||
else
|
||||
{
|
||||
sax.end_array();
|
||||
}
|
||||
open.pop_back();
|
||||
continue;
|
||||
}
|
||||
if (open.back().second)
|
||||
const node* entry = f.pos;
|
||||
if (f.object)
|
||||
{
|
||||
sax.end_object();
|
||||
string_t key(d.str(*entry), entry->len);
|
||||
sax.key(key);
|
||||
++entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
sax.end_array();
|
||||
}
|
||||
open.pop_back();
|
||||
}
|
||||
if (open.back().second)
|
||||
{
|
||||
// the key of the next member
|
||||
string_t key(d.str(*n), n->len);
|
||||
sax.key(key);
|
||||
++n;
|
||||
n = nav::value(entry);
|
||||
f.pos = document_data::after(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,20 +33,27 @@ static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::
|
||||
struct node_flags
|
||||
{
|
||||
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
||||
static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents)
|
||||
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
||||
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
|
||||
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
|
||||
};
|
||||
|
||||
/// kind of an entry of an edited sequence that stands for a value stored
|
||||
/// elsewhere (the address of the value's node is kept in the len/next bytes)
|
||||
constexpr std::uint8_t kind_link = 10;
|
||||
|
||||
/// One entry of the flat index, in document order. An object's members are
|
||||
/// stored as key node followed by the value's subtree. Integers keep their
|
||||
/// converted 64-bit value in the len/next bytes (the node after a scalar is
|
||||
/// always the next one, and the token length follows from `extra`).
|
||||
struct node
|
||||
{
|
||||
std::uint8_t kind; ///< value_t
|
||||
std::uint8_t kind; ///< value_t, or kind_link
|
||||
std::uint8_t flags; ///< node_flags
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; otherwise 0
|
||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if node_flags::escaped
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the hash index; otherwise 0
|
||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||
};
|
||||
@@ -57,6 +64,21 @@ NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept
|
||||
return static_cast<unsigned>(n.kind) - 1u <= 1u;
|
||||
}
|
||||
|
||||
/// the value a link node stands for
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* link_target(const node& n) noexcept
|
||||
{
|
||||
const node* t = nullptr;
|
||||
std::memcpy(static_cast<void*>(&t), reinterpret_cast<const unsigned char*>(&n) + 8, sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
return t;
|
||||
}
|
||||
|
||||
inline void make_link(node& n, const node* target) noexcept
|
||||
{
|
||||
n = node{};
|
||||
n.kind = kind_link;
|
||||
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
}
|
||||
|
||||
/// the converted value of an integer node (stored in len/next)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint64_t
|
||||
#include <cstring> // memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // integral_constant, is_same
|
||||
|
||||
@@ -137,11 +138,31 @@ NLOHMANN_VIEW_ALWAYS_INLINE double layout_double(const unsigned char* p, const u
|
||||
return negative ? -result : result;
|
||||
}
|
||||
|
||||
/// the value of a float set by an edit: its token (the shortest round-trip
|
||||
/// text, or "nan", "inf", "-inf") in the edit arena
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n)
|
||||
{
|
||||
if (token[0] == 'n')
|
||||
{
|
||||
return std::numeric_limits<FloatType>::quiet_NaN();
|
||||
}
|
||||
if (token[0] == 'i' || (token[0] == '-' && token[1] == 'i'))
|
||||
{
|
||||
return token[0] == 'i' ? std::numeric_limits<FloatType>::infinity() : -std::numeric_limits<FloatType>::infinity();
|
||||
}
|
||||
return float_value<FloatType>(token, n);
|
||||
}
|
||||
|
||||
/// the value of the float token of a node, as parse() converts it; doubles
|
||||
/// with at most 19 digits are converted from the digit layout
|
||||
template<typename FloatType>
|
||||
FloatType float_value(const document_data& d, const node& n)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY((n.flags & node_flags::storage) == node_flags::edited))
|
||||
{
|
||||
return edited_float<FloatType>(d.str(n), n);
|
||||
}
|
||||
return float_value<FloatType>(d, n, std::is_same<FloatType, double> {});
|
||||
}
|
||||
|
||||
|
||||
@@ -115,9 +115,10 @@ the library's conversion; integers are copied from the source, where they
|
||||
are canonical (except "-0", which parse() reads as 0). The walk is
|
||||
iterative, so the nesting depth is limited by memory only.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
class view_serializer
|
||||
{
|
||||
using nav = navigation<Editable>;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
|
||||
@@ -150,7 +151,7 @@ class view_serializer
|
||||
else
|
||||
{
|
||||
m_out.put(object ? '{' : '[');
|
||||
stack.push_back(frame{document_data::first_child(n), document_data::child_end(n), object, true});
|
||||
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -192,13 +193,14 @@ class view_serializer
|
||||
{
|
||||
m_out.put(':');
|
||||
}
|
||||
n = f.pos + 1;
|
||||
n = nav::value(f.pos + 1);
|
||||
f.pos = document_data::after(f.pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
n = f.pos;
|
||||
n = nav::value(f.pos);
|
||||
f.pos = document_data::after(f.pos);
|
||||
}
|
||||
f.pos = document_data::after(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -250,9 +252,9 @@ class view_serializer
|
||||
break;
|
||||
}
|
||||
case value_t::number_float:
|
||||
if (m_style.source_numbers)
|
||||
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
|
||||
{
|
||||
m_out.put(m_doc.str(n), n.len);
|
||||
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -299,9 +301,9 @@ class view_serializer
|
||||
{
|
||||
const char* const s = m_doc.str(n);
|
||||
m_out.put('"');
|
||||
if ((n.flags & node_flags::escaped) == 0 && !m_style.ensure_ascii)
|
||||
if ((n.flags & node_flags::storage) == 0 && !m_style.ensure_ascii)
|
||||
{
|
||||
// a string without escape sequences has nothing to escape
|
||||
// a string of the source without escape sequences has nothing to escape
|
||||
m_out.put(s, n.len);
|
||||
}
|
||||
else if (m_style.ensure_ascii)
|
||||
|
||||
+170
-19
@@ -50,6 +50,8 @@
|
||||
#include <nlohmann/detail/view/builder.hpp>
|
||||
#include <nlohmann/detail/view/compare.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/edit.hpp>
|
||||
#include <nlohmann/detail/view/edit_storage.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/input.hpp>
|
||||
#include <nlohmann/detail/view/iterator.hpp>
|
||||
@@ -65,7 +67,7 @@
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
class basic_json_document;
|
||||
|
||||
/*!
|
||||
@@ -74,11 +76,13 @@ class basic_json_document;
|
||||
Trivially copyable (two pointers). Valid as long as the document is alive and
|
||||
has not been re-parsed, and as long as a borrowed source text is alive.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_view
|
||||
{
|
||||
using node = detail::view::node;
|
||||
using document_data = detail::view::document_data;
|
||||
/// how the index is walked (with edits only for editable documents)
|
||||
using navigation = detail::view::navigation<Editable>;
|
||||
|
||||
public:
|
||||
using value_t = detail::value_t;
|
||||
@@ -271,7 +275,7 @@ class basic_json_view
|
||||
{
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view();
|
||||
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||
}
|
||||
|
||||
/// (an int argument would be ambiguous between size_type and const char*)
|
||||
@@ -327,7 +331,7 @@ class basic_json_view
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return basic_json_view(m_doc, detail::view::element_at(m_node, idx));
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||
}
|
||||
|
||||
basic_json_view at(int idx) const
|
||||
@@ -399,7 +403,7 @@ class basic_json_view
|
||||
{
|
||||
if (is_structured() && m_node->len != 0)
|
||||
{
|
||||
return basic_json_view(m_doc, detail::view::last_child(m_node) + (is_object() ? 1 : 0));
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||
}
|
||||
return front();
|
||||
}
|
||||
@@ -416,7 +420,7 @@ class basic_json_view
|
||||
{
|
||||
return end();
|
||||
}
|
||||
const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size());
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||
}
|
||||
|
||||
@@ -433,7 +437,7 @@ class basic_json_view
|
||||
/// whether this is an object with a member with this key
|
||||
bool contains(string_view_t key) const
|
||||
{
|
||||
return is_object() && detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||
}
|
||||
|
||||
bool contains(const char* key) const
|
||||
@@ -480,7 +484,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY(is_structured()))
|
||||
{
|
||||
return iterator(m_doc, document_data::first_child(m_node), is_object());
|
||||
return iterator(m_doc, navigation::first(*m_doc, m_node), is_object());
|
||||
}
|
||||
return iterator(m_doc, m_node, false);
|
||||
}
|
||||
@@ -489,7 +493,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY(is_structured()))
|
||||
{
|
||||
return iterator(m_doc, document_data::child_end(m_node), is_object());
|
||||
return iterator(m_doc, navigation::end(*m_doc, m_node), is_object());
|
||||
}
|
||||
return iterator(m_doc, (is_null() || is_discarded()) ? m_node : m_node + 1, false);
|
||||
}
|
||||
@@ -589,7 +593,7 @@ class basic_json_view
|
||||
style.source_numbers = numbers == number_format::source;
|
||||
// the compact text is about as long as the source text of the value
|
||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 64;
|
||||
detail::view::view_serializer<BasicJsonType>(*m_doc, out, estimate, style).dump(m_node);
|
||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -623,6 +627,19 @@ class basic_json_view
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/// a view of an editable document compares with one of a read-only document
|
||||
template < bool E, typename std::enable_if < E != Editable, int >::type = 0 >
|
||||
friend bool operator==(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b)
|
||||
{
|
||||
return detail::view::equal<BasicJsonType>(side(a), detail::view::view_side<BasicJsonType, basic_json_view<BasicJsonType, E>>(b));
|
||||
}
|
||||
|
||||
template < bool E, typename std::enable_if < E != Editable, int >::type = 0 >
|
||||
friend bool operator!=(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/// whether the value parse() would produce for a view equals a value
|
||||
friend bool operator==(const basic_json_view& a, const BasicJsonType& j)
|
||||
{
|
||||
@@ -656,7 +673,7 @@ class basic_json_view
|
||||
{
|
||||
return BasicJsonType(value_t::discarded);
|
||||
}
|
||||
return detail::view::materialize<BasicJsonType>(*m_doc, m_node);
|
||||
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
}
|
||||
|
||||
/// byte offset of this value in the source text (for strings: of the
|
||||
@@ -664,12 +681,14 @@ class basic_json_view
|
||||
/// a discarded view and for strings with escapes, which are decoded
|
||||
std::size_t source_offset() const noexcept
|
||||
{
|
||||
return m_node != nullptr && (m_node->flags & detail::view::node_flags::storage) == 0
|
||||
return m_node != nullptr && (m_node->flags & (detail::view::node_flags::storage | detail::view::node_flags::moved | detail::view::node_flags::is_new)) == 0
|
||||
? m_node->off : static_cast<std::size_t>(-1);
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename> friend class basic_json_document;
|
||||
template<typename, bool> friend class basic_json_document;
|
||||
template<typename, bool> friend class basic_json_view;
|
||||
template<typename, typename> friend class detail::view::editor;
|
||||
friend iterator;
|
||||
|
||||
basic_json_view(const document_data* d, const node* n) noexcept
|
||||
@@ -687,6 +706,11 @@ class basic_json_view
|
||||
/// decoded strings)
|
||||
std::size_t source_extent() const noexcept
|
||||
{
|
||||
if (Editable && m_doc->edits != nullptr)
|
||||
{
|
||||
// positions of moved and new values are not source offsets
|
||||
return m_node == m_doc->tape ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
}
|
||||
const node* const next = document_data::after(m_node);
|
||||
const bool in_source = (m_node->flags & detail::view::node_flags::storage) == 0;
|
||||
if (!in_source)
|
||||
@@ -704,8 +728,8 @@ class basic_json_view
|
||||
/// (object required)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||
{
|
||||
const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
||||
}
|
||||
|
||||
// --- get() dispatch ---
|
||||
@@ -796,7 +820,7 @@ Borrowed parses keep a pointer to the caller's text, which must outlive the
|
||||
document. Owned parses (parse_copy, rvalue std::string, streams, and inputs
|
||||
that are not contiguous byte ranges) keep their own copy.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_document
|
||||
{
|
||||
using document_data = detail::view::document_data;
|
||||
@@ -805,7 +829,7 @@ class basic_json_document
|
||||
"json_view supports 64-bit integer types only");
|
||||
|
||||
public:
|
||||
using view_type = basic_json_view<BasicJsonType>;
|
||||
using view_type = basic_json_view<BasicJsonType, Editable>;
|
||||
using value_t = detail::value_t;
|
||||
|
||||
/// an empty (discarded) document
|
||||
@@ -929,7 +953,8 @@ class basic_json_document
|
||||
+ (m_data->tape != m_data->inline_tape ? m_data->tape_cap * sizeof(detail::view::node) : 0)
|
||||
+ m_data->arena.capacity() + m_data->owned.capacity()
|
||||
+ (m_data->indexes.capacity() * sizeof(document_data::object_index)) + (m_data->index_slots.capacity() * sizeof(std::uint32_t))
|
||||
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t));
|
||||
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t))
|
||||
+ (m_data->edits != nullptr ? m_data->edits->bytes : 0);
|
||||
}
|
||||
|
||||
/// release unused capacity of the index and the decoded strings; like
|
||||
@@ -948,7 +973,8 @@ class basic_json_document
|
||||
// unchanged
|
||||
const bool shrink_arena = d.arena.capacity() > d.arena.size();
|
||||
std::string arena(shrink_arena ? d.arena : std::string());
|
||||
const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap;
|
||||
// (edits link to the nodes of the index, which then stays in place)
|
||||
const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap && d.edits == nullptr;
|
||||
const bool into_header = d.tape_size <= d.inline_cap;
|
||||
node* fresh = (shrink_tape && !into_header) ? static_cast<node*>(::operator new (d.tape_size * sizeof(node))) : d.inline_tape;
|
||||
|
||||
@@ -966,9 +992,124 @@ class basic_json_document
|
||||
}
|
||||
}
|
||||
|
||||
///////////
|
||||
// edits //
|
||||
///////////
|
||||
|
||||
// The source text is never written; new values go to storage owned by
|
||||
// the document. A view keeps referring to the same value: after an
|
||||
// assignment it sees the new value, and edits elsewhere do not affect it.
|
||||
// A view of an erased value keeps its last value. An edit of an
|
||||
// array/object invalidates the iterators over it. Values are accepted as
|
||||
// views (of any document), BasicJsonType values, and everything
|
||||
// BasicJsonType can be constructed from.
|
||||
|
||||
using string_view_t = typename view_type::string_view_t;
|
||||
using json_pointer = typename BasicJsonType::json_pointer;
|
||||
|
||||
/// replace a value (a view of this document); returns a view of it
|
||||
template<typename V>
|
||||
view_type set(view_type target, V&& value)
|
||||
{
|
||||
return editor().set(target, std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// set a member (added if missing; a null value becomes an object);
|
||||
/// returns a view of the member value
|
||||
template<typename V>
|
||||
view_type set(view_type object, string_view_t key, V&& value)
|
||||
{
|
||||
return editor().set(object, key, std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// assign an existing array element; returns a view of it
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
view_type set(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().set(array, index(idx), std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// set the value at a JSON pointer: its parent must exist; an object
|
||||
/// member is set (added if missing), an array element assigned, and "-"
|
||||
/// or the size of the array appends
|
||||
template<typename V>
|
||||
view_type set(const json_pointer& ptr, V&& value)
|
||||
{
|
||||
if (ptr.empty())
|
||||
{
|
||||
return set(root(), std::forward<V>(value));
|
||||
}
|
||||
const view_type parent = root().at(ptr.parent_pointer());
|
||||
const auto& token = ptr.back();
|
||||
if (parent.is_array())
|
||||
{
|
||||
const std::size_t idx = token == "-" ? parent.size() : pointer_index(token);
|
||||
if (idx == parent.size())
|
||||
{
|
||||
return push_back(parent, std::forward<V>(value));
|
||||
}
|
||||
return set(parent, idx, std::forward<V>(value));
|
||||
}
|
||||
return set(parent, string_view_t(token.data(), token.size()), std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// append to an array (a null value becomes an array); returns a view of
|
||||
/// the new element
|
||||
template<typename V>
|
||||
view_type push_back(view_type array, V&& value)
|
||||
{
|
||||
return editor().push_back(array, std::forward<V>(value));
|
||||
}
|
||||
|
||||
private:
|
||||
using input_kind = detail::view::input_kind;
|
||||
|
||||
detail::view::editor<BasicJsonType, view_type> editor()
|
||||
{
|
||||
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
|
||||
}
|
||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||
}
|
||||
|
||||
/// an index (out_of_range.401 if negative)
|
||||
template<typename I>
|
||||
static std::size_t index(I idx)
|
||||
{
|
||||
return index(idx, std::is_signed<I> {});
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
static std::size_t index(I idx, std::true_type /*signed*/)
|
||||
{
|
||||
if (idx < 0)
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
static std::size_t index(I idx, std::false_type /*unsigned*/)
|
||||
{
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
|
||||
/// the array index of a JSON pointer token (json_pointer's rules)
|
||||
template<typename StringType>
|
||||
static std::size_t pointer_index(const StringType& token)
|
||||
{
|
||||
std::size_t idx = 0;
|
||||
const detail::view::index_status status = detail::view::array_index(token, idx);
|
||||
if (status != detail::view::index_status::ok)
|
||||
{
|
||||
detail::view::throw_array_index_error(status, token);
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/// create the storage (sized for the input) on first use
|
||||
void ensure_data(const char* src, std::size_t size)
|
||||
{
|
||||
@@ -998,6 +1139,8 @@ class basic_json_document
|
||||
d.src = src;
|
||||
d.size = size;
|
||||
d.tape_size = 0;
|
||||
d.edits.reset(); // (views of the previous text end here anyway)
|
||||
d.base[2] = nullptr;
|
||||
d.arena.clear();
|
||||
d.indexes.clear();
|
||||
d.index_slots.clear();
|
||||
@@ -1133,6 +1276,14 @@ using json_view = basic_json_view<json>;
|
||||
using ordered_json_document = basic_json_document<ordered_json>;
|
||||
/// a value of an ordered_json_document
|
||||
using ordered_json_view = basic_json_view<ordered_json>;
|
||||
/// an editable parsed JSON text for json
|
||||
using json_editable_document = basic_json_document<json, true>;
|
||||
/// a value of a json_editable_document
|
||||
using json_editable_view = basic_json_view<json, true>;
|
||||
/// an editable parsed JSON text for ordered_json
|
||||
using ordered_json_editable_document = basic_json_document<ordered_json, true>;
|
||||
/// a value of an ordered_json_editable_document
|
||||
using ordered_json_editable_view = basic_json_view<ordered_json, true>;
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
+1682
-405
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,495 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json_view.hpp>
|
||||
using nlohmann::json;
|
||||
using nlohmann::ordered_json;
|
||||
using nlohmann::json_document;
|
||||
using nlohmann::json_editable_document;
|
||||
using nlohmann::json_editable_view;
|
||||
using nlohmann::ordered_json_document;
|
||||
using nlohmann::ordered_json_editable_document;
|
||||
using nlohmann::ordered_json_editable_view;
|
||||
using ptr_t = ordered_json::json_pointer;
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::uint32_t rng()
|
||||
{
|
||||
static std::mt19937 generator(5295); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
||||
// result_type is std::uint_fast32_t, which may be wider than 32 bits
|
||||
const std::mt19937::result_type value = generator();
|
||||
return static_cast<std::uint32_t>(value);
|
||||
}
|
||||
|
||||
int r(int n)
|
||||
{
|
||||
return static_cast<int>(rng() % static_cast<unsigned>(n));
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
|
||||
std::uint64_t bits(double x)
|
||||
{
|
||||
std::uint64_t b = 0;
|
||||
std::memcpy(&b, &x, sizeof(b));
|
||||
return b;
|
||||
}
|
||||
|
||||
std::string random_string()
|
||||
{
|
||||
static const std::array<const char*, 15> pieces = {{"a", "Z", " ", "~", "\n", "\"", "\\", "/", "\xc3\xa9", "\xe3\x81\x82", "\xf0\x9f\x98\x80", "\x7f", "\x1f", "0", "key"}};
|
||||
std::string s;
|
||||
for (int i = r(3) == 0 ? r(30) : r(6); i > 0; --i)
|
||||
{
|
||||
s += pieces[static_cast<std::size_t>(r(15))];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
ordered_json random_scalar()
|
||||
{
|
||||
switch (r(9))
|
||||
{
|
||||
case 0:
|
||||
return nullptr;
|
||||
case 1:
|
||||
return r(2) == 0;
|
||||
case 2:
|
||||
return static_cast<std::int64_t>(rng()) - 2147483648LL;
|
||||
case 3:
|
||||
return (static_cast<std::uint64_t>(rng()) * 4294967296ULL) + rng();
|
||||
case 4:
|
||||
return static_cast<double>(static_cast<std::int32_t>(rng())) / (1 + r(1000));
|
||||
case 5:
|
||||
return r(2) == 0 ? 1e300 * (r(2) == 0 ? 1 : -1) : 5e-324;
|
||||
case 6:
|
||||
return -0.0;
|
||||
default:
|
||||
return random_string();
|
||||
}
|
||||
}
|
||||
|
||||
ordered_json random_value(int depth)
|
||||
{
|
||||
const int k = depth > 3 ? 4 : r(7);
|
||||
if (k == 0)
|
||||
{
|
||||
ordered_json o = ordered_json::object();
|
||||
for (int i = r(5); i > 0; --i)
|
||||
{
|
||||
o[random_string() + "#" + std::to_string(counter++)] = random_value(depth + 1);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
if (k == 1)
|
||||
{
|
||||
ordered_json a = ordered_json::array();
|
||||
for (int i = r(5); i > 0; --i)
|
||||
{
|
||||
a.push_back(random_value(depth + 1));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return random_scalar();
|
||||
}
|
||||
|
||||
void collect(const ordered_json& j, const ptr_t& p, std::vector<ptr_t>& out)
|
||||
{
|
||||
out.push_back(p);
|
||||
if (j.is_object())
|
||||
{
|
||||
for (const auto& kv : j.items())
|
||||
{
|
||||
collect(kv.value(), p / kv.key(), out);
|
||||
}
|
||||
}
|
||||
else if (j.is_array())
|
||||
{
|
||||
for (std::size_t i = 0; i < j.size(); ++i)
|
||||
{
|
||||
collect(j[i], p / i, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// the edited view and the ordered_json value read the same through the whole
|
||||
// read API
|
||||
void compare(const ordered_json_editable_view& v, const ordered_json& j)
|
||||
{
|
||||
REQUIRE(v.type() == j.type());
|
||||
REQUIRE(v.size() == j.size());
|
||||
if (j.is_object())
|
||||
{
|
||||
auto jt = j.begin();
|
||||
for (auto it = v.begin(); it != v.end(); ++it, ++jt)
|
||||
{
|
||||
CHECK(std::string(it.key().data(), it.key().size()) == jt.key());
|
||||
CHECK(v[jt.key()].dump() == jt.value().dump());
|
||||
CHECK(v.contains(jt.key()));
|
||||
compare(*it, jt.value());
|
||||
}
|
||||
}
|
||||
else if (j.is_array())
|
||||
{
|
||||
for (std::size_t i = 0; i < j.size(); ++i)
|
||||
{
|
||||
CHECK(v[i].dump() == j[i].dump());
|
||||
}
|
||||
std::size_t i = 0;
|
||||
for (const auto e : v)
|
||||
{
|
||||
compare(e, j[i++]);
|
||||
}
|
||||
if (!j.empty())
|
||||
{
|
||||
CHECK(v.back().dump() == j.back().dump());
|
||||
}
|
||||
}
|
||||
else if (j.is_string())
|
||||
{
|
||||
CHECK(v.get<std::string>() == j.get<std::string>());
|
||||
}
|
||||
else if (j.is_number_float())
|
||||
{
|
||||
CHECK(bits(v.get<double>()) == bits(j.get<double>()));
|
||||
}
|
||||
else if (j.is_number_integer())
|
||||
{
|
||||
CHECK(v.get<std::int64_t>() == j.get<std::int64_t>());
|
||||
CHECK(v.get<std::uint64_t>() == j.get<std::uint64_t>());
|
||||
}
|
||||
}
|
||||
|
||||
void check_all(const ordered_json_editable_document& d, const ordered_json& j, bool deep)
|
||||
{
|
||||
const std::string text = d.root().dump();
|
||||
REQUIRE(text == j.dump());
|
||||
CHECK(d.root().materialize() == j);
|
||||
if (deep)
|
||||
{
|
||||
CHECK(d.root().dump(2) == j.dump(2));
|
||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||
CHECK(d.root() == j);
|
||||
compare(d.root(), j);
|
||||
// a fresh document of the text reads the same
|
||||
const ordered_json_document fresh = ordered_json_document::parse(text);
|
||||
CHECK(fresh.root() == d.root());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("json_view edits: differential")
|
||||
{
|
||||
// random edits are applied to an ordered_json_editable_document and to the
|
||||
// ordered_json parse() produces; after every edit both must serialize,
|
||||
// materialize, and read back the same
|
||||
for (int n = 0; n < 150; ++n)
|
||||
{
|
||||
ordered_json j = random_value(0);
|
||||
if (r(4) == 0)
|
||||
{
|
||||
j = ordered_json::object({{"a", random_value(1)}, {"b", random_value(1)}});
|
||||
}
|
||||
const std::string text = 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);
|
||||
const ordered_json_document other = ordered_json_document::parse(random_value(0).dump());
|
||||
const int edits = 30;
|
||||
for (int e = 0; e < edits; ++e)
|
||||
{
|
||||
std::vector<ptr_t> paths;
|
||||
collect(j, ptr_t(), paths);
|
||||
const ptr_t p = paths[static_cast<std::size_t>(r(static_cast<int>(paths.size())))];
|
||||
const ordered_json& target = j[p];
|
||||
const ordered_json_editable_view tv = d.root().at(p);
|
||||
const int op = r(12);
|
||||
{
|
||||
if (op == 0) // assign a scalar
|
||||
{
|
||||
const ordered_json v = random_scalar();
|
||||
if (v.is_string() && r(2) == 0)
|
||||
{
|
||||
d.set(tv, v.get<std::string>());
|
||||
}
|
||||
else if (v.is_number_unsigned() && r(2) == 0)
|
||||
{
|
||||
d.set(tv, v.get<std::uint64_t>());
|
||||
}
|
||||
else
|
||||
{
|
||||
d.set(tv, v);
|
||||
}
|
||||
j[p] = v;
|
||||
}
|
||||
else if (op == 1) // assign a new array/object (or anything), sometimes via the pointer API
|
||||
{
|
||||
const ordered_json v = random_value(2);
|
||||
if (r(2) == 0)
|
||||
{
|
||||
d.set(p, v);
|
||||
}
|
||||
else
|
||||
{
|
||||
d.set(tv, v);
|
||||
}
|
||||
j[p] = v;
|
||||
}
|
||||
else if (op == 2) // copy a value of the same document
|
||||
{
|
||||
const ptr_t& q = paths[static_cast<std::size_t>(r(static_cast<int>(paths.size())))];
|
||||
const ordered_json v = j[q];
|
||||
d.set(tv, d.root().at(q));
|
||||
j[p] = v;
|
||||
}
|
||||
else if (op == 3) // copy a value of another document
|
||||
{
|
||||
d.set(tv, other.root());
|
||||
j[p] = other.root().materialize();
|
||||
}
|
||||
else if ((op == 4 || op == 5) && target.is_object()) // set a member (new or existing)
|
||||
{
|
||||
std::string key = random_string() + "#" + std::to_string(counter++);
|
||||
if (op == 5 && !target.empty())
|
||||
{
|
||||
key = std::next(target.begin(), r(static_cast<int>(target.size()))).key();
|
||||
}
|
||||
const ordered_json v = random_value(2);
|
||||
d.set(tv, key, v);
|
||||
j[p][key] = v;
|
||||
}
|
||||
else if (op == 7 && (target.is_array() || target.is_null())) // push_back
|
||||
{
|
||||
const ordered_json v = random_value(2);
|
||||
d.push_back(tv, v);
|
||||
j[p].push_back(v);
|
||||
}
|
||||
else if (op == 10 && target.is_array() && !target.empty()) // assign an element
|
||||
{
|
||||
const auto i = static_cast<std::size_t>(r(static_cast<int>(target.size())));
|
||||
const ordered_json v = random_value(2);
|
||||
d.set(tv, i, v);
|
||||
j[p][i] = v;
|
||||
}
|
||||
else if (op == 11) // a held view sees the assignment
|
||||
{
|
||||
const ordered_json v = random_value(2);
|
||||
const ordered_json_editable_view held = d.root().at(p);
|
||||
d.set(p, v);
|
||||
j[p] = v;
|
||||
CHECK(held.dump() == v.dump());
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
CAPTURE(p.to_string());
|
||||
CAPTURE(op);
|
||||
check_all(d, j, e % 8 == 7 || e == edits - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// the exception a call throws, or "" if it throws none
|
||||
std::string exception_of_call(const std::function<void()>& f)
|
||||
{
|
||||
try
|
||||
{
|
||||
f();
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("json_view edits: errors")
|
||||
{
|
||||
SECTION("an empty document")
|
||||
{
|
||||
json_editable_document d;
|
||||
CHECK_THROWS_WITH_AS(d.set(d.root(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
|
||||
}
|
||||
|
||||
json_editable_document d = json_editable_document::parse(R"({"o": {"a": 1}, "a": [1, 2], "n": 1, "z": null})");
|
||||
const json_editable_view root = d.root();
|
||||
const json_document other = json_document::parse("[1]");
|
||||
json_editable_document other_editable = json_editable_document::parse("[1]");
|
||||
|
||||
CHECK_THROWS_WITH_AS(d.set(other_editable.root(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
|
||||
CHECK_THROWS_WITH_AS(d.set(json_editable_view(), 1), "[json.exception.invalid_iterator.202] view does not belong to this document", json::invalid_iterator&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root["a"], "k", 1), "[json.exception.type_error.305] cannot use operator[] with a string argument with array", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root["o"], 0, 1), "[json.exception.type_error.305] cannot use operator[] with a numeric argument with object", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root["a"], 2, 1), "[json.exception.out_of_range.401] array index 2 is out of range", json::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root["a"], -1, 1), "[json.exception.out_of_range.401] array index -1 is out of range", json::out_of_range&);
|
||||
CHECK_THROWS_WITH_AS(d.push_back(root["o"], 1), "[json.exception.type_error.308] cannot use push_back() with object", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
||||
CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&);
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// invalid UTF-8 is rejected when it enters the document, with the error
|
||||
// basic_json::dump() reports for the same string
|
||||
for (const std::string bad :
|
||||
{"\xC3\x28", "a\xE2\x28\xA1", "\xF0\x28\x8C\xBC", "\xE2\x82", "x\xF0\x9F\x98", "\xFF", "\xED\xA0\x80", "\xC0\xAF"
|
||||
})
|
||||
{
|
||||
CAPTURE(bad);
|
||||
const std::string expected = exception_of_call([&]
|
||||
{
|
||||
const std::string text = json(bad).dump();
|
||||
static_cast<void>(text);
|
||||
});
|
||||
CHECK(!expected.empty());
|
||||
CHECK(exception_of_call([&] { d.set(root["z"], bad); }) == expected);
|
||||
CHECK(exception_of_call([&] { d.set(root["o"], bad, 1); }) == expected);
|
||||
CHECK(exception_of_call([&] { d.set(root["z"], json{{"k", bad}}); }) == expected);
|
||||
}
|
||||
#endif
|
||||
// nothing of the failed edits is visible
|
||||
CHECK(root.dump() == R"({"o":{"a":1},"a":[1,2],"n":1,"z":null})");
|
||||
static_cast<void>(other);
|
||||
}
|
||||
|
||||
TEST_CASE("json_view edits: views and values")
|
||||
{
|
||||
SECTION("a value that is no longer part of the document")
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse("[[[1,2]]]");
|
||||
const json_editable_view inner = d.root()[0][0];
|
||||
d.set(d.root()[0], json::array({7}));
|
||||
d.set(inner, 5);
|
||||
CHECK(d.root().dump() == "[[7]]");
|
||||
CHECK(inner.get<int>() == 5);
|
||||
}
|
||||
|
||||
SECTION("strings stay valid while more edits come")
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse("[]");
|
||||
const auto first = d.push_back(d.root(), std::string(100, 'x')).get_string();
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
d.push_back(d.root(), std::string(static_cast<std::size_t>(i % 50), 'y'));
|
||||
}
|
||||
CHECK(std::string(first.data(), first.size()) == std::string(100, 'x'));
|
||||
CHECK(d.root().size() == 1001);
|
||||
}
|
||||
|
||||
SECTION("numbers")
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse(R"([1.50, 1E2, 3])");
|
||||
d.set(d.root(), 2, 0.1);
|
||||
d.push_back(d.root(), std::numeric_limits<double>::quiet_NaN());
|
||||
d.push_back(d.root(), -std::numeric_limits<double>::infinity());
|
||||
d.push_back(d.root(), (std::numeric_limits<std::uint64_t>::max)());
|
||||
d.push_back(d.root(), (std::numeric_limits<std::int64_t>::min)());
|
||||
CHECK(d.root().dump() == "[1.5,100.0,0.1,null,null,18446744073709551615,-9223372036854775808]");
|
||||
CHECK(d.root().dump(-1, ' ', false, json_editable_view::number_format::source) == "[1.50,1E2,0.1,null,null,18446744073709551615,-9223372036854775808]");
|
||||
CHECK(std::isnan(d.root()[3].get<double>()));
|
||||
CHECK(std::isinf(d.root()[4].get<double>()));
|
||||
CHECK(d.root()[2].number_token() == "0.1");
|
||||
CHECK(d.root()[5].get<std::uint64_t>() == 18446744073709551615u);
|
||||
CHECK(d.root()[6].number_token() == "-9223372036854775808");
|
||||
CHECK(d.root().materialize().dump() == json::parse(R"([1.5, 100.0, 0.1, null, null, 18446744073709551615, -9223372036854775808])").dump());
|
||||
}
|
||||
|
||||
SECTION("nulls become containers, and the root can be replaced")
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse("[null, null]");
|
||||
d.set(d.root()[0], "k", 1);
|
||||
d.push_back(d.root()[1], true);
|
||||
CHECK(d.root().dump() == R"([{"k":1},[true]])");
|
||||
d.set(d.root(), "scalar");
|
||||
CHECK(d.root().dump() == R"("scalar")");
|
||||
d.set(d.root(), json{{"x", {1, 2}}});
|
||||
d.set(json::json_pointer("/x/-"), 3);
|
||||
d.set(json::json_pointer("/x/3"), 4); // the size of the array appends too
|
||||
d.set(json::json_pointer("/y"), false);
|
||||
CHECK(d.root().dump() == R"({"x":[1,2,3,4],"y":false})");
|
||||
}
|
||||
|
||||
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 first member is assigned, the others dropped
|
||||
CHECK(d.root().dump() == R"({"a":4,"b":2})");
|
||||
}
|
||||
|
||||
SECTION("values from other documents")
|
||||
{
|
||||
const json_document source = json_document::parse(R"({"list": [1, "two", {"three": 3.5}], "text": "a\nb"})");
|
||||
json_editable_document edited = json_editable_document::parse("[0]");
|
||||
edited.set(edited.root(), 0, json{{"inner", {1, 2}}});
|
||||
json_editable_document d = json_editable_document::parse("{}");
|
||||
d.set(d.root(), "copy", source.root()["list"]);
|
||||
d.set(d.root(), "text", source.root()["text"]);
|
||||
d.set(d.root(), "edited", edited.root()[0]);
|
||||
d.set(d.root(), "self", d.root()["copy"]);
|
||||
CHECK(d.root().dump() == R"({"copy":[1,"two",{"three":3.5}],"text":"a\nb","edited":{"inner":[1,2]},"self":[1,"two",{"three":3.5}]})");
|
||||
CHECK(d.root()["copy"] == source.root()["list"]);
|
||||
CHECK(source.root()["list"] == d.root()["self"]);
|
||||
CHECK(d.root() != source.root());
|
||||
}
|
||||
|
||||
SECTION("large objects")
|
||||
{
|
||||
std::string text = "{";
|
||||
for (int i = 0; i < 200; ++i)
|
||||
{
|
||||
text += (i != 0 ? ",\"k" : "\"k") + std::to_string(i) + "\":" + std::to_string(i);
|
||||
}
|
||||
text += '}';
|
||||
json_editable_document d = json_editable_document::parse(text);
|
||||
d.set(d.root(), "k7", "seven"); // assigned in place: the index stays in use
|
||||
CHECK(d.root()["k7"].get_string() == "seven");
|
||||
d.set(d.root(), "new", 1); // appended: the members move, the lookup is linear
|
||||
CHECK(d.root()["new"].get<int>() == 1);
|
||||
CHECK(d.root()["k199"].get<int>() == 199);
|
||||
CHECK(d.root().size() == 201);
|
||||
}
|
||||
|
||||
SECTION("reuse and memory")
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse("[1, 2, 3]");
|
||||
const std::size_t before = d.memory_usage();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
d.push_back(d.root(), "some text");
|
||||
}
|
||||
CHECK(d.memory_usage() > before);
|
||||
const json_editable_view first = d.root()[0];
|
||||
d.shrink_to_fit(); // (with edits, the index stays in place)
|
||||
CHECK(first.get<int>() == 1);
|
||||
d.read(std::string("[true]"));
|
||||
CHECK(d.root().dump() == "[true]");
|
||||
d.push_back(d.root(), false);
|
||||
CHECK(d.root().dump() == "[true,false]");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user