JSON Schema validation library

Overview

jsonschema

ci codecov Crates.io docs.rs gitter

A JSON Schema validator implementation. It compiles schema into a validation tree to have validation as fast as possible.

Supported drafts:

  • Draft 7 (except optional idn-hostname.json test case)
  • Draft 6
  • Draft 4 (except optional bignum.json test case)
# Cargo.toml
jsonschema = "0.13"

To validate documents against some schema and get validation errors (if any):

use jsonschema::JSONSchema;
use serde_json::json;

fn main() {
    let schema = json!({"maxLength": 5});
    let instance = json!("foo");
    let compiled = JSONSchema::compile(&schema)
        .expect("A valid schema");
    let result = compiled.validate(&instance);
    if let Err(errors) = result {
        for error in errors {
            println!("Validation error: {}", error);
            println!(
                "Instance path: {}", error.instance_path
            );
        }
    }
}

Each error has an instance_path attribute that indicates the path to the erroneous part within the validated instance. It could be transformed to JSON Pointer via .to_string() or to Vec via .into_vec().

If you only need to know whether document is valid or not (which is faster):

use jsonschema::is_valid;
use serde_json::json;

fn main() {
    let schema = json!({"maxLength": 5});
    let instance = json!("foo");
    assert!(is_valid(&schema, &instance));
}

Or use a compiled schema (preferred):

use jsonschema::JSONSchema;
use serde_json::json;

fn main() {
    let schema = json!({"maxLength": 5});
    let instance = json!("foo");
    // Draft is detected automatically
    // with fallback to Draft7
    let compiled = JSONSchema::compile(&schema)
        .expect("A valid schema");
    assert!(compiled.is_valid(&instance));
}

Output styles

jsonschema supports basic & flag output styles from Draft 2019-09, so you can serialize the validation results with serde:

use jsonschema::{Output, BasicOutput, JSONSchema};

fn main() {
    let schema_json = serde_json::json!({
        "title": "string value",
        "type": "string"
    });
    let instance = serde_json::json!{"some string"};
    let schema = JSONSchema::options()
        .compile(&schema_json)
        .expect("A valid schema");
    
    let output: BasicOutput = schema.apply(&instance).basic();
    let output_json = serde_json::to_value(output)
        .expect("Failed to serialize output");
    
    assert_eq!(
        output_json, 
        serde_json::json!({
            "valid": true,
            "annotations": [
                {
                    "keywordLocation": "",
                    "instanceLocation": "",
                    "annotations": {
                        "title": "string value"
                    }
                }
            ]
        })
    );
}

Status

This library is functional and ready for use, but its API is still evolving to the 1.0 API.

Bindings

  • Python - See the ./bindings/python directory
  • Ruby - a crate by @driv3r
  • NodeJS - a package by @ahungrynoob

Performance

There is a comparison with other JSON Schema validators written in Rust - jsonschema_valid==0.4.0 and valico==3.6.0.

Test machine i8700K (12 cores), 32GB RAM.

Input values and schemas:

Case Schema size Instance size
OpenAPI 18 KB 4.5 MB
Swagger 25 KB 3.0 MB
Canada 4.8 KB 2.1 MB
CITM catalog 2.3 KB 501 KB
Fast (valid) 595 B 55 B
Fast (invalid) 595 B 60 B

Here is the average time for each contender to validate. Ratios are given against compiled JSONSchema using its validate method. The is_valid method is faster, but gives only a boolean return value:

Case jsonschema_valid valico jsonschema (validate) jsonschema (is_valid)
OpenAPI - (1) - (1) 4.717 ms 4.279 ms (x0.90)
Swagger - (2) 83.357 ms (x12.47) 6.681 ms 4.533 ms (x0.67)
Canada 32.987 ms (x31.38) 141.41 ms (x134.54) 1.051 ms 1.046 ms (x0.99)
CITM catalog 4.735 ms (x2.00) 13.222 ms (x5.58) 2.367 ms 535.07 us (x0.22)
Fast (valid) 2.00 us (x3.85) 3.18 us (x6.13) 518.39 ns 97.91 ns (x0.18)
Fast (invalid) 339.28 ns (x0.50) 3.34 us (x5.00) 667.55 ns 5.41ns (x0.01)

Notes:

  1. jsonschema_valid and valico do not handle valid path instances matching the ^\\/ regex.

  2. jsonschema_valid fails to resolve local references (e.g. #/definitions/definitions).

You can find benchmark code in benches/jsonschema.rs, Rust version is 1.57.

Support

If you have anything to discuss regarding this library, please, join our gitter!

Comments
  • Add a `path` field to `ValidationError`

    Add a `path` field to `ValidationError`

    Summary

    On validation error make the path to the problematic field available to the client. Currently the path is only a field in ValidationError but later it can be used to improve the error messages (related to #100, #144)

    Example

    Schema
    {
       "type":"object",
       "properties":{
          "foo":{
             "type":"object",
             "properties":{
                "bar":{
                   "type":"integer"
                }
             }
          }
       }
    }
    
    Input
    {
      "foo": {
        "bar": "some string"
      }
    }
    
    Produced Error

    path: [foo, bar]

    opened by gavadinov 16
  • patternProperties interferes with properties

    patternProperties interferes with properties

    It seems like the presence of "patternProperties" interferes with validation that should happen in "properties".

    This replit shows an example of an instance that should fail validation (and does fail in jsonschemavalidator.net). If you comment out the "patternProperties" portion of the schema, it fails as expected.

    I don't have a good enough understanding of the jsonschema internals, but it seems like this could be related to changes from #173 .

    UPDATE: I updated the replit and the jsonschemavalidator link to provide a more minimal example.

    opened by duckontheweb 16
  • [FEATURE] User-defined validation for the `format` keyword

    [FEATURE] User-defined validation for the `format` keyword

    At the moment, jsonschema supports the format keyword for formats defined in Drafts 4, 6, 7. However, JSON Schema allows custom values for the format keyword.

    It will be awesome to provide a way for the end-user to validate their own formats.

    As all validators are "compiled" to some structs (to avoid some run-time overhead e.g., map lookup, etc.), I think that the public API might look like this:

    use jsonschema;
    use std::collections::HashMap;
    use serde_json::{json, Value};
    
    struct CustomFormatValidator {}
    
    // New trait somehow derived from `Validate`?
    impl jsonschema::SomeNewTrait for CustomFormatValidator {
        fn is_valid(&self, _: &jsonschema::JSONSchema, instance: &Value) -> bool {
            if let Value::String(item) = instance {
                item == "bar"  // Format matching logic goes here
            } else {
                true
            }
        }
    }
    
    fn main() -> Result<(), jsonschema::CompilationError> {
        let schema = json!({"format": "custom"});
        let instance = json!("foo");
        let mut formats = HashMap::new();
        formats.insert(
            "custom": CustomFormatValidator
        );
        // Shortcuts
        jsonschema::is_valid(&schema, &instance, Some(formats))
        // Compiled schema
        let compiled = jsonschema::JSONSchema::options()
            .formats(formats)
            .compile(&schema)
            .expect("Invalid schema");
        compiled.is_valid(&instance)
    }
    

    It will be nice to avoid the boilerplate that extracts the actual string from Value::String, so the user won't write this code for each format.

    Anticipated changes to implement this:

    • Take extra argument for the formats map in jsonschema::is_valid;
    • Extend CompilationOptions builder to handle formats;
    • Store the formats map as a part of CompilationContext;
    • Inside format.rs take a look at this mapping before going to take a look at the built-in validators;
    • Make the Validate trait public and implement some trait on top of it to avoid boilerplate?

    For now, I think it is OK to keep the scope on the Rust code only. A similar feature for bindings could be implemented later.

    help wanted good first issue Priority: Medium Type: Feature Difficulty: Medium 
    opened by Stranger6667 16
  • HTTP Resolver Not Working?

    HTTP Resolver Not Working?

    Hi,

    Sorry, I've been trying to work this out myself, but I'm not sure what's going wrong.

    Validation error: failed to resolve https://schema.rawkode.dev/organization: error sending request for url (https://schema.rawkode.dev/organization): error trying to connect: invalid URL, scheme is not http
    Instance path:
    Validation error: failed to resolve https://schema.rawkode.dev/person: error sending request for url (https://schema.rawkode.dev/person): error trying to connect: invalid URL, scheme is not http
    Instance path:
    

    These URLs are definitely OK and I'm not sure what I've done wrong.

    I've tried using http instead of https in my schema, but the problem is the same.

    Any help appreciated.

    Priority: High Type: Bug 
    opened by rawkode 15
  • Allow Schema Value to be Owned

    Allow Schema Value to be Owned

    In the current form it is impossible to retain a non-static reference to JSONSchema until the end of the program's execution. The below block demonstrates the problem.

    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
        let schema_file = fs::File::open("config/schema.json").unwrap();
    
        let schema: Value = serde_json::from_reader(schema_file).unwrap();
        let validator = JSONSchema::compile(&schema, None).unwrap();
    
        let ad = AppData{
            validator: Arc::new(validator)
        };
    
        HttpServer::new(move ||{
            App::new()
                .data(ad.clone())
                .service(echo)
        })
        .bind("localhost:9070")?
        .run()
        .await
    }
    
    #[derive(Clone)]
    pub struct AppData<'a> {
        pub validator: Arc<JSONSchema<'a>>
    }
    

    The above code results in the below compiler error:

    error[E0597]: `schema` does not live long enough
      --> src/main.rs:15:41
       |
    15 |     let validator = JSONSchema::compile(&schema, None).unwrap();
       |                     --------------------^^^^^^^-------
       |                     |                   |
       |                     |                   borrowed value does not live long enough
       |                     argument requires that `schema` is borrowed for `'static`
    ...
    31 | }
       | - `schema` dropped here while still borrowed
    

    One(or the only?) way to get this fixed is to let JSONSchema own the Value containing schema.

    opened by kayyagari 12
  • fix: convert python tuples into lists

    fix: convert python tuples into lists

    We use tuples for geographic coordinates and bounding boxes which doesn't seem to have a direct mapping in JSON Schema.

    This pull requests converts the tuple into a list to fix ValueError: Unsupported type: 'tuple' when validating a object that contains a tuple.

    { "bbox": (1,2,3,4) }
    
    opened by blacha 11
  • Add support for look-around patterns using fancy-regex

    Add support for look-around patterns using fancy-regex

    Uses fancy-regex in place of regex for matching against all patterns in JSON schemas.

    regex is still used when converting a RegEx for ECMA 262 to take advantage of the Regex::replace_all method, which is not included in fancy-regex. Since fancy-regex depends on regex anyway, this does not affect the final size of the compiled binary (thanks to this commit for the idea).

    I also removed the test from #214, since that pattern is supported in this PR.

    opened by duckontheweb 10
  • Report schema paths in validation errors

    Report schema paths in validation errors

    When a validation error is reported, it contains only instance_path that points to the erroneous part of the input, but there is no information about what part of the schema caused this error.

    The gist of the implementation is to keep a stack of strings while the input schema is compiled. The stack should be convertible to paths::JSONPointer.

    The process for regular, non-composite validators (more on this below):

    • When the compilation process enters a validator, push its relevant keyword name to the stack (e.g. properties);
    • When the validator struct is created, clone this stack and store it within this struct;
    • When the validator is compiled - pop from the stack

    I think that pushing/popping could be implemented similarly to instance_path - a linked list without heap allocations. This way, the actual popping step is not needed at all.

    Composite validators are slightly more complicated - they are single structs that implement the logic for multiple different keywords or their specific variants (e.g. this one handles patternProperties and additionalProperties: false) which is done due to performance reasons.

    To handle them, the stack should not be pushed during compilation, but rather during validation - if a validation error happens, the stored stack should be cloned, and the exact keyword variant should be pushed to it. And finally, it is moved to ValidationError.

    Validators live in keywords.

    A lot of work in this direction was done by @gavadinov in https://github.com/gavadinov/jsonschema-rs/commit/40149322bfdf5549a0150122a00fa66dfe770828, and I think that it could be a great base for implementing this feature.

    @gavadinov, let me know if you are OK if somebody can base their work on your commit, or if you want to continue it yourself :)

    help wanted good first issue Priority: High Type: Feature Difficulty: Medium 
    opened by Stranger6667 10
  • Replace `ValidationError::schema` with custom errors

    Replace `ValidationError::schema` with custom errors

    Hi there, bump into here via hacktoberfest mark.

    Honestly most likely it doesn't address JSONPointer correctly in errors. And I was not sure which type of error better use for things like as64 error or regex syntax error but I did used ValidationError::format.

    Note: there might be an issue with cargo fmt.

    closes #235

    Thank you for the library.

    opened by zhiburt 9
  • Use the original value in error formatting for some numeric validators

    Use the original value in error formatting for some numeric validators

    Validating 3.0 against {"exclusiveMaximum": 3.0} will give 3.0 is greater than or equal to the maximum of 3. But the last number in the error message should be 3.0. It affects exclusiveMaximum, exclusiveMinimum, minimum, maximum.

    Priority: Low Type: Bug Difficulty: Easy 
    opened by Stranger6667 9
  • Use a better structure instead of `Map` in validators

    Use a better structure instead of `Map` in validators

    E.g. in AdditionalPropertiesNotEmptyValidator. Because during the validation we use self.properties.contains_key(*property) which will be O(logN) (because of BTreeMap). We can use HashMap to have O(1).

    As perf showed, significant time is spent on it

    Priority: Low Type: Enhancement Topic: Performance 
    opened by Stranger6667 8
  • stack overflow

    stack overflow

    use jsonschema::JSONSchema;
    use serde_json::json;
    
    fn main() {
        let schema = json!({
      "$defs": {
        "root": {
          "oneOf": [
            {
              "type": "object",
              "properties": {
                "a": {
                  "type": "number"
                }
              }
            },
            {
              "$ref": "#/$defs/root"
            }
          ]
        }
      },
      "type": "object",
      "patternProperties": {
        ".*": {
          "$ref": "#/$defs/root"
        }
      }
    });
        let instance = json!({
      "x": {
        "a": 3
      },
      "y": {
        "x": {
          "a": 3
        }
      }
    });
        let compiled = JSONSchema::compile(&schema)
            .expect("A valid schema");
        let result = compiled.validate(&instance);
        if let Err(errors) = result {
            for error in errors {
                println!("Validation error: {}", error);
                println!(
                    "Instance path: {}", error.instance_path
                );
            }
        }
    }
    
    thread 'main' has overflowed its stack
    fatal runtime error: stack overflow
    Aborted
    
    [dependencies]
    jsonschema = "0.16.0"
    serde_json = "1.0.82"
    
    opened by sigoden 0
  • i17n support (custom error messages)

    i17n support (custom error messages)

    Thanks for that great crate!

    I want to use this crate via Python but this module lacks i17n support so I should try parse English and translate into Japanese. I believe this support make this more end user friendly.

    opened by conao3 0
  • Custom Validators

    Custom Validators

    I got a feature request in a project that uses the library to validate file paths in a document. It would make sense to simply add a validator that would be able to do this, but it's currently not possible. The closest I can get is format validators but they lack any context.

    Would it be desirable to expose a way to hook into the compilation process and add entirely custom validators?

    This might not be a good idea because:

    • it has nothing to do with the JSON schema spec
    • complexity and larger API surface

    However it would be very convenient for my use-case.

    If this is desirable I imagine in order to do this we'd need to:

    • expose the Validate trait or something similar
    • expose a way to hook into the compilation process and create validators
    • add a custom error type that could be pretty much anything (probably Any + Display)
    • add a way to pass contextual information to validators to avoid recompilations (optional, I have no idea how much complexity this would add)
    opened by tamasfe 0
  • unexpected draft2019-09 (and up) $ref behavior

    unexpected draft2019-09 (and up) $ref behavior

    In draft2019-09, $ref was changed to allow other keywords alongside it.

    I ran into this while trying to validate an openapi spec against the published OAS 3.1 schema. In particular, check out the way, in that schema, "$ref": "#/$defs/specification-extensions", is used to factor a common patternProperties out of a bunch of different objects.

    Unfortunately, jsonschema doesn't seem to handle the $ref behavior correctly when it has peer keywords. For example:

    use jsonschema::{Draft, JSONSchema};
    use serde_json::{json, to_string_pretty};
    
    fn main() -> Result<(), std::io::Error> {
        let ok_instance = json!(6);
        let err_instance = json!(7);
    
        for schema in vec![
            json!({
                "type": "integer",
                "multipleOf": 3,
                "minimum": 6
            }),
            json!({
                "type": "integer",
                "multipleOf": 3,
                "$ref": "#/$defs/mini",
                "$defs": {
                    "mini": {
                        "minimum": 6
                    }
                }
            }),
        ] {
            let compiled = JSONSchema::options()
                .with_draft(Draft::Draft202012)
                .compile(&schema)
                .expect("A valid schema");
    
            if compiled.validate(&ok_instance).is_err() {
                println!(
                    "instance:\n{}\n... incorrectly *failed* validation against schema:\n{}",
                    to_string_pretty(&ok_instance).unwrap(),
                    to_string_pretty(&schema).unwrap()
                )
            }
    
            if compiled.validate(&err_instance).is_ok() {
                println!(
                    "instance:\n{}\n... incorrectly *passed* validation against schema:\n{}",
                    to_string_pretty(&err_instance).unwrap(),
                    to_string_pretty(&schema).unwrap()
                )
            }
        }
        Ok(())
    }
    

    fails with:

    instance:
    7
    ... incorrectly *passed* validation against schema:
    {
      "$defs": {
        "mini": {
          "minimum": 6
        }
      },
      "$ref": "#/$defs/mini",
      "multipleOf": 3,
      "type": "integer"
    }
    

    Does that make sense? Have I missed something about the way this is supposed to work?

    opened by dlowe 4
Releases(python-v0.16.0)
  • python-v0.16.0(May 12, 2022)

    Added

    • Python 3.10 support

    Fixed

    • Installation error due to pyo3-built incompatibility
    • Memory leak in iter_errors. #325

    Changed

    • Update pyo3 to 0.16.

    Removed

    • Support for Python 3.6
    Source code(tar.gz)
    Source code(zip)
  • rust-v0.16.0(Apr 21, 2022)

Owner
Dmitry Dygalo
Building a platform for effortless API testing
Dmitry Dygalo
Typify - Compile JSON Schema documents into Rust types.

Typify Compile JSON Schema documents into Rust types. This can be used ... via the macro import_types!("types.json") to generate Rust types directly i

Oxide Computer Company 55 Sep 22, 2022
Get JSON values quickly - JSON parser for Rust

get json values quickly GJSON is a Rust crate that provides a fast and simple way to get values from a json document. It has features such as one line

Josh Baker 153 Sep 30, 2022
Strongly typed JSON library for Rust

Serde JSON   Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. [dependencies] serde_json = "1.0

null 3.3k Sep 29, 2022
A port of the Node.js library json-file-store

A port of the Node.js library json-file-store

Markus Kohlhase 58 Jul 19, 2022
JSON parser which picks up values directly without performing tokenization in Rust

Pikkr JSON parser which picks up values directly without performing tokenization in Rust Abstract Pikkr is a JSON parser which picks up values directl

Pikkr 609 Sep 16, 2022
JSON implementation in Rust

json-rust Parse and serialize JSON with ease. Changelog - Complete Documentation - Cargo - Repository Why? JSON is a very loose format where anything

Maciej Hirsz 489 Sep 25, 2022
Rust port of gjson,get JSON value by dotpath syntax

A-JSON Read JSON values quickly - Rust JSON Parser change name to AJSON, see issue Inspiration comes from gjson in golang Installation Add it to your

Chen Jiaju 86 Sep 4, 2022
rurl is like curl but with a json configuration file per request

rurl rurl is a curl-like cli tool made in rust, the difference is that it takes its params from a json file so you can have all different requests sav

Bruno Ribeiro da Silva 6 Sep 10, 2022
A rust script to convert a better bibtex json file from Zotero into nice organised notes in Obsidian

Zotero to Obsidian script This is a script that takes a better bibtex JSON file exported by Zotero and generates an organised collection of reference

Sashin Exists 2 Feb 12, 2022
A tool for outputs semantic difference of json

jsondiff A tool for outputs semantic difference of json. "semantic" means: sort object key before comparison sort array before comparison (optional, b

niboshi 3 Sep 22, 2021
Easily create dynamic css using json notation

jss! This crate provides an easy way to write dynamic css using json notation. This gives you more convenient than you think. Considering using a dyna

Jovansonlee Cesar 7 May 14, 2022
Decode Metaplex mint account metadata into a JSON file.

Simple Metaplex Decoder (WIP) Install From Source Install Rust. curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh Clone the source: git c

Samuel Vanderwaal 8 Aug 25, 2022
A fast and simple command-line tool for common operations over JSON-lines files

rjp: Rapid JSON-lines processor A fast and simple command-line tool for common operations over JSON-lines files, such as: converting to and from text

Ales Tamchyna 3 Jul 8, 2022
CLI tool to convert HOCON into valid JSON or YAML written in Rust.

{hocon:vert} CLI Tool to convert HOCON into valid JSON or YAML. Under normal circumstances this is mostly not needed because hocon configs are parsed

Mathias Oertel 21 Sep 17, 2022
Tools for working with Twitter JSON data

Twitter stream user info extractor This project lets you parse JSON data from the Twitter API or other sources to extract some basic user information,

Travis Brown 4 Apr 21, 2022
A easy and declarative way to test JSON input in Rust.

assert_json A easy and declarative way to test JSON input in Rust. assert_json is a Rust macro heavily inspired by serde json macro. Instead of creati

Charles Vandevoorde 9 Nov 30, 2021
A fast way to minify JSON

COMPACTO (work in progress) A fast way to minify JSON. Usage/Examples # Compress # Input example (~0.11 KB) # { # "id": "123", # "name": "Edua

Eduardo Stuart 4 Feb 27, 2022
Jq - Command-line JSON processor

jq jq is a lightweight and flexible command-line JSON processor. , Unix: , Windows: If you want to learn to use jq, read the documentation at https://

Stephen Dolan 23.1k Sep 23, 2022
A small rust database that uses json in memory.

Tiny Query Database (TQDB) TQDB is a small library for creating a query-able database that is encoded with json. The library is well tested (~96.30% c

Kace Cottam 2 Jan 4, 2022