GitHub - lud/jsv: Json Schema Validator for Elixir with full spec support

hex.pm Version Build Status License

JSV is a JSON Schema validator for Elixir, designed for modern applications. It provides full compliance with the latest JSON Schema specifications while offering a seamless, Elixir-native developer experience.

Key Features

  • Full Specification Compliance: 100% support for Draft 2020-12 (including $dynamicRef, $dynamicAnchor, and all vocabularies) and Draft 7.
  • High Performance: Schemas can be pre-compiled into an optimized internal representation. Build your validation roots at compile-time for near-zero runtime overhead.
  • Elixir-Native Schemas: Use defschema to define schemas as Elixir modules. Supports automatic casting to Elixir structs with default values.
  • Advanced Casting & Transformation:
    • Built-in support for casting to Date, DateTime, Duration, and Decimal.
    • Extensible casting system using the jsv-cast keyword and defcast macros.
  • Extensible Resolution: Fetch remote schemas via HTTP or resolve them from local files and directories using custom or built-in resolvers.
  • Flexible Workflows: Supports schemas as atoms or binaries, from Elixir code, JSON files, or the network. JSV supports many ways to work with schemas.
  • Extensible Vocabularies: Thanks to features of Draft 2020-12, custom meta-schemas with custom schema keywords are supported out of the box.
  • Functional Builder API: Compose schemas dynamically using a functional API.
  • Rich Error Handling: Detailed validation errors that can be easily normalized into JSON-compatible structures for API responses.
  • Complete support in Oaskit: Oaskit is an OpenAPI 3.1 validator for Phoenix and is entirely built on JSV.

Documentation

Comprehensive guides and API documentation are available on hexdocs.pm.

Supported Dialects

JSV supports 100% of features from Draft 2020-12 and Draft 7 as verified by the JSON Schema Compliance Test Suite.

  • Draft 2020-12
  • Draft 7

Installation

Add jsv to your mix.exs:

def deps do
  [
    {:jsv, "~> 0.18"},
  ]
end

Optional Dependencies

JSV integrates with popular Elixir libraries to provide enhanced functionality:

def deps do
  [
    # JSV Supports Decimal and will validate Decimal structs as numbers.
    {:decimal, "~> 2.0"},

    # Required for resolving schemas via HTTP on Elixir < 1.18.
    {:jason, "~> 1.0"}, # OR {:poison, "~> 6.0"}
  ]
end

Usage

Simple Validation

schema = %{
  type: :object,
  properties: %{
    name: %{type: :string}
  },
  required: [:name]
}

root = JSV.build!(schema)

case JSV.validate(%{"name" => "Alice"}, root) do
  # %{"name" => "Alice"}
  {:ok, data} -> IO.inspect(data)
  {:error, err} -> IO.inspect(JSV.normalize_error(err))
end

Module-Based Schemas

Define your business objects and validation logic in one place:

defmodule MyApp.User do
  use JSV.Schema

  defschema %{
    type: :object,
    properties: %{
      name: string(minLength: 1),
      age: integer(minimum: 0, default: 18)
    },
    required: [:name]
  }
end

# Build at compile-time for maximum performance
root = JSV.build!(MyApp.User)

# Casting to structs is enabled by default
%MyApp.User{name: "Alice", age: 18} = JSV.validate!(%{"name" => "Alice"}, root)

Pydantic style modules

use JSV.Schema

defschema MyApp.Data.Food,
          ~SD"""
          A Tasty dish, hopefully
          """,
          name: string(),
          origin: string()

defschema MyApp.Data.Profile,
          ~SD"""
          Information about a user profile
          """,
          name: string(),
          birthdate: optional(date()),
          favorite_food: MyApp.Data.Food

defschema MyApp.Data.User,
          ~SD"""
          System user information
          """,
          profile: MyApp.Data.Profile,
          role: string_enum_to_atom([:admin, :writer, :reader])

data = %{
  "profile" => %{
    "name" => "Alice",
    "birthdate" => "1994-01-08",
    "favorite_food" => %{
      "name" => "Pad Thai",
      "origin" => "Thailand"
    }
  },
  "role" => "admin"
}

root = JSV.build!(MyApp.Data.User, formats: true)
JSV.validate!(data, root, cast_formats: true)

With this simple module form you can define many struct schemas in a compact way. The code above will cast the data (and the birthdate as well):

%MyApp.Data.User{
  profile: %MyApp.Data.Profile{
    birthdate: ~D[1994-01-08],
    favorite_food: %MyApp.Data.Food{name: "Pad Thai", origin: "Thailand"},
    name: "Alice"
  },
  role: :admin
}

Contributing

Please ensure your changes include thorough tests and follow the existing documentation style.

License

JSV is released under the Apache License Version 2.0.