Skip to content

xinz/jsonschex

Repository files navigation

JSONSchex

hex.pm version

JSONSchex is an implementation of the JSON Schema Draft 2020-12 and latest specification for Elixir, with a design that focuses on practical performance.

Features

  • Implements JSON Schema Draft 2020-12 in full, including all core, applicator, validation, unevaluated, and content vocabulary keywords.
  • Passes 100% of the official JSON Schema Test Suite for Draft 2020-12.
  • Designed for performance and simplicity: compile a schema once into an executable Schema struct, then validate data repeatedly with no repeated parsing overhead.

Installation

def deps do
  [
    {:jsonschex, "~> 0.4"}
  ]
end

Quick start

{:ok, compiled} =
  JSONSchex.compile(%{
    "type" => "array",
    "items" => %{"type" => "integer"}
  })

:ok = JSONSchex.validate(compiled, [1, 2, 3])
{:error, errors} = JSONSchex.validate(compiled, [1, "bad"])

How it works

JSONSchex follows a two-phase approach for optimal performance:

  1. Compile — Parse and optimize a JSON Schema into an executable Schema struct. During compilation:

    • All $id, $anchor, and discovered local fragment references are scanned and registered
    • Keywords are converted into executable validation functions
    • Remote $ref schemas can be loaded via an external loader
    • The built-in Draft 2020-12 dialect is recognized without requiring a remote meta-schema load
    • Vocabularies are resolved based on $schema and $vocabulary declarations
  2. Validate — Execute the compiled schema against data. During validation:

    • Rules are executed in order, accumulating errors
    • Evaluated property/item keys are tracked for unevaluatedProperties and unevaluatedItems
    • References ($ref, $dynamicRef) are resolved from the compiled registry
    • All errors are collected and returned together

This design allows you to compile a schema once and reuse it for multiple validations, significantly improving performance for repeated validations.

Error reporting

When validation fails, JSONSchex.validate/2 returns {:error, errors} where errors is a list of JSONSchex.Types.Error structs.

JSONSchex uses a lazy error reporting model for performance. Errors contain raw data (path lists, context maps) rather than pre-formatted strings. You can use JSONSchex.format_error/1 to generate human-readable messages when needed.

Each error contains:

  • path — List of path segments indicating where the error occurred (e.g., ["users", 0, "email"])
  • rule — Atom identifying the failed validation rule (e.g., :type, :minimum)
  • context — Map containing details about the failure (e.g., %JSONSchex.Types.ErrorContext{contrast: "integer", input: "string"})
  • value - The input value that caused the error

Example:

schema = %{
  "type" => "object",
  "properties" => %{
    "email" => %{"type" => "string", "format" => "email"},
    "age" => %{"type" => "integer", "minimum" => 0}
  },
  "required" => ["email"]
}

{:ok, compiled} = JSONSchex.compile(schema, format_assertion: true)

{:error, errors} = JSONSchex.validate(compiled, %{"age" => -5})

# Inspect raw errors
# [
#   %JSONSchex.Types.Error{
#     path: ["age"],
#     rule: :minimum,
#     context: %JSONSchex.Types.ErrorContext{
#       contrast: 0,
#       input: -5,
#       error_detail: nil
#     },
#     value: -5
#   },
#   %JSONSchex.Types.Error{
#     path: [],
#     rule: :required,
#     context: %JSONSchex.Types.ErrorContext{
#       contrast: ["email"],
#       input: nil,
#       error_detail: nil
#     },
#     value: nil
#   }
# ]

# Format errors for display
Enum.map(errors, &JSONSchex.format_error/1)
# [
#   "At /age: Value -5 is less than minimum 0",
#   "Missing required properties: email"
# ]

Compile options

JSONSchex.compile/2 accepts an optional keyword list with the following options:

  • :external_loader — Function for loading remote $ref schemas (see Loader guide)
  • :base_uri — Starting base URI for resolving relative references (see Loader guide)
  • :format_assertion — Enable strict format validation (default: false; the built-in Draft 2020-12 dialect keeps format annotation-only unless explicitly enabled, see Content and format guide)
  • :content_assertion — Enable strict content vocabulary validation (default: false, see Content and format guide)

Optional Dependencies

JSONSchex has these optional dependencies that enable additional functionality:

  • jason (~> 1.0): Required for JSON decoding if using a version of Elixir is earlier than 1.18.

  • decimal (~> 2.0): Required for arbitrary precision decimal validation in the multipleOf keyword. Without this dependency, multipleOf validation may have precision issues with very large or very small decimal numbers.

  • idna (~> 6.0 or ~> 7.1): Required for internationalized domain name (IDN) support. Enables validation of idn-hostname and idn-email formats. Without this dependency, these formats may not be validated in expected ways.

To include these dependencies, add them to your mix.exs:

def deps do
  [
    {:jsonschex, "~> 0.4"},
    {:jason, "~> 1.4"},
    {:decimal, "~> 2.0"},
    {:idna, "~> 6.0 or ~> 7.1"}
  ]
end

Guides

See the guide/ directory for detailed documentation:

Development

Clone the repository and initialize the git submodules that provide the local test fixtures:

git clone https://github.com/xinz/jsonschex.git
cd jsonschex
git submodule update --init --recursive

This pulls two external test suites into test/fixtures/:

Then fetch dependencies and run the tests:

mix deps.get
mix test

Test suite summary

JSONSchex runs the JSON Schema Test Suite for Draft 2020-12, all tests passing.

  • Default suite path:
    • test/fixtures/JSON-Schema-Test-Suite/tests/draft2020-12
  • Exclusions:
    • optional/cross-draft

Debug test files can selectively run single suite files for focused investigation.

Benchmark

More about the benchmark can be found at bench.

About

An implementation of the JSON Schema draft 2020-12 and latest specification for Elixir

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages