Compare commits

...
Author SHA1 Message Date
Niels Lohmann ea3dca202b json_view: no save() and load() on big-endian targets
The image format is little-endian only. Instead of throwing
type_error.320 at runtime, save_image(), load_image() and the members
save() and load() do not exist on a big-endian target, so a call fails to
compile, like set() on a read-only document. type_error.320 remains for
saving a discarded document.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 12:12:21 +01:00
Niels Lohmann bcc19dca3f json_view: view_mode enum instead of bool Editable, node_kind for node::kind
basic_json_document and basic_json_view take a view_mode (read_only, the
default, or editable) instead of a bool: basic_json_document<json, true>
did not say what true means. The shortcut aliases are unchanged.

The kind of a node is a node_kind (the value_t values plus link, 10 as
before), which removes the static_cast<std::uint8_t>(value_t::...) of
every comparison. The node and image layouts are unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-25 12:07:13 +01:00
37 changed files with 512 additions and 221 deletions

No files matched your search

+1
View File
@@ -90,6 +90,7 @@ cc_library(
"include/nlohmann/detail/view/simd.hpp", "include/nlohmann/detail/view/simd.hpp",
"include/nlohmann/detail/view/string_ref.hpp", "include/nlohmann/detail/view/string_ref.hpp",
"include/nlohmann/detail/view/value.hpp", "include/nlohmann/detail/view/value.hpp",
"include/nlohmann/detail/view/view_mode.hpp",
"include/nlohmann/json.hpp", "include/nlohmann/json.hpp",
"include/nlohmann/json_fwd.hpp", "include/nlohmann/json_fwd.hpp",
"include/nlohmann/json_literals.hpp", "include/nlohmann/json_literals.hpp",
@@ -12,7 +12,7 @@ void erase(view_type array, I idx);
std::size_t erase(const json_pointer& ptr); std::size_t erase(const json_pointer& ptr);
``` ```
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md))
has `erase`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). has `erase`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many 1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many
@@ -3,7 +3,7 @@
<small>Defined in header `<nlohmann/json_view.hpp>`</small> <small>Defined in header `<nlohmann/json_view.hpp>`</small>
```cpp ```cpp
template<typename BasicJsonType, bool Editable = false> template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
class basic_json_document; class basic_json_document;
``` ```
@@ -20,9 +20,9 @@ is move-only: a copy would duplicate the whole index, the decoded-string buffer,
enough in every use we know of. The index, the decoded strings, and the owned text live in one heap block that the enough in every use we know of. The index, the decoded strings, and the owned text live in one heap block that the
document holds through a `#!cpp std::unique_ptr`; this is also why views survive a move of the document. document holds through a `#!cpp std::unique_ptr`; this is also why views survive a move of the document.
With `#!cpp Editable == true`, the document also offers [`set`](set.md), [`push_back`](push_back.md), With `#!cpp Mode == view_mode::editable`, the document also offers [`set`](set.md), [`push_back`](push_back.md),
[`insert`](insert.md), and [`erase`](erase.md) to change values in place, see [Edits](#edits) below. The source text [`insert`](insert.md), and [`erase`](erase.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 itself is never written; a read-only document (`#!cpp view_mode::read_only`, the default) does not carry any of the
bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp static_assert`). bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp static_assert`).
## Template parameters ## Template parameters
@@ -32,9 +32,10 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp
[`ordered_json`](../ordered_json.md). Only 64-bit `number_integer_t`/`number_unsigned_t` types are supported; this [`ordered_json`](../ordered_json.md). Only 64-bit `number_integer_t`/`number_unsigned_t` types are supported; this
is checked with a `static_assert`. is checked with a `static_assert`.
`Editable` `Mode`
: whether the document supports [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and : a [`view_mode`](../view_mode.md): `#!cpp view_mode::editable` if the document supports [`set`](set.md),
[`erase`](erase.md) (optional, `#!cpp false` by default). See [Edits](#edits) below. [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md) (optional, `#!cpp view_mode::read_only`
by default). See [Edits](#edits) below.
## Specializations ## Specializations
@@ -47,7 +48,7 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp
## Member types ## Member types
- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view<BasicJsonType, Editable>`) - **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view<BasicJsonType, Mode>`)
- **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md) - **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md)
- **image_check** - how [`load()`](load.md) validates an image (`full`, `bounds`, `none`), see [`image_check`](load.md#image_check) - **image_check** - how [`load()`](load.md) validates an image (`full`, `bounds`, `none`), see [`image_check`](load.md#image_check)
@@ -80,15 +81,15 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp
### Edits ### Edits
- [**set**](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers - [**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) to (editable documents only)
- [**push_back**](push_back.md) - append to an array (`#!cpp Editable` documents only) - [**push_back**](push_back.md) - append to an array (editable documents only)
- [**insert**](insert.md) - insert an element into an array before a given position (`#!cpp Editable` documents only) - [**insert**](insert.md) - insert an element into an array before a given position (editable documents only)
- [**erase**](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to - [**erase**](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
(`#!cpp Editable` documents only) (editable documents only)
## Edits ## Edits
An editable document (`#!cpp Editable == true`) can be changed after parsing, with [`set`](set.md), An editable document (`#!cpp view_mode::editable`) can be changed after parsing, with [`set`](set.md),
[`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md); [`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md);
[`json_editable_document`](../json_editable_document.md) and [`json_editable_document`](../json_editable_document.md) and
[`ordered_json_editable_document`](../ordered_json_editable_document.md) are the corresponding specializations. A few [`ordered_json_editable_document`](../ordered_json_editable_document.md) are the corresponding specializations. A few
@@ -5,7 +5,7 @@ template<typename I, typename V>
view_type insert(view_type array, I idx, V&& value); view_type insert(view_type array, I idx, V&& value);
``` ```
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) Only an **editable** document (`#!cpp view_mode::editable`, e.g. [`json_editable_document`](../json_editable_document.md))
has `insert`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`). has `insert`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
Inserts `value` into `array` as a new element before position `idx`, which must not be past the end Inserts `value` into `array` as a new element before position `idx`, which must not be past the end
@@ -55,10 +55,7 @@ validate with overload 2 first, if the original vector must survive a failed loa
## Exceptions ## Exceptions
On a big-endian target, throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) -- the same Throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one
exception [`save()`](save.md#exceptions) throws there, since the image format is little-endian only.
Otherwise throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one
`save()` could have written, or fails the requested `check`: `save()` could have written, or fails the requested `check`:
| message | when | | message | when |
@@ -95,6 +92,9 @@ are checked once, however many nodes refer to it.
## Notes ## Notes
**Little-endian targets only.** On a big-endian target, `load()` does not exist, just like
[`save()`](save.md#notes): the image format is little-endian, so a call fails to compile.
### `image_check` { #image_check } ### `image_check` { #image_check }
```cpp ```cpp
@@ -13,7 +13,7 @@ editable, and it does not have to be `array`'s own document -- which is copied s
source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers, source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers,
strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...). strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md)) Only an **editable** document (`#!cpp view_mode::editable`, 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`). has `push_back`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
## Template parameters ## Template parameters
@@ -12,7 +12,7 @@ An edited document is written in its *current* state, with its values in documen
parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an
[`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity) [`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity)
becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to
the same bytes -- also across `BasicJsonType` and `#!cpp Editable`, since the image reflects document order and the same bytes -- also across `BasicJsonType` and the [`view_mode`](../view_mode.md), since the image reflects document order and
values only, not which specialization produced them. values only, not which specialization produced them.
## Return value ## Return value
@@ -31,9 +31,6 @@ Throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) i
[discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/ [discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/
[`read()`](read.md) with `allow_exceptions == false` left discarded. [`read()`](read.md) with `allow_exceptions == false` left discarded.
On a big-endian target, throws `type_error.320` with a different message instead: the image format is little-endian
only (see [Notes](#notes)).
Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or
the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets
[`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to. [`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to.
@@ -44,9 +41,6 @@ the decoded strings of the image would individually reach 4 GiB -- the same 32-b
[json.exception.type_error.320] cannot save a discarded json_document [json.exception.type_error.320] cannot save a discarded json_document
``` ```
``` ```
[json.exception.type_error.320] json_document images need a little-endian target
```
```
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document [json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
``` ```
@@ -70,9 +64,9 @@ else -- see [`load`'s Exceptions](load.md#exceptions).
the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original
JSON text if you need to read a saved document back with a future library version. JSON text if you need to read a saved document back with a future library version.
**Little-endian only.** The image is written as raw little-endian bytes, with no byte-swapping. `save()` (and **Little-endian targets only.** The image is written as raw little-endian bytes, with no byte-swapping. On a
[`load`](load.md)) throw `type_error.320` on a big-endian target rather than silently produce bytes a big-endian big-endian target, `save()` and [`load`](load.md) do not exist: a call fails to compile rather than produce bytes a
reader could not interpret correctly. big-endian reader could not interpret correctly.
## Examples ## Examples
@@ -18,7 +18,7 @@ template<typename V>
view_type set(const json_pointer& ptr, V&& value); 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)) Only an **editable** document (`#!cpp view_mode::editable`, 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`). 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`. 1. Replaces the value `target` refers to with `value`.
@@ -3,7 +3,7 @@
<small>Defined in header `<nlohmann/json_view.hpp>`</small> <small>Defined in header `<nlohmann/json_view.hpp>`</small>
```cpp ```cpp
template<typename BasicJsonType, bool Editable = false> template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
class basic_json_view; class basic_json_view;
``` ```
@@ -37,7 +37,7 @@ Moving the document itself does not invalidate its views: the index is heap-allo
`#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or `#!cpp get_ref()`; see [`get`](get.md). `#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or `#!cpp get_ref()`; see [`get`](get.md).
`basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an `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 **editable** document (`#!cpp view_mode::editable`) sees every edit made through
[`basic_json_document::set`](../basic_json_document/set.md) and [`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 [`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 still refers to it -- including ones taken before the change -- reads the new value. See
@@ -49,9 +49,10 @@ still refers to it -- including ones taken before the change -- reads the new va
: a specialization of [`basic_json`](../basic_json/index.md), matching the : a specialization of [`basic_json`](../basic_json/index.md), matching the
[`basic_json_document`](../basic_json_document/index.md) the view was taken from. [`basic_json_document`](../basic_json_document/index.md) the view was taken from.
`Editable` `Mode`
: whether the view is of an editable document, matching the [`basic_json_document`](../basic_json_document/index.md) : a [`view_mode`](../view_mode.md): whether the view is of an editable document, matching the
it was taken from (optional, `#!cpp false` by default). See [Edits](../basic_json_document/index.md#edits). [`basic_json_document`](../basic_json_document/index.md) it was taken from (optional, `#!cpp view_mode::read_only` by
default). See [Edits](../basic_json_document/index.md#edits).
## Specializations ## Specializations
@@ -3,7 +3,7 @@
<small>Defined in header `<nlohmann/json_view.hpp>`</small> <small>Defined in header `<nlohmann/json_view.hpp>`</small>
```cpp ```cpp
using json_editable_document = basic_json_document<json, true>; using json_editable_document = basic_json_document<json, view_mode::editable>;
``` ```
This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default
+1 -1
View File
@@ -3,7 +3,7 @@
<small>Defined in header `<nlohmann/json_view.hpp>`</small> <small>Defined in header `<nlohmann/json_view.hpp>`</small>
```cpp ```cpp
using json_editable_view = basic_json_view<json, true>; using json_editable_view = basic_json_view<json, view_mode::editable>;
``` ```
This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a
@@ -3,7 +3,7 @@
<small>Defined in header `<nlohmann/json_view.hpp>`</small> <small>Defined in header `<nlohmann/json_view.hpp>`</small>
```cpp ```cpp
using ordered_json_editable_document = basic_json_document<ordered_json, true>; using ordered_json_editable_document = basic_json_document<ordered_json, view_mode::editable>;
``` ```
This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the
@@ -3,7 +3,7 @@
<small>Defined in header `<nlohmann/json_view.hpp>`</small> <small>Defined in header `<nlohmann/json_view.hpp>`</small>
```cpp ```cpp
using ordered_json_editable_view = basic_json_view<ordered_json, true>; using ordered_json_editable_view = basic_json_view<ordered_json, view_mode::editable>;
``` ```
This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an
+80
View File
@@ -0,0 +1,80 @@
# <small>nlohmann::</small>view_mode
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
```cpp
enum class view_mode : std::uint8_t
{
read_only,
editable
};
```
The second template parameter of [`basic_json_document`](basic_json_document/index.md) and
[`basic_json_view`](basic_json_view/index.md): whether a document can be changed after parsing, and whether a view is
of such a document.
`read_only`
: the default: the document is parsed once and then only read. It carries none of the bookkeeping edits need, and
calling [`set`](basic_json_document/set.md), [`push_back`](basic_json_document/push_back.md),
[`insert`](basic_json_document/insert.md), or [`erase`](basic_json_document/erase.md) on it fails to compile.
`editable`
: the document also offers [`set`](basic_json_document/set.md), [`push_back`](basic_json_document/push_back.md),
[`insert`](basic_json_document/insert.md), and [`erase`](basic_json_document/erase.md); its views see every edit,
see [Edits](basic_json_document/index.md#edits).
The shortcuts name both modes: [`json_document`](json_document.md) and [`json_view`](json_view.md) are read-only,
[`json_editable_document`](json_editable_document.md) and [`json_editable_view`](json_editable_view.md) are editable
(and likewise for `ordered_json`).
## Notes
The mode is part of the view's type, and there is no conversion between the two modes: a read-only view reads a
container in place, while a view of an editable document follows the links that edits leave behind. A function that
accepts views therefore names the modes it accepts in its signature:
```cpp
// views of editable documents only
template<typename BasicJsonType>
void f(basic_json_view<BasicJsonType, view_mode::editable> v);
// views of read-only documents only
template<typename BasicJsonType>
void f(basic_json_view<BasicJsonType, view_mode::read_only> v);
// either
template<typename BasicJsonType, view_mode Mode>
void f(basic_json_view<BasicJsonType, Mode> v);
```
A function that takes a [`json_view`](json_view.md) accepts neither an [`ordered_json_view`](ordered_json_view.md)
nor a [`json_editable_view`](json_editable_view.md). Views of the two modes still compare with
[`operator==`](basic_json_view/operator_eq.md).
## Examples
??? example
The example below shows a function template that accepts the views of both modes, and one that accepts only
views of editable documents.
```cpp
--8<-- "examples/view_mode.cpp"
```
Output:
```
--8<-- "examples/view_mode.output"
```
## See also
- [basic_json_document](basic_json_document/index.md) - a parsed JSON text
- [basic_json_view](basic_json_view/index.md) - a handle to one value of a document
- [json_editable_document](json_editable_document.md) - an editable document of `json`
## Version history
Added in version 3.13.0.
@@ -5,7 +5,7 @@ using ordered_json_editable_document = nlohmann::ordered_json_editable_document;
int main() int main()
{ {
// ordered_json_editable_document is basic_json_document<nlohmann::ordered_json, true> // ordered_json_editable_document is basic_json_document<nlohmann::ordered_json, nlohmann::view_mode::editable>
ordered_json_editable_document doc = ordered_json_editable_document::parse(R"({"z": 1, "a": 2, "m": 3})"); 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 doc.set(doc.root(), "b", 4); // set() always appends a new member at the end
+29
View File
@@ -0,0 +1,29 @@
#include <iostream>
#include <nlohmann/json_view.hpp>
using nlohmann::basic_json_view;
using nlohmann::view_mode;
// accepts the views of read-only and of editable documents, of json and of ordered_json
template<typename BasicJsonType, view_mode Mode>
std::size_t count_members(basic_json_view<BasicJsonType, Mode> v)
{
return v.is_object() ? v.size() : 0;
}
// accepts only the views of editable documents
template<typename BasicJsonType>
bool is_edited(basic_json_view<BasicJsonType, view_mode::editable> v)
{
return v.is_string() && v.get_string() == "edited";
}
int main()
{
const nlohmann::json_document doc = nlohmann::json_document::parse(R"({"a": 1, "b": 2})");
nlohmann::json_editable_document edoc = nlohmann::json_editable_document::parse(R"({"a": 1})");
edoc.set(edoc.root()["a"], "edited");
std::cout << count_members(doc.root()) << ' ' << count_members(edoc.root()) << '\n';
std::cout << std::boolalpha << is_edited(edoc.root()["a"]) << '\n';
}
@@ -0,0 +1,2 @@
2 1
true
+6 -5
View File
@@ -252,14 +252,14 @@ the stream's `width`/`fill` for indentation.
## Editing a document ## Editing a document
Everything above is read-only: a `json_document`/`json_view` lets you look at a parsed text without copying it, but 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 not change it. [`basic_json_document<BasicJsonType, view_mode::editable>`](../api/basic_json_document/index.md) -- more conveniently
spelled [`json_editable_document`](../api/json_editable_document.md) or spelled [`json_editable_document`](../api/json_editable_document.md) or
[`ordered_json_editable_document`](../api/ordered_json_editable_document.md) -- also lets you [`ordered_json_editable_document`](../api/ordered_json_editable_document.md) -- also lets you
[`set`](../api/basic_json_document/set.md) a value, [`push_back`](../api/basic_json_document/push_back.md) onto or [`set`](../api/basic_json_document/set.md) a value, [`push_back`](../api/basic_json_document/push_back.md) onto or
[`insert`](../api/basic_json_document/insert.md) into an array, and [`erase`](../api/basic_json_document/erase.md) [`insert`](../api/basic_json_document/insert.md) into an array, and [`erase`](../api/basic_json_document/erase.md)
an object member or an array element, still without ever building a `basic_json` tree for parts you do not touch. an object member or an array element, 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 The [mode](../api/view_mode.md) defaults to `#!cpp view_mode::read_only`, so `json_document`/`ordered_json_document` are unaffected -- they carry
none of the bookkeeping edits need, and calling `set`/`push_back`/`insert`/`erase` on one is a compile error, not a none of the bookkeeping edits need, and calling `set`/`push_back`/`insert`/`erase` on one is a compile error, not a
runtime one. runtime one.
@@ -354,9 +354,10 @@ validation entirely, for an image trusted as much as the process's own memory. S
!!! warning "Experimental" !!! warning "Experimental"
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
stable. It is little-endian only, and tied to the library build that wrote it -- use it to cache a document or to stable. It is little-endian only (on big-endian targets, `save()` and `load()` do not exist), and tied to the
hand one to another process running the *same* build, not as a long-term storage format; keep the original JSON library build that wrote it -- use it to cache a document or to hand one to another process running the *same*
text if a saved document needs to be readable by a future library version. build, not as a long-term storage format; keep the original JSON text if a saved document needs to be readable by
a future library version.
The idea of a document you can read without parsing comes from zero-copy formats such as The idea of a document you can read without parsing comes from zero-copy formats such as
[FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check [FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check
-5
View File
@@ -853,17 +853,12 @@ from JSON text.
[`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an [`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an
[image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document. [image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document.
[`save()`](../api/basic_json_document/save.md) and [`load()`](../api/basic_json_document/load.md) also throw this
exception on a big-endian target, since the image format is little-endian only.
!!! failure "Example messages" !!! failure "Example messages"
``` ```
[json.exception.type_error.320] cannot save a discarded json_document [json.exception.type_error.320] cannot save a discarded json_document
``` ```
```
[json.exception.type_error.320] json_document images need a little-endian target
```
!!! note !!! note
+1
View File
@@ -407,6 +407,7 @@ nav:
- 'ordered_json_editable_view': api/ordered_json_editable_view.md - 'ordered_json_editable_view': api/ordered_json_editable_view.md
- 'ordered_json_view': api/ordered_json_view.md - 'ordered_json_view': api/ordered_json_view.md
- 'ordered_map': api/ordered_map.md - 'ordered_map': api/ordered_map.md
- 'view_mode': api/view_mode.md
- macros: - macros:
- 'Overview': api/macros/index.md - 'Overview': api/macros/index.md
- 'JSON_ASSERT': api/macros/json_assert.md - 'JSON_ASSERT': api/macros/json_assert.md
+1 -1
View File
@@ -102,7 +102,7 @@ enum class exception_id : int
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
document_binary_value = 319, ///< a binary value is stored in a json_document document_binary_value = 319, ///< a binary value is stored in a json_document
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) document_image_unsupported = 320, ///< a discarded json_document cannot be saved as an image
discarded_value_used = 321, ///< a discarded value is used discarded_value_used = 321, ///< a discarded value is used
// out_of_range // out_of_range
array_index_out_of_range = 401, ///< an array index is out of range array_index_out_of_range = 401, ///< an array index is out of range
+1 -1
View File
@@ -978,7 +978,7 @@ indent_done:
std::memcpy(reinterpret_cast<unsigned char*>(n), &first, 8); std::memcpy(reinterpret_cast<unsigned char*>(n), &first, 8);
std::memcpy(reinterpret_cast<unsigned char*>(n) + 8, &second, 8); std::memcpy(reinterpret_cast<unsigned char*>(n) + 8, &second, 8);
#else #else
n->kind = static_cast<std::uint8_t>(k); n->kind = static_cast<node_kind>(k);
n->flags = flags; n->flags = flags;
n->extra = extra; n->extra = extra;
n->off = static_cast<std::uint32_t>(off); n->off = static_cast<std::uint32_t>(off);
@@ -232,7 +232,7 @@ struct document_data
/// following loads. /// following loads.
static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept
{ {
return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n); return NLOHMANN_VIEW_LIKELY(n->kind != node_kind::link) ? n : follow_link(n);
} }
static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept
+19 -21
View File
@@ -26,12 +26,10 @@
#include <nlohmann/detail/view/lookup.hpp> #include <nlohmann/detail/view/lookup.hpp>
#include <nlohmann/detail/view/macro_scope.hpp> #include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp> #include <nlohmann/detail/view/node.hpp>
#include <nlohmann/detail/view/view_mode.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
template<typename BasicJsonType, bool Editable>
class basic_json_view;
namespace detail namespace detail
{ {
namespace view namespace view
@@ -120,13 +118,13 @@ class editor
View set(const View& object, string_view_t key, V&& value) View set(const View& object, string_view_t key, V&& value)
{ {
node* const o = own(object); 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)) if (o->kind != node_kind::object && o->kind != node_kind::null)
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
} }
check_utf8(key.data(), key.size()); check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (o->kind == static_cast<std::uint8_t>(value_t::null)) if (o->kind == node_kind::null)
{ {
become_empty(o, value_t::object); become_empty(o, value_t::object);
} }
@@ -171,7 +169,7 @@ class editor
View set(const View& array, std::size_t idx, V&& value) View set(const View& array, std::size_t idx, V&& value)
{ {
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != node_kind::array)
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
} }
@@ -188,12 +186,12 @@ class editor
View push_back(const View& array, V&& value) View push_back(const View& array, V&& value)
{ {
node* const a = own(array); 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)) if (a->kind != node_kind::array && a->kind != node_kind::null)
{ {
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
} }
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (a->kind == static_cast<std::uint8_t>(value_t::null)) if (a->kind == node_kind::null)
{ {
become_empty(a, value_t::array); become_empty(a, value_t::array);
} }
@@ -212,7 +210,7 @@ class editor
View insert(const View& array, std::size_t idx, V&& value) View insert(const View& array, std::size_t idx, V&& value)
{ {
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != node_kind::array)
{ {
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
} }
@@ -232,7 +230,7 @@ class editor
std::size_t erase(const View& object, string_view_t key) std::size_t erase(const View& object, string_view_t key)
{ {
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object)) if (o->kind != node_kind::object)
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
} }
@@ -250,7 +248,7 @@ class editor
void erase(const View& array, std::size_t idx) void erase(const View& array, std::size_t idx)
{ {
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != node_kind::array)
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
} }
@@ -328,7 +326,7 @@ class editor
{ {
const std::uint8_t linked = n->flags & node_flags::linked; const std::uint8_t linked = n->flags & node_flags::linked;
*n = node{}; *n = node{};
n->kind = static_cast<std::uint8_t>(k); n->kind = static_cast<node_kind>(k);
n->flags = static_cast<std::uint8_t>(node_flags::is_new | linked); n->flags = static_cast<std::uint8_t>(node_flags::is_new | linked);
n->next = 1; n->next = 1;
} }
@@ -405,8 +403,8 @@ class editor
template<typename T> template<typename T>
struct is_view : std::false_type {}; struct is_view : std::false_type {};
template<typename J, bool E> template<typename J, view_mode M>
struct is_view<basic_json_view<J, E>> : std::true_type {}; struct is_view<basic_json_view<J, M>> : std::true_type {};
template<typename V> template<typename V>
encoded encode(V&& v) encoded encode(V&& v)
@@ -421,8 +419,8 @@ class editor
} }
/// a view of any document (copied; nothing is shared with it) /// a view of any document (copied; nothing is shared with it)
template<typename J, bool E> template<typename J, view_mode M>
encoded encode_impl(const basic_json_view<J, E>& v, encode_tag<0> /*view*/) encoded encode_impl(const basic_json_view<J, M>& v, encode_tag<0> /*view*/)
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
{ {
@@ -434,8 +432,8 @@ class editor
r.scalar = copy_scalar(*v.m_doc, *v.m_node); r.scalar = copy_scalar(*v.m_doc, *v.m_node);
return r; return r;
} }
r.region = alloc_nodes(m_doc, count_nodes<E>(*v.m_doc, v.m_node)); r.region = alloc_nodes(m_doc, count_nodes<M == view_mode::editable>(*v.m_doc, v.m_node));
fill_nodes<E>(*v.m_doc, v.m_node, r.region); fill_nodes<M == view_mode::editable>(*v.m_doc, v.m_node, r.region);
edit_state_of(m_doc).regions.emplace(r.region, nullptr); edit_state_of(m_doc).regions.emplace(r.region, nullptr);
return r; return r;
} }
@@ -496,7 +494,7 @@ class editor
static node plain_node(value_t k) noexcept static node plain_node(value_t k) noexcept
{ {
node n{}; node n{};
n.kind = static_cast<std::uint8_t>(k); n.kind = static_cast<node_kind>(k);
n.flags = node_flags::is_new; n.flags = node_flags::is_new;
return n; return n;
} }
@@ -681,7 +679,7 @@ class editor
++r; ++r;
if (is_container(*n)) if (is_container(*n))
{ {
open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast<std::uint8_t>(value_t::object)}); open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object});
} }
// the next value: close finished containers, then step over the key // the next value: close finished containers, then step over the key
for (;;) for (;;)
@@ -730,7 +728,7 @@ class editor
node* const self = out++; node* const self = out++;
*self = plain_node(static_cast<value_t>(n->kind)); *self = plain_node(static_cast<value_t>(n->kind));
self->len = n->len; self->len = n->len;
open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast<std::uint8_t>(value_t::object), self}); open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object, self});
} }
else else
{ {
@@ -27,7 +27,7 @@
// live in chunks that never move; strings and number tokens written by edits // 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 // 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_flags::moved: its elements then live in a separate sequence (a header
// node, then the entries), whose entries link to the values (kind_link). // node, then the entries), whose entries link to the values (node_kind::link).
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -185,7 +185,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra)
return nh; return nh;
} }
reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before) reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before)
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object); const bool object = n->kind == node_kind::object;
const std::size_t used = 1 + (static_cast<std::size_t>(n->len) * (object ? 2 : 1)); const std::size_t used = 1 + (static_cast<std::size_t>(n->len) * (object ? 2 : 1));
const std::size_t cap = used + extra; const std::size_t cap = used + extra;
node* const h = alloc_nodes(d, cap); node* const h = alloc_nodes(d, cap);
@@ -251,7 +251,7 @@ inline node* find_parent(const document_data& d, const node* target)
{ {
return nullptr; // LCOV_EXCL_LINE (the value is inside 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 bool object = c->kind == node_kind::object;
const node* down = nullptr; const node* down = nullptr;
for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;) for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;)
{ {
+14 -16
View File
@@ -117,7 +117,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
r.len = 0; // counted below r.len = 0; // counted below
r.next = 0; // set when the container is complete r.next = 0; // set when the container is complete
stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast<std::uint8_t>(value_t::object)}); stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == node_kind::object});
break; break;
case value_t::string: case value_t::string:
r = string_node(*v); r = string_node(*v);
@@ -138,7 +138,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i'))
{ {
r = node{}; // nan and infinity: null, as dump() writes them r = node{}; // nan and infinity: null, as dump() writes them
r.kind = static_cast<std::uint8_t>(value_t::null); r.kind = node_kind::null;
break; break;
} }
r.off = static_cast<std::uint32_t>(d.size + text_tail.size()); r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
@@ -182,12 +182,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
} }
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no save() elsewhere)
/// the document as an image /// the document as an image
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
std::string text_tail; std::string text_tail;
@@ -221,7 +219,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
// extra of an object, and the same document saves to the same bytes // extra of an object, and the same document saves to the same bytes
for (std::size_t i = 0; i < count; ++i) for (std::size_t i = 0; i < count; ++i)
{ {
if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0) if (nodes[i].kind == node_kind::object && nodes[i].extra != 0)
{ {
std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra)); std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra));
} }
@@ -243,6 +241,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
*o = 0; *o = 0;
return image; return image;
} }
#endif
/// the parts of a number token that the checks need /// the parts of a number token that the checks need
struct number_token struct number_token
@@ -337,7 +336,7 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu
if (static_cast<std::int64_t>(t.int_digits) + t.exponent > 300) if (static_cast<std::int64_t>(t.int_digits) + t.exponent > 300)
{ {
node n{}; node n{};
n.kind = static_cast<std::uint8_t>(value_t::number_float); n.kind = node_kind::number_float;
n.len = static_cast<std::uint32_t>(len); n.len = static_cast<std::uint32_t>(len);
const auto v = float_value<double>(reinterpret_cast<const char*>(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto v = float_value<double>(reinterpret_cast<const char*>(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
return v <= (std::numeric_limits<double>::max)() && v >= -(std::numeric_limits<double>::max)(); return v <= (std::numeric_limits<double>::max)() && v >= -(std::numeric_limits<double>::max)();
@@ -358,7 +357,7 @@ inline bool check_integer(const node& n, const unsigned char* text)
} }
// the token's value is the stored one; number_integer nodes of edits can // the token's value is the stored one; number_integer nodes of edits can
// be non-negative (as basic_json keeps the type of a value) // be non-negative (as basic_json keeps the type of a value)
const bool integer = n.kind == static_cast<std::uint8_t>(value_t::number_integer); const bool integer = n.kind == node_kind::number_integer;
if (t.is_float || t.int_digits > 20 || (t.negative && !integer)) if (t.is_float || t.int_digits > 20 || (t.negative && !integer))
{ {
return false; return false;
@@ -540,7 +539,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
{ {
return false; return false;
} }
if (n.kind != static_cast<std::uint8_t>(value_t::number_float)) if (n.kind != node_kind::number_float)
{ {
return (n.extra >> 8u) == 0; return (n.extra >> 8u) == 0;
} }
@@ -581,7 +580,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
const node& n = nodes[i]; const node& n = nodes[i];
if (!stack.empty() && stack.back().object && stack.back().expect_key) if (!stack.empty() && stack.back().object && stack.back().expect_key)
{ {
if (n.kind != static_cast<std::uint8_t>(value_t::string) || !check_string(n)) if (n.kind != node_kind::string || !check_string(n))
{ {
return false; return false;
} }
@@ -622,7 +621,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
{ {
// (the contents of float tokens are checked later; the token of an // (the contents of float tokens are checked later; the token of an
// integer has at most 256 characters) // integer has at most 256 characters)
if (n.kind == static_cast<std::uint8_t>(value_t::number_float)) if (n.kind == node_kind::number_float)
{ {
floats.push_back(float_range{n.off, n.len, n.extra}); floats.push_back(float_range{n.off, n.len, n.extra});
} }
@@ -640,7 +639,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
{ {
return false; return false;
} }
stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast<std::uint8_t>(value_t::object), true}); stack.push_back(frame{i + n.next, n.len, 0, n.kind == node_kind::object, true});
complete = false; complete = false;
break; break;
} }
@@ -663,13 +662,11 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no load() elsewhere)
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
/// the nodes are copied (so that they are aligned, and edits can change them). /// the nodes are copied (so that they are aligned, and edits can change them).
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
throw_invalid_image("too short"); throw_invalid_image("too short");
@@ -729,7 +726,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
for (std::size_t i = 0; i < count; ++i) for (std::size_t i = 0; i < count; ++i)
{ {
const node& n = d.nodes[i]; const node& n = d.nodes[i];
if (n.kind == static_cast<std::uint8_t>(value_t::object) && n.len >= document_data::index_min_members) if (n.kind == node_kind::object && n.len >= document_data::index_min_members)
{ {
d.large_objects.push_back(static_cast<std::uint32_t>(i)); d.large_objects.push_back(static_cast<std::uint32_t>(i));
} }
@@ -738,6 +735,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building) std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building)
d.discarded = false; d.discarded = false;
} }
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
+1 -1
View File
@@ -182,7 +182,7 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
template<bool Editable> template<bool Editable>
const node* last_child(const document_data& d, const node* container) noexcept 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 std::size_t value_offset = container->kind == node_kind::object ? 1 : 0;
const node* const end = navigation<Editable>::end(d, container); const node* const end = navigation<Editable>::end(d, container);
const node* last = navigation<Editable>::first(d, container); const node* last = navigation<Editable>::first(d, container);
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0)) if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
+1 -1
View File
@@ -101,7 +101,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
{ {
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object); const bool object = n->kind == node_kind::object;
if (object) if (object)
{ {
sax.start_object(n->len); sax.start_object(n->len);
+22 -9
View File
@@ -41,9 +41,21 @@ struct node_flags
static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters
}; };
/// kind of an entry of an edited sequence that stands for a value stored /// the kind of a node: the value_t of its value, or link
/// elsewhere (the address of the value's node is kept in the len/next bytes) enum class node_kind : std::uint8_t
constexpr std::uint8_t kind_link = 10; {
null = static_cast<std::uint8_t>(value_t::null),
object = static_cast<std::uint8_t>(value_t::object),
array = static_cast<std::uint8_t>(value_t::array),
string = static_cast<std::uint8_t>(value_t::string),
boolean = static_cast<std::uint8_t>(value_t::boolean),
number_integer = static_cast<std::uint8_t>(value_t::number_integer),
number_unsigned = static_cast<std::uint8_t>(value_t::number_unsigned),
number_float = static_cast<std::uint8_t>(value_t::number_float),
/// 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)
link = static_cast<std::uint8_t>(value_t::discarded) + 1,
};
/// One entry of the flat index, in document order. An object's members are /// 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 /// stored as key node followed by the value's subtree. Integers keep their
@@ -51,7 +63,7 @@ constexpr std::uint8_t kind_link = 10;
/// always the next one, and the token length follows from `extra`). /// always the next one, and the token length follows from `extra`).
struct node struct node
{ {
std::uint8_t kind; ///< value_t, or kind_link node_kind kind; ///< value_t, or link
std::uint8_t flags; ///< node_flags std::uint8_t flags; ///< node_flags
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); 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 off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
@@ -60,8 +72,9 @@ struct node
}; };
static_assert(sizeof(node) == 16, "node must stay 16 bytes"); static_assert(sizeof(node) == 16, "node must stay 16 bytes");
// the node kinds are value_t values; the test below (object and array are 1 // the node kinds are value_t values, and images store them as bytes; the test
// and 2) and those of the number kinds depend on this numbering // below (object and array are 1 and 2) and those of the number kinds depend
// on this numbering
static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::uint8_t>(value_t::object) == 1 static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::uint8_t>(value_t::object) == 1
&& static_cast<std::uint8_t>(value_t::array) == 2 && static_cast<std::uint8_t>(value_t::string) == 3 && static_cast<std::uint8_t>(value_t::array) == 2 && static_cast<std::uint8_t>(value_t::string) == 3
&& static_cast<std::uint8_t>(value_t::boolean) == 4 && static_cast<std::uint8_t>(value_t::number_integer) == 5 && static_cast<std::uint8_t>(value_t::boolean) == 4 && static_cast<std::uint8_t>(value_t::number_integer) == 5
@@ -86,7 +99,7 @@ inline void make_link(node& n, node* target) noexcept
{ {
target->flags = static_cast<std::uint8_t>(target->flags | node_flags::linked); target->flags = static_cast<std::uint8_t>(target->flags | node_flags::linked);
n = node{}; n = node{};
n.kind = kind_link; n.kind = node_kind::link;
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
} }
@@ -116,8 +129,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noex
/// token length of a number node /// token length of a number node
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
{ {
return n.kind == static_cast<std::uint8_t>(value_t::number_float) ? n.len return n.kind == node_kind::number_float ? n.len
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u); : (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u);
} }
/// estimated number of nodes for an input of `size` bytes (one node per ~12 /// estimated number of nodes for an input of `size` bytes (one node per ~12
+1 -1
View File
@@ -874,7 +874,7 @@ class view_serializer
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
{ {
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object); const bool object = n->kind == node_kind::object;
room(8); room(8);
if (n->len == 0) if (n->len == 0)
{ {
@@ -0,0 +1,27 @@
// __ _____ _____ _____
// __| | __| | | | 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 <cstdint> // uint8_t
#include <nlohmann/json.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
/// whether a basic_json_document and its views can be edited
enum class view_mode : std::uint8_t
{
read_only, ///< parsed once, then only read (the default)
editable, ///< also has set(), push_back(), insert(), and erase()
};
template<typename BasicJsonType, view_mode Mode>
class basic_json_view;
NLOHMANN_JSON_NAMESPACE_END
+37 -25
View File
@@ -84,6 +84,7 @@
#include <nlohmann/detail/view/serializer.hpp> #include <nlohmann/detail/view/serializer.hpp>
#include <nlohmann/detail/view/string_ref.hpp> #include <nlohmann/detail/view/string_ref.hpp>
#include <nlohmann/detail/view/value.hpp> #include <nlohmann/detail/view/value.hpp>
#include <nlohmann/detail/view/view_mode.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -93,13 +94,16 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
Trivially copyable (two pointers). Valid as long as the document is alive and 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. has not been re-parsed, and as long as a borrowed source text is alive.
*/ */
template<typename BasicJsonType, bool Editable = false> template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
class basic_json_view class basic_json_view
{ {
/// whether this is a view of an editable document
static constexpr bool is_editable = Mode == view_mode::editable;
using node = detail::view::node; using node = detail::view::node;
using document_data = detail::view::document_data; using document_data = detail::view::document_data;
/// how the index is walked (with edits only for editable documents) /// how the index is walked (with edits only for editable documents)
using navigation = detail::view::navigation<Editable>; using navigation = detail::view::navigation<is_editable>;
public: public:
using value_t = detail::value_t; using value_t = detail::value_t;
@@ -446,7 +450,7 @@ class basic_json_view
if (is_structured() && m_node->len != 0) if (is_structured() && m_node->len != 0)
{ {
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0)) return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0))); : basic_json_view(m_doc, navigation::value(detail::view::last_child<is_editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
} }
return front(); return front();
} }
@@ -504,7 +508,7 @@ class basic_json_view
/// more than once); 0 if this is not an object /// more than once); 0 if this is not an object
size_type count(string_view_t key) const size_type count(string_view_t key) const
{ {
return is_object() ? detail::view::count_members<Editable>(*m_doc, m_node, key.data(), key.size()) : 0; return is_object() ? detail::view::count_members<is_editable>(*m_doc, m_node, key.data(), key.size()) : 0;
} }
size_type count(const char* key) const size_type count(const char* key) const
@@ -646,7 +650,7 @@ class basic_json_view
} }
else else
{ {
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node); detail::view::view_serializer<BasicJsonType, is_editable>(*m_doc, out, estimate, style).dump(m_node);
} }
return out; return out;
} }
@@ -682,14 +686,14 @@ class basic_json_view
} }
/// a view of an editable document compares with one of a read-only document /// 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 > template < view_mode M, typename std::enable_if < M != Mode, int >::type = 0 >
friend bool operator==(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b) friend bool operator==(const basic_json_view& a, const basic_json_view<BasicJsonType, M>& b)
{ {
return detail::view::equal<BasicJsonType>(side(a), detail::view::view_side<BasicJsonType, basic_json_view<BasicJsonType, E>>(b)); return detail::view::equal<BasicJsonType>(side(a), detail::view::view_side<BasicJsonType, basic_json_view<BasicJsonType, M>>(b));
} }
template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > template < view_mode M, typename std::enable_if < M != Mode, int >::type = 0 >
friend bool operator!=(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b) friend bool operator!=(const basic_json_view& a, const basic_json_view<BasicJsonType, M>& b)
{ {
return !(a == b); return !(a == b);
} }
@@ -727,7 +731,7 @@ class basic_json_view
{ {
return BasicJsonType(value_t::discarded); return BasicJsonType(value_t::discarded);
} }
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node); return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, is_editable>(*m_doc, m_node);
} }
/// byte offset of this value in the source text (for strings: of the /// byte offset of this value in the source text (for strings: of the
@@ -740,8 +744,8 @@ class basic_json_view
} }
private: private:
template<typename, bool> friend class basic_json_document; template<typename, view_mode> friend class basic_json_document;
template<typename, bool> friend class basic_json_view; template<typename, view_mode> friend class basic_json_view;
template<typename, typename> friend class detail::view::editor; template<typename, typename> friend class detail::view::editor;
friend iterator; friend iterator;
@@ -757,11 +761,11 @@ class basic_json_view
} }
/// the template argument as a runtime condition: testing it directly in /// the template argument as a runtime condition: testing it directly in
/// `Editable && ...` makes the condition constant, which MSVC reports as /// `is_editable && ...` makes the condition constant, which MSVC reports as
/// C4127 /// C4127
static bool editable() noexcept static bool editable() noexcept
{ {
return Editable; return is_editable;
} }
/// the number of source bytes of this value (estimated for values with /// the number of source bytes of this value (estimated for values with
@@ -810,7 +814,7 @@ class basic_json_view
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()); const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); 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()); const node* const k = detail::view::find_member<is_editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
} }
@@ -824,7 +828,7 @@ class basic_json_view
/// the member of this object with @a key (the last one if the key occurs more than once), or nullptr /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
{ {
return find_key(key, std::integral_constant<bool, Editable> {}); return find_key(key, std::integral_constant<bool, is_editable> {});
} }
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
@@ -846,7 +850,7 @@ class basic_json_view
{ {
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx)); return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
} }
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))); return basic_json_view(m_doc, navigation::value(detail::view::element_at<is_editable>(*m_doc, m_node, idx)));
} }
// --- get() dispatch --- // --- get() dispatch ---
@@ -952,16 +956,19 @@ 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 document. Owned parses (parse_copy, rvalue std::string, streams, and inputs
that are not contiguous byte ranges) keep their own copy. that are not contiguous byte ranges) keep their own copy.
*/ */
template<typename BasicJsonType, bool Editable = false> template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
class basic_json_document class basic_json_document
{ {
/// whether the document can be edited
static constexpr bool is_editable = Mode == view_mode::editable;
using document_data = detail::view::document_data; using document_data = detail::view::document_data;
static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8, static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8,
"json_view supports 64-bit integer types only"); "json_view supports 64-bit integer types only");
public: public:
using view_type = basic_json_view<BasicJsonType, Editable>; using view_type = basic_json_view<BasicJsonType, Mode>;
using value_t = detail::value_t; using value_t = detail::value_t;
/// an empty (discarded) document /// an empty (discarded) document
@@ -1175,6 +1182,10 @@ class basic_json_document
/// how load() checks an image (full, bounds, or none) /// how load() checks an image (full, bounds, or none)
using image_check = detail::view::image_check; using image_check = detail::view::image_check;
#if NLOHMANN_VIEW_LITTLE_ENDIAN
// (the image format is little-endian: big-endian targets have no save()
// and load(), so that a call fails to compile instead of always throwing)
/// The document as an image that load() reads without parsing: the node /// The document as an image that load() reads without parsing: the node
/// index, the text, and the decoded strings. An edited document is /// index, the text, and the decoded strings. An edited document is
/// written in its current state (floats that are not finite become null, /// written in its current state (floats that are not finite become null,
@@ -1216,6 +1227,7 @@ class basic_json_document
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
return d; return d;
} }
#endif
/////////// ///////////
// edits // // edits //
@@ -1357,7 +1369,7 @@ class basic_json_document
detail::view::editor<BasicJsonType, view_type> editor() 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)"); static_assert(is_editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, view_mode::editable> (json_editable_document)");
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
@@ -1642,13 +1654,13 @@ using ordered_json_document = basic_json_document<ordered_json>;
/// a value of an ordered_json_document /// a value of an ordered_json_document
using ordered_json_view = basic_json_view<ordered_json>; using ordered_json_view = basic_json_view<ordered_json>;
/// an editable parsed JSON text for json /// an editable parsed JSON text for json
using json_editable_document = basic_json_document<json, true>; using json_editable_document = basic_json_document<json, view_mode::editable>;
/// a value of a json_editable_document /// a value of a json_editable_document
using json_editable_view = basic_json_view<json, true>; using json_editable_view = basic_json_view<json, view_mode::editable>;
/// an editable parsed JSON text for ordered_json /// an editable parsed JSON text for ordered_json
using ordered_json_editable_document = basic_json_document<ordered_json, true>; using ordered_json_editable_document = basic_json_document<ordered_json, view_mode::editable>;
/// a value of an ordered_json_editable_document /// a value of an ordered_json_editable_document
using ordered_json_editable_view = basic_json_view<ordered_json, true>; using ordered_json_editable_view = basic_json_view<ordered_json, view_mode::editable>;
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+1 -1
View File
@@ -5565,7 +5565,7 @@ enum class exception_id : int
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
document_binary_value = 319, ///< a binary value is stored in a json_document document_binary_value = 319, ///< a binary value is stored in a json_document
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target) document_image_unsupported = 320, ///< a discarded json_document cannot be saved as an image
discarded_value_used = 321, ///< a discarded value is used discarded_value_used = 321, ///< a discarded value is used
// out_of_range // out_of_range
array_index_out_of_range = 401, ///< an array index is out of range array_index_out_of_range = 401, ///< an array index is out of range
+127 -77
View File
@@ -239,9 +239,21 @@ struct node_flags
static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters
}; };
/// kind of an entry of an edited sequence that stands for a value stored /// the kind of a node: the value_t of its value, or link
/// elsewhere (the address of the value's node is kept in the len/next bytes) enum class node_kind : std::uint8_t
constexpr std::uint8_t kind_link = 10; {
null = static_cast<std::uint8_t>(value_t::null),
object = static_cast<std::uint8_t>(value_t::object),
array = static_cast<std::uint8_t>(value_t::array),
string = static_cast<std::uint8_t>(value_t::string),
boolean = static_cast<std::uint8_t>(value_t::boolean),
number_integer = static_cast<std::uint8_t>(value_t::number_integer),
number_unsigned = static_cast<std::uint8_t>(value_t::number_unsigned),
number_float = static_cast<std::uint8_t>(value_t::number_float),
/// 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)
link = static_cast<std::uint8_t>(value_t::discarded) + 1,
};
/// One entry of the flat index, in document order. An object's members are /// 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 /// stored as key node followed by the value's subtree. Integers keep their
@@ -249,7 +261,7 @@ constexpr std::uint8_t kind_link = 10;
/// always the next one, and the token length follows from `extra`). /// always the next one, and the token length follows from `extra`).
struct node struct node
{ {
std::uint8_t kind; ///< value_t, or kind_link node_kind kind; ///< value_t, or link
std::uint8_t flags; ///< node_flags std::uint8_t flags; ///< node_flags
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0 std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); 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 off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
@@ -258,8 +270,9 @@ struct node
}; };
static_assert(sizeof(node) == 16, "node must stay 16 bytes"); static_assert(sizeof(node) == 16, "node must stay 16 bytes");
// the node kinds are value_t values; the test below (object and array are 1 // the node kinds are value_t values, and images store them as bytes; the test
// and 2) and those of the number kinds depend on this numbering // below (object and array are 1 and 2) and those of the number kinds depend
// on this numbering
static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::uint8_t>(value_t::object) == 1 static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::uint8_t>(value_t::object) == 1
&& static_cast<std::uint8_t>(value_t::array) == 2 && static_cast<std::uint8_t>(value_t::string) == 3 && static_cast<std::uint8_t>(value_t::array) == 2 && static_cast<std::uint8_t>(value_t::string) == 3
&& static_cast<std::uint8_t>(value_t::boolean) == 4 && static_cast<std::uint8_t>(value_t::number_integer) == 5 && static_cast<std::uint8_t>(value_t::boolean) == 4 && static_cast<std::uint8_t>(value_t::number_integer) == 5
@@ -284,7 +297,7 @@ inline void make_link(node& n, node* target) noexcept
{ {
target->flags = static_cast<std::uint8_t>(target->flags | node_flags::linked); target->flags = static_cast<std::uint8_t>(target->flags | node_flags::linked);
n = node{}; n = node{};
n.kind = kind_link; n.kind = node_kind::link;
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
} }
@@ -314,8 +327,8 @@ NLOHMANN_VIEW_ALWAYS_INLINE void set_integer_bits(node& n, std::uint64_t v) noex
/// token length of a number node /// token length of a number node
NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept NLOHMANN_VIEW_ALWAYS_INLINE std::uint32_t number_length(const node& n) noexcept
{ {
return n.kind == static_cast<std::uint8_t>(value_t::number_float) ? n.len return n.kind == node_kind::number_float ? n.len
: (n.extra & 0xFFu) + (n.kind == static_cast<std::uint8_t>(value_t::number_integer) ? 1u : 0u); : (n.extra & 0xFFu) + (n.kind == node_kind::number_integer ? 1u : 0u);
} }
/// estimated number of nodes for an input of `size` bytes (one node per ~12 /// estimated number of nodes for an input of `size` bytes (one node per ~12
@@ -547,7 +560,7 @@ struct document_data
/// following loads. /// following loads.
static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept
{ {
return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n); return NLOHMANN_VIEW_LIKELY(n->kind != node_kind::link) ? n : follow_link(n);
} }
static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept
@@ -2169,7 +2182,7 @@ indent_done:
std::memcpy(reinterpret_cast<unsigned char*>(n), &first, 8); std::memcpy(reinterpret_cast<unsigned char*>(n), &first, 8);
std::memcpy(reinterpret_cast<unsigned char*>(n) + 8, &second, 8); std::memcpy(reinterpret_cast<unsigned char*>(n) + 8, &second, 8);
#else #else
n->kind = static_cast<std::uint8_t>(k); n->kind = static_cast<node_kind>(k);
n->flags = flags; n->flags = flags;
n->extra = extra; n->extra = extra;
n->off = static_cast<std::uint32_t>(off); n->off = static_cast<std::uint32_t>(off);
@@ -3001,7 +3014,7 @@ NLOHMANN_JSON_NAMESPACE_END
// live in chunks that never move; strings and number tokens written by edits // 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 // 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_flags::moved: its elements then live in a separate sequence (a header
// node, then the entries), whose entries link to the values (kind_link). // node, then the entries), whose entries link to the values (node_kind::link).
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
@@ -3159,7 +3172,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra)
return nh; return nh;
} }
reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before) reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before)
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object); const bool object = n->kind == node_kind::object;
const std::size_t used = 1 + (static_cast<std::size_t>(n->len) * (object ? 2 : 1)); const std::size_t used = 1 + (static_cast<std::size_t>(n->len) * (object ? 2 : 1));
const std::size_t cap = used + extra; const std::size_t cap = used + extra;
node* const h = alloc_nodes(d, cap); node* const h = alloc_nodes(d, cap);
@@ -3225,7 +3238,7 @@ inline node* find_parent(const document_data& d, const node* target)
{ {
return nullptr; // LCOV_EXCL_LINE (the value is inside 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 bool object = c->kind == node_kind::object;
const node* down = nullptr; const node* down = nullptr;
for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;) for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;)
{ {
@@ -3692,7 +3705,7 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
template<bool Editable> template<bool Editable>
const node* last_child(const document_data& d, const node* container) noexcept 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 std::size_t value_offset = container->kind == node_kind::object ? 1 : 0;
const node* const end = navigation<Editable>::end(d, container); const node* const end = navigation<Editable>::end(d, container);
const node* last = navigation<Editable>::first(d, container); const node* last = navigation<Editable>::first(d, container);
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0)) if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
@@ -3714,12 +3727,38 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/node.hpp> // #include <nlohmann/detail/view/node.hpp>
// #include <nlohmann/detail/view/view_mode.hpp>
// __ _____ _____ _____
// __| | __| | | | 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
#include <cstdint> // uint8_t
// #include <nlohmann/json.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
template<typename BasicJsonType, bool Editable> /// whether a basic_json_document and its views can be edited
enum class view_mode : std::uint8_t
{
read_only, ///< parsed once, then only read (the default)
editable, ///< also has set(), push_back(), insert(), and erase()
};
template<typename BasicJsonType, view_mode Mode>
class basic_json_view; class basic_json_view;
NLOHMANN_JSON_NAMESPACE_END
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
{ {
namespace view namespace view
@@ -3808,13 +3847,13 @@ class editor
View set(const View& object, string_view_t key, V&& value) View set(const View& object, string_view_t key, V&& value)
{ {
node* const o = own(object); 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)) if (o->kind != node_kind::object && o->kind != node_kind::null)
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name()); throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
} }
check_utf8(key.data(), key.size()); check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (o->kind == static_cast<std::uint8_t>(value_t::null)) if (o->kind == node_kind::null)
{ {
become_empty(o, value_t::object); become_empty(o, value_t::object);
} }
@@ -3859,7 +3898,7 @@ class editor
View set(const View& array, std::size_t idx, V&& value) View set(const View& array, std::size_t idx, V&& value)
{ {
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != node_kind::array)
{ {
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name()); throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
} }
@@ -3876,12 +3915,12 @@ class editor
View push_back(const View& array, V&& value) View push_back(const View& array, V&& value)
{ {
node* const a = own(array); 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)) if (a->kind != node_kind::array && a->kind != node_kind::null)
{ {
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name()); throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
} }
const encoded e = encode(std::forward<V>(value)); const encoded e = encode(std::forward<V>(value));
if (a->kind == static_cast<std::uint8_t>(value_t::null)) if (a->kind == node_kind::null)
{ {
become_empty(a, value_t::array); become_empty(a, value_t::array);
} }
@@ -3900,7 +3939,7 @@ class editor
View insert(const View& array, std::size_t idx, V&& value) View insert(const View& array, std::size_t idx, V&& value)
{ {
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != node_kind::array)
{ {
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name()); throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
} }
@@ -3920,7 +3959,7 @@ class editor
std::size_t erase(const View& object, string_view_t key) std::size_t erase(const View& object, string_view_t key)
{ {
node* const o = own(object); node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object)) if (o->kind != node_kind::object)
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name()); throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
} }
@@ -3938,7 +3977,7 @@ class editor
void erase(const View& array, std::size_t idx) void erase(const View& array, std::size_t idx)
{ {
node* const a = own(array); node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array)) if (a->kind != node_kind::array)
{ {
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name()); throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
} }
@@ -4016,7 +4055,7 @@ class editor
{ {
const std::uint8_t linked = n->flags & node_flags::linked; const std::uint8_t linked = n->flags & node_flags::linked;
*n = node{}; *n = node{};
n->kind = static_cast<std::uint8_t>(k); n->kind = static_cast<node_kind>(k);
n->flags = static_cast<std::uint8_t>(node_flags::is_new | linked); n->flags = static_cast<std::uint8_t>(node_flags::is_new | linked);
n->next = 1; n->next = 1;
} }
@@ -4093,8 +4132,8 @@ class editor
template<typename T> template<typename T>
struct is_view : std::false_type {}; struct is_view : std::false_type {};
template<typename J, bool E> template<typename J, view_mode M>
struct is_view<basic_json_view<J, E>> : std::true_type {}; struct is_view<basic_json_view<J, M>> : std::true_type {};
template<typename V> template<typename V>
encoded encode(V&& v) encoded encode(V&& v)
@@ -4109,8 +4148,8 @@ class editor
} }
/// a view of any document (copied; nothing is shared with it) /// a view of any document (copied; nothing is shared with it)
template<typename J, bool E> template<typename J, view_mode M>
encoded encode_impl(const basic_json_view<J, E>& v, encode_tag<0> /*view*/) encoded encode_impl(const basic_json_view<J, M>& v, encode_tag<0> /*view*/)
{ {
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
{ {
@@ -4122,8 +4161,8 @@ class editor
r.scalar = copy_scalar(*v.m_doc, *v.m_node); r.scalar = copy_scalar(*v.m_doc, *v.m_node);
return r; return r;
} }
r.region = alloc_nodes(m_doc, count_nodes<E>(*v.m_doc, v.m_node)); r.region = alloc_nodes(m_doc, count_nodes<M == view_mode::editable>(*v.m_doc, v.m_node));
fill_nodes<E>(*v.m_doc, v.m_node, r.region); fill_nodes<M == view_mode::editable>(*v.m_doc, v.m_node, r.region);
edit_state_of(m_doc).regions.emplace(r.region, nullptr); edit_state_of(m_doc).regions.emplace(r.region, nullptr);
return r; return r;
} }
@@ -4184,7 +4223,7 @@ class editor
static node plain_node(value_t k) noexcept static node plain_node(value_t k) noexcept
{ {
node n{}; node n{};
n.kind = static_cast<std::uint8_t>(k); n.kind = static_cast<node_kind>(k);
n.flags = node_flags::is_new; n.flags = node_flags::is_new;
return n; return n;
} }
@@ -4369,7 +4408,7 @@ class editor
++r; ++r;
if (is_container(*n)) if (is_container(*n))
{ {
open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast<std::uint8_t>(value_t::object)}); open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object});
} }
// the next value: close finished containers, then step over the key // the next value: close finished containers, then step over the key
for (;;) for (;;)
@@ -4418,7 +4457,7 @@ class editor
node* const self = out++; node* const self = out++;
*self = plain_node(static_cast<value_t>(n->kind)); *self = plain_node(static_cast<value_t>(n->kind));
self->len = n->len; self->len = n->len;
open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == static_cast<std::uint8_t>(value_t::object), self}); open.push_back(frame{walk::first(d, n), walk::end(d, n), n->kind == node_kind::object, self});
} }
else else
{ {
@@ -4903,7 +4942,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off; r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
r.len = 0; // counted below r.len = 0; // counted below
r.next = 0; // set when the container is complete r.next = 0; // set when the container is complete
stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast<std::uint8_t>(value_t::object)}); stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == node_kind::object});
break; break;
case value_t::string: case value_t::string:
r = string_node(*v); r = string_node(*v);
@@ -4924,7 +4963,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i')) if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i'))
{ {
r = node{}; // nan and infinity: null, as dump() writes them r = node{}; // nan and infinity: null, as dump() writes them
r.kind = static_cast<std::uint8_t>(value_t::null); r.kind = node_kind::null;
break; break;
} }
r.off = static_cast<std::uint32_t>(d.size + text_tail.size()); r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
@@ -4968,12 +5007,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
} }
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no save() elsewhere)
/// the document as an image /// the document as an image
inline std::vector<std::uint8_t> save_image(const document_data& d) inline std::vector<std::uint8_t> save_image(const document_data& d)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
std::vector<node> compacted; std::vector<node> compacted;
std::string text_tail; std::string text_tail;
@@ -5007,7 +5044,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
// extra of an object, and the same document saves to the same bytes // extra of an object, and the same document saves to the same bytes
for (std::size_t i = 0; i < count; ++i) for (std::size_t i = 0; i < count; ++i)
{ {
if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0) if (nodes[i].kind == node_kind::object && nodes[i].extra != 0)
{ {
std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra)); std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra));
} }
@@ -5029,6 +5066,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
*o = 0; *o = 0;
return image; return image;
} }
#endif
/// the parts of a number token that the checks need /// the parts of a number token that the checks need
struct number_token struct number_token
@@ -5123,7 +5161,7 @@ inline bool float_token_finite(const unsigned char* s, std::size_t len, const nu
if (static_cast<std::int64_t>(t.int_digits) + t.exponent > 300) if (static_cast<std::int64_t>(t.int_digits) + t.exponent > 300)
{ {
node n{}; node n{};
n.kind = static_cast<std::uint8_t>(value_t::number_float); n.kind = node_kind::number_float;
n.len = static_cast<std::uint32_t>(len); n.len = static_cast<std::uint32_t>(len);
const auto v = float_value<double>(reinterpret_cast<const char*>(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto v = float_value<double>(reinterpret_cast<const char*>(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
return v <= (std::numeric_limits<double>::max)() && v >= -(std::numeric_limits<double>::max)(); return v <= (std::numeric_limits<double>::max)() && v >= -(std::numeric_limits<double>::max)();
@@ -5144,7 +5182,7 @@ inline bool check_integer(const node& n, const unsigned char* text)
} }
// the token's value is the stored one; number_integer nodes of edits can // the token's value is the stored one; number_integer nodes of edits can
// be non-negative (as basic_json keeps the type of a value) // be non-negative (as basic_json keeps the type of a value)
const bool integer = n.kind == static_cast<std::uint8_t>(value_t::number_integer); const bool integer = n.kind == node_kind::number_integer;
if (t.is_float || t.int_digits > 20 || (t.negative && !integer)) if (t.is_float || t.int_digits > 20 || (t.negative && !integer))
{ {
return false; return false;
@@ -5326,7 +5364,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
{ {
return false; return false;
} }
if (n.kind != static_cast<std::uint8_t>(value_t::number_float)) if (n.kind != node_kind::number_float)
{ {
return (n.extra >> 8u) == 0; return (n.extra >> 8u) == 0;
} }
@@ -5367,7 +5405,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
const node& n = nodes[i]; const node& n = nodes[i];
if (!stack.empty() && stack.back().object && stack.back().expect_key) if (!stack.empty() && stack.back().object && stack.back().expect_key)
{ {
if (n.kind != static_cast<std::uint8_t>(value_t::string) || !check_string(n)) if (n.kind != node_kind::string || !check_string(n))
{ {
return false; return false;
} }
@@ -5408,7 +5446,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
{ {
// (the contents of float tokens are checked later; the token of an // (the contents of float tokens are checked later; the token of an
// integer has at most 256 characters) // integer has at most 256 characters)
if (n.kind == static_cast<std::uint8_t>(value_t::number_float)) if (n.kind == node_kind::number_float)
{ {
floats.push_back(float_range{n.off, n.len, n.extra}); floats.push_back(float_range{n.off, n.len, n.extra});
} }
@@ -5426,7 +5464,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
{ {
return false; return false;
} }
stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast<std::uint8_t>(value_t::object), true}); stack.push_back(frame{i + n.next, n.len, 0, n.kind == node_kind::object, true});
complete = false; complete = false;
break; break;
} }
@@ -5449,13 +5487,11 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what)); throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
} }
#if NLOHMANN_VIEW_LITTLE_ENDIAN // (the image format is little-endian: no load() elsewhere)
/// Read an image into d. The text and the decoded strings stay in the image; /// Read an image into d. The text and the decoded strings stay in the image;
/// the nodes are copied (so that they are aligned, and edits can change them). /// the nodes are copied (so that they are aligned, and edits can change them).
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check) inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{ {
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
if (image == nullptr || size < sizeof(image_header)) if (image == nullptr || size < sizeof(image_header))
{ {
throw_invalid_image("too short"); throw_invalid_image("too short");
@@ -5515,7 +5551,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
for (std::size_t i = 0; i < count; ++i) for (std::size_t i = 0; i < count; ++i)
{ {
const node& n = d.nodes[i]; const node& n = d.nodes[i];
if (n.kind == static_cast<std::uint8_t>(value_t::object) && n.len >= document_data::index_min_members) if (n.kind == node_kind::object && n.len >= document_data::index_min_members)
{ {
d.large_objects.push_back(static_cast<std::uint32_t>(i)); d.large_objects.push_back(static_cast<std::uint32_t>(i));
} }
@@ -5524,6 +5560,7 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building) std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building)
d.discarded = false; d.discarded = false;
} }
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
@@ -6108,7 +6145,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
{ {
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object); const bool object = n->kind == node_kind::object;
if (object) if (object)
{ {
sax.start_object(n->len); sax.start_object(n->len);
@@ -7222,7 +7259,7 @@ class view_serializer
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
{ {
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object); const bool object = n->kind == node_kind::object;
room(8); room(8);
if (n->len == 0) if (n->len == 0)
{ {
@@ -7773,6 +7810,8 @@ struct is_direct_value<std::unordered_map<K, V, H, E, A>, BasicJsonType, View> :
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/view/view_mode.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -7782,13 +7821,16 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
Trivially copyable (two pointers). Valid as long as the document is alive and 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. has not been re-parsed, and as long as a borrowed source text is alive.
*/ */
template<typename BasicJsonType, bool Editable = false> template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
class basic_json_view class basic_json_view
{ {
/// whether this is a view of an editable document
static constexpr bool is_editable = Mode == view_mode::editable;
using node = detail::view::node; using node = detail::view::node;
using document_data = detail::view::document_data; using document_data = detail::view::document_data;
/// how the index is walked (with edits only for editable documents) /// how the index is walked (with edits only for editable documents)
using navigation = detail::view::navigation<Editable>; using navigation = detail::view::navigation<is_editable>;
public: public:
using value_t = detail::value_t; using value_t = detail::value_t;
@@ -8135,7 +8177,7 @@ class basic_json_view
if (is_structured() && m_node->len != 0) if (is_structured() && m_node->len != 0)
{ {
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0)) return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0))); : basic_json_view(m_doc, navigation::value(detail::view::last_child<is_editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
} }
return front(); return front();
} }
@@ -8193,7 +8235,7 @@ class basic_json_view
/// more than once); 0 if this is not an object /// more than once); 0 if this is not an object
size_type count(string_view_t key) const size_type count(string_view_t key) const
{ {
return is_object() ? detail::view::count_members<Editable>(*m_doc, m_node, key.data(), key.size()) : 0; return is_object() ? detail::view::count_members<is_editable>(*m_doc, m_node, key.data(), key.size()) : 0;
} }
size_type count(const char* key) const size_type count(const char* key) const
@@ -8335,7 +8377,7 @@ class basic_json_view
} }
else else
{ {
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node); detail::view::view_serializer<BasicJsonType, is_editable>(*m_doc, out, estimate, style).dump(m_node);
} }
return out; return out;
} }
@@ -8371,14 +8413,14 @@ class basic_json_view
} }
/// a view of an editable document compares with one of a read-only document /// 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 > template < view_mode M, typename std::enable_if < M != Mode, int >::type = 0 >
friend bool operator==(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b) friend bool operator==(const basic_json_view& a, const basic_json_view<BasicJsonType, M>& b)
{ {
return detail::view::equal<BasicJsonType>(side(a), detail::view::view_side<BasicJsonType, basic_json_view<BasicJsonType, E>>(b)); return detail::view::equal<BasicJsonType>(side(a), detail::view::view_side<BasicJsonType, basic_json_view<BasicJsonType, M>>(b));
} }
template < bool E, typename std::enable_if < E != Editable, int >::type = 0 > template < view_mode M, typename std::enable_if < M != Mode, int >::type = 0 >
friend bool operator!=(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b) friend bool operator!=(const basic_json_view& a, const basic_json_view<BasicJsonType, M>& b)
{ {
return !(a == b); return !(a == b);
} }
@@ -8416,7 +8458,7 @@ class basic_json_view
{ {
return BasicJsonType(value_t::discarded); return BasicJsonType(value_t::discarded);
} }
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node); return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, is_editable>(*m_doc, m_node);
} }
/// byte offset of this value in the source text (for strings: of the /// byte offset of this value in the source text (for strings: of the
@@ -8429,8 +8471,8 @@ class basic_json_view
} }
private: private:
template<typename, bool> friend class basic_json_document; template<typename, view_mode> friend class basic_json_document;
template<typename, bool> friend class basic_json_view; template<typename, view_mode> friend class basic_json_view;
template<typename, typename> friend class detail::view::editor; template<typename, typename> friend class detail::view::editor;
friend iterator; friend iterator;
@@ -8446,11 +8488,11 @@ class basic_json_view
} }
/// the template argument as a runtime condition: testing it directly in /// the template argument as a runtime condition: testing it directly in
/// `Editable && ...` makes the condition constant, which MSVC reports as /// `is_editable && ...` makes the condition constant, which MSVC reports as
/// C4127 /// C4127
static bool editable() noexcept static bool editable() noexcept
{ {
return Editable; return is_editable;
} }
/// the number of source bytes of this value (estimated for values with /// the number of source bytes of this value (estimated for values with
@@ -8499,7 +8541,7 @@ class basic_json_view
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()); const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view(); 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()); const node* const k = detail::view::find_member<is_editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
} }
@@ -8513,7 +8555,7 @@ class basic_json_view
/// the member of this object with @a key (the last one if the key occurs more than once), or nullptr /// the member of this object with @a key (the last one if the key occurs more than once), or nullptr
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
{ {
return find_key(key, std::integral_constant<bool, Editable> {}); return find_key(key, std::integral_constant<bool, is_editable> {});
} }
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
@@ -8535,7 +8577,7 @@ class basic_json_view
{ {
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx)); return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
} }
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))); return basic_json_view(m_doc, navigation::value(detail::view::element_at<is_editable>(*m_doc, m_node, idx)));
} }
// --- get() dispatch --- // --- get() dispatch ---
@@ -8641,16 +8683,19 @@ 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 document. Owned parses (parse_copy, rvalue std::string, streams, and inputs
that are not contiguous byte ranges) keep their own copy. that are not contiguous byte ranges) keep their own copy.
*/ */
template<typename BasicJsonType, bool Editable = false> template<typename BasicJsonType, view_mode Mode = view_mode::read_only>
class basic_json_document class basic_json_document
{ {
/// whether the document can be edited
static constexpr bool is_editable = Mode == view_mode::editable;
using document_data = detail::view::document_data; using document_data = detail::view::document_data;
static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8, static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8,
"json_view supports 64-bit integer types only"); "json_view supports 64-bit integer types only");
public: public:
using view_type = basic_json_view<BasicJsonType, Editable>; using view_type = basic_json_view<BasicJsonType, Mode>;
using value_t = detail::value_t; using value_t = detail::value_t;
/// an empty (discarded) document /// an empty (discarded) document
@@ -8864,6 +8909,10 @@ class basic_json_document
/// how load() checks an image (full, bounds, or none) /// how load() checks an image (full, bounds, or none)
using image_check = detail::view::image_check; using image_check = detail::view::image_check;
#if NLOHMANN_VIEW_LITTLE_ENDIAN
// (the image format is little-endian: big-endian targets have no save()
// and load(), so that a call fails to compile instead of always throwing)
/// The document as an image that load() reads without parsing: the node /// The document as an image that load() reads without parsing: the node
/// index, the text, and the decoded strings. An edited document is /// index, the text, and the decoded strings. An edited document is
/// written in its current state (floats that are not finite become null, /// written in its current state (floats that are not finite become null,
@@ -8905,6 +8954,7 @@ class basic_json_document
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check); detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
return d; return d;
} }
#endif
/////////// ///////////
// edits // // edits //
@@ -9046,7 +9096,7 @@ class basic_json_document
detail::view::editor<BasicJsonType, view_type> editor() 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)"); static_assert(is_editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, view_mode::editable> (json_editable_document)");
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded)) if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{ {
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document"); detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
@@ -9331,13 +9381,13 @@ using ordered_json_document = basic_json_document<ordered_json>;
/// a value of an ordered_json_document /// a value of an ordered_json_document
using ordered_json_view = basic_json_view<ordered_json>; using ordered_json_view = basic_json_view<ordered_json>;
/// an editable parsed JSON text for json /// an editable parsed JSON text for json
using json_editable_document = basic_json_document<json, true>; using json_editable_document = basic_json_document<json, view_mode::editable>;
/// a value of a json_editable_document /// a value of a json_editable_document
using json_editable_view = basic_json_view<json, true>; using json_editable_view = basic_json_view<json, view_mode::editable>;
/// an editable parsed JSON text for ordered_json /// an editable parsed JSON text for ordered_json
using ordered_json_editable_document = basic_json_document<ordered_json, true>; using ordered_json_editable_document = basic_json_document<ordered_json, view_mode::editable>;
/// a value of an ordered_json_editable_document /// a value of an ordered_json_editable_document
using ordered_json_editable_view = basic_json_view<ordered_json, true>; using ordered_json_editable_view = basic_json_view<ordered_json, view_mode::editable>;
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+2 -2
View File
@@ -592,7 +592,7 @@ TEST_CASE("json_view node integer bits")
}) })
{ {
CAPTURE(v) CAPTURE(v)
n.kind = 0x5A; n.kind = static_cast<nlohmann::detail::view::node_kind>(0x5A);
n.flags = 0xA5; n.flags = 0xA5;
n.extra = 0x1234; n.extra = 0x1234;
n.off = 0x89ABCDEFu; n.off = 0x89ABCDEFu;
@@ -601,7 +601,7 @@ TEST_CASE("json_view node integer bits")
CHECK(n.len == static_cast<std::uint32_t>(v)); CHECK(n.len == static_cast<std::uint32_t>(v));
CHECK(n.next == static_cast<std::uint32_t>(v >> 32)); CHECK(n.next == static_cast<std::uint32_t>(v >> 32));
// the other fields are untouched // the other fields are untouched
CHECK(n.kind == 0x5A); CHECK(static_cast<std::uint8_t>(n.kind) == 0x5A);
CHECK(n.flags == 0xA5); CHECK(n.flags == 0xA5);
CHECK(n.extra == 0x1234); CHECK(n.extra == 0x1234);
CHECK(n.off == 0x89ABCDEFu); CHECK(n.off == 0x89ABCDEFu);
+68 -1
View File
@@ -26,12 +26,79 @@ using nlohmann::json_editable_view;
#include <limits> #include <limits>
#include <map> #include <map>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
// These tests were split off unit-json_view_edit.cpp, whose object file got too // These tests were split off unit-json_view_edit.cpp, whose object file got too
// large for the MinGW linker (see json_view_test_helpers.hpp). // large for the MinGW linker (see json_view_test_helpers.hpp).
namespace
{
// the three ways a function can accept views: editable ones, read-only ones, or both
template<typename BasicJsonType>
int accepts_editable(nlohmann::basic_json_view<BasicJsonType, nlohmann::view_mode::editable> /*unused*/)
{
return 1;
}
template<typename BasicJsonType>
int accepts_read_only(nlohmann::basic_json_view<BasicJsonType, nlohmann::view_mode::read_only> /*unused*/)
{
return 2;
}
template<typename BasicJsonType, nlohmann::view_mode Mode>
int accepts_any(nlohmann::basic_json_view<BasicJsonType, Mode> /*unused*/)
{
return Mode == nlohmann::view_mode::editable ? 3 : 4;
}
template<typename View>
auto overload(View v) -> decltype(accepts_editable(v))
{
return accepts_editable(v);
}
template<typename View>
auto overload(View v) -> decltype(accepts_read_only(v))
{
return accepts_read_only(v);
}
} // namespace
TEST_CASE("json_view modes")
{
using nlohmann::basic_json_document;
using nlohmann::basic_json_view;
using nlohmann::ordered_json;
using nlohmann::view_mode;
SECTION("read-only is the default, and the shortcuts name the modes")
{
static_assert(std::is_same<basic_json_view<json>, basic_json_view<json, view_mode::read_only>>::value, "default mode");
static_assert(std::is_same<basic_json_document<json>, basic_json_document<json, view_mode::read_only>>::value, "default mode");
static_assert(std::is_same<nlohmann::json_view, basic_json_view<json, view_mode::read_only>>::value, "json_view");
static_assert(std::is_same<json_editable_view, basic_json_view<json, view_mode::editable>>::value, "json_editable_view");
static_assert(std::is_same<json_editable_document, basic_json_document<json, view_mode::editable>>::value, "json_editable_document");
static_assert(std::is_same<nlohmann::ordered_json_editable_view, basic_json_view<ordered_json, view_mode::editable>>::value, "ordered_json_editable_view");
static_assert(std::is_same<json_editable_document::view_type, json_editable_view>::value, "view_type");
static_assert(std::is_same<json_document::view_type, nlohmann::json_view>::value, "view_type");
}
SECTION("a function template selects views by their mode")
{
const json_document d = json_document::parse("[1]");
const json_editable_document e = json_editable_document::parse("[1]");
const nlohmann::ordered_json_editable_document o = nlohmann::ordered_json_editable_document::parse("[1]");
CHECK(overload(e.root()) == 1);
CHECK(overload(o.root()) == 1);
CHECK(overload(d.root()) == 2);
CHECK(accepts_any(e.root()) == 3);
CHECK(accepts_any(d.root()) == 4);
}
}
TEST_CASE("json_view edits: views and values") TEST_CASE("json_view edits: views and values")
{ {
SECTION("a value that is no longer part of the document") SECTION("a value that is no longer part of the document")
@@ -100,7 +167,7 @@ TEST_CASE("json_view edits: views and values")
// doubles have their own path to the output; other float types are // doubles have their own path to the output; other float types are
// written as basic_json writes them, non-finite values as null // written as basic_json writes them, non-finite values as null
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>; using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
using document_float = nlohmann::basic_json_document<json_float, true>; using document_float = nlohmann::basic_json_document<json_float, nlohmann::view_mode::editable>;
document_float d = document_float::parse("[1.5]"); document_float d = document_float::parse("[1.5]");
d.push_back(d.root(), std::numeric_limits<float>::quiet_NaN()); d.push_back(d.root(), std::numeric_limits<float>::quiet_NaN());
d.push_back(d.root(), -std::numeric_limits<float>::infinity()); d.push_back(d.root(), -std::numeric_limits<float>::infinity());
+31 -10
View File
@@ -28,11 +28,25 @@ using nlohmann::detail::view::node;
#include <random> #include <random>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <test_data.hpp> #include <test_data.hpp>
namespace json_view_image_test
{
// whether a document type has save() and load() (only on little-endian targets)
template<typename T, typename = void>
struct has_save : std::false_type {};
template<typename T>
struct has_save<T, decltype(static_cast<void>(std::declval<const T&>().save()))> : std::true_type {};
template<typename T, typename = void>
struct has_load : std::false_type {};
template<typename T>
struct has_load<T, decltype(static_cast<void>(T::load(std::declval<const std::vector<std::uint8_t>&>())))> : std::true_type {};
} // namespace json_view_image_test
#if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #if !(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
namespace namespace
@@ -171,6 +185,9 @@ std::uint32_t rng()
TEST_CASE("json_view images: round trips") TEST_CASE("json_view images: round trips")
{ {
static_assert(json_view_image_test::has_save<json_document>::value && json_view_image_test::has_load<json_document>::value, "save() and load() on a little-endian target");
static_assert(json_view_image_test::has_save<ordered_json_editable_document>::value && json_view_image_test::has_load<ordered_json_editable_document>::value, "save() and load() on a little-endian target");
SECTION("small documents") SECTION("small documents")
{ {
for (const char* text : for (const char* text :
@@ -480,7 +497,7 @@ TEST_CASE("json_view images: edited documents")
{ {
CAPTURE(i) CAPTURE(i)
CHECK((node_at(image, i).flags & nlohmann::detail::view::node_flags::linked) == 0); CHECK((node_at(image, i).flags & nlohmann::detail::view::node_flags::linked) == 0);
CHECK(node_at(image, i).kind != nlohmann::detail::view::kind_link); CHECK((node_at(image, i).kind != nlohmann::detail::view::node_kind::link));
} }
for (const image_check check : for (const image_check check :
{ {
@@ -810,13 +827,13 @@ TEST_CASE("json_view images: check")
{ {
rejected(corrupted(image, 12, [&](node & n) rejected(corrupted(image, 12, [&](node & n)
{ {
n.kind = kind; n.kind = static_cast<nlohmann::detail::view::node_kind>(kind);
}), true); }), true);
} }
// a key that is not a string // a key that is not a string
rejected(corrupted(image, 1, [](node & n) rejected(corrupted(image, 1, [](node & n)
{ {
n.kind = 0; n.kind = nlohmann::detail::view::node_kind::null;
n.len = 0; n.len = 0;
n.off = 0; n.off = 0;
}), true); }), true);
@@ -960,11 +977,11 @@ TEST_CASE("json_view images: check")
}), true); }), true);
rejected(corrupted(image, 0, [](node & n) rejected(corrupted(image, 0, [](node & n)
{ {
n.kind = 2; // an array: the "keys" are values, and the counts do not match n.kind = nlohmann::detail::view::node_kind::array; // an array: the "keys" are values, and the counts do not match
}), true); }), true);
const std::vector<std::uint8_t> as_array = corrupted(image, 16, [](node & n) const std::vector<std::uint8_t> as_array = corrupted(image, 16, [](node & n)
{ {
n.kind = 2; // {} as []: fine n.kind = nlohmann::detail::view::node_kind::array; // {} as []: fine
}); });
CHECK(load_result(as_array, image_check::full).empty()); CHECK(load_result(as_array, image_check::full).empty());
const std::string expected_dump = R"({"s":"x\"y","i":-12,"u":7,"f":1.5e+300,"b":true,"n":null,"a":["t",[]]})"; const std::string expected_dump = R"({"s":"x\"y","i":-12,"u":7,"f":1.5e+300,"b":true,"n":null,"a":["t",[]]})";
@@ -1013,13 +1030,13 @@ TEST_CASE("json_view images: check")
rejected(at_token(u6, 0, '8'), false); // 8 != 7 rejected(at_token(u6, 0, '8'), false); // 8 != 7
rejected(corrupted(image, 4, [](node & n) rejected(corrupted(image, 4, [](node & n)
{ {
n.kind = 6; // "-12" as unsigned: a sign n.kind = nlohmann::detail::view::node_kind::number_unsigned; // "-12" as unsigned: a sign
n.extra = 3; n.extra = 3;
}), false); }), false);
// a non-negative number_integer (as edits write it): fine // a non-negative number_integer (as edits write it): fine
const std::vector<std::uint8_t> positive = corrupted(image, 6, [](node & n) const std::vector<std::uint8_t> positive = corrupted(image, 6, [](node & n)
{ {
n.kind = 5; n.kind = nlohmann::detail::view::node_kind::number_integer;
n.extra = 0; n.extra = 0;
}); });
CHECK(load_result(positive, image_check::full).empty()); CHECK(load_result(positive, image_check::full).empty());
@@ -1027,7 +1044,7 @@ TEST_CASE("json_view images: check")
CHECK(pos.root()["u"].is_number_integer()); CHECK(pos.root()["u"].is_number_integer());
rejected(corrupted(image, 8, [](node & n) rejected(corrupted(image, 8, [](node & n)
{ {
n.kind = 6; // a float token as integer n.kind = nlohmann::detail::view::node_kind::number_unsigned; // a float token as integer
n.extra = 7; n.extra = 7;
}), false); }), false);
} }
@@ -1197,7 +1214,7 @@ TEST_CASE("json_view images: check")
{ {
std::vector<std::uint8_t> b = img; std::vector<std::uint8_t> b = img;
node n = node_at(b, i); node n = node_at(b, i);
n.kind = kind; n.kind = static_cast<nlohmann::detail::view::node_kind>(kind);
n.extra = extra; n.extra = extra;
set_node(b, i, n); set_node(b, i, n);
return load_result(b, image_check::full); return load_result(b, image_check::full);
@@ -1275,8 +1292,12 @@ TEST_CASE("json_view images: damaged images")
TEST_CASE("json_view images: big-endian targets") TEST_CASE("json_view images: big-endian targets")
{ {
// the image format is little-endian: save() and load() do not exist here
static_assert(!json_view_image_test::has_save<json_document>::value, "no save() on a big-endian target");
static_assert(!json_view_image_test::has_load<json_document>::value, "no load() on a big-endian target");
static_assert(!json_view_image_test::has_save<json_editable_document>::value, "no save() on a big-endian target");
const json_document d = json_document::parse("[1]"); const json_document d = json_document::parse("[1]");
CHECK_THROWS_WITH_AS(d.save(), "[json.exception.type_error.320] json_document images need a little-endian target", json::type_error&); CHECK(d.root().size() == 1);
} }
#endif #endif