jsonschema: a Rust JSON Schema validator that compiles the schema first
A high-performance JSON Schema validator for Rust
At a glance
- What is it?
- jsonschema is a Rust crate for validating JSON instances against JSON Schema, covering drafts 4 through 2020-12, with reusable validators, a compile-time validator macro, a CLI, WebAssembly support and Python and Ruby bindings. The performance claims in its documentation are the project's own, measured against its own benchmark suite.
- Who is it for?
- jsonschema suits Rust services that validate request payloads, config files or API responses against a schema, and teams that want the same validator exposed to Python or Ruby services through the published bindings.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Validate against a compiled Validator, not a string of rules
jsonschema is a JSON Schema validator for Rust, and its central design choice is that a schema is compiled into a reusable Validator object rather than interpreted on every call.
The shortest form of the API is a pair of free functions, one for a boolean and one for a result:
let schema = json!({"maxLength": 5});
let instance = json!("foo");
// One-off validation
assert!(jsonschema::is_valid(&schema, &instance));
assert!(jsonschema::validate(&schema, &instance).is_ok());For repeated validation the schema is compiled once into a validator and reused:
let validator = jsonschema::validator_for(&schema)?;
// Fail on first error
assert!(validator.validate(&instance).is_ok());
// Iterate over errors
for error in validator.iter_errors(&instance) {
eprintln!("Error: {error}");
eprintln!("Location: {}", error.instance_path());
}Three levels of detail are available on that object. validate returns on the first error, iter_errors walks every error with a pointer to where in the instance it occurred, and is_valid answers the boolean question without building error objects at all. The error location is reported through instance_path, which is the piece a service actually needs to put a field level error into an API response.
There is a fourth form, evaluate, which returns a structured output rather than a pass or fail. Its annotations carry a schema_location and a value, and the documentation describes this as JSON Schema Output v1, with reports in flag, list and hierarchical form.
The macro moves compilation to build time
When the schema is known at build time, the crate can generate the validator at compile time instead of compiling it at runtime, and the documentation says plainly that generated validators are significantly faster than runtime ones, so you should prefer them when the schema is known at build time.
The mechanism is an attribute macro, and it needs the macros feature:
#[jsonschema::validator(schema = r#"{"maxLength": 5}"#)]
struct Short;
assert!(Short::is_valid(&instance));
Short::validate(&instance)?;The schema can be inlined as in that example, or loaded from a file with path = "schema.json". The result is a type, Short in this case, that carries the same interface as the runtime validator, so is_valid and validate are called on the type rather than on an instance.
The stated speedup is in the range of 2.3 to 10 times faster than the runtime validator across the project's own benchmark schemas. That is the number to hold on to, and it comes with a condition worth respecting: the schema has to be fixed at compile time. A schema that changes per request, or that a user uploads, cannot use this path, which is why both APIs exist rather than one replacing the other.
The library also ships bindings for Python and Ruby, and the documentation lists compile-time validators for Rust, Python and Ruby extension modules as one of the highlights, which is the same trick applied to the foreign language bindings rather than to the Rust crate itself.
Remote references are on by default, and that has consequences
The Cargo feature table is the part of this project most likely to affect your build and your security review.
Four features are enabled by default. resolve-http fetches remote references over HTTP, resolve-file reads references from the filesystem, tls-aws-lc-rs provides the TLS provider used by resolve-http, and idna enables the idn-hostname and idn-email formats.
Two of the optional ones change behaviour meaningfully. tls-ring swaps the TLS provider to ring instead of aws-lc-rs, which matters for environments with a policy preference on one provider over the other, and resolve-async turns on non-blocking reference resolution, which is what you want if validation happens inside an async runtime.
The default posture is worth thinking about carefully. A validator that can fetch remote references over HTTP on your behalf is a component that can make network requests during validation, and if the schema comes from a source you do not control, that is a network egress path in your service. resolve-file has the same shape on the filesystem side. The project also lists schema bundling into Compound Schema Documents and $ref dereferencing among the highlights, which is the mitigation: bundle the schema with its references ahead of time and validate against the bundle.
The remaining feature, arbitrary-precision, covers arbitrarily large integers and exact decimal handling, which is the escape hatch for financial data where a JSON number should not be silently rounded by a float.
Drafts, structured output and meta-schema validation
The supported drafts are listed explicitly: Draft 2020-12, Draft 2019-09, Draft 7, Draft 6 and Draft 4, each with a Bowtie Report compliance badge for the implementation. The documentation points to the Bowtie Report for the current status rather than making a claim about completeness, which is the right place to look if draft conformance is a requirement for you.
Beyond validation, the crate covers a few adjacent jobs. Meta-schema validation checks a schema document against a schema, and the documentation notes this includes custom metaschemas, so you can validate your own schema vocabulary rather than only the standard ones. Schema bundling produces a Compound Schema Document, and $ref dereferencing flattens references. Experimental schema canonicalization is also listed, which is the machinery for producing a stable form of a schema, useful when you want to compare two schemas for equivalence.
Custom keywords and format validators are supported, and the notable users section shows why that matters in practice. Tauri validates its configuration file with this crate, the Apollo Router validates its config file, and qsv uses it for CSV record validation with a custom keyword and a custom format validator.
That last example is the one to borrow from. A format validator that encodes a house rule, such as an identifier format only your system issues, is the extension point that turns a generic validator into a domain validator.
A CLI, a WebAssembly playground and two language bindings
Beyond the Rust API, three other entry points are documented.
The command line interface is the jsonschema-cli crate, and the documented invocation takes a schema file and an instance file:
$ jsonschema-cli validate schema.json -i instance.jsonThat is the shape to reach for in a CI job, where a checked-in schema is validated against fixture files without writing any Rust.
The playground is WebAssembly powered and hosted at the project homepage, and the documentation describes it as a place to see results instantly without installing anything. For a validator, that matters because the interesting questions are usually about a specific schema and a specific instance, and being able to paste both is faster than writing a test.
The language bindings live in the repository as the jsonschema-py and jsonschema-rb crates, and the examples directory holds a magnus extension and a pyo3 extension, which are the Rust binding layers for Ruby and Python respectively. That arrangement is the notable design choice: the core stays in Rust, and Python and Ruby users get the same validator without a port.
The workspace also contains a profiler directory and a fuzz directory, which tells you something about how the project treats the edges of the specification, since JSON Schema has a long tail of unusual documents.
One practical warning from the documentation: if you are upgrading from an older version, it points to a Migration Guide for the key changes, which is worth reading before you bump a dependency that other language bindings depend on.
What the performance claims say and who is making them
The performance section of the documentation is explicit about provenance, which matters because the numbers are large.
For the runtime validator, the documented comparison is 75 to 692 times faster than valico and 10 to 78 times faster than jsonschema_valid for complex schemas, with a figure above 1,000,000 times on FHIR. Against boon, the claim is generally 2.8 to 40 times faster, above 280 times on FHIR and above 5,000 times for recursive schemas. The macro generated validator is put at roughly 2.3 to 10 times faster than the runtime validator across the project's benchmark schemas.
These are the project's own measurements, taken against a benchmark suite that lives in the repository under crates/benchmark-suite, and the documentation says so rather than presenting the numbers as independent results. The order of magnitude of the FHIR and recursive schema figures is explained by what those workloads stress: a large schema with many references, and a schema whose evaluation recurses, are exactly the cases where a naive interpreter redoes work on every instance while a compiled validator does not.
So the honest way to use this section is as a statement about the shape of the problem rather than as a number to quote. If your schema is small and your instances are few, any validator will do. If you are validating large or recursive schemas on a hot path, the compiled path is where the difference lives, and you can reproduce the comparison yourself from the benchmark suite before you rely on it.
The build configuration behind this is visible in the workspace manifest, which sets lto = "fat" and codegen-units = 1 for release builds, so the published numbers come from a build tuned for speed rather than for compile time.
A workspace with strict lints and a young version line
The repository is a Cargo workspace whose members are crates/* and examples/*, using resolver 3, edition 2021 and a minimum Rust version of 1.85.0. The licence is MIT, declared in the manifest and with a LICENSE file at the root.
The lints in the workspace manifest are worth noting for a library that other people depend on. unsafe_code is set to warn at the crate level, unreachable_pub is warn, and the clippy configuration enables the pedantic group with a list of specific lints allowed or escalated, including banning direct printing to stdout and stderr, the dbg macro, exit, get_unwrap and several unwrap oriented patterns. For a validator that runs inside other people's services, keeping the implementation free of unsafe code and of stray printing is a reasonable stance, and it also means a fork or a patch has to live with those settings.
The release profile is tuned as described earlier, and the manifest contains a patch section that keeps the workspace crates, including the language bindings, on the local core crate while the sub crates still declare a versioned dependency, which is how the Python and Ruby bindings stay in step with the core.
On maintenance, the record is dense: rust-v0.59.0 on 2026-10-16, rust-v0.58.6 on 2026-10-06 and ruby-v0.58.6 the same day, with the last push on 2026-10-19. Separate release lines per language, rather than one version for everything, is a sign of a project where the bindings version independently of the core.
Editorial conclusion
jsonschema suits Rust services that validate request payloads, config files or API responses against a schema, and teams that want the same validator exposed to Python or Ruby services through the published bindings. It is the wrong choice for a project that only needs a one-off check in a script, since the dependency weight of remote reference resolution and TLS support exceeds what that use needs, and the wrong choice if you cannot pin a modern Rust toolchain, because the workspace requires Rust 1.85.0 or newer. Verify three things before you adopt it: that the draft you need is on the supported list and its Bowtie compliance status is acceptable to you, that the schema you validate against is resolved the way you expect, in particular whether remote $ref fetching is allowed in your environment, and that the performance numbers you are relying on come from the benchmark suite in the repository rather than from a summary.
Frequently asked questions
What JSON Schema drafts does the jsonschema crate support?
Draft 2020-12, Draft 2019-09, Draft 7, Draft 6 and Draft 4, each shown with a Bowtie Report compliance badge. The documentation points to the Bowtie Report for the current conformance status of each draft.
How do I validate a JSON file against a schema from the command line?
Use the jsonschema-cli crate, for example jsonschema-cli validate schema.json -i instance.json, which takes a schema file and an instance file. The crate is published alongside the library.
What is the difference between the runtime validator and the macro?
The runtime validator compiles a schema into a reusable Validator at run time, while the #[jsonschema::validator] macro generates a specialized validator at compile time, which the documentation puts at roughly 2.3 to 10 times faster across its benchmark schemas. The macro requires the macros feature and a schema fixed at build time.
Does jsonschema fetch remote references over the network by default?
Yes. The resolve-http and resolve-file features are enabled by default, along with the aws-lc-rs TLS provider and idna formats. Non-blocking resolution is available behind the optional resolve-async feature, and you can bundle schemas with their references to avoid remote fetches.
Can I use jsonschema from Python or Ruby?
Yes. The project ships Python and Ruby bindings in the jsonschema-py and jsonschema-rb crates, with magnus and pyo3 extension examples, and it publishes separate release tags for the Rust and Ruby lines.
What Rust version does jsonschema require?
The workspace manifest sets rust-version = 1.85.0 with edition 2021 and resolver 3, so a toolchain from that release or newer is needed.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/stranger6667-jsonschema)Was this useful?