ata-validator
1.61.3 · 29 Oct 2026MIT21.8k / weekRepositoryHomepagenpmAtom feed
1.61.3 is the newest release of 1.x.
In short
2 fixed5 changed
Release note
Fixed
enumcompared object members by their serialized form, so{"b": 2, "a": 1}was rejected againstenum: [{"a": 1, "b": 2}]. JSON Schema compares instances by value: two objects with the same members are equal whatever order their keys were written in. All three code generation paths did this, so they agreed with each other and disagreed with the interpreted engine, which has always compared structurally.constwas already correct but reached the answer by building a canonical string on every call. Both now use one structural comparison hoisted per compiled function. The official suite does not cover reordered keys forenum, and the entry-point agreement test compares the generators against each other, so neither would have caught this;tests/test_value_equality.jspins the answer through every engine that can produce one and runs as part ofnpm test.A draft-07 document passed through the
schemasoption was rejected with "Schema in schemas option must have $id" whenever it carried$refnext to$id. Draft-07 ignores every keyword sitting beside$ref, so normalization drops them and$idgoes with them, which is the right reading for evaluation: the reference resolves against the URI the document was retrieved from, not the$idit declares. It is the wrong reading for registration, where$idis the only name the caller gave the document. The identity is now read from what the caller passed when normalization has dropped it, so such a document registers under its$idin the array form and is addressable by both its retrieval URI and its$idin the map form. A document that declares no$idat all is still refused, and a fragment-only$idis still an anchor rather than a document name. This is what made all 23 remote-reference cases of the draft-07 suite error out in the Bowtie compliance report; they now answer, and answer correctly.
Changed
The closure-tree compiler now covers every schema the interpreted engine accepts. It previously declined
unevaluatedProperties/unevaluatedItemsand$dynamicRefin any schema with more than one resource, which left 192 of the 987 suite schemas walking the generic evaluator; that count is now zero. Annotations flow through the compiled tree: the compiler decides at compile time which nodes anyone reads annotations from, and those nodes carry a record that in-place children write into directly, with a failed child rolled back by truncating the record rather than by allocating a fresh one per child.$dynamicRefresolves against the dynamic scope computed at compile time, so multi-resource schemas no longer search it per call. A schema whose compiled graph has no cycle drops the recursion guard and the(schema, data)stack entirely. An unresolvable$refcompiles into the same runtime rejection the evaluator produces instead of declining the schema.Measured on the official suite with prebuilt validators, several runs each on the same machine: draft 2020-12 went from 94 ns to 77-80 ns per case overall, and the cases that route to the interpreted engine from about 240 ns to about 125 ns when they accept and 235 ns to 160 ns when they reject. Draft 7 went from 59 ns to 49 ns overall. The metaschema-validation case, the worst outlier in the suite, went from 5.5 µs to about 1.4 µs. On a longer steady-state loop over the same suite, where construction is not part of what is timed, draft 2020-12 went from 46 ns to 43 ns per case and draft 7 from 33 ns to 29 ns.
The value-level keywords of a schema node are compiled into closures over their own operands instead of being answered by a generic evaluator that re-reads a dozen presence flags off the plan on every call. The type of the value is computed once per node rather than once per keyword group, and a node whose keywords never need it does not compute it at all.
uniqueItemson arrays whose item type is not known ahead of time no longer builds a canonicalizing closure and aSeton every call. The helpers are hoisted once per compiled function; arrays of twelve items or fewer compare pairwise with a structural equality that ignores key order and allocates nothing, and longer arrays keep theSet, using canonical strings only once an object actually appears. Over the draft 7 suite theuniqueItemscases went from 125 ns to 24 ns each.ARCHITECTURE.mddocuments the interpreted engine and the closure-tree compiler, and no longer claims$dynamicReffalls back to the native engine, which stopped being true in 1.7.1.