jsonvetdocs14. --schema checks the document's own nodes, through jsonschema's Json trait

playgroundthis page on GitHub

14. --schema checks the document's own nodes, through jsonschema's Json trait

Status: accepted (2026-11-01)

Context

--schema folded each document into a serde_json::Value and handed that to jsonschema. The copy held every string and key again, and most of a check's memory was that copy. jsonschema 0.60 can validate any representation that implements its jsonschema::json::Json, Node, Object and Array traits, with the same keywords, formats, $refs and unevaluated* handling. The traits are new and its author expects them to change.

Options

Decision

The second. crates/jsonvet-cli/src/schema/nodes.rs holds the tree and the trait impls. The validators are built with jsonschema::options_for::<Own>(). Objects keep their members sorted by key with later duplicates replacing earlier ones, so member order, counts and lookups match what serde_json::Map gave. The part of a schema checked on each member of an object sees a view of the object with one member, instead of a one-member copy. Errors carry only an instance path, so each failing document's errors are placed by looking that path up in the tree, which keeps the offset of every value and key and the spelling of keys with lone surrogates. No second walk over the events is needed.

A container's value is not copied when jsonschema builds an error: the error holds the container's text span and reads it again only if something asks for the value. A message that quotes a value only when it is short checks that on the tree first. This needs jsonschema-value as a direct dependency, pinned to jsonschema's version, because LazyInstance is not re-exported. Offsets are kept in 32 bits, so a value more than 4 GiB into a text is reported as not checked.

Consequences

Each value is a 16-byte entry (a const assertion in nodes.rs holds it there): strings live in a side table and numbers are kept as the integer or float they are, so a file of small numbers takes 16 times its size at most.

An escaped string is still decoded by the parser, so only strings without escapes are borrowed. A string is copied into an error when it fails; a container is read again only when asked for. The traits are pinned by the exact jsonschema version in Cargo.toml and Cargo.lock. When a release changes them, the build fails in nodes.rs and nowhere else: the changes to expect are a new required method on Node, Object or Array, a changed signature of with_string_node, or the traits moving out of jsonschema::json. If one cannot be met cheaply, go back to the first option: Tree::value already builds the Value, so the change is to validate that instead of the tree, as the code did before this record.

Confirmation

cargo nextest run -p jsonvet-cli runs the unit test in nodes.rs, the folding property tests in fold.rs, which compare the tree's values with what serde_json reads, the split-schema property tests in mod.rs, the schemastore snapshots and the heap bounds in tests/schema_memory.rs.

Revisit if

A jsonschema release changes the traits in a way that costs more than a day, or its error type starts to carry the failing node, which would save the path lookup.