Expand description
A high-performance JSON Schema validator for Rust.
- š Support for popular JSON Schema drafts
- š§ Custom keywords and format validators
- š Blocking & non-blocking remote reference fetching (network/file)
- šØ Structured Output v1 reports (flag/list/hierarchical)
- ⨠Meta-schema validation for schema documents
- š WebAssembly support
§Supported drafts
Compliance levels vary across drafts, with newer versions having some unimplemented keywords.
§Validation
The jsonschema crate offers two main approaches to validation: one-off validation and reusable validators.
When external references are involved, the validator can be constructed using either blocking or non-blocking I/O.
For simple use cases where you need to validate an instance against a schema once, use is_valid or validate functions:
use serde_json::json;
let schema = json!({"type": "string"});
let instance = json!("Hello, world!");
assert!(jsonschema::is_valid(&schema, &instance));
assert!(jsonschema::validate(&schema, &instance).is_ok());For better performance, especially when validating multiple instances against the same schema, build a validator once and reuse it: If your schema contains external references, you can choose between blocking and non-blocking construction:
use serde_json::json;
let schema = json!({"type": "string"});
// Blocking construction - will fetch external references synchronously
let validator = jsonschema::validator_for(&schema)?;
// Non-blocking construction - will fetch external references asynchronously
let validator = jsonschema::async_validator_for(&schema).await?;
// Once constructed, validation is always synchronous as it works with in-memory data
assert!(validator.is_valid(&json!("Hello, world!")));
assert!(!validator.is_valid(&json!(42)));
assert!(validator.validate(&json!(42)).is_err());
// Iterate over all errors
let instance = json!(42);
for error in validator.iter_errors(&instance) {
eprintln!("Error: {}", error);
eprintln!("Location: {}", error.instance_path);
}§Note on format keyword
By default, format validation is draftādependent. To opt in for format checks, you can configure your validator like this:
let validator = jsonschema::draft202012::options()
.should_validate_formats(true)
.build(&schema)?;Once built, any format keywords in your schema will be actively validated according to the chosen draft.
§Structured Output
The evaluate() method provides access to structured validation output formats defined by
JSON Schema Output v1.
This is useful when you need detailed information about the validation process beyond simple pass/fail results.
use serde_json::json;
let schema = json!({
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number", "minimum": 0}
},
"required": ["name"]
});
let validator = jsonschema::validator_for(&schema)?;
let instance = json!({"name": "Alice", "age": 30});
// Evaluate the instance
let evaluation = validator.evaluate(&instance);
// Flag format: Simple boolean validity
let flag = evaluation.flag();
assert!(flag.valid);
// List format: Flat list of all evaluation steps
let list_output = serde_json::to_value(evaluation.list())?;
println!("List output: {}", serde_json::to_string_pretty(&list_output)?);
// Hierarchical format: Nested tree structure
let hierarchical_output = serde_json::to_value(evaluation.hierarchical())?;
println!(
"Hierarchical output: {}",
serde_json::to_string_pretty(&hierarchical_output)?
);
// Iterate over annotations collected during validation
for annotation in evaluation.iter_annotations() {
println!("Annotation at {}: {:?}",
annotation.instance_location,
annotation.annotations
);
}
// Iterate over errors (if any)
for error in evaluation.iter_errors() {
println!("Error: {}", error.error);
}The structured output formats are particularly useful for:
- Debugging: Understanding exactly which schema keywords matched or failed
- User feedback: Providing detailed, actionable error messages
- Annotations: Collecting metadata produced by successful validation
- Tooling: Building development tools that work with JSON Schema
For example, validating ["hello", "oops"] against a schema with both prefixItems and
items produces list output similar to:
{
"valid": false,
"details": [
{"valid": false, "evaluationPath": "", "schemaLocation": "", "instanceLocation": ""},
{
"valid": false,
"evaluationPath": "/items",
"schemaLocation": "/items",
"instanceLocation": "",
"droppedAnnotations": true
},
{
"valid": false,
"evaluationPath": "/items",
"schemaLocation": "/items",
"instanceLocation": "/1"
},
{
"valid": false,
"evaluationPath": "/items/type",
"schemaLocation": "/items/type",
"instanceLocation": "/1",
"errors": {"type": "\"oops\" is not of type \"integer\""}
},
{"valid": true, "evaluationPath": "/prefixItems", "schemaLocation": "/prefixItems", "instanceLocation": "", "annotations": 0}
]
}§Output Formats
§Flag Format
The simplest format, containing only a boolean validity indicator:
let evaluation = validator.evaluate(&json!("hello"));
let flag = evaluation.flag();
let output = serde_json::to_value(flag)?;
// Output: {"valid": true}§List Format
A flat list of all evaluation units, where each unit describes a validation step:
let schema = json!({
"allOf": [
{"type": "number"},
{"minimum": 0}
]
});
let validator = jsonschema::validator_for(&schema)?;
let evaluation = validator.evaluate(&json!(42));
let list = evaluation.list();
let output = serde_json::to_value(list)?;
// Output includes all evaluation steps in a flat array§Hierarchical Format
A nested tree structure that mirrors the schemaās logical structure:
let schema = json!({
"allOf": [
{"type": "number"},
{"minimum": 0}
]
});
let validator = jsonschema::validator_for(&schema)?;
let evaluation = validator.evaluate(&json!(42));
let hierarchical = evaluation.hierarchical();
let output = serde_json::to_value(hierarchical)?;
// Output has nested "details" arrays for sub-schema evaluations§Meta-Schema Validation
The crate provides functionality to validate JSON Schema documents themselves against their meta-schemas. This ensures your schema documents are valid according to the JSON Schema specification.
use serde_json::json;
let schema = json!({
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0}
}
});
// Validate schema with automatic draft detection
assert!(jsonschema::meta::is_valid(&schema));
assert!(jsonschema::meta::validate(&schema).is_ok());
// Invalid schema example
let invalid_schema = json!({
"type": "invalid_type", // must be one of the valid JSON Schema types
"minimum": "not_a_number"
});
assert!(!jsonschema::meta::is_valid(&invalid_schema));
assert!(jsonschema::meta::validate(&invalid_schema).is_err());§Configuration
jsonschema provides several ways to configure and use JSON Schema validation.
§Draft-specific Modules
The library offers modules for specific JSON Schema draft versions:
Each module provides:
- A
newfunction to create a validator - An
is_validfunction for validation with a boolean result - An
validatefunction for getting the first validation error - An
optionsfunction to create a draft-specific configuration builder - A
metamodule for draft-specific meta-schema validation
Hereās how you can explicitly use a specific draft version:
use serde_json::json;
let schema = json!({"type": "string"});
// Instance validation
let validator = jsonschema::draft7::new(&schema)?;
assert!(validator.is_valid(&json!("Hello")));
// Meta-schema validation
assert!(jsonschema::draft7::meta::is_valid(&schema));You can also use the convenience is_valid and validate functions:
use serde_json::json;
let schema = json!({"type": "number", "minimum": 0});
let instance = json!(42);
assert!(jsonschema::draft202012::is_valid(&schema, &instance));
assert!(jsonschema::draft202012::validate(&schema, &instance).is_ok());For more advanced configuration, you can use the draft-specific options function:
use serde_json::json;
let schema = json!({"type": "string", "format": "ends-with-42"});
let validator = jsonschema::draft202012::options()
.with_format("ends-with-42", |s| s.ends_with("42"))
.should_validate_formats(true)
.build(&schema)?;
assert!(validator.is_valid(&json!("Hello 42")));
assert!(!validator.is_valid(&json!("No!")));§General Configuration
For configuration options that are not draft-specific, jsonschema provides a builder via jsonschema::options().
Hereās an example:
use serde_json::json;
let schema = json!({"type": "string"});
let validator = jsonschema::options()
// Add configuration options here
.build(&schema)?;
assert!(validator.is_valid(&json!("Hello")));For a complete list of configuration options and their usage, please refer to the ValidationOptions struct.
§Automatic Draft Detection
If you donāt need to specify a particular draft version, you can use jsonschema::validator_for
which automatically detects the appropriate draft:
use serde_json::json;
let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"});
let validator = jsonschema::validator_for(&schema)?;
assert!(validator.is_valid(&json!("Hello")));§External References
By default, jsonschema resolves HTTP references using reqwest and file references from the local file system.
Both blocking and non-blocking retrieval is supported during validator construction. Note that the validation
itself is always synchronous as it operates on in-memory data only.
use serde_json::json;
let schema = json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "string"});
// Building a validator with blocking retrieval (default)
let validator = jsonschema::validator_for(&schema)?;
// Building a validator with non-blocking retrieval (requires `resolve-async` feature)
let validator = jsonschema::async_validator_for(&schema).await?;
// Validation is always synchronous
assert!(validator.is_valid(&json!("Hello")));To enable HTTPS support, add the rustls-tls feature to reqwest in your Cargo.toml:
reqwest = { version = "*", features = ["rustls-tls"] }You can disable the default behavior using crate features:
- Disable HTTP resolving:
default-features = false, features = ["resolve-file"] - Disable file resolving:
default-features = false, features = ["resolve-http"] - Enable async resolution:
features = ["resolve-async"] - Disable all resolving:
default-features = false
§Custom retrievers
You can implement custom retrievers for both blocking and non-blocking retrieval:
use std::{collections::HashMap, sync::Arc};
use jsonschema::{Retrieve, Uri};
use serde_json::{json, Value};
struct InMemoryRetriever {
schemas: HashMap<String, Value>,
}
impl Retrieve for InMemoryRetriever {
fn retrieve(
&self,
uri: &Uri<String>,
) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
self.schemas
.get(uri.as_str())
.cloned()
.ok_or_else(|| format!("Schema not found: {uri}").into())
}
}
let mut schemas = HashMap::new();
schemas.insert(
"https://example.com/person.json".to_string(),
json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name", "age"]
}),
);
let retriever = InMemoryRetriever { schemas };
let schema = json!({
"$ref": "https://example.com/person.json"
});
let validator = jsonschema::options()
.with_retriever(retriever)
.build(&schema)?;
assert!(validator.is_valid(&json!({
"name": "Alice",
"age": 30
})));
assert!(!validator.is_valid(&json!({
"name": "Bob"
})));And non-blocking version with the resolve-async feature enabled:
use jsonschema::{AsyncRetrieve, Registry, Resource, Uri};
use serde_json::{Value, json};
struct HttpRetriever;
#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
impl AsyncRetrieve for HttpRetriever {
async fn retrieve(
&self,
uri: &Uri<String>,
) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
reqwest::get(uri.as_str())
.await?
.json()
.await
.map_err(Into::into)
}
}
// Then use it to build a validator
let validator = jsonschema::async_options()
.with_retriever(HttpRetriever)
.build(&json!({"$ref": "https://example.com/user.json"}))
.await?;On wasm32 targets, use async_trait::async_trait(?Send) so your retriever can rely on Rc, JsFuture, or other non-thread-safe types.
§Validating against schema definitions
When working with large schemas containing multiple definitions (e.g., Open API schemas, DAP schemas), you may want to validate data against a specific definition rather than the entire schema. This can be achieved by registering the root schema as a resource and creating a wrapper schema that references the target definition:
use serde_json::json;
use jsonschema::Resource;
// Root schema with multiple definitions
let root_schema = json!({
"$id": "https://example.com/root",
"definitions": {
"User": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name"]
},
"Product": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"title": {"type": "string"}
},
"required": ["id", "title"]
}
}
});
// Create a schema that references the specific definition you want to validate against
let user_schema = json!({"$ref": "https://example.com/root#/definitions/User"});
// Register the root schema and build validator for the specific definition
let validator = jsonschema::options()
.with_resource("https://example.com/root", Resource::from_contents(root_schema))
.build(&user_schema)?;
// Now validate data against just the User definition
assert!(validator.is_valid(&json!({"name": "Alice", "age": 30})));
assert!(!validator.is_valid(&json!({"age": 25}))); // Missing required "name"This pattern is particularly useful when:
- Working with API schemas that define multiple request/response types
- Validating configuration snippets against specific sections of a larger schema
- Testing individual schema components in isolation
§Regular Expression Configuration
The jsonschema crate allows configuring the regular expression engine used for validating
keywords like pattern or patternProperties.
By default, the crate uses fancy-regex, which supports advanced
regular expression features such as lookaround and backreferences.
The primary motivation for switching to the regex engine is security and performance:
it guarantees linear-time matching, preventing potential Denial of Service attacks from malicious patterns
in user-provided schemas while offering better performance with a smaller feature set.
You can configure the engine at runtime using the PatternOptions API:
§Example: Configure fancy-regex with Backtracking Limit
use serde_json::json;
use jsonschema::PatternOptions;
let schema = json!({
"type": "string",
"pattern": "^(a+)+$"
});
let validator = jsonschema::options()
.with_pattern_options(
PatternOptions::fancy_regex()
.backtrack_limit(10_000)
)
.build(&schema)
.expect("A valid schema");§Example: Use the regex Engine Instead
use serde_json::json;
use jsonschema::PatternOptions;
let schema = json!({
"type": "string",
"pattern": "^a+$"
});
let validator = jsonschema::options()
.with_pattern_options(PatternOptions::regex())
.build(&schema)
.expect("A valid schema");§Notes
- If neither engine is explicitly set,
fancy-regexis used by default. - Regular expressions that rely on advanced features like
(?<=...)(lookbehind) or backreferences (\1) will fail with theregexengine.
§Custom Keywords
jsonschema allows you to extend its functionality by implementing custom validation logic through custom keywords.
This feature is particularly useful when you need to validate against domain-specific rules that arenāt covered by the standard JSON Schema keywords.
To implement a custom keyword, you need to:
- Create a struct that implements the
Keywordtrait - Create a factory function or closure that produces instances of your custom keyword
- Register the custom keyword with the
Validatorinstance using theValidationOptions::with_keywordmethod
Hereās a complete example:
use jsonschema::{
paths::{LazyLocation, Location},
Keyword, ValidationError,
};
use serde_json::{json, Map, Value};
use std::iter::once;
// Step 1: Implement the Keyword trait
struct EvenNumberValidator;
impl Keyword for EvenNumberValidator {
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
) -> Result<(), ValidationError<'i>> {
if let Value::Number(n) = instance {
if n.as_u64().map_or(false, |n| n % 2 == 0) {
Ok(())
} else {
return Err(ValidationError::custom(
Location::new(),
location.into(),
instance,
"Number must be even",
));
}
} else {
Err(ValidationError::custom(
Location::new(),
location.into(),
instance,
"Value must be a number",
))
}
}
fn is_valid(&self, instance: &Value) -> bool {
instance.as_u64().map_or(false, |n| n % 2 == 0)
}
}
// Step 2: Create a factory function
fn even_number_validator_factory<'a>(
_parent: &'a Map<String, Value>,
value: &'a Value,
path: Location,
) -> Result<Box<dyn Keyword>, ValidationError<'a>> {
// You can use the `value` parameter to configure your validator if needed
if value.as_bool() == Some(true) {
Ok(Box::new(EvenNumberValidator))
} else {
Err(ValidationError::custom(
Location::new(),
path,
value,
"The 'even-number' keyword must be set to true",
))
}
}
// Step 3: Use the custom keyword
fn main() -> Result<(), Box<dyn std::error::Error>> {
let schema = json!({"even-number": true, "type": "integer"});
let validator = jsonschema::options()
.with_keyword("even-number", even_number_validator_factory)
.build(&schema)?;
assert!(validator.is_valid(&json!(2)));
assert!(!validator.is_valid(&json!(3)));
assert!(!validator.is_valid(&json!("not a number")));
Ok(())
}In this example, weāve created a custom even-number keyword that validates whether a number is even.
The EvenNumberValidator implements the actual validation logic, while the even_number_validator_factory
creates instances of the validator and allows for additional configuration based on the keywordās value in the schema.
You can also use a closure instead of a factory function for simpler cases:
let schema = json!({"even-number": true, "type": "integer"});
let validator = jsonschema::options()
.with_keyword("even-number", |_, _, _| {
Ok(Box::new(EvenNumberValidator))
})
.build(&schema)?;§Custom Formats
JSON Schema allows for format validation through the format keyword. While jsonschema
provides built-in validators for standard formats, you can also define custom format validators
for domain-specific string formats.
To implement a custom format validator:
- Define a function or a closure that takes a
&strand returns abool. - Register the function with
jsonschema::options().with_format().
use serde_json::json;
// Step 1: Define the custom format validator function
fn ends_with_42(s: &str) -> bool {
s.ends_with("42!")
}
// Step 2: Create a schema using the custom format
let schema = json!({
"type": "string",
"format": "ends-with-42"
});
// Step 3: Build the validator with the custom format
let validator = jsonschema::options()
.with_format("ends-with-42", ends_with_42)
.with_format("ends-with-43", |s| s.ends_with("43!"))
.should_validate_formats(true)
.build(&schema)?;
// Step 4: Validate instances
assert!(validator.is_valid(&json!("Hello42!")));
assert!(!validator.is_valid(&json!("Hello43!")));
assert!(!validator.is_valid(&json!(42))); // Not a string§Notes on Custom Format Validators
- Custom format validators are only called for string instances.
- In newer drafts,
formatis purely an annotation and wonāt do any checking unless you opt in by calling.should_validate_formats(true)on your options builder. If you omit it, allformatkeywords are ignored at validation time.
§Arbitrary Precision Numbers
Enable the arbitrary-precision feature for exact validation of numbers beyond standard numeric ranges:
jsonschema = { version = "x.y.z", features = ["arbitrary-precision"] }This provides:
- Arbitrarily large integers (e.g.,
18446744073709551616) - Exact decimal precision without
f64rounding (e.g.,0.1,0.3)
Important: Precision is only preserved when parsing JSON from strings. Using Rust literals
or the json!() macro converts numbers to f64, losing precision.
// Precision preserved - parsed from JSON string
let schema = serde_json::from_str(r#"{"minimum": 0.1}"#)?;
let instance = serde_json::from_str("0.3")?;
let validator = Validator::new(&schema)?;
assert!(validator.is_valid(&instance));§WebAssembly support
jsonschema supports WebAssembly with different capabilities based on the target platform:
§Browser/JavaScript (wasm32-unknown-unknown)
When targeting browser or JavaScript environments, external reference resolution is not supported by default due to platform limitations:
- No filesystem access (
resolve-filefeature is not available) - No synchronous HTTP requests (
resolve-httpfeature is not available)
To use jsonschema in these environments, disable default features:
jsonschema = { version = "x.y.z", default-features = false }Note: Attempting to compile with resolve-http or resolve-file features on
wasm32-unknown-unknown will result in a compile error.
For external references in browser environments, implement a custom retriever that uses
browser APIs (like fetch). See the External References section.
§WASI (wasm32-wasip1 / wasm32-wasip2)
WASI environments (preview 1 and preview 2) can compile schemas and run validators, but the bundled
HTTP retriever depends on reqwestās blocking client, which isnāt available on these targets. Use
file access and custom retrievers instead.
Supported:
- Blocking file resolution (
resolve-filefeature) - Custom blocking retrievers (including wrapping async operations)
- Custom async retrievers via the
resolve-asyncfeature (for example,jsonschema::async_optionstogether with your own async runtime)
Not Supported:
- The bundled HTTP retriever (depends on
reqwestās blocking client)
jsonschema = { version = "x.y.z", default-features = false, features = ["resolve-file"] }Workaround for HTTP: Implement a custom blocking or async Retrieve that uses your preferred
HTTP client, and enable resolve-async if you want to build validators through async_options()
on WASI.
Re-exports§
pub use error::ErrorIterator;pub use error::MaskedValidationError;pub use error::ValidationError;pub use types::JsonType;pub use types::JsonTypeSet;pub use types::JsonTypeSetIterator;
Modules§
- draft4
- Functionality specific to JSON Schema Draft 4.
- draft6
- Functionality specific to JSON Schema Draft 6.
- draft7
- Functionality specific to JSON Schema Draft 7.
- draft201909
- Functionality specific to JSON Schema Draft 2019-09.
- draft202012
- Functionality specific to JSON Schema Draft 2020-12.
- error
- Error Handling
- meta
- Functionality for validating JSON Schema documents against their meta-schemas.
- output
- Backwards-compatible re-exports for structured output helpers.
- paths
- Facilities for working with paths within schemas or validated instances.
- types
- JSON type representations for schema validation.
Structs§
- Annotation
Entry - Entry describing annotations emitted by a keyword during evaluation.
- Error
Entry - Entry describing errors emitted by a keyword during evaluation.
- Evaluation
- Result of evaluating a JSON instance against a schema.
- Fancy
Regex - Marker for using the
fancy-regexengine that includes advanced features like lookarounds. - Flag
Output - Flag output format containing only a validity indicator.
- Hierarchical
Output - Hierarchical output format providing a tree structure of evaluation results.
- List
Output - List output format providing a flat list of evaluation units.
- Pattern
Options - Configuration for how regular expressions are handled in schema keywords like
patternandpatternProperties. - Regex
- Marker for using the
regexengine, that has fewer features thanfancy-regexbut guarantees linear time performance. - Registry
- A registry of JSON Schema resources, each identified by their canonical URIs.
- Registry
Options - Configuration options for creating a
Registry. - Resource
- An owned document with a concrete interpretation under a JSON Schema specification.
- Uri
- A URI.
- Validation
Options - Configuration options for JSON Schema validation.
- Validator
- A compiled JSON Schema validator.
Enums§
- Draft
- JSON Schema specification versions.
- Referencing
Error - Errors that can occur during reference resolution and resource handling.
Traits§
- Keyword
- Trait that allows implementing custom validation for keywords.
- Retrieve
- Trait for retrieving resources from external sources.
Functions§
- is_
valid - Validate
instanceagainstschemaand get atrueif the instance is valid andfalseotherwise. Draft is detected automatically. - options
- Create a builder for configuring JSON Schema validation options.
- validate
- Validate
instanceagainstschemaand return the first error if any. Draft is detected automatically. - validator_
for - Create a validator for the input schema with automatic draft detection and default options.