Skip to content

Benchmarks

json-tology is not yet uniformly faster than its comparators. Where we win, where we lose, and where we have work to do.

This page is the honest read on json-tology's performance against four comparators: AJV, Zod, TypeBox (interpreted Value and compiled TypeCompiler), and Valibot. The numbers below come from the bench suite under bench/. Run npm run bench:report to regenerate.

The headline: today, json-tology loses most warm validation scenarios to TypeBox compiled, and loses some warm scenarios to AJV / Valibot. It wins at structural diff, transform encode, schema authoring (extend build), and validating invalid simple data with full error collection. Most of the loss surface comes from the canonical-graph-first execution model: every validate goes through the graph, which buys downstream features (ABox projection, OWL/SHACL emission, semantic round-trip) but pays a per-call cost a code-gen-only validator does not.

We're publishing the full picture rather than cherry-picking. Where we are slow, we say so.

How to read this page

  • WINjson-tology is at least 1.05x faster than the comparator on the listed scenario.
  • LOSSjson-tology is at least 1.05x slower.
  • EVEN — within 5% either way.
  • ops/s — operations per second after warmup. Higher is better.
  • ns/op — nanoseconds per operation. Lower is better.
  • json-tology vs this — the multiplier between json-tology and the listed library. "2.39x faster" means json-tology runs 2.39 ops for every 1 op of the comparator on this scenario.

The latest run is auto-generated from bench/results/latest.md and is included in full at the end of this page.

What's in scope

The bench suite covers the operations json-tology actually exposes:

Suitejson-tology surfaceComparators
Validationregistry.validateAJV validate, TypeBox TypeCompiler.Check, Zod safeParse, Valibot safeParse
Instantiationregistry.instantiate (no coercion)TypeBox Value.Parse, Zod parse, Valibot parse
Coerceregistry.instantiate with castTypes: true, defaultsTypeBox Value.Parse, Zod parse, Valibot parse
Value operationsValue.clone, Value.diff, registry.clean, registry.convertTypeBox Value.Clean / Convert / Diff, structuredClone
TransformsTransform.create decode + facade encodeTypeBox Value.Decode / Value.Encode, Zod .transform
CompositionCompose.extend / intersection / discriminatedUnionTypeBox Type.Composite / Intersect / Union, Zod .extend / intersection / discriminatedUnion, Valibot variant
Serializationdump, dumpJson, facade encodeJSON.stringify, structuredClone, TypeBox Value.Encode
Registrycold register + first validate, warm validateTypeBox TypeCompiler.Compile + Check, Zod, Valibot
Compiled vs InterpretedSchemaCompiler vs GraphEngine.executeinternal — measures the speedup of the compile path

What's unique

These are operations no comparator implements, so they appear only as a single-library row in the report. They are included for completeness, not as head-to-head wins.

  • toTbox — OWL TBox projection from the canonical graph.
  • toShacl — SHACL shape projection.
  • toQuads / fromQuads — RDF round-trip via projection.
  • ABox projection through Materializer.projectAbox.
  • findDuplicates over the registry.
  • The jt: keyword set (computed properties, invariants, decoders, brands).
  • OWL / SHACL emission through OntologyBuilder.

These are surface area that competitors do not expose. They are not wins; they are work the competitors do not do.

Where we have work to do

Pulled directly from bench/results/latest.md. These are scenarios where json-tology is more than 5x slower than the median comparator. Each is a known issue.

  • simple valid validation (~6x slower than median) — the per-validate graph traversal cost dominates a 5-property flat schema. Tracked: rework hot path so flat schemas skip subgraph dispatch when no $refs are present.
  • nested valid validation (~7x slower than median) — same root cause as simple valid, amplified by per-property subschema lookup. Tracked: precompile a flattened property dispatch table per schema graph.
  • convert simple (~32x slower than TypeBox) — castTypes: true runs a separate normalize pass over the value before validate. Tracked: fold normalize into the compiled validator.
  • extend + validate cold path (~12x slower than TypeBox) — registering a derived schema rebuilds the canonical graph from scratch per call. Tracked: cache subgraph fragments at register time.
  • intersection cold path (~12x slower than Zod) — same root cause as extend + validate.
  • dumpJson nested (~8x slower than JSON.stringify) — dump walks the schema graph for every property. Tracked: short-circuit when no Transform encoders are attached anywhere in the subgraph.
  • discriminated union warm (~74x slower than TypeBox compiled) — every variant is currently re-resolved through oneOf semantics. Tracked: discriminator-aware fast path that switches directly on the discriminator key.
  • cold first validate (~156x slower than Valibot) — Valibot has no compile step at all; the cold path is the warm path. Our cold path includes graph construction, subschema linking, and JIT compilation. Acceptable cost because subsequent calls are fast, but the gap is honest. Tracked: lazy subgraph build for unreachable parts of the schema.

If we are slower on a scenario by more than 5x, you can expect a corresponding tracked item above. None of these are blockers for using the library — but they are real, and we are working on them.

Reproduce

bash
npm install
npm run bench:report

The runner prints to console and writes bench/results/latest.md. Bench numbers move with hardware. The values embedded below came from the developer machine listed in the Environment section. CI runs are uploaded as workflow artifacts (see .github/workflows/bench.yml); use those as the canonical reference once bench-in-CI is established.

For deeper investigation:

bash
npm run bench         # human-readable console output, no markdown
npm run bench:flame   # 0x flame graph profiling under .flame/

Latest run

The block below is auto-generated. To refresh it, run npm run bench:report.

json-tology benchmarks — latest run

Generated by npm run bench:report from bench/run.ts. Numbers move with hardware; treat as a directional snapshot, not absolutes.

Environment

  • Date: 2026-05-06T01:04:02.503Z
  • Node: v24.13.0
  • OS: darwin 25.3.0
  • CPU: Apple M4 Max

Scorecard

ComparatorWinsTiesLossesWin rate
typebox601529%
ajv0030%
zod201115%
valibot0090%
structuredClone10150%
JSON.stringify0010%

Validation

simple valid

Libraryops/sns/opjson-tology vs thisStatus
json-tology435,3622297--
typebox22,388,0634551.42x slowerLOSS
ajv1,596,0296273.67x slowerLOSS
zod550,70618161.26x slowerLOSS
valibot1,254,7567972.88x slowerLOSS

simple invalid

Libraryops/sns/opjson-tology vs thisStatus
json-tology240,1344164--
typebox293,32434091.22x slowerLOSS
ajv9,980,95310041.56x slowerLOSS
zod26,308380119.13x fasterWIN
valibot527,77718952.20x slowerLOSS

nested valid

Libraryops/sns/opjson-tology vs thisStatus
json-tology144,6096915--
typebox5,565,69518038.49x slowerLOSS
ajv2,817,98635519.49x slowerLOSS
zod197,37650661.36x slowerLOSS
valibot307,72532502.13x slowerLOSS

Instantiation

instantiate simple

Libraryops/sns/opjson-tology vs thisStatus
json-tology240,6524155--
typebox415,65724061.73x slowerLOSS
zod622,68716062.59x slowerLOSS
valibot1,235,2638105.13x slowerLOSS

instantiate nested

Libraryops/sns/opjson-tology vs thisStatus
json-tology90,97610992--
typebox62,495160011.46x fasterWIN
zod365,13327394.01x slowerLOSS
valibot275,90536243.03x slowerLOSS

Coerce

coerce valid

Libraryops/sns/opjson-tology vs thisStatus
json-tology336,8062969--
typebox217,64545951.55x fasterWIN
zod3,342,0692999.92x slowerLOSS
valibot2,628,6713807.80x slowerLOSS

coerce defaults

Libraryops/sns/opjson-tology vs thisStatus
json-tology162,9316138--
typebox490,13920403.01x slowerLOSS

Value operations

clean simple

Libraryops/sns/opjson-tology vs thisStatus
json-tology174,3045737--
typebox407,69024532.34x slowerLOSS

clean nested

Libraryops/sns/opjson-tology vs thisStatus
json-tology58,01317237--
typebox103,76196381.79x slowerLOSS

convert simple

Libraryops/sns/opjson-tology vs thisStatus
json-tology197,0505075--
typebox1,889,4605299.59x slowerLOSS

clone nested

Libraryops/sns/opjson-tology vs thisStatus
json-tology217,0004608--
structuredClone150,70366361.44x fasterWIN

diff nested

Libraryops/sns/opjson-tology vs thisStatus
json-tology414,9352410--
typebox54,124184767.67x fasterWIN

Transforms

decode date

Libraryops/sns/opjson-tology vs thisStatus
json-tology269,3013713--
typebox3,168,26731611.76x slowerLOSS
zod741,59713482.75x slowerLOSS

encode date

Libraryops/sns/opjson-tology vs thisStatus
json-tology1,870,937534--
typebox409,69324414.57x fasterWIN

Composition

extend build

Libraryops/sns/opjson-tology vs thisStatus
json-tology1,747,352572--
typebox705,30414182.48x fasterWIN
zod12,70278729137.57x fasterWIN

extend + validate

Libraryops/sns/opjson-tology vs thisStatus
json-tology8,773113987--
typebox81,403122859.28x slowerLOSS
zod9,4461058671.08x slowerLOSS

discriminated union

Libraryops/sns/opjson-tology vs thisStatus
json-tology396,4122523--
typebox50,588,08720127.61x slowerLOSS
zod1,772,7715644.47x slowerLOSS
valibot1,634,6346124.12x slowerLOSS

intersection

Libraryops/sns/opjson-tology vs thisStatus
json-tology9,907100940--
typebox59,743167386.03x slowerLOSS
zod65,392152926.60x slowerLOSS

Serialization

dump nested

Libraryops/sns/opjson-tology vs thisStatus
json-tology46,86521338--
structuredClone185,01154053.95x slowerLOSS
typebox105,60594692.25x slowerLOSS

dumpJson nested

Libraryops/sns/opjson-tology vs thisStatus
json-tology44,88922277--
JSON.stringify853,520117219.01x slowerLOSS

encode event

Libraryops/sns/opjson-tology vs thisStatus
json-tology2,275,822439--
typebox955,10510472.38x fasterWIN

Registry

cold first validate

Libraryops/sns/opjson-tology vs thisStatus
json-tology2,311432654--
typebox19,703507528.53x slowerLOSS
zod6,2711594712.71x slowerLOSS
valibot295,2603387127.76x slowerLOSS

warm validate

Libraryops/sns/opjson-tology vs thisStatus
json-tology117,7238495--
typebox730,47813696.21x slowerLOSS
zod1,208,84282710.27x slowerLOSS
valibot292,18034232.48x slowerLOSS

Compiled vs Interpreted

compiled simple valid

Unique to json-tology — no head-to-head comparator.

Libraryops/sns/op
compiled824,4331213
interpreted33,28830041

compiled simple invalid

Unique to json-tology — no head-to-head comparator.

Libraryops/sns/op
compiled509,9281961
interpreted34,53628955

compiled nested valid

Unique to json-tology — no head-to-head comparator.

Libraryops/sns/op
compiled183,3755453
interpreted5,785172859

Where we have work to do

Scenarios where json-tology is more than 5x slower than the median comparator:

  • nested valid: 19.49x slower than median comparator
  • coerce valid: 7.80x slower than median comparator
  • convert simple: 9.59x slower than median comparator
  • decode date: 11.76x slower than median comparator
  • extend + validate: 9.28x slower than median comparator
  • intersection: 6.60x slower than median comparator
  • dumpJson nested: 19.01x slower than median comparator
  • cold first validate: 8.53x slower than median comparator
  • warm validate: 6.21x slower than median comparator

Reproduce

bash
npm install
npm run bench:report

Bench numbers move with hardware. The values committed here came from the developer machine listed in Environment. CI runs are uploaded as workflow artifacts (see .github/workflows/bench.yml).

Released under the MIT License.