Crate jsonschema

Source
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)
  • 🎨 Basic output style as per JSON Schema spec
  • ✨ Meta-schema validation for schema documents
  • 🚀 WebAssembly support

§Supported drafts

Compliance levels vary across drafts, with newer versions having some unimplemented keywords.

  • Draft 2020-12
  • Draft 2019-09
  • Draft 7
  • Draft 6
  • Draft 4

§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);
}

§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 new function to create a validator
  • An is_valid function for validation with a boolean result
  • An validate function for getting the first validation error
  • An options function to create a draft-specific configuration builder
  • A meta module 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;

#[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?;

§Output Styles

jsonschema supports the basic output style as defined in JSON Schema Draft 2019-09. This styles allow you to serialize validation results in a standardized format using serde.

use serde_json::json;

let schema_json = json!({
    "title": "string value",
    "type": "string"
});
let instance = json!("some string");
let validator = jsonschema::validator_for(&schema_json)?;

let output = validator.apply(&instance).basic();

assert_eq!(
    serde_json::to_value(output)?,
    json!({
        "valid": true,
        "annotations": [
            {
                "keywordLocation": "",
                "instanceLocation": "",
                "annotations": {
                    "title": "string value"
                }
            }
        ]
    })
);

§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:

  1. Create a struct that implements the Keyword trait
  2. Create a factory function or closure that produces instances of your custom keyword
  3. Register the custom keyword with the Validator instance using the ValidationOptions::with_keyword method

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:

  1. Define a function or a closure that takes a &str and returns a bool.
  2. 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.
  • Format validation can be disabled globally or per-draft using ValidationOptions. Ensure format validation is enabled if you’re using custom formats.

§WebAssembly support

When using jsonschema in WASM environments, be aware that external references are not supported by default due to WASM limitations:

  • No filesystem access (resolve-file feature)
  • No direct HTTP requests, at least right now (resolve-http feature)

To use jsonschema in WASM, disable default features:

jsonschema = { version = "x.y.z", default-features = false }

For external references in WASM you may want to implement a custom retriever. See the External References section for implementation details.

Re-exports§

pub use error::ErrorIterator;
pub use error::MaskedValidationError;
pub use error::ValidationError;
pub use output::BasicOutput;

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
Implementation of json schema output formats specified in https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.12.2
paths
Facilities for working with paths within schemas or validated instances.
primitive_type
Primitive types for property type validators

Structs§

Registry
A registry of JSON Schema resources, each identified by their canonical URIs.
RegistryOptions
Configuration options for creating a Registry.
Resource
An owned document with a concrete interpretation under a JSON Schema specification.
Uri
A URI.
ValidationOptions
Configuration options for JSON Schema validation.
Validator
A compiled JSON Schema validator.

Enums§

Draft
JSON Schema specification versions.
ReferencingError
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 instance against schema and get a true if the instance is valid and false otherwise. Draft is detected automatically.
options
Create a builder for configuring JSON Schema validation options.
validate
Validate instance against schema and 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.