Split the json_view feature page into sub-pages (#6018)

* Split the json_view feature page into sub-pages

The single features/json_view.md page grew to ~600 lines covering eight
topics. Split it into features/json_view/ with an overview page and
pages for ownership and lifetime, differences from basic_json, reading
values, generic code, editing, and images.

The new differences page collects what was spread over several sections
("What is the same", "What is different", "Duplicate keys", and notes in
the conversion and serialization sections) under one summary table, and
adds the parts of the basic_json interface a view does not have. The
other pages now link to it instead of restating the rules.

Internal links across the documentation point to the new pages.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Docs: fix stale comments and trim repeated text in json_view pages

- fig-jsonview.js/.css: point the header comments at the new pages
- reading.md: views' strings are valid as long as the document (and a
  borrowed source text), as lifetime.md says
- reading.md, generic_code.md: link to differences.md instead of
  restating the member-order and repeated-key rules
- differences.md: drop the third mention of ordering comparisons, name
  rbegin()/rend() in the iterator row
- exceptions.md: link editable documents to editing.md

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-11-09 08:12:00 +01:00
1 parent 7757de3a58
commit 6a852ab96d
73 files changed
+880 -686

No files matched your search

+2 -2
View File
@@ -48,7 +48,7 @@ which avoids repeating it:
[`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written. Every other
type is obtained from a subtree that is built on demand with
[`materialize()`](../api/basic_json_view/materialize.md). See
[Zero-copy JSON views](json_view.md#getting-values-out-without-copying).
[Zero-copy JSON views](json_view/reading.md#getting-values-out-without-copying).
The library already knows how to convert to and from the scalar types and the STL containers (such as
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
@@ -269,7 +269,7 @@ The conversions above are built in for standard types. To make the same syntax w
- [`get_ref`](../api/basic_json/get_ref.md) - / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
- [Zero-copy JSON views](json_view.md) - get values out of a document or a view
- [Zero-copy JSON views](json_view/index.md) - get values out of a document or a view
- [`adl_serializer`](../api/adl_serializer/index.md) - the default serializer behind the conversions
- [`type`](../api/basic_json/type.md) - query the type of a value
- [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) - toggle conversion to tuples of references
+2 -2
View File
@@ -3,7 +3,7 @@
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
instead, see [parsing](parsing/index.md). A JSON text can also be parsed into a
[document](../api/basic_json_document/index.md) and read through a [view](../api/basic_json_view/index.md) instead of
into a value; see [Zero-copy JSON views](json_view.md).
into a value; see [Zero-copy JSON views](json_view/index.md).
## From C++ values
@@ -122,7 +122,7 @@ string `#!json "42"`.
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
- [Converting values](conversions.md) - which C++ types can be used
- [Parsing](parsing/index.md) - create a value from JSON text
- [Zero-copy JSON views](json_view.md) - create a document and a view from JSON text instead of a value
- [Zero-copy JSON views](json_view/index.md) - create a document and a view from JSON text instead of a value
- [`basic_json`](../api/basic_json/index.md) - the class template and its member functions
- [Types](types/index.md) - the JSON value types
- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) - change how a single braced value is interpreted
+1 -1
View File
@@ -37,7 +37,7 @@ json read_json_file(const std::string& path)
[input sources and performance](parsing/index.md#input-sources-and-performance)). The common way to read a file with
`#!cpp std::istreambuf_iterator<char>` is so slow that it takes about twice as long as parsing from the stream. Read
the file into a string only if you need the text itself, for example for an error message. This advice is for
`#!cpp json::parse`. [`json_document`](json_view.md) reads a stream into a buffer that it owns anyway, so there is no
`#!cpp json::parse`. [`json_document`](json_view/index.md) reads a stream into a buffer that it owns anyway, so there is no
copy to save: reading the file into a `#!cpp std::string` yourself and passing that as an lvalue, which the document
borrows, was about 6 to 12 % faster than parsing from the stream, with the same single buffer.
- `parse` requires the file to hold exactly one JSON value. Files with one value per line are read with
+3 -3
View File
@@ -13,10 +13,10 @@ C++ types, and finally serialize it again.
[SAX interface](parsing/sax_interface.md), [error handling](parsing/parse_exceptions.md),
[large and partial input](parsing/large_and_partial_input.md), and
[parsing untrusted input](parsing/untrusted_input.md).
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
- [Zero-copy JSON views](json_view/index.md) — read a JSON text through a flat index instead of building a `json` tree;
strings and numbers stay in the input and are only decoded when needed.
- [Editable documents](json_view.md#editing-a-document) — modify a parsed document without building a `json` tree.
- [Images](json_view.md#images) — save a parsed document and load it again without parsing it.
- [Editable documents](json_view/editing.md) — modify a parsed document without building a `json` tree.
- [Images](json_view/images.md) — save a parsed document and load it again without parsing it.
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
## Accessing and modifying values
-591
View File
@@ -1,591 +0,0 @@
# Zero-copy JSON views
`#!cpp <nlohmann/json_view.hpp>` adds a read-only, non-owning way to look at a parsed JSON text, as an alternative to
building a [`basic_json`](../api/basic_json/index.md) tree with [`parse()`](../api/basic_json/parse.md).
## The problem
[`basic_json::parse()`](../api/basic_json/parse.md) builds a tree of `basic_json` values: one allocation for every
array and object, and every string copied into its own `std::string`. That is the right trade-off when the program
goes on to read and write the value freely, but it does more work than necessary when only a small part of a large
JSON text is actually needed, or when the same text is parsed over and over (many small messages, for instance) and
most of the resulting tree is thrown away almost immediately.
## The idea
[`basic_json_document::parse()`](../api/basic_json_document/parse.md) parses the same JSON grammar, with the same
options, but instead of a tree it builds a flat index of the values it found: one
[16-byte entry](../home/architecture.md#node-index-of-json-views) per value (and one per object key), in document
order. Integers are converted while parsing and stored in their entry. Floating-point numbers and strings are
referenced in place in the source text: a float is converted when it is read, and only strings that contain escape
sequences are decoded, into one shared buffer owned by the document.
[`basic_json_view`](../api/basic_json_view/index.md) is a small, trivially copyable handle (two pointers) into that
index. It gives you the read-only, type-inspection part of the `basic_json` interface —
[`type()`](../api/basic_json_view/type.md) and the `is_*()` predicates,
[`size()`](../api/basic_json_view/size.md)/[`empty()`](../api/basic_json_view/empty.md) — as well as element access
([`operator[]`](../api/basic_json_view/operator%5B%5D.md), [`at`](../api/basic_json_view/at.md),
[`front`](../api/basic_json_view/front.md)/[`back`](../api/basic_json_view/back.md)), lookup
([`find`](../api/basic_json_view/find.md), [`contains`](../api/basic_json_view/contains.md),
[`count`](../api/basic_json_view/count.md)), and iteration
([`begin`](../api/basic_json_view/begin.md)/[`end`](../api/basic_json_view/end.md),
[`items`](../api/basic_json_view/items.md)) — without ever allocating a `basic_json` value. When you do need an
actual `basic_json` value for a subtree, [`materialize()`](../api/basic_json_view/materialize.md) builds exactly the
one [`parse()`](../api/basic_json/parse.md) would have produced for it.
The same small text, parsed both ways:
<div class="nj-fig-wrap" markdown>
--8<-- "images/json_view_layout.svg"
</div>
<p class="nj-caption" markdown>Top: the tree of separate heap allocations that `basic_json::parse()` builds. Bottom: `json_document::parse()` keeps the source text and builds one flat index of 16-byte nodes that point back into it, plus a buffer of decoded strings.</p>
## How to use it
Include `<nlohmann/json_view.hpp>` in addition to (or instead of) `<nlohmann/json.hpp>`. Parse into a
[`json_document`](../api/json_document.md), inspect its [`root()`](../api/basic_json_document/root.md), and
`materialize()` when you need a real value. `materialize()` works on any view, not just the root, so you can turn just
the subtree you need into a value:
??? example "Example: parse a document, inspect its root, and materialize it"
```cpp
--8<-- "examples/json_document.cpp"
```
Output:
```json
--8<-- "examples/json_document.output"
```
[`ordered_json_document`](../api/ordered_json_document.md) is the equivalent for
[`ordered_json`](../api/ordered_json.md), just as [`ordered_json`](../api/ordered_json.md) is to
[`json`](../api/json.md).
## Ownership and lifetime
A document either **borrows** the text it was parsed from, or **owns** its own copy of it; call
[`owns_source()`](../api/basic_json_document/owns_source.md) to find out which happened.
[`parse()`](../api/basic_json_document/parse.md) decides this from the value category and type of its argument (an
lvalue `#!cpp std::string` is borrowed; an rvalue `#!cpp std::string` is moved in, owned without a copy; a stream is
read into an owned buffer; and so on — see [`parse`'s Notes](../api/basic_json_document/parse.md#notes) for the full
table). [`parse_copy()`](../api/basic_json_document/parse_copy.md) always owns a copy, regardless of the input.
!!! warning "A borrowed document depends on your buffer"
If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
exactly as it would be for a dangling `#!cpp std::string_view`.
A view is valid only while all of the following hold:
- the document is alive,
- the document has not been re-parsed since the view was taken (with [`read()`](../api/basic_json_document/read.md)
or [`parse()`](../api/basic_json_document/parse.md) into it),
- the document has not had [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and
- if the document borrows its source text, that text is still alive and unmodified.
Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap
allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md)
whenever any of the other conditions above was not met.
<div class="nj-fig-wrap" markdown>
--8<-- "images/json_view_lifetime.svg"
</div>
<p class="nj-caption" markdown>Four timelines of a document and its views, each bar showing when an object is alive or valid and when it dangles. Destroying the borrowed text, re-parsing, or shrinking the document invalidates the views taken before; moving the document does not.</p>
A view can dangle, exactly like a `#!cpp std::string_view`: nothing stops you from using it after its document is
gone. This is deliberate. A view is two pointers and trivially copyable; making it safe (a `#!cpp std::shared_ptr` in
the document and a `#!cpp std::weak_ptr` in each view) would make it larger, add reference counting to every copy,
and a `lock()` to every access — exactly the costs the view exists to avoid. What is caught is the obvious case:
because a view dies with its document, [`root()`](../api/basic_json_document/root.md) is not callable on a temporary
document: `#!cpp auto v = json_document::parse(text).root();` does not compile. Give the document a name first.
??? example "Example: borrowed and owned documents, and when views become invalid"
```cpp
--8<-- "examples/json_view_ownership.cpp"
```
Output:
```json
--8<-- "examples/json_view_ownership.output"
```
## What is the same as `parse()`
- **Accept/reject.** [`accept()`](../api/basic_json_document/accept.md) and
[`parse()`](../api/basic_json_document/parse.md) accept and reject exactly the same inputs as
[`basic_json::accept()`](../api/basic_json/accept.md)/[`basic_json::parse()`](../api/basic_json/parse.md), with the
same `ignore_comments` and `ignore_trailing_commas` options.
- **Errors.** A failing parse throws the same exception — the same id, message, and position — because on failure
the library's own parser is run on the same bytes to produce the diagnostic. Valid input never pays for this, and
the replay builds no values: it costs about a lexing pass up to the error.
`#!cpp allow_exceptions == false` skips the replay and gives a
[discarded](../api/basic_json_document/is_discarded.md) document instead of throwing, just as it gives a discarded
value for `#!cpp basic_json::parse()`.
- **Number classification.** An integer literal that does not fit into the 64-bit integer type becomes a
floating-point value, exactly as it does for `#!cpp basic_json::parse()`.
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
how it reports errors. Two more macros add context to the exceptions of views, as they do for those of
`#!cpp basic_json`:
- With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md), an exception names the JSON Pointer path of the
value within the document.
- With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), an exception names the bytes of
the value in the source text.
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
exception is thrown. This takes time linear in the size of the document. One exception has neither:
`key()` of an iterator over an array or a primitive value
([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
not know the value it iterates over.
The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
## What is different
- **Only 64-bit integers.** `basic_json_document<BasicJsonType>` requires `BasicJsonType::number_integer_t` and
`number_unsigned_t` to both be 64 bits wide; this is a compile-time `#!cpp static_assert`.
- **A 4 GiB input limit.** An input of 4294967280 bytes (4 GiB minus 16 bytes) or more throws
[`out_of_range.416`](../home/exceptions.md#jsonexceptionout_of_range416), a limit
`#!cpp basic_json::parse()` does not have. It keeps an index entry at 16 bytes: offsets and counts are 32 bits
wide. The limit is 16 bytes below 2^32, because the parser reads in 16-byte steps, and a position one step past the
end must still fit.
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
is read to its end into a buffer the document owns, and parsing starts after that. For a file, reading it into a
`#!cpp std::string` yourself and parsing that (an lvalue is borrowed) is therefore not more expensive in memory, and
was about 6 to 12 % faster than parsing from an `#!cpp std::ifstream`.
- **Fewer source positions after edits.** With
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled,
[`materialize()`](../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`.
In an [editable document](../api/json_editable_document.md), a value written by an edit has none, and neither has
an array or object whose elements changed or whose last element has none; a `basic_json` value keeps its positions
when it is changed. See [`materialize()`](../api/basic_json_view/materialize.md#notes).
- **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/
[`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
sorts by key, so iterating a [`materialize()`](../api/basic_json_view/materialize.md)d value can print members in
a different order than iterating the view they came from.
- **Chained access is safe.** [`operator[]`](../api/basic_json_view/operator%5B%5D.md) with a missing key, an index
out of range, or an unresolvable JSON pointer returns a [discarded](../api/basic_json_view/is_discarded.md) view, and
`operator[]` on a discarded view returns a discarded view without throwing: `#!cpp v["a"]["b"][0]` can be tested
once at the end. Type errors on values that exist (a key on an array, an index on an object) still throw, and
[`at`](../api/basic_json_view/at.md) throws for every missing value.
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
below.
- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
[`materialize()`](../api/basic_json_view/materialize.md) or [`parse()`](../api/basic_json/parse.md) would produce
equal values for them, without ever building a tree to do it. For ordering, too,
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can compare.
## Duplicate keys
!!! warning "A lookup in a view and in the parsed value can give different answers"
If an object in the source text repeats a key, [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md), and JSON pointer
resolution all return the **first** member with that key. `basic_json::parse()` instead keeps the **last** value
of a repeated key, so for `#!cpp {"a":1,"a":2}`, `#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is
`#!cpp 2`. [`count`](../api/basic_json_view/count.md) counts every member with the key, so it is `#!cpp 2` here.
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence, in document order, and
[`size()`](../api/basic_json_view/size.md) counts all of them, so `#!cpp view.size()` can be larger than
`#!cpp view.materialize().size()`.
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
to scan all members of the object, even when the key is found at the very first one: this made lookups in small
objects 1.6 to 3.4 times slower. The hash index of the keys of objects with 128 members or more leads to the first member of a
key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return the first
member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees
duplicates is unpredictable, so neither choice is wrong.
The same holds for [`get<T>()`](../api/basic_json_view/get.md) of a type whose `from_json()` reads the view, such as
the types of the `NLOHMANN_DEFINE_TYPE_*` macros: their members are looked up, so `#!cpp get<point>()` of
`#!cpp {"x":1,"x":2}` reads `#!cpp x == 1`, while `#!cpp materialize().get<point>()` reads `#!cpp 2`.
**What stays the same as `parse()`?** [`materialize()`](../api/basic_json_view/materialize.md) and
[`get<std::map<...>>()`](../api/basic_json_view/get.md) replay every member in order, so they keep the *last* value
exactly like `#!cpp basic_json::parse()` (at the position of the first occurrence of the key, for an
[`ordered_json`](../api/ordered_json.md)).
**How do I get the value `parse()` would give?** Either call `#!cpp view.materialize()` and look the key up in the
result, or iterate the members and keep the last match:
```cpp
// the last member with the key "a", as parse() would keep it
json_view last;
for (const auto item : view.items())
{
if (item.key() == "a")
{
last = item.value();
}
}
```
If you cannot trust the source text to have unique keys, check [`size()`](../api/basic_json_view/size.md) against the
number of distinct keys, or reject duplicates when iterating.
## Getting values out without copying
[`get<T>()`](../api/basic_json_view/get.md) converts most `T` directly from the flat index, without ever building a
`basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`,
`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json_view` itself, the standard containers
and other types the library converts (`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::list`, `#!cpp std::array`,
`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, ...), and the types of the `NLOHMANN_DEFINE_TYPE_*` and
`NLOHMANN_JSON_SERIALIZE_ENUM` macros. Types whose `from_json()` is written for `basic_json` only go through
[`materialize()`](../api/basic_json_view/materialize.md)`.get<T>()` instead: the subtree is built into a real
`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there. See
[Writing generic code](#writing-generic-code) for how to write a `from_json()` that reads views.
Two conversions never copy at all:
- [`get_string()`](../api/basic_json_view/get_string.md) (equivalently, `#!cpp get<string_view_t>()`) returns a
string as a `string_view_t` pointing into the document's [`source()`](../api/basic_json_document/source.md) text —
or, for a string that contains escape sequences, into the document's own buffer of decoded strings — instead of
allocating a new `#!cpp std::string`.
- [`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written in the
source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of
rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get<T>()` (and
[`basic_json::parse()`](../api/basic_json/parse.md)) would.
Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is.
### `string_view` members
A struct whose member is a `#!cpp std::string_view` (C++17) can be decoded from a view with the
`NLOHMANN_DEFINE_TYPE_*` macros, and the member is not copied either: for a string without escape sequences it points
into the document's source text, and for a string with escape sequences into the document's buffer of decoded strings.
The member is valid as long as the document is alive and, for a document that
[borrows its source text](#ownership-and-lifetime) (as [`parse()`](../api/basic_json_document/parse.md) does for
an lvalue `#!cpp std::string`, a `#!cpp std::string_view`, or a C string), as long as that text is.
```cpp
struct record
{
std::string_view name;
int count;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(record, name, count)
};
const std::string text = R"({"name": "widget", "count": 3})";
const json_document doc = json_document::parse(text); // borrows text
const record r = doc.root().get<record>(); // r.name points into text, valid while text and doc are
```
!!! warning "Decoding into a `string_view` from a `json` value dangles at once"
The same struct also compiles with `#!cpp json::parse(text).get<record>()`, but there `name` points into the
temporary `json` value, which is destroyed at the end of the statement. Use a named
[`json_document`](../api/json_document.md), as above, to decode such a struct.
## Writing generic code
A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md):
[`json_view`](../api/json_view.md) is `#!cpp basic_json_view<json, view_mode::read_only>`, and
[`json_editable_view`](../api/json_editable_view.md) is `#!cpp basic_json_view<json, view_mode::editable>`. These are
different types. The only conversion between them turns a view of a read-only document into a view of an editable
document of the same `BasicJsonType`, so a function written for one shortcut accepts few other views:
```cpp
void print_names(json_view v); // accepts neither an ordered_json_view nor a json_editable_view
void print_names(json_editable_view v); // also accepts a json_view, but no ordered_json_view
```
This is the same pitfall as a `to_json`/`from_json` written for `json` instead of any `BasicJsonType`, which then does
not work for `ordered_json`. The shortcuts are for declaring documents and views; a function that takes a view names
the views it accepts with a template:
```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);
// views of either document type
template<typename BasicJsonType, view_mode Mode>
void f(basic_json_view<BasicJsonType, Mode> v);
```
Views are passed by value: they are two pointers, cheap to copy, like a `#!cpp std::string_view`. A `const&` works as
well, but a non-`const` reference does not bind to the views that [`root()`](../api/basic_json_document/root.md),
[`operator[]`](../api/basic_json_view/operator%5B%5D.md), and [`at()`](../api/basic_json_view/at.md) return, so
`#!cpp f(doc.root())` would not compile.
There is no conversion from an editable view to a read-only one, by design: the mode decides how a view walks the
index. A read-only view reads a container in place, while a view of an editable document follows the links that edits
leave behind, so a read-only view of an edited document would read stale values. The other direction is safe: a
read-only document has no edits, so a view of it [converts](../api/basic_json_view/basic_json_view.md) to an
editable view, which reads it like an editable document before its first edit. A template deduces the mode without
conversions, so the template forms above still accept only the modes they name. Views of both modes compare with
[`operator==`](../api/basic_json_view/operator_eq.md).
[`is_json_view<T>`](../api/is_json_view.md) tells views from other types, for constraints that cannot be written as a
`basic_json_view<...>` parameter.
### A `from_json()` that reads views
[`get<T>()`](../api/basic_json_view/get.md) on a view calls `T`'s `from_json()` with the view itself if that
`from_json()` accepts views, and with [`materialize()`](../api/basic_json_view/materialize.md) otherwise. The
`from_json()` functions of the [`NLOHMANN_DEFINE_TYPE_*`](../api/macros/nlohmann_define_type_intrusive.md),
[`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../api/macros/nlohmann_define_derived_type.md), and
[`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md) macros, and those of the library
for the standard types, accept views, so these types need nothing else. For decoding the statuses of
twitter.json into structs, reading the view is about 5 times faster than `#!cpp materialize().get<T>()`.
A hand-written `from_json()` reads views if it is constrained to `json` values and views:
```cpp
template < typename J, typename std::enable_if <
nlohmann::is_json_view<J>::value || std::is_same<J, nlohmann::json>::value, int >::type = 0 >
void from_json(const J& j, person& p)
{
p.name = j.at("name").template get<std::string>();
p.age = j.at("age").template get<int>();
}
```
Its body may only use what views and `basic_json` have in common: the type checks, `at()`, `operator[]`, `contains()`,
`value()`, `size()`, iteration and `items()`, and `get<T>()`/`get_to()`. An unconstrained
`#!cpp template<typename BasicJsonType> void from_json(const BasicJsonType& j, person& p)`, as in
[arbitrary type conversions](arbitrary_types.md), is never called with a view, as it might use `basic_json` functions
a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
converted through `materialize()` as well.
One thing differs when a `from_json()` reads the view: members looked up by name (`at()`, `value()`, ... and thus
the macros) find the **first** member with a repeated key, see [Duplicate keys](#duplicate-keys);
`#!cpp materialize().get<T>()` would see the last one. Containers built from an object, such as `#!cpp std::map`,
keep the **last** value either way, as [`parse()`](../api/basic_json/parse.md) does.
`to_json()` is unaffected: it writes `basic_json` values, never views.
## Writing a view back
[`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a
`basic_json` value. An object's members are written in document order, not sorted by key, and *every* occurrence of a
repeated key is written, not only the last one — the same two ways [iteration](#what-is-different) already differs
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see [Duplicate keys](#duplicate-keys).
`#!cpp materialize().dump()` gives a different result in both respects for a `json_view`.
By default, numbers are written the way [`basic_json::dump()`](../api/basic_json/dump.md) would.
[`number_format::source`](../api/basic_json_view/number_format.md) instead copies every number exactly as it was
written in the source text — a price like `#!cpp 19.90`, a long order or account ID with more digits than any number
type holds, or a high-precision coordinate — something `basic_json` cannot do at all, since parsing already reduces
a number to its parsed `#!cpp double`/`#!cpp int64_t` value.
[`operator<<`](../api/basic_json_view/operator_ltlt.md) writes a view to a stream the way `basic_json`'s does, using
the stream's `width`/`fill` for indentation.
## Editing a document
Everything above is read-only: a `json_document`/`json_view` lets you look at a parsed text without copying it, but
not change it. [`basic_json_document<BasicJsonType, view_mode::editable>`](../api/basic_json_document/index.md) — more conveniently
spelled [`json_editable_document`](../api/json_editable_document.md) or
[`ordered_json_editable_document`](../api/ordered_json_editable_document.md) — also lets you
[`set`](../api/basic_json_document/set.md) a value, [`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)
an object member or an array element, still without ever building a `basic_json` tree for parts you do not touch.
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
runtime one.
### Why: editing without reformatting
The `#!cpp 19.90` price from [above](#writing-a-view-back) is exactly the kind of value that makes editing a `json`
or `ordered_json` value in place lossy. Say you parse a configuration file, patch one field, and write it back:
- **`json`** re-sorts every key on the way in (`object_t` is a `#!cpp std::map`) and rewrites every number to its
shortest round-trip form on the way out — a one-field patch turns into a diff that reorders the whole file and
rewrites `#!cpp 19.90` to `#!cpp 19.9`.
- **`ordered_json`** keeps the key order, but still rewrites every number the same way: parsing has already reduced
it to a `#!cpp double`/`#!cpp int64_t`, and there is no way back to how it was spelled in the source text.
The patch of the example below (set `"host"`, add `"retries"`) written back by each of them:
<div class="nj-jsonview-cmp" role="group" aria-label="The same patch written back by json, ordered_json, and json_editable_document">
<div><p class="nj-jsonview-cmp-h"><code>json</code></p><p class="nj-jsonview-cmp-v">keys sorted, numbers rewritten</p><pre>{<span class="nj-jsonview-bad">"host"</span>:"db2",
"name":"cache",
"port":6379,
"price":<span class="nj-jsonview-bad">19.9</span>,
"retries":3}</pre></div>
<div><p class="nj-jsonview-cmp-h"><code>ordered_json</code></p><p class="nj-jsonview-cmp-v">key order kept, numbers rewritten</p><pre>{"name":"cache",
"host":"db2",
"port":6379,
"price":<span class="nj-jsonview-bad">19.9</span>,
"retries":3}</pre></div>
<div class="nj-jsonview-cmp--best"><p class="nj-jsonview-cmp-h"><code>json_editable_document</code></p><p class="nj-jsonview-cmp-v">order and spelling kept</p><pre>{"name":"cache",
"host":"db2",
"port":6379,
"price":<span class="nj-jsonview-ok">19.90</span>,
"retries":3}</pre></div>
</div>
An editable document keeps both. [`dump()`](../api/basic_json_view/dump.md) of an edited document writes members in
document order — a member [`set`](../api/basic_json_document/set.md) added goes at the end, exactly where it was
inserted, and an [`erase`](../api/basic_json_document/erase.md)d member simply leaves a gap: everything around it
keeps its place — and [`number_format::source`](../api/basic_json_view/number_format.md) keeps the exact spelling
of every number an edit did not itself touch; a number an edit *did* touch is written the way
[`BasicJsonType::dump()`](../api/basic_json/dump.md) would write it, since there is no source spelling for a brand
new value.
??? example "Example: patch a configuration, keeping member order and number spellings"
```cpp
--8<-- "examples/json_editable_document.cpp"
```
Output:
```json
--8<-- "examples/json_editable_document.output"
```
### What stays valid, and what an edit costs
The source text itself is **never written**, and the parsed index never moves — a value keeps the node it was
parsed into for as long as it is not itself replaced. So every [view](../api/basic_json_view/index.md) taken before
an edit, including a previously obtained [`root()`](../api/basic_json_document/root.md), stays valid and, if it
still refers to the edited value, sees the edit; a view of a value a later edit drops or replaces just keeps showing
what it last held. New values go to storage the document allocates and owns on demand. The one thing an edit does
invalidate is the **iterators** taken over an edited array or object: the first time an element is added to it,
inserted into it, erased from it, or replaced by a value that spans a different number of nodes, its elements move from the parsed, fixed layout to a growable block of links
so that [`push_back`](../api/basic_json_document/push_back.md) can later grow it in amortized constant time —
existing elements are not touched, but an iterator that was walking the old layout no longer matches. A string
obtained with
[`get_string()`](../api/basic_json_view/get_string.md) is unaffected either way and stays valid across further
edits. See [`basic_json_document`'s Edits](../api/basic_json_document/index.md#edits) for the details, and
[`set`'s Exception safety](../api/basic_json_document/set.md#exception-safety) for what an edit guarantees if it
throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in
the index is described in the [architecture overview](../home/architecture.md#node-index-of-json-views).
Step through the edits of a small document (the node fields follow the [architecture overview](../home/architecture.md#node-index-of-json-views)):
<div class="nj-jsonview-steps" data-nj-jsonview-steps>
<figure class="nj-jsonview-step" data-label="After parsing">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_0.svg"
</div>
<figcaption>Step 1 of 5. After parsing: nine nodes in the parsed index, no owned storage yet.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="set an existing member">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_1.svg"
</div>
<figcaption>Step 2 of 5. <code>doc.set(doc.root(), "port", 6380)</code> overwrites the node of the integer in place; no member is added or removed, so the object keeps its parsed layout.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="erase an array element">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_2.svg"
</div>
<figcaption>Step 3 of 5. <code>doc.erase(doc.root()["tags"], 0)</code>: the array is edited for the first time, so its elements move to a block of links in owned storage. The node of <code>"eu"</code> stays in the index, but nothing refers to it any more; iterators over the array taken before are invalid.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="push_back onto the array">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_3.svg"
</div>
<figcaption>Step 4 of 5. <code>doc.push_back(doc.root()["tags"], "new")</code>: the new string gets its own node, its text goes to the buffer of edited text, and a link to it is appended to the block of the array.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="set a new member">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_4.svg"
</div>
<figcaption>Step 5 of 5. <code>doc.set(doc.root(), "retries", 3)</code>: a new member, so the object moves to a block too: copies of its keys, links to the values it already had, then the new key and a link to the new integer.</figcaption>
</figure>
</div>
<p class="nj-caption" markdown>The node index and the owned storage of the document after each of five edits. Use *Back*, *Next*, or the numbered buttons to switch between the steps; without JavaScript all steps are shown one below the other.</p>
## Images
[`save()`](../api/basic_json_document/save.md) writes a document as an *image*: a byte buffer that
[`load()`](../api/basic_json_document/load.md) reads back into a document without parsing — no lexing, no building
the node index, nothing but copying the nodes and pointing the text and the decoded strings at the image. Where
[`parse_copy()`](../api/basic_json_document/parse_copy.md) still has to scan the whole input,
[`load()`](../api/basic_json_document/load.md) turns that scan into a copy of the node index alone.
**Why.** A document that is parsed once and then read many times — a configuration loaded at startup, a template
rendered on every request, a large reference dataset a worker process needs in memory — pays for parsing once but
can amortize [`save()`](../api/basic_json_document/save.md)'s cost across every later load. That makes images useful
for a cache: save a document the first time it is parsed (to a file, a shared-memory segment, an in-process cache),
and [`load()`](../api/basic_json_document/load.md) it on every later use instead of parsing the source text again.
They are just as useful for handing a parsed document to another process (or a forked worker) running the same build
of the library, since [`load()`](../api/basic_json_document/load.md) turns the transfer into a copy of the node index
plus pointers into the received bytes, not a re-parse.
**Choosing a check.** [`load()`](../api/basic_json_document/load.md) takes an
[`image_check`](../api/basic_json_document/load.md#image_check) that trades validation against speed:
`image_check::full` (the default) checks everything the parser itself guarantees, so a checked image is exactly as
safe to read and serialize as a freshly parsed document — the right choice whenever the image did not come straight
from this process's own [`save()`](../api/basic_json_document/save.md), such as a file or a network peer.
`image_check::bounds` only checks structure and bounds — cheaper, since it skips scanning the text and the decoded
strings — and fits a cache the process trusts, one it wrote and reads back itself. `image_check::none` skips
validation entirely, for an image trusted as much as the process's own memory. See
[`load()`'s Notes](../api/basic_json_document/load.md#notes) for exactly what each level does and does not guarantee.
??? example "Example: cache a parsed configuration as an image"
```cpp
--8<-- "examples/basic_json_document__save.cpp"
```
Output:
```json
--8<-- "examples/basic_json_document__save.output"
```
!!! warning "Experimental"
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 (on big-endian targets, `save()` and `load()` do not exist), and tied to the
library build that wrote it — use it to cache a document or to hand one to another process running the *same*
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
[FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check
[`load()`](../api/basic_json_document/load.md) runs follows the idea of FlatBuffers' Verifier. No code is taken from
either.
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) | [`json_editable_document`](../api/json_editable_document.md) / [`json_editable_view`](../api/json_editable_view.md) |
|---|---|---|---|---|
| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document | same as `json_document`; edits go to storage the document owns |
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../api/basic_json_document/set.md)/[`push_back`](../api/basic_json_document/push_back.md)/[`insert`](../api/basic_json_document/insert.md)/[`erase`](../api/basic_json_document/erase.md) edit in place; the source text is never rewritten |
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../api/basic_json_view/number_format.md), the spelling of every untouched number |
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back — a configuration file, for instance — where the rest of it should come back exactly as it was |
| **Caching/reload** | not applicable — re-parse, or roll your own serialization | not applicable | [`save()`](../api/basic_json_document/save.md)/[`load()`](../api/basic_json_document/load.md): cache the parsed index as an image and reload it without parsing | same, saving the document's current — possibly edited — state |
<p class="nj-table-caption" markdown>Each column is one way to work with JSON in this library, and each row compares one property of them.</p>
## See also
- [`basic_json_document`](../api/basic_json_document/index.md) - the document that owns the parsed index (parse, access, images, edits)
- [`basic_json_view`](../api/basic_json_view/index.md) - the non-owning handle to a value in a document
- [`json_document`](../api/json_document.md) - and [`ordered_json_document`](../api/ordered_json_document.md) - the read-only documents
- [`json_editable_document`](../api/json_editable_document.md) - and [`ordered_json_editable_document`](../api/ordered_json_editable_document.md) - the documents that can be edited in place
- [Architecture](../home/architecture.md#node-index-of-json-views) - the layout of the node index
- [SAX interface](parsing/sax_interface.md) - event-based parsing without building a tree
- [Performance](performance.md) - benchmarks and tuning of parsing and serialization
- [Large, Streamed, and Partial Input](parsing/large_and_partial_input.md) - reading only the part of a text you need
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,220 @@
# Differences from `basic_json`
A [`basic_json_view`](../../api/basic_json_view/index.md) offers the read-only part of the
[`basic_json`](../../api/basic_json/index.md) interface, and in most respects it behaves exactly like the value
[`basic_json::parse()`](../../api/basic_json/parse.md) would have built from the same text. This page lists where it
does not: what a document parses differently, what a view's functions do differently, and which parts of the
`basic_json` interface a view does not have.
## Summary
| | `basic_json` value from `parse()` | view of a `json_document` | to get the `basic_json` behavior |
|---|---|---|---|
| [Integer types](#parsing) | any `number_integer_t`/`number_unsigned_t` | 64-bit only (compile-time check) | — |
| [Input size](#parsing) | unlimited | less than 4 GiB | — |
| [Parser callbacks](#parsing) | `parser_callback_t` | none | filter while iterating |
| [`operator[]` on a missing key or index](#element-access) | inserts (non-`const`) or undefined behavior (`const`) | returns a discarded view | — |
| [Object member order](#object-member-order) | sorted by key (`json`), insertion order (`ordered_json`) | document order | `materialize()` |
| [Repeated keys](#duplicate-keys) | the last value is kept | lookups find the first member; iteration, `size()`, and `dump()` see all of them | `materialize()` |
| [Conversions](#conversions) | `get<T>()`, implicit conversions, `get_ptr()`, `get_ref()` | `get<T>()`, `get_string()`, `number_token()` | `materialize()` |
| [Number spelling](#serialization) | lost when parsing | kept: `number_token()`, `number_format::source` | — |
| [Comparison](#comparison) | `==`, `!=`, `<`, `<=>`, ... | `==` and `!=` only | `materialize()` |
| [Modification](#what-a-view-does-not-have) | member functions of the value | only through an [editable document](editing.md) | `materialize()` |
| [Iterators](#what-a-view-does-not-have) | bidirectional (`rbegin()`/`rend()`) | forward only | `materialize()` |
<p class="nj-table-caption" markdown>Each row is one difference; the last column names the way to get `basic_json`'s behavior from a view, where there is one.</p>
## Parsing
### What is the same
- **Accept/reject.** [`accept()`](../../api/basic_json_document/accept.md) and
[`parse()`](../../api/basic_json_document/parse.md) accept and reject exactly the same inputs as
[`basic_json::accept()`](../../api/basic_json/accept.md)/[`basic_json::parse()`](../../api/basic_json/parse.md),
with the same `ignore_comments` and `ignore_trailing_commas` options.
- **Errors.** A failing parse throws the same exception — the same id, message, and position — because on failure
the library's own parser is run on the same bytes to produce the diagnostic. Valid input never pays for this, and
the replay builds no values: it costs about a lexing pass up to the error.
`#!cpp allow_exceptions == false` skips the replay and gives a
[discarded](../../api/basic_json_document/is_discarded.md) document instead of throwing, just as it gives a
discarded value for `#!cpp basic_json::parse()`.
- **Number classification.** An integer literal that does not fit into the 64-bit integer type becomes a
floating-point value, exactly as it does for `#!cpp basic_json::parse()`.
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../../api/macros/json_strict_nul_handling.md) and
[`JSON_NOEXCEPTION`](../../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../../api/macros/json_throw_user.md)
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
how it reports errors. The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
Two of them also add context to the exceptions of views, see [Exception context](#exception-context).
### What is different
- **Only 64-bit integers.** `basic_json_document<BasicJsonType>` requires `BasicJsonType::number_integer_t` and
`number_unsigned_t` to both be 64 bits wide; this is a compile-time `#!cpp static_assert`.
- **A 4 GiB input limit.** An input of 4294967280 bytes (4 GiB minus 16 bytes) or more throws
[`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416), a limit
`#!cpp basic_json::parse()` does not have. It keeps an index entry at 16 bytes: offsets and counts are 32 bits
wide. The limit is 16 bytes below 2^32, because the parser reads in 16-byte steps, and a position one step past the
end must still fit.
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
is read to its end into a buffer the document owns, and parsing starts after that. For a file, reading it into a
`#!cpp std::string` yourself and parsing that (an lvalue is borrowed) is therefore not more expensive in memory, and
was about 6 to 12 % faster than parsing from an `#!cpp std::ifstream`.
- **No parser callbacks.** [`parse()`](../../api/basic_json_document/parse.md) takes no
[`parser_callback_t`](../../api/basic_json/parser_callback_t.md): a document always indexes the whole text. Skip
the values you do not need when reading the view instead.
- **JSON text only.** A document is parsed from JSON text; there is no counterpart of
[`from_cbor()`](../../api/basic_json/from_cbor.md) and the other binary formats, and
[`is_binary()`](../../api/basic_json_view/is_binary.md) is always `#!cpp false`.
## Element access
**Chained access is safe.** [`operator[]`](../../api/basic_json_view/operator%5B%5D.md) with a missing key, an index
out of range, or an unresolvable JSON pointer returns a [discarded](../../api/basic_json_view/is_discarded.md) view,
and `operator[]` on a discarded view returns a discarded view without throwing: `#!cpp v["a"]["b"][0]` can be tested
once at the end. Type errors on values that exist (a key on an array, an index on an object) still throw, and
[`at`](../../api/basic_json_view/at.md) throws for every missing value.
`basic_json`'s non-`const` `operator[]` instead inserts a missing key, and its `const` `operator[]` has undefined
behavior for one.
## Objects
### Object member order
[`begin()`](../../api/basic_json_view/begin.md)/[`end()`](../../api/basic_json_view/end.md) and
[`items()`](../../api/basic_json_view/items.md) visit an object's members in the order they appear in the source text.
`basic_json`'s default `object_t` is a `std::map`, which sorts by key, so iterating a
[`materialize()`](../../api/basic_json_view/materialize.md)d value can print members in a different order than
iterating the view they came from. [`dump()`](../../api/basic_json_view/dump.md) of a view writes members in document
order as well.
### Duplicate keys
!!! warning "A lookup in a view and in the parsed value can give different answers"
If an object in the source text repeats a key, [`operator[]`](../../api/basic_json_view/operator%5B%5D.md),
[`at`](../../api/basic_json_view/at.md), [`find`](../../api/basic_json_view/find.md),
[`value`](../../api/basic_json_view/value.md), [`contains`](../../api/basic_json_view/contains.md), and JSON
pointer resolution all return the **first** member with that key. `basic_json::parse()` instead keeps the **last**
value of a repeated key, so for `#!cpp {"a":1,"a":2}`, `#!cpp view["a"]` is `#!cpp 1` while
`#!cpp parse(text)["a"]` is `#!cpp 2`. [`count`](../../api/basic_json_view/count.md) counts every member with the
key, so it is `#!cpp 2` here.
[`begin()`](../../api/basic_json_view/begin.md)/[`end()`](../../api/basic_json_view/end.md) and
[`items()`](../../api/basic_json_view/items.md) visit *every* occurrence, in document order, and
[`size()`](../../api/basic_json_view/size.md) counts all of them, so `#!cpp view.size()` can be larger than
`#!cpp view.materialize().size()`. [`dump()`](../../api/basic_json_view/dump.md) writes every occurrence, too, so
`#!cpp materialize().dump()` gives a different result for such an object.
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
to scan all members of the object, even when the key is found at the very first one: this made lookups in small
objects 1.6 to 3.4 times slower. The hash index of the keys of objects with 128 members or more leads to the first
member of a key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return
the first member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver
that sees duplicates is unpredictable, so neither choice is wrong.
The same holds for [`get<T>()`](../../api/basic_json_view/get.md) of a type whose `from_json()` reads the view, such
as the types of the `NLOHMANN_DEFINE_TYPE_*` macros: their members are looked up, so `#!cpp get<point>()` of
`#!cpp {"x":1,"x":2}` reads `#!cpp x == 1`, while `#!cpp materialize().get<point>()` reads `#!cpp 2`.
**What stays the same as `parse()`?** [`materialize()`](../../api/basic_json_view/materialize.md) and
[`get<std::map<...>>()`](../../api/basic_json_view/get.md) (and the other containers built from an object) replay
every member in order, so they keep the *last* value exactly like `#!cpp basic_json::parse()` (at the position of the
first occurrence of the key, for an [`ordered_json`](../../api/ordered_json.md)).
**How do I get the value `parse()` would give?** Either call `#!cpp view.materialize()` and look the key up in the
result, or iterate the members and keep the last match:
```cpp
// the last member with the key "a", as parse() would keep it
json_view last;
for (const auto item : view.items())
{
if (item.key() == "a")
{
last = item.value();
}
}
```
If you cannot trust the source text to have unique keys, check [`size()`](../../api/basic_json_view/size.md) against
the number of distinct keys, or reject duplicates when iterating.
## Conversions
- **No implicit conversions.** A view has no `#!cpp operator ValueType()`, no `#!cpp get_ptr()`, and no
`#!cpp get_ref()`; use [`get<T>()`](../../api/basic_json_view/get.md), or
[`get_string()`](../../api/basic_json_view/get_string.md) for a string without a copy.
- **`from_json()` overloads.** [`get<T>()`](../../api/basic_json_view/get.md) calls `T`'s `from_json()` with the view
only if that `from_json()` accepts views, and with [`materialize()`](../../api/basic_json_view/materialize.md)
otherwise; see [A `from_json()` that reads views](generic_code.md#a-from_json-that-reads-views). Both give the same
result except for [repeated keys](#duplicate-keys).
- **`string_view` members.** A struct with a `#!cpp std::string_view` member decoded from a view points into the
document, see [`string_view` members](reading.md#string_view-members). Decoding the same struct from a `json` value
compiles, too, but dangles at once if the value is a temporary.
## Serialization
[`dump()`](../../api/basic_json_view/dump.md) writes numbers the way
[`basic_json::dump()`](../../api/basic_json/dump.md) would by default, but writes object members in
[document order](#object-member-order) and writes [every occurrence of a repeated key](#duplicate-keys).
A view can also do what a `basic_json` value cannot: [`number_token()`](../../api/basic_json_view/number_token.md)
and [`number_format::source`](../../api/basic_json_view/number_format.md) give every number exactly as it was spelled
in the source text, while parsing into `basic_json` already reduces a number to its `#!cpp double`/`#!cpp int64_t`
value. See [Writing a view back](reading.md#writing-a-view-back).
## Comparison
**Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
[`operator==`](../../api/basic_json_view/operator_eq.md) and [`operator!=`](../../api/basic_json_view/operator_ne.md)
are provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
[`materialize()`](../../api/basic_json_view/materialize.md) or [`parse()`](../../api/basic_json/parse.md) would produce
equal values for them, without ever building a tree to do it. For ordering,
[`materialize()`](../../api/basic_json_view/materialize.md) is the way to get a value you can compare.
## Exception context
With [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md), an exception of a view names the JSON Pointer path of
the value within the document, and with
[`JSON_DIAGNOSTIC_POSITIONS`](../../api/macros/json_diagnostic_positions.md), it names the bytes of the value in the
source text, as they do for the exceptions of `#!cpp basic_json`.
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
exception is thrown. This takes time linear in the size of the document. One exception has neither: `key()` of an
iterator over an array or a primitive value
([`invalid_iterator.207`](../../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
not know the value it iterates over.
**Fewer source positions after edits.** With
[`JSON_DIAGNOSTIC_POSITIONS`](../../api/macros/json_diagnostic_positions.md) enabled,
[`materialize()`](../../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`. In
an [editable document](editing.md), a value written by an edit has none, and neither has an array or object whose
elements changed or whose last element has none; a `basic_json` value keeps its positions when it is changed. See
[`materialize()`](../../api/basic_json_view/materialize.md#notes).
## What a view does not have
A view is a read-only handle. These parts of the `basic_json` interface have no counterpart on a view;
[`materialize()`](../../api/basic_json_view/materialize.md) the subtree to use them:
- **Modifiers** such as `push_back`, `insert`, `erase`, `update`, or `operator=`. A view never changes a value
itself; an [editable document](editing.md) changes the values its views refer to through
[`set`](../../api/basic_json_document/set.md), [`push_back`](../../api/basic_json_document/push_back.md),
[`insert`](../../api/basic_json_document/insert.md), and [`erase`](../../api/basic_json_document/erase.md).
- **Reverse iteration.** A view's iterator is a forward iterator; there is no `rbegin()`/`rend()`.
- **JSON Patch and friends**: [`patch()`](../../api/basic_json/patch.md),
[`merge_patch()`](../../api/basic_json/merge_patch.md), [`diff()`](../../api/basic_json/diff.md),
[`flatten()`](../../api/basic_json/flatten.md).
- **Binary formats**: [`to_cbor()`](../../api/basic_json/to_cbor.md) and the other writers, and binary values
([`get_binary()`](../../api/basic_json/get_binary.md)).
## See also
- [Zero-copy JSON views](index.md) - overview
- [`basic_json_view`](../../api/basic_json_view/index.md) - the full interface of a view
- [`materialize()`](../../api/basic_json_view/materialize.md) - build the `basic_json` value of a view
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,133 @@
# Editing a document
A [`json_document`](../../api/json_document.md)/[`json_view`](../../api/json_view.md) lets you look at a parsed text
without copying it, but 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
[`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 [`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.
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 runtime one.
## Why: editing without reformatting
A price like `#!cpp 19.90` is exactly the kind of value that makes editing a `json` or `ordered_json` value in place
lossy. Say you parse a configuration file, patch one field, and write it back:
- **`json`** re-sorts every key on the way in (`object_t` is a `#!cpp std::map`) and rewrites every number to its
shortest round-trip form on the way out — a one-field patch turns into a diff that reorders the whole file and
rewrites `#!cpp 19.90` to `#!cpp 19.9`.
- **`ordered_json`** keeps the key order, but still rewrites every number the same way: parsing has already reduced
it to a `#!cpp double`/`#!cpp int64_t`, and there is no way back to how it was spelled in the source text.
The patch of the example below (set `"host"`, add `"retries"`) written back by each of them:
<div class="nj-jsonview-cmp" role="group" aria-label="The same patch written back by json, ordered_json, and json_editable_document">
<div><p class="nj-jsonview-cmp-h"><code>json</code></p><p class="nj-jsonview-cmp-v">keys sorted, numbers rewritten</p><pre>{<span class="nj-jsonview-bad">"host"</span>:"db2",
"name":"cache",
"port":6379,
"price":<span class="nj-jsonview-bad">19.9</span>,
"retries":3}</pre></div>
<div><p class="nj-jsonview-cmp-h"><code>ordered_json</code></p><p class="nj-jsonview-cmp-v">key order kept, numbers rewritten</p><pre>{"name":"cache",
"host":"db2",
"port":6379,
"price":<span class="nj-jsonview-bad">19.9</span>,
"retries":3}</pre></div>
<div class="nj-jsonview-cmp--best"><p class="nj-jsonview-cmp-h"><code>json_editable_document</code></p><p class="nj-jsonview-cmp-v">order and spelling kept</p><pre>{"name":"cache",
"host":"db2",
"port":6379,
"price":<span class="nj-jsonview-ok">19.90</span>,
"retries":3}</pre></div>
</div>
An editable document keeps both. [`dump()`](../../api/basic_json_view/dump.md) of an edited document writes members in
document order — a member [`set`](../../api/basic_json_document/set.md) added goes at the end, exactly where it was
inserted, and an [`erase`](../../api/basic_json_document/erase.md)d member simply leaves a gap: everything around it
keeps its place — and [`number_format::source`](../../api/basic_json_view/number_format.md) keeps the exact spelling
of every number an edit did not itself touch; a number an edit *did* touch is written the way
[`BasicJsonType::dump()`](../../api/basic_json/dump.md) would write it, since there is no source spelling for a brand
new value. See [Writing a view back](reading.md#writing-a-view-back).
??? example "Example: patch a configuration, keeping member order and number spellings"
```cpp
--8<-- "examples/json_editable_document.cpp"
```
Output:
```json
--8<-- "examples/json_editable_document.output"
```
## What stays valid, and what an edit costs
The source text itself is **never written**, and the parsed index never moves — a value keeps the node it was
parsed into for as long as it is not itself replaced. So every [view](../../api/basic_json_view/index.md) taken before
an edit, including a previously obtained [`root()`](../../api/basic_json_document/root.md), stays valid and, if it
still refers to the edited value, sees the edit; a view of a value a later edit drops or replaces just keeps showing
what it last held. New values go to storage the document allocates and owns on demand. The one thing an edit does
invalidate is the **iterators** taken over an edited array or object: the first time an element is added to it,
inserted into it, erased from it, or replaced by a value that spans a different number of nodes, its elements move
from the parsed, fixed layout to a growable block of links so that
[`push_back`](../../api/basic_json_document/push_back.md) can later grow it in amortized constant time — existing
elements are not touched, but an iterator that was walking the old layout no longer matches. A string obtained with
[`get_string()`](../../api/basic_json_view/get_string.md) is unaffected either way and stays valid across further
edits. See [`basic_json_document`'s Edits](../../api/basic_json_document/index.md#edits) for the details, and
[`set`'s Exception safety](../../api/basic_json_document/set.md#exception-safety) for what an edit guarantees if it
throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in
the index is described in the [architecture overview](../../home/architecture.md#node-index-of-json-views).
Step through the edits of a small document (the node fields follow the
[architecture overview](../../home/architecture.md#node-index-of-json-views)):
<div class="nj-jsonview-steps" data-nj-jsonview-steps>
<figure class="nj-jsonview-step" data-label="After parsing">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_0.svg"
</div>
<figcaption>Step 1 of 5. After parsing: nine nodes in the parsed index, no owned storage yet.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="set an existing member">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_1.svg"
</div>
<figcaption>Step 2 of 5. <code>doc.set(doc.root(), "port", 6380)</code> overwrites the node of the integer in place; no member is added or removed, so the object keeps its parsed layout.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="erase an array element">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_2.svg"
</div>
<figcaption>Step 3 of 5. <code>doc.erase(doc.root()["tags"], 0)</code>: the array is edited for the first time, so its elements move to a block of links in owned storage. The node of <code>"eu"</code> stays in the index, but nothing refers to it any more; iterators over the array taken before are invalid.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="push_back onto the array">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_3.svg"
</div>
<figcaption>Step 4 of 5. <code>doc.push_back(doc.root()["tags"], "new")</code>: the new string gets its own node, its text goes to the buffer of edited text, and a link to it is appended to the block of the array.</figcaption>
</figure>
<figure class="nj-jsonview-step" data-label="set a new member">
<div class="nj-jsonview-scroll">
--8<-- "images/json_view_edit_4.svg"
</div>
<figcaption>Step 5 of 5. <code>doc.set(doc.root(), "retries", 3)</code>: a new member, so the object moves to a block too: copies of its keys, links to the values it already had, then the new key and a link to the new integer.</figcaption>
</figure>
</div>
<p class="nj-caption" markdown>The node index and the owned storage of the document after each of five edits. Use *Back*, *Next*, or the numbered buttons to switch between the steps; without JavaScript all steps are shown one below the other.</p>
## See also
- [Zero-copy JSON views](index.md) - overview
- [`json_editable_document`](../../api/json_editable_document.md) - and [`ordered_json_editable_document`](../../api/ordered_json_editable_document.md) - the documents that can be edited in place
- [`basic_json_document`'s Edits](../../api/basic_json_document/index.md#edits) - the rules of edits in detail
- [Differences from `basic_json`](differences.md#exception-context) - source positions after edits
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,93 @@
# Writing generic code
## View types
A view's type names its `BasicJsonType` and its [`view_mode`](../../api/view_mode.md):
[`json_view`](../../api/json_view.md) is `#!cpp basic_json_view<json, view_mode::read_only>`, and
[`json_editable_view`](../../api/json_editable_view.md) is `#!cpp basic_json_view<json, view_mode::editable>`. These
are different types. The only conversion between them turns a view of a read-only document into a view of an
editable document of the same `BasicJsonType`, so a function written for one shortcut accepts few other views:
```cpp
void print_names(json_view v); // accepts neither an ordered_json_view nor a json_editable_view
void print_names(json_editable_view v); // also accepts a json_view, but no ordered_json_view
```
This is the same pitfall as a `to_json`/`from_json` written for `json` instead of any `BasicJsonType`, which then does
not work for `ordered_json`. The shortcuts are for declaring documents and views; a function that takes a view names
the views it accepts with a template:
```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);
// views of either document type
template<typename BasicJsonType, view_mode Mode>
void f(basic_json_view<BasicJsonType, Mode> v);
```
Views are passed by value: they are two pointers, cheap to copy, like a `#!cpp std::string_view`. A `const&` works as
well, but a non-`const` reference does not bind to the views that
[`root()`](../../api/basic_json_document/root.md), [`operator[]`](../../api/basic_json_view/operator%5B%5D.md), and
[`at()`](../../api/basic_json_view/at.md) return, so `#!cpp f(doc.root())` would not compile.
There is no conversion from an editable view to a read-only one, by design: the mode decides how a view walks the
index. A read-only view reads a container in place, while a view of an editable document follows the links that edits
leave behind, so a read-only view of an edited document would read stale values. The other direction is safe: a
read-only document has no edits, so a view of it [converts](../../api/basic_json_view/basic_json_view.md) to an
editable view, which reads it like an editable document before its first edit. A template deduces the mode without
conversions, so the template forms above still accept only the modes they name. Views of both modes compare with
[`operator==`](../../api/basic_json_view/operator_eq.md).
[`is_json_view<T>`](../../api/is_json_view.md) tells views from other types, for constraints that cannot be written as
a `basic_json_view<...>` parameter.
## A `from_json()` that reads views
[`get<T>()`](../../api/basic_json_view/get.md) on a view calls `T`'s `from_json()` with the view itself if that
`from_json()` accepts views, and with [`materialize()`](../../api/basic_json_view/materialize.md) otherwise. The
`from_json()` functions of the [`NLOHMANN_DEFINE_TYPE_*`](../../api/macros/nlohmann_define_type_intrusive.md),
[`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../../api/macros/nlohmann_define_derived_type.md), and
[`NLOHMANN_JSON_SERIALIZE_ENUM`](../../api/macros/nlohmann_json_serialize_enum.md) macros, and those of the library
for the standard types, accept views, so these types need nothing else. For decoding the statuses of
twitter.json into structs, reading the view is about 5 times faster than `#!cpp materialize().get<T>()`.
A hand-written `from_json()` reads views if it is constrained to `json` values and views:
```cpp
template < typename J, typename std::enable_if <
nlohmann::is_json_view<J>::value || std::is_same<J, nlohmann::json>::value, int >::type = 0 >
void from_json(const J& j, person& p)
{
p.name = j.at("name").template get<std::string>();
p.age = j.at("age").template get<int>();
}
```
Its body may only use what views and `basic_json` have in common: the type checks, `at()`, `operator[]`,
`contains()`, `value()`, `size()`, iteration and `items()`, and `get<T>()`/`get_to()`. An unconstrained
`#!cpp template<typename BasicJsonType> void from_json(const BasicJsonType& j, person& p)`, as in
[arbitrary type conversions](../arbitrary_types.md), is never called with a view, as it might use `basic_json`
functions a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
converted through `materialize()` as well.
Reading the view and `#!cpp materialize().get<T>()` give the same result, except for an object with a repeated key;
see [Duplicate keys](differences.md#duplicate-keys).
`to_json()` is unaffected: it writes `basic_json` values, never views.
## See also
- [Zero-copy JSON views](index.md) - overview
- [`is_json_view`](../../api/is_json_view.md) - type trait to detect views
- [`view_mode`](../../api/view_mode.md) - read-only and editable views
- [Reading values](reading.md) - what `get<T>()` converts without materializing
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,70 @@
# Images
[`save()`](../../api/basic_json_document/save.md) writes a document as an *image*: a byte buffer that
[`load()`](../../api/basic_json_document/load.md) reads back into a document without parsing — no lexing, no building
the node index, nothing but copying the nodes and pointing the text and the decoded strings at the image. Where
[`parse_copy()`](../../api/basic_json_document/parse_copy.md) still has to scan the whole input,
[`load()`](../../api/basic_json_document/load.md) turns that scan into a copy of the node index alone.
## Why
A document that is parsed once and then read many times — a configuration loaded at startup, a template rendered on
every request, a large reference dataset a worker process needs in memory — pays for parsing once but can amortize
[`save()`](../../api/basic_json_document/save.md)'s cost across every later load. That makes images useful for a
cache: save a document the first time it is parsed (to a file, a shared-memory segment, an in-process cache), and
[`load()`](../../api/basic_json_document/load.md) it on every later use instead of parsing the source text again.
They are just as useful for handing a parsed document to another process (or a forked worker) running the same build
of the library, since [`load()`](../../api/basic_json_document/load.md) turns the transfer into a copy of the node
index plus pointers into the received bytes, not a re-parse.
## Choosing a check
[`load()`](../../api/basic_json_document/load.md) takes an
[`image_check`](../../api/basic_json_document/load.md#image_check) that trades validation against speed:
- `image_check::full` (the default) checks everything the parser itself guarantees, so a checked image is exactly as
safe to read and serialize as a freshly parsed document — the right choice whenever the image did not come straight
from this process's own [`save()`](../../api/basic_json_document/save.md), such as a file or a network peer.
- `image_check::bounds` only checks structure and bounds — cheaper, since it skips scanning the text and the decoded
strings — and fits a cache the process trusts, one it wrote and reads back itself.
- `image_check::none` skips validation entirely, for an image trusted as much as the process's own memory.
See [`load()`'s Notes](../../api/basic_json_document/load.md#notes) for exactly what each level does and does not
guarantee.
??? example "Example: cache a parsed configuration as an image"
```cpp
--8<-- "examples/basic_json_document__save.cpp"
```
Output:
```json
--8<-- "examples/basic_json_document__save.output"
```
!!! warning "Experimental"
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 (on big-endian targets, `save()` and `load()` do not exist), and tied to the
library build that wrote it — use it to cache a document or to hand one to another process running the *same*
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.
## Credits
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
[`load()`](../../api/basic_json_document/load.md) runs follows the idea of FlatBuffers' Verifier. No code is taken from
either.
## See also
- [Zero-copy JSON views](index.md) - overview
- [`save()`](../../api/basic_json_document/save.md) - write a document as an image
- [`load()`](../../api/basic_json_document/load.md) - read an image back into a document
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,106 @@
# Zero-copy JSON views
`#!cpp <nlohmann/json_view.hpp>` adds a read-only, non-owning way to look at a parsed JSON text, as an alternative to
building a [`basic_json`](../../api/basic_json/index.md) tree with [`parse()`](../../api/basic_json/parse.md).
## The problem
[`basic_json::parse()`](../../api/basic_json/parse.md) builds a tree of `basic_json` values: one allocation for every
array and object, and every string copied into its own `std::string`. That is the right trade-off when the program
goes on to read and write the value freely, but it does more work than necessary when only a small part of a large
JSON text is actually needed, or when the same text is parsed over and over (many small messages, for instance) and
most of the resulting tree is thrown away almost immediately.
## The idea
[`basic_json_document::parse()`](../../api/basic_json_document/parse.md) parses the same JSON grammar, with the same
options, but instead of a tree it builds a flat index of the values it found: one
[16-byte entry](../../home/architecture.md#node-index-of-json-views) per value (and one per object key), in document
order. Integers are converted while parsing and stored in their entry. Floating-point numbers and strings are
referenced in place in the source text: a float is converted when it is read, and only strings that contain escape
sequences are decoded, into one shared buffer owned by the document.
[`basic_json_view`](../../api/basic_json_view/index.md) is a small, trivially copyable handle (two pointers) into that
index. It gives you the read-only, type-inspection part of the `basic_json` interface —
[`type()`](../../api/basic_json_view/type.md) and the `is_*()` predicates,
[`size()`](../../api/basic_json_view/size.md)/[`empty()`](../../api/basic_json_view/empty.md) — as well as element
access ([`operator[]`](../../api/basic_json_view/operator%5B%5D.md), [`at`](../../api/basic_json_view/at.md),
[`front`](../../api/basic_json_view/front.md)/[`back`](../../api/basic_json_view/back.md)), lookup
([`find`](../../api/basic_json_view/find.md), [`contains`](../../api/basic_json_view/contains.md),
[`count`](../../api/basic_json_view/count.md)), and iteration
([`begin`](../../api/basic_json_view/begin.md)/[`end`](../../api/basic_json_view/end.md),
[`items`](../../api/basic_json_view/items.md)) — without ever allocating a `basic_json` value. When you do need an
actual `basic_json` value for a subtree, [`materialize()`](../../api/basic_json_view/materialize.md) builds exactly the
one [`parse()`](../../api/basic_json/parse.md) would have produced for it.
The same small text, parsed both ways:
<div class="nj-fig-wrap" markdown>
--8<-- "images/json_view_layout.svg"
</div>
<p class="nj-caption" markdown>Top: the tree of separate heap allocations that `basic_json::parse()` builds. Bottom: `json_document::parse()` keeps the source text and builds one flat index of 16-byte nodes that point back into it, plus a buffer of decoded strings.</p>
## How to use it
Include `<nlohmann/json_view.hpp>` in addition to (or instead of) `<nlohmann/json.hpp>`. Parse into a
[`json_document`](../../api/json_document.md), inspect its [`root()`](../../api/basic_json_document/root.md), and
`materialize()` when you need a real value. `materialize()` works on any view, not just the root, so you can turn just
the subtree you need into a value:
??? example "Example: parse a document, inspect its root, and materialize it"
```cpp
--8<-- "examples/json_document.cpp"
```
Output:
```json
--8<-- "examples/json_document.output"
```
[`ordered_json_document`](../../api/ordered_json_document.md) is the equivalent for
[`ordered_json`](../../api/ordered_json.md), just as [`ordered_json`](../../api/ordered_json.md) is to
[`json`](../../api/json.md).
A view behaves like a `basic_json` value in most respects, but not in all: lookups of a repeated key, the order in
which an object's members are visited, and what a missing key does to chained `operator[]` are the most visible
differences. [Differences from `basic_json`](differences.md) lists all of them.
## Where to go next
- [Ownership and lifetime](lifetime.md) - when a document borrows its source text, and how long a view stays valid
- [Differences from `basic_json`](differences.md) - everything a view does differently from a parsed `basic_json` value
- [Reading values](reading.md) - converting with `get<T>()`, reading strings and numbers without copying, and writing a
view back with `dump()`
- [Writing generic code](generic_code.md) - functions and `from_json()` overloads that accept views
- [Editing a document](editing.md) - patching a document in place, keeping its member order and number spellings
- [Images](images.md) - saving a parsed document and loading it back without parsing
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
| | [`json`](../../api/json.md) / [`ordered_json`](../../api/ordered_json.md) | [SAX interface](../parsing/sax_interface.md) | [`json_document`](../../api/json_document.md) / [`json_view`](../../api/json_view.md) | [`json_editable_document`](../../api/json_editable_document.md) / [`json_editable_view`](../../api/json_editable_view.md) |
|---|---|---|---|---|
| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document | same as `json_document`; edits go to storage the document owns |
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../../api/basic_json_document/set.md)/[`push_back`](../../api/basic_json_document/push_back.md)/[`insert`](../../api/basic_json_document/insert.md)/[`erase`](../../api/basic_json_document/erase.md) edit in place; the source text is never rewritten |
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../../api/basic_json_view/number_format.md), the spelling of every untouched number |
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back — a configuration file, for instance — where the rest of it should come back exactly as it was |
| **Caching/reload** | not applicable — re-parse, or roll your own serialization | not applicable | [`save()`](../../api/basic_json_document/save.md)/[`load()`](../../api/basic_json_document/load.md): cache the parsed index as an [image](images.md) and reload it without parsing | same, saving the document's current — possibly edited — state |
<p class="nj-table-caption" markdown>Each column is one way to work with JSON in this library, and each row compares one property of them.</p>
## See also
- [`basic_json_document`](../../api/basic_json_document/index.md) - the document that owns the parsed index (parse, access, images, edits)
- [`basic_json_view`](../../api/basic_json_view/index.md) - the non-owning handle to a value in a document
- [`json_document`](../../api/json_document.md) - and [`ordered_json_document`](../../api/ordered_json_document.md) - the read-only documents
- [`json_editable_document`](../../api/json_editable_document.md) - and [`ordered_json_editable_document`](../../api/ordered_json_editable_document.md) - the documents that can be edited in place
- [Architecture](../../home/architecture.md#node-index-of-json-views) - the layout of the node index
- [SAX interface](../parsing/sax_interface.md) - event-based parsing without building a tree
- [Performance](../performance.md) - benchmarks and tuning of parsing and serialization
- [Large, Streamed, and Partial Input](../parsing/large_and_partial_input.md) - reading only the part of a text you need
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,73 @@
# Ownership and lifetime
A document either **borrows** the text it was parsed from, or **owns** its own copy of it; call
[`owns_source()`](../../api/basic_json_document/owns_source.md) to find out which happened.
[`parse()`](../../api/basic_json_document/parse.md) decides this from the value category and type of its argument (an
lvalue `#!cpp std::string` is borrowed; an rvalue `#!cpp std::string` is moved in, owned without a copy; a stream is
read into an owned buffer; and so on — see [`parse`'s Notes](../../api/basic_json_document/parse.md#notes) for the
full table). [`parse_copy()`](../../api/basic_json_document/parse_copy.md) always owns a copy, regardless of the input.
!!! warning "A borrowed document depends on your buffer"
If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
exactly as it would be for a dangling `#!cpp std::string_view`.
## When a view is valid
A view is valid only while all of the following hold:
- the document is alive,
- the document has not been re-parsed since the view was taken (with
[`read()`](../../api/basic_json_document/read.md) or [`parse()`](../../api/basic_json_document/parse.md) into it),
- the document has not had [`shrink_to_fit()`](../../api/basic_json_document/shrink_to_fit.md) called on it since, and
- if the document borrows its source text, that text is still alive and unmodified.
Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap
allocation that keeps its address across the move. Take a fresh view from
[`root()`](../../api/basic_json_document/root.md) whenever any of the other conditions above was not met. Edits of an
[editable document](editing.md) keep views valid, too; see [What stays valid](editing.md#what-stays-valid-and-what-an-edit-costs).
<div class="nj-fig-wrap" markdown>
--8<-- "images/json_view_lifetime.svg"
</div>
<p class="nj-caption" markdown>Four timelines of a document and its views, each bar showing when an object is alive or valid and when it dangles. Destroying the borrowed text, re-parsing, or shrinking the document invalidates the views taken before; moving the document does not.</p>
## Why views can dangle
A view can dangle, exactly like a `#!cpp std::string_view`: nothing stops you from using it after its document is
gone. This is deliberate. A view is two pointers and trivially copyable; making it safe (a `#!cpp std::shared_ptr` in
the document and a `#!cpp std::weak_ptr` in each view) would make it larger, add reference counting to every copy,
and a `lock()` to every access — exactly the costs the view exists to avoid. What is caught is the obvious case:
because a view dies with its document, [`root()`](../../api/basic_json_document/root.md) is not callable on a
temporary document: `#!cpp auto v = json_document::parse(text).root();` does not compile. Give the document a name
first.
The same holds for what a view hands out without copying: a string from
[`get_string()`](../../api/basic_json_view/get_string.md), a token from
[`number_token()`](../../api/basic_json_view/number_token.md), and [`string_view` members](reading.md#string_view-members)
of decoded structs are valid only as long as the document and, if it borrows, its source text.
??? example "Example: borrowed and owned documents, and when views become invalid"
```cpp
--8<-- "examples/json_view_ownership.cpp"
```
Output:
```json
--8<-- "examples/json_view_ownership.output"
```
## See also
- [Zero-copy JSON views](index.md) - overview
- [`basic_json_document`](../../api/basic_json_document/index.md) - the document that owns the parsed index
- [`owns_source()`](../../api/basic_json_document/owns_source.md) - whether a document borrows its source text
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,83 @@
# Reading values
## Getting values out without copying
[`get<T>()`](../../api/basic_json_view/get.md) converts most `T` directly from the flat index, without ever building a
`basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`,
`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json_view` itself, the standard containers
and other types the library converts (`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::list`, `#!cpp std::array`,
`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, ...), and the types of the `NLOHMANN_DEFINE_TYPE_*` and
`NLOHMANN_JSON_SERIALIZE_ENUM` macros. Types whose `from_json()` is written for `basic_json` only go through
[`materialize()`](../../api/basic_json_view/materialize.md)`.get<T>()` instead: the subtree is built into a real
`basic_json` value first, exactly as [`parse()`](../../api/basic_json/parse.md) would, and converted from there. See
[A `from_json()` that reads views](generic_code.md#a-from_json-that-reads-views) for how to write a `from_json()` that
reads views, and [Duplicate keys](differences.md#duplicate-keys) for the one case in which the two ways give different
results.
Two conversions never copy at all:
- [`get_string()`](../../api/basic_json_view/get_string.md) (equivalently, `#!cpp get<string_view_t>()`) returns a
string as a `string_view_t` pointing into the document's [`source()`](../../api/basic_json_document/source.md) text
— or, for a string that contains escape sequences, into the document's own buffer of decoded strings — instead of
allocating a new `#!cpp std::string`.
- [`number_token()`](../../api/basic_json_view/number_token.md) returns a number exactly as it was written in the
source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of
rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get<T>()` (and
[`basic_json::parse()`](../../api/basic_json/parse.md)) would.
Both results are only valid as long as the document and, if it [borrows its source text](lifetime.md), that text.
### `string_view` members
A struct whose member is a `#!cpp std::string_view` (C++17) can be decoded from a view with the
`NLOHMANN_DEFINE_TYPE_*` macros, and the member is not copied either: for a string without escape sequences it points
into the document's source text, and for a string with escape sequences into the document's buffer of decoded
strings. The member is valid as long as the document is alive and, for a document that
[borrows its source text](lifetime.md) (as [`parse()`](../../api/basic_json_document/parse.md) does for an lvalue
`#!cpp std::string`, a `#!cpp std::string_view`, or a C string), as long as that text is.
```cpp
struct record
{
std::string_view name;
int count;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(record, name, count)
};
const std::string text = R"({"name": "widget", "count": 3})";
const json_document doc = json_document::parse(text); // borrows text
const record r = doc.root().get<record>(); // r.name points into text, valid while text and doc are
```
!!! warning "Decoding into a `string_view` from a `json` value dangles at once"
The same struct also compiles with `#!cpp json::parse(text).get<record>()`, but there `name` points into the
temporary `json` value, which is destroyed at the end of the statement. Use a named
[`json_document`](../../api/json_document.md), as above, to decode such a struct.
## Writing a view back
[`dump()`](../../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a
`basic_json` value. It writes object members in document order and every occurrence of a repeated key, so it can
differ from `#!cpp materialize().dump()`; see [Serialization](differences.md#serialization).
By default, numbers are written the way [`basic_json::dump()`](../../api/basic_json/dump.md) would.
[`number_format::source`](../../api/basic_json_view/number_format.md) instead copies every number exactly as it was
written in the source text — a price like `#!cpp 19.90`, a long order or account ID with more digits than any number
type holds, or a high-precision coordinate — something `basic_json` cannot do at all, since parsing already reduces
a number to its parsed `#!cpp double`/`#!cpp int64_t` value. Together with an [editable document](editing.md), this
lets you patch a file without reformatting the rest of it.
[`operator<<`](../../api/basic_json_view/operator_ltlt.md) writes a view to a stream the way `basic_json`'s does, using
the stream's `width`/`fill` for indentation.
## See also
- [Zero-copy JSON views](index.md) - overview
- [`get`](../../api/basic_json_view/get.md) - convert a view
- [`dump`](../../api/basic_json_view/dump.md) - serialize a view
- [Writing generic code](generic_code.md) - functions and `from_json()` overloads that accept views
## Version history
- Added in version 3.13.0.
@@ -88,7 +88,7 @@ a.erase(1); // [1,3,4] (erase by index)
## Editing a document
The operations above work on a `basic_json` value. A JSON text that was parsed into a
[document](json_view.md#editing-a-document) can also be changed in place, without building a `basic_json` tree for the
[document](json_view/editing.md) can also be changed in place, without building a `basic_json` tree for the
parts that are not touched: [`set`](../api/basic_json_document/set.md) replaces a value, adds or replaces an object
member, or replaces an array element (also through a JSON pointer),
[`push_back`](../api/basic_json_document/push_back.md) appends to an array,
@@ -105,7 +105,7 @@ editable document types, such as [`json_editable_document`](../api/json_editable
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch
- [`erase`](../api/basic_json/erase.md) - / [`clear`](../api/basic_json/clear.md) - remove elements
- [JSON Patch and Diff](json_patch.md) - and [JSON Merge Patch](merge_patch.md) - structured modifications
- [Editing a document](json_view.md#editing-a-document) - change a parsed document in place
- [Editing a document](json_view/editing.md) - change a parsed document in place
- [`insert`](../api/basic_json/insert.md) - insert elements at an iterator position or merge a range
- [`operator+=`](../api/basic_json/operator+=.md) - shorthand for `push_back`
- [Element Access](element_access/index.md) - read elements
+4 -4
View File
@@ -86,7 +86,7 @@ ahead: after parsing, it is positioned exactly as if every character had been re
[`operator>>`](../../api/operator_gtgt.md) can still read several values from one stream (see also
[JSON Lines](json_lines.md)).
This does not hold for [`json_document`](../json_view.md): it reads a stream into a buffer it owns, so the stream saves
This does not hold for [`json_document`](../json_view/index.md): it reads a stream into a buffer it owns, so the stream saves
no memory, and reading the file into a `#!cpp std::string` yourself and borrowing it is about 6 to 12 % faster.
Two macros switch these optimizations off without changing any result:
@@ -115,7 +115,7 @@ The library offers two parsing models, and a third, read-only alternative:
reads the same JSON grammar but builds a flat, read-only index of the values instead of a `basic_json` tree. Strings and
floating-point numbers are read from the text on demand, and only the parts you
[`materialize()`](../../api/basic_json_view/materialize.md) become `basic_json` values. This avoids most of the work when you
need only a few values from a large or frequently parsed text; see [Zero-copy JSON views](../json_view.md). It is read-only, so use
need only a few values from a large or frequently parsed text; see [Zero-copy JSON views](../json_view/index.md). It is read-only, so use
`parse` when you need to modify the value.
You can influence a DOM parse without switching to the SAX interface by passing a
@@ -139,7 +139,7 @@ json_document doc = json_document::parse(R"({"happy": true, "pi": 3.141})");
std::cout << doc.root()["happy"].get<bool>() << '\n';
```
See [Zero-copy JSON views](../json_view.md) for the concept and the trade-offs.
See [Zero-copy JSON views](../json_view/index.md) for the concept and the trade-offs.
## Exceptions
@@ -158,7 +158,7 @@ options.
- [Large, streamed, and partial input](large_and_partial_input.md) - chunked, concatenated, and very large input
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
- [SAX interface](sax_interface.md) - implement a custom SAX handler
- [Zero-copy JSON views](../json_view.md) - parse into a read-only document and read it through views
- [Zero-copy JSON views](../json_view/index.md) - parse into a read-only document and read it through views
- [parsing and exceptions](parse_exceptions.md) - control error handling
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream
@@ -156,11 +156,11 @@ record the reason in the handler (here: `id`) if you need to tell the two apart.
### Read parts of a large document with a view
If the whole text is available and you only need to read some of it, a [`json_document`](../json_view.md) from
If the whole text is available and you only need to read some of it, a [`json_document`](../json_view/index.md) from
`<nlohmann/json_view.hpp>` (added in version 3.13.0) avoids the `basic_json` tree altogether: it keeps the text, builds a flat index of the values,
and gives read-only [views](../../api/basic_json_view/index.md) of the parts you look at. Only what you
[`materialize()`](../../api/basic_json_view/materialize.md) becomes a `basic_json` value. See
[Zero-copy JSON views](../json_view.md) for details and for the lifetime rules.
[Zero-copy JSON views](../json_view/index.md) for details and for the lifetime rules.
## See also
@@ -168,7 +168,7 @@ and gives read-only [views](../../api/basic_json_view/index.md) of the parts you
- [JSON Lines](json_lines.md) - newline-delimited JSON
- [Parser callbacks](parser_callbacks.md) - discard values while parsing
- [SAX interface](sax_interface.md) - event-based parsing
- [Zero-copy JSON views](../json_view.md) - read parts of a large document without building a tree
- [Zero-copy JSON views](../json_view/index.md) - read parts of a large document without building a tree
- [Untrusted input](untrusted_input.md) - limits for input you do not control
- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
+6 -6
View File
@@ -40,7 +40,7 @@ optimization but not the second, since the byte-scanning fast path requires cont
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
into a string first is no longer necessary for speed (this is for `#!cpp json::parse`; a
[`json_document`](json_view.md) reads a stream into an owned buffer anyway, so for it reading the file into a string
[`json_document`](json_view/index.md) reads a stream into an owned buffer anyway, so for it reading the file into a string
and borrowing that is the faster way):
```cpp
@@ -73,12 +73,12 @@ a DOM, three alternatives avoid building it:
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
unparsed remainder of the input) instead of the whole document — see the [recipe for streaming a large homogeneous
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
- Parse into a [`json_document`](json_view.md) (`#!cpp <nlohmann/json_view.hpp>`) instead of a `basic_json`. It keeps
- Parse into a [`json_document`](json_view/index.md) (`#!cpp <nlohmann/json_view.hpp>`) instead of a `basic_json`. It keeps
the input text and builds a flat index of 16 bytes per value; strings and floating-point numbers are not copied, but
read from the text when needed. Read-only [views](../api/basic_json_view/index.md) give the familiar element access, and only the
parts you [`materialize()`](../api/basic_json_view/materialize.md) become `basic_json` values. A document that
borrows the text instead of owning a copy needs the text to outlive it; see
[choosing between `json`, the SAX interface, and `json_view`](json_view.md#choosing-between-json-ordered_json-the-sax-interface-and-json_view).
[choosing between `json`, the SAX interface, and `json_view`](json_view/index.md#choosing-between-json-ordered_json-the-sax-interface-and-json_view).
If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON
document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory
@@ -182,11 +182,11 @@ an adapter, since their template argument order does not match what `basic_json`
large value therefore doubles its memory; see [Avoiding copies](#avoiding-copies). `#!cpp auto x = j["key"];` is a
copy, `#!cpp auto& x = j["key"];` is not.
- **A moved-from value is `null`.** After `#!cpp json c = std::move(a);` the value `a` is `#!json null` and owns nothing.
- **Reading does not require a tree.** If you only read the data, parse it into a [`json_document`](json_view.md)
- **Reading does not require a tree.** If you only read the data, parse it into a [`json_document`](json_view/index.md)
(`#!cpp <nlohmann/json_view.hpp>`, added in version 3.13.0) instead of a `basic_json`. It stores 16 bytes per value, does not copy floating-point
numbers or strings without escape sequences out of the text, and creates no `basic_json` value except for the parts
you [`materialize()`](../api/basic_json_view/materialize.md). A document that borrows the text needs the text to
outlive it; see [Zero-copy JSON views](json_view.md).
outlive it; see [Zero-copy JSON views](json_view/index.md).
- **Containers keep their capacity.** `clear()` and `erase()` on an array remove the elements, but the underlying
`#!cpp std::vector` keeps its allocated capacity, like `#!cpp std::vector::clear` does. To give the memory back,
assign a new value (`#!cpp j = json::array();`) or destroy the value.
@@ -267,7 +267,7 @@ those headers are then never processed by the compiler at all.
- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together
- [Parsing](parsing/index.md) - the available parsing functions and inputs
- [SAX interface](parsing/sax_interface.md) - parse without building a DOM
- [Zero-copy JSON views](json_view.md) - parse into a flat index of the text and read it without building a DOM
- [Zero-copy JSON views](json_view/index.md) - parse into a flat index of the text and read it without building a DOM
- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types