Crate jsonschema

source ·
Expand description

A high-performance JSON Schema validator for Rust.

  • 📚 Support for popular JSON Schema drafts
  • 🔧 Custom keywords and format validators
  • 🌐 Remote reference fetching (network/file)
  • 🎨 Basic output style as per JSON Schema spec

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

§One-off Validation

For simple use cases where you need to validate an instance against a schema once, use the is_valid function:

use serde_json::json;

let schema = json!({"type": "string"});
let instance = json!("Hello, world!");

assert!(jsonschema::is_valid(&schema, &instance));

§Reusable Validators

For better performance, especially when validating multiple instances against the same schema, build a validator once and reuse it:

use serde_json::json;

let schema = json!({"type": "string"});
let validator = jsonschema::compile(&schema)
    .expect("Invalid schema");

assert!(validator.is_valid(&json!("Hello, world!")));
assert!(!validator.is_valid(&json!(42)));

// Iterate over all errors
let instance = json!(42);
let result = validator.validate(&instance);
if let Err(errors) = result {
    for error in errors {
        eprintln!("Error: {}", error);
        eprintln!("Location: {}", error.instance_path);
    }
}

§Configuration

jsonschema provides a builder for configuration options via JSONSchema::options().

Here is how you can explicitly set the JSON Schema draft version:

use jsonschema::{JSONSchema, Draft};
use serde_json::json;

let schema = json!({"type": "string"});
let validator = JSONSchema::options()
    .with_draft(Draft::Draft7)
    .compile(&schema)
    .expect("Invalid schema");

For a complete list of configuration options and their usage, please refer to the CompilationOptions struct.

§Reference Resolving

By default, jsonschema resolves HTTP references using reqwest and file references from the local file system.

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"]
  • Disable both: default-features = false

You can implement a custom resolver to handle external references. Here’s an example that uses a static map of schemas:

use std::{collections::HashMap, sync::Arc};
use anyhow::anyhow;
use jsonschema::{JSONSchema, SchemaResolver, SchemaResolverError};
use serde_json::{json, Value};
use url::Url;

struct StaticSchemaResolver {
    schemas: HashMap<String, Arc<Value>>,
}

impl SchemaResolver for StaticSchemaResolver {
    fn resolve(
        &self,
        _root_schema: &serde_json::Value,
        url: &Url,
        _original_reference: &str
    ) -> Result<Arc<Value>, SchemaResolverError> {
        self.schemas
            .get(url.as_str())
            .cloned()
            .ok_or_else(|| anyhow!("Schema not found: {}", url))
    }
}

let mut schemas = HashMap::new();
schemas.insert(
    "https://example.com/person.json".to_string(),
    Arc::new(json!({
        "type": "object",
        "properties": {
            "name": { "type": "string" },
            "age": { "type": "integer" }
        },
        "required": ["name", "age"]
    })),
);

let resolver = StaticSchemaResolver { schemas };

let schema = json!({
    "$ref": "https://example.com/person.json"
});

let validator = JSONSchema::options()
    .with_resolver(resolver)
    .compile(&schema)
    .expect("Invalid schema");

assert!(validator.is_valid(&json!({
    "name": "Alice",
    "age": 30
})));

assert!(!validator.is_valid(&json!({
    "name": "Bob"
})));

§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 jsonschema::BasicOutput;
use serde_json::json;

let schema_json = json!({
    "title": "string value",
    "type": "string"
});
let instance = json!("some string");
let schema = jsonschema::compile(&schema_json)
    .expect("Invalid schema");

let output: BasicOutput = schema.apply(&instance).basic();
let output_json = serde_json::to_value(output)?;

assert_eq!(
    output_json,
    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 JSONSchema instance using the with_keyword method

Here’s a complete example:

use jsonschema::{
    paths::{JSONPointer, JsonPointerNode},
    ErrorIterator, JSONSchema, 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<'instance>(
        &self,
        instance: &'instance Value,
        instance_path: &JsonPointerNode,
    ) -> ErrorIterator<'instance> {
        if let Value::Number(n) = instance {
            if n.as_u64().map_or(false, |n| n % 2 == 0) {
                Box::new(None.into_iter())
            } else {
                let error = ValidationError::custom(
                    JSONPointer::default(),
                    instance_path.into(),
                    instance,
                    "Number must be even",
                );
                Box::new(once(error))
            }
        } else {
            let error = ValidationError::custom(
                JSONPointer::default(),
                instance_path.into(),
                instance,
                "Value must be a number",
            );
            Box::new(once(error))
        }
    }

    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: JSONPointer,
) -> 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(
            JSONPointer::default(),
            JSONPointer::default(),
            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)
        .compile(&schema)
        .expect("Invalid 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))
    })
    .compile(&schema)
    .expect("Invalid 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 jsonschema::JSONSchema;
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: Compile the schema with the custom format
let validator = JSONSchema::options()
    .with_format("ends-with-42", ends_with_42)
    .with_format("ends-with-43", |s: &str| s.ends_with("43!"))
    .compile(&schema)
    .expect("Invalid 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 CompilationOptions. Ensure format validation is enabled if you’re using custom formats.

Re-exports§

Modules§

Structs§

Enums§

  • JSON Schema Draft version

Traits§

  • Trait that allows implementing custom validation for keywords.
  • A resolver that resolves external schema references. Internal references such as #/definitions and JSON pointers are handled internally.

Functions§

  • Compile the input schema for faster validation.
  • A shortcut for validating instance against schema. Draft version is detected automatically.

Type Aliases§