LogoLogo

Schema Benchmarks

    • home
      Home

    • download_2
      Download

    • timer
      Initialization
    • check_circle
      Validation
    • output_circle
      Parsing
    • swap_horiz
      Codec
    • schema
      Standard Schema
    • format_quote
      String

    • error
      Stack

  • JSON Schema

    • data_object
      Schema to JSON
    • code
      JSON to Schema
    • verified
      Compliance

    • deployed_code
      Libraries

    • article
      Blog
Expand sidebarExpand sidebar
HomeHome
github
GitHubGitHub
DiscordDiscord
PreferencesPreferences

Welcome to the Schema Benchmarks project. This aims to compare the performance of different schema validation libraries in detail, including separating each step of the process.

format_quoteNote

Some libraries have multiple benchmarks for the same thing (e.g. zod has both a safeParse and a parse method). Where that's the case, every variant is plotted in the same row - hover over a point for details about that specific benchmark.

Download

We first test the bundle size of each library. This is important for browser usage, where this will affect download time.

We do this by compiling example usage files with Rolldown, and measuring the size of the output, both minified and unminified.

With minification and tree shaking:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby0 byte20 kB39 kB59 kB78 kBSize (gzipped)@railway-ts/pipelines@remix-run/data-schematypiasuperstructvalibotdecodersio-ts@paseri/compilerruntypes@paseri/paserizod/miniyupzod/v3typeboxsuryeffect@rczod@sapphire/shapeshiftzod-compilerajvarktypeeffectjoiata-validatorLibrary

Without minification (but still treeshaken):

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby0 byte20 kB39 kB59 kB78 kB98 kB117 kBSize (gzipped)@railway-ts/pipelinestypiavalibot@remix-run/data-schemasuperstructdecoders@paseri/compilerio-tsruntypeszod/mini@paseri/paserizod/v3yuptypeboxsuryzod@sapphire/shapeshiftzod-compilerajvjoiarktypeeffect@rcata-validatoreffectLibrary

Benchmarks

Runtime benchmarks are run in sequence, on a GitHub runner.

Steps benchmarked include:

Initialization

Creating the schema itself. This is usually a one time cost.

schemas.ts
Copy to clipboardCopy to clipboard
import * as v from "valibot"; export const personSchema = v.object({ name: v.string(), age: v.number(), }); export type Person = v.InferOutput< typeof personSchema >;
schemas.ts
Copy to clipboardCopy to clipboard
import * as v from "valibot"; export const personSchema = v.object({ name: v.string(), age: v.number(), }); export type Person = v.InferOutput<typeof personSchema>;
stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby01 ms2 ms3 ms4 ms5 msTimetypia@railway-ts/pipelines@remix-run/data-schemavalibot@paseri/paseriio-tssuperstructdecoderssuryata-validatortypebox@sapphire/shapeshiftzod/v3zod/minizodyupjoieffect@rceffectarktyperuntypesajvLibrary

Validation

Checking if a given value matches the schema. Crucially, this is different to parsing because it doesn't return a new value.

Copy to clipboardCopy to clipboard
import * as v from "valibot"; import { personSchema } from "./schemas"; if (v.is(personSchema, data)) { // data is narrowed to Person }

Validating valid data:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby020 μs40 μs60 μs80 μs100 μs120 μs140 μsTimezod/minizodtypeboxata-validatorsuryzod-compilerio-tsajvvalibottypiaarktypeeffect@rc@railway-ts/pipelineseffect@sapphire/shapeshiftsuperstructruntypesyupLibrary

Validating invalid data:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby0100 μs200 μs300 μs400 μsTimetypiazodzod/minitypeboxata-validatorajvio-tsvalibot@railway-ts/pipelineseffecteffect@rcarktypesurysuperstructzod-compileryupruntypes@sapphire/shapeshiftLibrary
format_quoteNote

Some libraries only support validation or parsing. In these cases, we categorise them accordingly.

Parsing

Checking if a given value matches the schema, and returning a new value. This will include any transformations.

Copy to clipboardCopy to clipboard
import * as v from "valibot"; import { personSchema } from "./schemas"; const person = v.parse( personSchema, data, ); // person is of type Person
Copy to clipboardCopy to clipboard
import * as v from "valibot"; import { personSchema } from "./schemas"; const person = v.parse(personSchema, data); // person is of type Person
infoInfo

Some libraries were benchmarked with a method that throws an error when parsing invalid data, so the benchmark includes a try/catch - which may have an unknown performance impact.

Some libraries abort early when parsing invalid data, so will tend to be faster than those that collect every error.

Some libraries return the same object when parsing, instead of creating a new value, so will tend to be faster.

Parsing valid data:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby020 μs40 μs60 μs80 μs100 μs120 μsTime@paseri/compilertypeboxzodzod/minisuryata-validatorzod-compiler@paseri/paseriio-tstypiavalibotarktype@remix-run/data-schemaeffect@rczod/v3@railway-ts/pipelinesdecoderseffect@sapphire/shapeshiftjoisuperstructruntypesyupLibrary

Parsing invalid data:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby0100 μs200 μs300 μs400 μsTimeata-validator@remix-run/data-schemavalibot@railway-ts/pipelineseffect@rc@paseri/compilerjoi@paseri/paseriio-tssurysuperstructeffectzod-compilerzodtypiazod/minitypeboxzod/v3decodersyuparktyperuntypes@sapphire/shapeshiftLibrary

Tags

Optimizations

Some libraries utilise specific optimizations to improve performance. We specifically track:

  • JIT: Libraries that use Just-In-Time compilation (usually via new Function) to generate optimized code at runtime, e.g. arktype
  • Precompiled: Libraries that generate optimized code at build time, e.g. typia
Error handling

Some libraries support different error handling strategies. We specifically track:

  • All errors: Parse the entire value before returning/throwing an error.
  • Abort early: Return/throw an error as soon as an issue is found.

Standard Schema

Many libraries implement the Standard Schema interface, which allows many other libraries to accept them without needing to specialise for each library.

Copy to clipboardCopy to clipboard
import { personSchema } from "./schemas"; const person = await upfetch(url, { schema: personSchema, });
Copy to clipboardCopy to clipboard
import { personSchema } from "./schemas"; const person = await upfetch(url, { schema: personSchema });

We benchmark the time taken to parse using a standard schema.

infoInfo

Some libraries require an adapter before they can be used as a standard schema. The time to convert the schema is not measured, only the time to parse using it.

Parsing valid data:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby050 μs100 μs150 μsTime@paseri/compilerzodzod/minisuryata-validatorzod-compiler@paseri/paseritypiavalibotarktype@remix-run/data-schemaeffect@rczod/v3decoders@railway-ts/pipelineseffectjoiyupLibrary

Parsing invalid data:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby0200 μs400 μs600 μs800 μsTime@paseri/compilerjoieffectata-validator@paseri/paserisuryvalibotzodzod-compiler@remix-run/data-schemazod/minitypiaeffect@rc@railway-ts/pipelineszod/v3decodersarktypeyupLibrary

Codec

Some libraries support two way conversion of data, often referred to as "encoding" and "decoding".

We benchmark the time taken to encode and decode a BigInt to and from a string.

Copy to clipboardCopy to clipboard
import * as z from "zod"; const bigIntFromString = z.codec( z.string(), z.bigint(), { decode: (str) => BigInt(str), encode: (bigInt) => bigInt.toString(), }, ); bigIntFromString.encode(123n); // "123" bigIntFromString.decode("123"); // 123n
Copy to clipboardCopy to clipboard
import * as z from "zod"; const bigIntFromString = z.codec(z.string(), z.bigint(), { decode: (str) => BigInt(str), encode: (bigInt) => bigInt.toString(), }); bigIntFromString.encode(123n); // "123" bigIntFromString.decode("123"); // 123n
format_quoteInvalid data

We don't benchmark codecs with invalid data, as many libraries require the input to be correctly typed before passing it to the codec.

Codecs that do accept unknown input may be slower - hover over a point in the chart below for details, including whether the library was benchmarked in more than one way.

Encode () and decode ():

stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearbynearby500 ns1 μs2 μs2 μs3 μs3 μsTimesuryio-tszod/minizodeffecteffect@rctypeboxLibrary

JSON Schema

Schema to JSON

Some libraries can convert a schema into a JSON Schema, which lets documentation generators, OpenAPI builders and LLM structured outputs consume it.

Copy to clipboardCopy to clipboard
import { personSchema } from "./schemas"; const jsonSchema = z.toJSONSchema( personSchema, { target: "draft-2020-12" }, );
Copy to clipboardCopy to clipboard
import { personSchema } from "./schemas"; const jsonSchema = z.toJSONSchema(personSchema, { target: "draft-2020-12", });
Copy to clipboardCopy to clipboard
import { personSchema } from "./schemas"; const jsonSchema = z.toJSONSchema(personSchema, { target: "draft-2020-12" });

We collect data on which targets and type (input/output) each library supports, then benchmark the time taken to generate a JSON schema from a small schema with a codec, for supported combinations. Libraries that implement the standard JSON schema interface are marked in the results.

With a target of draft-2020-12 and input type:

stat_0stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearbynearby0 ms (0)0 ms (2 μs)0 ms (4 μs)0.01 ms (6 μs)0.01 ms (8 μs)0.01 ms (10 μs)Timearktypevalibotsuryeffecteffect@rczod/minizodLibrary

JSON to Schema

Some libraries can also go the other way, converting a JSON Schema into a library schema at runtime.

Copy to clipboardCopy to clipboard
const schema = z.fromJSONSchema(jsonSchema);
Copy to clipboardCopy to clipboard
const schema = z.fromJSONSchema(jsonSchema);
stat_0stat_0stat_0nearbynearbynearby0 ms (0)0.02 ms (20 μs)0.06 ms (60 μs)0.1 ms (100 μs)TimesuryzodarktypeLibrary

Compliance

We test JSON Schema implementations against the JSON Schema Test Suite.

Validation
Copy to clipboardCopy to clipboard
assert( Schema.Check( remotes, schema, data, ) === expected, );
Validation
Copy to clipboardCopy to clipboard
assert(Schema.Check(remotes, schema, data) === expected);

The graph shows each library's validation results for Draft 2020-12.

stat_0stat_0stat_0stat_0stat_0stat_0nearbynearbynearbynearbynearbynearby0%20%40%60%80%100%Compliancetypeboxz-schemaata-validator@cfworker/json-schemaajvjsonschemaLibrary

We also test libraries that allow schemas to be created from JSON Schema, both using their native schema validation ("semantics"), and after converting back into JSON Schema ("roundtrip").

Semantics
Copy to clipboardCopy to clipboard
assert( z .fromJSONSchema(schema) .safeParse(data).success === expected, );
Semantics
Copy to clipboardCopy to clipboard
assert( z.fromJSONSchema(schema).safeParse(data).success === expected, );
Semantics
Copy to clipboardCopy to clipboard
assert(z.fromJSONSchema(schema).safeParse(data).success === expected);

For semantics with Draft 2020-12:

stat_0stat_0stat_0nearbynearbynearby0%20%40%60%80%100%CompliancesuryzodarktypeLibrary
Roundtrip
Copy to clipboardCopy to clipboard
assert( Schema.Check( remotes, z.toJSONSchema( z.fromJSONSchema(schema), ), data, ) === expected, );
Roundtrip
Copy to clipboardCopy to clipboard
assert( Schema.Check( remotes, z.toJSONSchema(z.fromJSONSchema(schema)), data, ) === expected, );
Roundtrip
Copy to clipboardCopy to clipboard
assert( Schema.Check(remotes, z.toJSONSchema(z.fromJSONSchema(schema)), data) === expected, );
Roundtrip
Copy to clipboardCopy to clipboard
assert(Schema.Check(remotes, z.toJSONSchema(z.fromJSONSchema(schema)), data) === expected);

For roundtrip with Draft 2020-12:

stat_0stat_0stat_0nearbynearbynearby0%20%40%60%80%100%CompliancesuryzodarktypeLibrary
Created by eskimojo for Open Circle

Preferences

Style
Theme
NPM browser
Code ligatures