Files
json/docs/mkdocs/docs/api/basic_json_document/load.md
T
Niels Lohmann 4971200b54 Add images of json_documents: save() and load() (#5632)
* Add images of json_documents: save() and load()

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>

* Fix MSVC errors in image test and add image_check doc anchor

Move the raw string literal out of the CHECK macro (MSVC preprocessor),
cast 64-bit header fields to std::size_t (C4244 on 32-bit), and give the
image_check section in load.md a real anchor for the mkdocs strict build.

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

* Check each distinct string and float range once in the full image check

The full check scanned the contents of every string node and every float token over its own range, so many nodes pointing to one large range made load() quadratic. The structural walk now only collects the ranges; after it, each kind is sorted and checked once per distinct range. Ranges of one kind that overlap without being identical are rejected, as save() never writes them (nodes that share a value share the whole range). The cost is linear in the size of the image plus sorting the ranges.

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

* Adapt the image tests to named documents and last-wins lookups

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

* Test that the targets of links never reach an image

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

* Test lookups in loaded images with duplicate and colliding keys

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

* Release the list of large objects once load() has built the indexes

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

* Test that invalid UTF-8 of a loaded image is not copied into an editable document

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

* Document that editable documents refuse invalid UTF-8 copied from a loaded image

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

* Expect first-wins lookups in the image tests

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

* Avoid useless casts of header fields in the image tests

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

* Fix clang-tidy 22 findings in unit-json_view_image.cpp and image.hpp

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

* Fix MSVC C4244 and C2017 in the image test

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

* Fix the indentation of a loop in the image test

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

* Make json_view.hpp pass include-what-you-use

ci_single_binaries runs IWYU with --error on every header. For json_view.hpp
it suggested adding <array> (std::array is used), the headers that json.hpp
already provides (abi_config, abi_macros, input_adapters, json_pointer,
cpp_future, string_concat, value_t, json_fwd), and <version> for
std::nullptr_t, and removing <cstddef>.

- include <array>
- keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>)
- tell IWYU not to suggest the headers that json.hpp provides: the amalgamated
  json_view.hpp only includes json.hpp, so including them here would duplicate
  their definitions
- drop edit_storage.hpp, which edit.hpp includes

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

* Silence a clang-tidy C++17 finding in the image test

bugprone-suspicious-stringview-data-usage flags data() of the source view
under C++17, although the length is passed and checked by the REQUIRE
before it.

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

* Build the image link test without exceptions

load_result() exists only with exceptions; with JSON_NOEXCEPTION the test
loads the image with the check directly (a failed check aborts).

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

* Regenerate the amalgamated headers

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

* Follow the renames from the review of the view builder

The review of #5620 renamed document_data's tape/tape_size/tape_cap to
nodes/node_count/node_capacity and made the decode arena a
std::vector<char>; adapt the code of this change to it.

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

* Rename the emit lambda in the image writer for Qt compatibility

Qt defines emit as an empty macro, so a Qt header included before
json_view.hpp broke the lambda's declaration (see #5865). The slots
variables in the image tests are renamed as well.

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

* Address the review of json_document images

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

* Regenerate the amalgamated headers

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

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:29 +02:00

8.9 KiB

nlohmann::basic_json_document::load

// (1)
static basic_json_document load(const std::uint8_t* image, std::size_t size,
                                 const image_check check = image_check::full);

// (2)
static basic_json_document load(const std::vector<std::uint8_t>& image,
                                 const image_check check = image_check::full);

// (3)
static basic_json_document load(std::vector<std::uint8_t>&& image,
                                 const image_check check = image_check::full);
  1. Reads an image save() wrote, from a pointer and a byte count. The image is borrowed: image must stay alive and unchanged for as long as the returned document, and any view taken from it, is used.
  2. Reads an image from a #!cpp std::vector. Also borrowed -- equivalent to overload 1 called with #!cpp image.data() and #!cpp image.size().
  3. Reads an image, keeping the vector instead of copying it: image is moved into the document (no copy), which then owns it for as long as it needs the text and the decoded strings. owns_source() is #!cpp true afterward.

In every overload, the node index is copied into storage the document itself owns -- so that it is properly aligned, and, for an editable document, can be edited -- while the text and the decoded strings stay in image. The hash indexes large objects use for lookup are rebuilt, exactly as after parsing.

Parameters

image (in)
the image save() wrote (overloads 1 and 2), or one to take ownership of (overload 3)
size (in)
the number of bytes at image (overload 1)
check (in)
how thoroughly to validate image before trusting it; see image_check below (optional, #!cpp image_check::full by default)

Return value

The document read from the image.

Exception safety

Overloads 1 and 2 give the strong guarantee: image is only read, never written, so a thrown exception leaves the caller's buffer untouched.

Overload 3 moves image into the document before validating it, so that a good image is kept without a copy. If loading then fails, the partially built document -- and the vector now inside it -- is discarded along with the exception, and image itself is left empty, not restored to what was passed in. Move a copy in instead, or validate with overload 2 first, if the original vector must survive a failed load.

Exceptions

On a big-endian target, throws type_error.320 -- the same exception save() throws there, since the image format is little-endian only.

Otherwise throws parse_error.116 if image is not one save() could have written, or fails the requested check:

message when
too short image is #!cpp nullptr, or size is smaller than the 64-byte header
unknown format the header's magic bytes or version do not match, or a reserved header field is not zero
sizes out of range the node count, text size, or decoded-string size the header describes does not fit size, or the #!cpp '\0' after the text or after the decoded strings is missing
the check failed check is not #!cpp image_check::none, and the image fails it -- see image_check

!!! failure "Example messages"

```
[json.exception.parse_error.116] parse error: invalid json_document image: too short
```
```
[json.exception.parse_error.116] parse error: invalid json_document image: unknown format
```
```
[json.exception.parse_error.116] parse error: invalid json_document image: sizes out of range
```
```
[json.exception.parse_error.116] parse error: invalid json_document image: the check failed
```

Complexity

Linear in the number of nodes, which are always copied into the document. #!cpp image_check::none does not read the text or the decoded strings. #!cpp image_check::bounds reads one byte of the text for each float token (its sign, to check the recorded digits against the token's length), and nothing else of the text or the decoded strings.

With #!cpp image_check::full, linear in the size of the image (the nodes, the text, and the decoded strings), plus sorting the ranges of the strings and of the float tokens (at most one per node): the contents of each distinct range are checked once, however many nodes refer to it.

Notes

image_check

using image_check = detail::view::image_check;

enum class image_check
{
    full,
    bounds,
    none
};

How thoroughly load() validates image before trusting it.

full (the default)
Checks everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a string still in the source text, that it contains no quote, backslash, or control character); and that every number token is well-formed and matches the value stored for it. Strings and float tokens may share a range only if the ranges are identical (as nodes that share a value do), and never overlap otherwise. Reading and serializing a checked image is safe and always produces valid JSON, exactly as for a parsed document. This is the right choice for an image from anything you do not fully control -- a file, a cache shared with other processes, a peer on the network.
bounds
Checks structure and bounds only: that every offset and count in the node index stays inside the image. So load(), root(), element access, and materialize() can never read outside the image, even on a damaged one. It does not guarantee that the image describes valid JSON: a string that a full check would have rejected can make dump() write invalid UTF-8 or invalid JSON, or throw type_error.316, and a number can read back with a value that does not match how it is spelled. An editable document does not take such a string over: set, push_back and insert throw type_error.316 when they copy it from a view of the loaded document, and leave the editable document unchanged. bounds skips scanning the text and the decoded strings, so it fits a cache your own process just wrote and reads straight back, where damage would mean a bug or a hardware fault rather than adversarial input.
none
Checks nothing. Reading a damaged image is undefined behavior, so use it only for an image you trust as much as your own memory.

Lifetime. Overloads 1 and 2 borrow image: it must stay alive and byte-for-byte unchanged for as long as the returned document, and any view taken from it, is used -- exactly like a document parse() borrowed its input for. Overload 3 avoids this by keeping the vector itself; see owns_source.

!!! warning "Experimental"

The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
stable; `load()` already rejects an image written by a different format version with `parse_error.116`
("unknown format"). 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.

Examples

??? example "Caching a document, ownership, and a rejected image"

The example below saves a parsed document as an image, checks that `load()` reproduces the original
[`dump()`](../basic_json_view/dump.md) without parsing, and shows the difference between
`load(std::move(image))` (owned) and `load(image)` (borrowed). It then damages one byte of the image and shows
`image_check::full` rejecting it with `parse_error.116`, while `image_check::bounds` -- meant for a cache the
process already trusts -- still reads it without going out of bounds.

```cpp
--8<-- "examples/basic_json_document__load.cpp"
```

Output:

```json
--8<-- "examples/basic_json_document__load.output"
```

See also

  • save - write the document as an image
  • owns_source - return whether the document holds its own copy of the text
  • parse - deserialize from JSON text instead of an image
  • Images - why and when to use images

Version history

  • Added in version 3.13.0.