An image is a document stored so that loading it needs no
parsing: save() writes the node index, the text and the decoded
strings; the static load() reads an image written by save().
load() takes a pointer and size, a borrowed vector, or an owned
rvalue vector; the nodes are copied so they are aligned and can
be edited, while the text and decoded strings stay in the image.
image_check controls how much load() trusts the input: full
checks structure, bounds, strings and numbers, the parser's own
guarantees; bounds checks structure and bounds only; none skips
all checks, for images from a trusted source.
Layout is little-endian only ("NJVI" header, nodes, text, decoded
strings), following the idea of zero-copy formats such as
FlatBuffers and YaFF; the check follows FlatBuffers' Verifier.
New errors: parse_error.116 for a malformed image or a failed
check, type_error.320 for a discarded document or a big-endian
target.
A dedicated fuzzer and 6,000 seeded corruptions, checked under
ASan/UBSan, found and fixed two gaps: unchecked reserved header
fields, and unbounded null/boolean offsets that could make
dump() throw std::length_error.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
4.7 KiB
nlohmann::basic_json_document::save
std::vector<std::uint8_t> save() const;
Writes the document as an image: a byte buffer that load reads back without parsing. The image holds
the node index, the source text (plus, for an edited document, the number tokens edits wrote), and the decoded
strings (plus the strings edits wrote) -- everything root() needs, with nothing left to parse.
An edited document is written in its current state, with its values in document order, the way the library's own
parser would have produced them for that JSON text: a member set added goes at the end, an
erased member leaves no trace, and a float that is not finite (NaN or positive/negative infinity)
becomes null, the same substitution dump() makes. The same document always saves to
the same bytes -- also across BasicJsonType and #!cpp Editable, since the image reflects document order and
values only, not which specialization produced them.
Return value
The image, as a #!cpp std::vector<std::uint8_t>. Pass it, or a pointer to its data together with its size, to
load to read the document back.
Exception safety
Strong guarantee: save() does not modify #!cpp *this (it is #!cpp const), so if it throws, the document is left
exactly as it was, and the partially built image is discarded with the exception.
Exceptions
Throws type_error.320 if the document is
discarded -- a default-constructed document, or one a failed parse()/
read() with allow_exceptions == false left discarded.
On a big-endian target, throws type_error.320 with a different message instead: the image format is little-endian
only (see Notes).
Throws out_of_range.416 if the node count, the text, or
the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets
parse() and, for edits, set/push_back are already limited to.
!!! failure "Example messages"
```
[json.exception.type_error.320] cannot save a discarded json_document
```
```
[json.exception.type_error.320] json_document images need a little-endian target
```
```
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
```
Complexity
Linear in the size of the document: the number of nodes, plus the length of the text and the decoded strings that end up in the image.
Notes
Format. The image begins with a 64-byte header (the magic bytes #!cpp "NJVI", a version number, the node count,
and the sizes of the text and the decoded strings, all little-endian), followed by the nodes
(16 bytes each), the text and a #!cpp '\0', and the decoded
strings and a #!cpp '\0'. load checks the header, and the sizes it describes, before reading anything
else -- see load's Exceptions.
!!! warning "Experimental"
The image format is versioned but not yet stable: it may change in an incompatible way before it is declared
stable. Use images to cache a document within one build of the library, or to hand one to another process running
the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original
JSON text if you need to read a saved document back with a future library version.
Little-endian only. The image is written as raw little-endian bytes, with no byte-swapping. save() (and
load) throw type_error.320 on a big-endian target rather than silently produce bytes a big-endian
reader could not interpret correctly.
Examples
??? example "Caching a parsed document as an image"
The example below saves a parsed configuration as an image -- the way a service might cache one to answer later
requests without parsing the text again -- and confirms that loading it back gives exactly the same result as
parsing did, and that saving is deterministic.
```cpp
--8<-- "examples/basic_json_document__save.cpp"
```
Output:
```json
--8<-- "examples/basic_json_document__save.output"
```
See also
- load - read an image written by
save() - owns_source - return whether the document holds its own copy of the text
basic_json_view::dump- serialize the document to JSON text instead of an image- Images - why and when to use images
Version history
- Added in version 3.13.0.