basic_jsonA basic_json_view offers the read-only part of the basic_json interface, and in most respects it behaves exactly like the value basic_json::parse() 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.
basic_json value from parse() | view of a json_document | to get the basic_json behavior | |
|---|---|---|---|
| Integer types | any number_integer_t/number_unsigned_t | 64-bit only (compile-time check) | — |
| Input size | unlimited | less than 4 GiB | — |
| Parser callbacks | parser_callback_t | none | filter while iterating |
operator[] on a missing key or index | inserts (non-const) or undefined behavior (const) | returns a discarded view | — |
| Object member order | sorted by key (json), insertion order (ordered_json) | document order | materialize() |
| Repeated keys | the last value is kept | lookups find the first member; iteration, size(), and dump() see all of them | materialize() |
| Conversions | get<T>(), implicit conversions, get_ptr(), get_ref() | get<T>(), get_string(), number_token() | materialize() |
| Number spelling | lost when parsing | kept: number_token(), number_format::source | — |
| Comparison | ==, !=, <, <=>, ... | == and != only | materialize() |
| Modification | member functions of the value | only through an editable document | materialize() |
| Iterators | bidirectional (rbegin()/rend()) | forward only | materialize() |
accept() and parse() accept and reject exactly the same inputs as basic_json::accept()/basic_json::parse(), with the same ignore_comments and ignore_trailing_commas options.#!cpp allow_exceptions == false skips the replay and gives a discarded document instead of throwing, just as it gives a discarded value for #!cpp basic_json::parse().#!cpp basic_json::parse().JSON_STRICT_NUL_HANDLING and JSON_NOEXCEPTION/JSON_THROW_USER 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.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.out_of_range.416, 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.#!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.parse() takes no parser_callback_t: a document always indexes the whole text. Skip the values you do not need when reading the view instead.from_cbor() and the other binary formats, and is_binary() is always #!cpp false.Chained access is safe. operator[] with a missing key, an index out of range, or an unresolvable JSON pointer returns a discarded 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 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.
begin()/end() and items() 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()d value can print members in a different order than iterating the view they came from. dump() of a view writes members in document order as well.
!!! 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()/end() and items() visit every occurrence, in document order, and size() counts all of them, so #!cpp view.size() can be larger than #!cpp view.materialize().size(). dump() 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>() 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() and get<std::map<...>>() (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).
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:
// 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() against the number of distinct keys, or reject duplicates when iterating.
#!cpp operator ValueType(), no #!cpp get_ptr(), and no #!cpp get_ref(); use get<T>(), or get_string() for a string without a copy.from_json() overloads. get<T>() calls T's from_json() with the view only if that from_json() accepts views, and with materialize() otherwise; see A from_json() that reads views. Both give the same result except for repeated 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. Decoding the same struct from a json value compiles, too, but dangles at once if the value is a temporary.dump() writes numbers the way basic_json::dump() would by default, but writes object members in document order and writes every occurrence of a repeated key.
A view can also do what a basic_json value cannot: number_token() and number_format::source 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.
Ordering comparisons are not provided by basic_json_view — there is no #!cpp operator<. operator== and operator!= are provided, though: two views, or a view and a BasicJsonType value, compare equal exactly when materialize() or parse() would produce equal values for them, without ever building a tree to do it. For ordering, materialize() is the way to get a value you can compare.
With JSON_DIAGNOSTICS, an exception of a view names the JSON Pointer path of the value within the document, and with JSON_DIAGNOSTIC_POSITIONS, 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), because a view’s iterator does not know the value it iterates over.
Fewer source positions after edits. With JSON_DIAGNOSTIC_POSITIONS enabled, materialize() sets the same positions as #!cpp basic_json::parse(). In an editable document, 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().
A view is a read-only handle. These parts of the basic_json interface have no counterpart on a view; materialize() the subtree to use them:
push_back, insert, erase, update, or operator=. A view never changes a value itself; an editable document changes the values its views refer to through set, push_back, insert, and erase.rbegin()/rend().patch(), merge_patch(), diff(), flatten().to_cbor() and the other writers, and binary values (get_binary()).basic_json_view - the full interface of a viewmaterialize() - build the basic_json value of a view