Repository navigation
Conversation
nlohmann
added this pull request to stack #5636
September 29, 2026 14:20
Owner
Author
|
CI fixes for this PR (commit 58679a7, plus the lower fixes merged in). CI had not run yet; these were found locally with
Verified: — posted by Claude Code on behalf of @nlohmann |
7 tasks done
nlohmann
removed this pull request from stack #5636
September 30, 2026 13:19
nlohmann
force-pushed
the
json-view/21-images
branch
from
September 30, 2026 13:20
c5b5bbe to
7cc4e5d
Compare
nlohmann
added this pull request to stack #5739
September 30, 2026 13:21
nlohmann
force-pushed
the
json-view/21-images
branch
3 times, most recently
from
September 30, 2026 18:06
a68db0d to
7b0b80a
Compare
nlohmann
marked this pull request as ready for review
September 30, 2026 18:16
nlohmann
force-pushed
the
json-view/21-images
branch
from
September 30, 2026 19:19
7b0b80a to
a279e6f
Compare
nlohmann
removed this pull request from stack #5739
October 6, 2026 09:26
nlohmann
changed the base branch from
json-view/20-edit-structure
to
json-view/19-edit-set
October 6, 2026 09:27
nlohmann
force-pushed
the
json-view/21-images
branch
from
October 6, 2026 09:28
923917b to
9cb9f7f
Compare
nlohmann
added this pull request to stack #5768
October 6, 2026 09:29
nlohmann
force-pushed
the
json-view/21-images
branch
from
October 7, 2026 14:44
9cb9f7f to
73ceb71
Compare
🔴 Amalgamation check failed! 🔴The source code has not been amalgamated and/or formatted correctly, or 📎 A ready-to-apply patch is attached to the failed workflow run as the git apply amalgamation.patchThis does not require installing astyle yourself. |
nlohmann
force-pushed
the
json-view/21-images
branch
from
October 18, 2026 08:54
92b79f6 to
c16e6cb
Compare
nlohmann
force-pushed
the
json-view/21-images
branch
from
October 18, 2026 11:41
c16e6cb to
719516c
Compare
nlohmann
force-pushed
the
json-view/21-images
branch
from
October 18, 2026 12:52
719516c to
be99aca
Compare
4 tasks
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>
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>
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>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
…ble document Signed-off-by: Niels Lohmann <mail@nlohmann.me>
…oaded image Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
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>
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>
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>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
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>
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>
nlohmann
force-pushed
the
json-view/21-images
branch
from
October 18, 2026 20:30
be99aca to
93f9769
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the stack for the zero-copy view (#5295). This PR adds images to
json_document:save()writes a document so thatload()can read it back without parsing. Experimental: the image format is versioned, but it may change before it is declared stable.Summary
An image is a document stored so that loading it needs no parsing:
std::vector<std::uint8_t> save() constwrites the node index, the text, and the decoded strings.dump().BasicJsonTypeit was parsed with.static load(image, check = image_check::full)reads an image written bysave().const std::vector<std::uint8_t>&(both borrowed: the image must outlive the document), or astd::vector<std::uint8_t>&&(kept without a copy;owns_source()is then true).shrink_to_fit()leaves the decoded strings in the image.load(std::vector&&)fails, the vector is left empty (documented inload).image_checksays how much of an imageload()trusts:fullchecks everything the parser guarantees: structure, bounds, strings (valid UTF-8; source strings without quotes, backslashes, or control characters), and numbers (well-formed, and matching the stored values). Reading and serializing a checked image is safe and yields valid JSON.boundschecks structure and bounds only. Reading and serializing stay safe, but a crafted image can yield invalid UTF-8, strings that serialize to invalid JSON, or numbers that differ from their text.noneis for images from a trusted source only; a damaged image is undefined behavior.Layout (little-endian only):
"NJVI", version 1, the counts, and reserved fields that must be zero);The idea comes from zero-copy formats such as FlatBuffers and YaFF, and the check follows the idea of FlatBuffers' Verifier. No code is taken from either.
The full check is linear
The structure and the bounds are checked node by node. The contents of strings and float tokens are checked afterwards, once for each distinct range. Nodes that share a value (copies within a document) share the whole range; ranges that overlap without being identical are rejected. Without this, many nodes pointing at the same long string made the check quadratic in the size of the image.
load()releases its list of large objects after building the indexes.Changes to existing code
Float conversion. Supporting
boundsneeded two changes:float_token_layout) checks the token's grammar and locates the point and the exponent itself, instead of trusting the layout the node records; a token that is not a JSON number yields 0;Parsed and edited documents only have well-formed tokens, so for them nothing changes.
Serializer.
write_escapedvalidates non-ASCII bytes. Strings of a document are valid UTF-8, so this only matters for a damaged image loaded withbounds:dump()then throws whatbasic_json::dump()throws (type_error.316).Editable documents do not take over such a string:
set,push_backandinsertthrowtype_error.316when copying it from a view of the loaded document, and leave the document unchanged.Errors
Documentation
Pages for
saveandload(with examples), theimage_checkmember type, an "Images" section in the feature page, the layout and the rule "a change of the node layout raisesimage_version" in the architecture page, the new exception ids, and a complexity section forload(linear in the image;fulladds sorting the ranges of strings and float tokens).Performance
load()of a document's image vs.json_document::parse()of its text.Measured on Apple M1 Max (Apple clang -O2), µs, best of 3 rounds of 200 runs:
noneis 6–17x faster than parsing, andboundsis 1.6–3.8x faster.fullscans every string and number token as the parser does, on top of the structure check, so it costs 1.4–2.4x a parse (canada, with its many float tokens, is the worst case). It is meant for images from untrusted places, where the alternative is parsing the text again.Tests
unit-json_view_image.cpp:read()afterload(), andshrink_to_fit()on a loaded document. Lookups work afterload(), and loading releases the list of large objects.boundsornoneimage is rejected when it is copied into an editable document (type_error.316), as a value, as a key, and nested.fulland withbounds. Each load must either be rejected or be read safely under ASan/UBSan; with the full check, the dump must also parse to the materialized value.-Wuseless-cast.fuzzer-json_view_image.cpp(documented intests/fuzzing.md) uses each input both as an image and as a JSON text. As an image,load()must reject it with parse_error.116 or read it safely (withfull, the dump must parse to the materialized value andsave()must give the input back). As a text, the image of the parsed document must load with every check and dump to the same text.Public API
No breaking changes. Images are new:
basic_json_document::save,load(three overloads), and the member typeimage_check(full,bounds,none);load()rejects other format versions (parse_error.116, "unknown format");load(std::vector&&)leaves the vector empty if it throws;dump()of a document loaded withboundsornonecan throw type_error.316 if the image was damaged. This cannot happen for parsed documents or for images checked withfull.Nothing in
json.hppchanges.Written by Claude Code.
🤖 Generated with Claude Code