Differences from basic_json¶
A 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.
Summary¶
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() |
Each row is one difference; the last column names the way to get basic_json's behavior from a view, where there is one.
Parsing¶
What is the same¶
- Accept/reject.
accept()andparse()accept and reject exactly the same inputs asbasic_json::accept()/basic_json::parse(), with the sameignore_commentsandignore_trailing_commasoptions. - 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.
allow_exceptions == falseskips the replay and gives a discarded document instead of throwing, just as it gives a discarded value forbasic_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
basic_json::parse(). - Macros.
JSON_STRICT_NUL_HANDLINGandJSON_NOEXCEPTION/JSON_THROW_USERbehave 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 valuesmaterialize()produces, throughBasicJsonType. Two of them also add context to the exceptions of views, see Exception context.
What is different¶
- Only 64-bit integers.
basic_json_document<BasicJsonType>requiresBasicJsonType::number_integer_tandnumber_unsigned_tto both be 64 bits wide; this is a compile-timestatic_assert. - A 4 GiB input limit. An input of 4294967280 bytes (4 GiB minus 16 bytes) or more throws
out_of_range.416, a limitbasic_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
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 astd::stringyourself and parsing that (an lvalue is borrowed) is therefore not more expensive in memory, and was about 6 to 12 % faster than parsing from anstd::ifstream. - No parser callbacks.
parse()takes noparser_callback_t: 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()and the other binary formats, andis_binary()is alwaysfalse.
Element access¶
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: 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.
Objects¶
Object member order¶
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.
Duplicate keys¶
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[], at, find, value, contains, 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 {"a":1,"a":2}, view["a"] is 1 while parse(text)["a"] is 2. count counts every member with the key, so it is 2 here.
begin()/end() and items() visit every occurrence, in document order, and size() counts all of them, so view.size() can be larger than view.materialize().size(). dump() writes every occurrence, too, so 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 get<point>() of {"x":1,"x":2} reads x == 1, while materialize().get<point>() reads 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 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 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.
Conversions¶
- No implicit conversions. A view has no
operator ValueType(), noget_ptr(), and noget_ref(); useget<T>(), orget_string()for a string without a copy. from_json()overloads.get<T>()callsT'sfrom_json()with the view only if thatfrom_json()accepts views, and withmaterialize()otherwise; see Afrom_json()that reads views. Both give the same result except for repeated keys.string_viewmembers. A struct with astd::string_viewmember decoded from a view points into the document, seestring_viewmembers. Decoding the same struct from ajsonvalue compiles, too, but dangles at once if the value is a temporary.
Serialization¶
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 double/int64_t value. See Writing a view back.
Comparison¶
Ordering comparisons are not provided by basic_json_view — there is no 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.
Exception context¶
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 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 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().
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() the subtree to use them:
- Modifiers such as
push_back,insert,erase,update, oroperator=. A view never changes a value itself; an editable document changes the values its views refer to throughset,push_back,insert, anderase. - Reverse iteration. A view's iterator is a forward iterator; there is no
rbegin()/rend(). - JSON Patch and friends:
patch(),merge_patch(),diff(),flatten(). - Binary formats:
to_cbor()and the other writers, and binary values (get_binary()).
See also¶
- Zero-copy JSON views - overview
basic_json_view- the full interface of a viewmaterialize()- build thebasic_jsonvalue of a view
Version history¶
- Added in version 3.13.0 unreleased.