Hello, sorry to be spammy but I love working on JSV, my JSON Schema Validator library.
This is a small release with three important things:
Module-based schemas now need to export a json_schema/0 function instead of a schema/0 function. This makes more sense since we are starting to export json_schemas from other modules like Ecto schema modules. A generic schema/0 function is confusing in that case. Codebases using the old callback will still work, but a warning will be emitted.
Example:
defmodule ItemModule do
def json_schema do
%{type: :string}
end
end
schema = %{type: :array, items: ItemModule}
data = ["foo", "bar"]
Modules using JSV.defschema/1 will automatically export the new function as well as the old one, with a deprecation warning.
That defschema macro now supports passing the properties as a list directly.
So instead of this:
defmodule MyApp.UserSchema do
use JSV.Schema
defschema %{
type: :object,
properties: %{
name: %{type: :string},
age: %{type: :integer, default: 0}
}
}
end
You can do this:
defmodule MyApp.UserSchema do
use JSV.Schema
defschema name: %{type: :string},
age: %{type: :integer, default: 0}
end
And because use JSV.Schema imports the new schema definition helpers, it can be as short as this:
defmodule MyApp.UserSchema do
use JSV.Schema
defschema name: string(),
age: integer(default: 0)
end
Finally, I’ve added the defschema/3 macro that works like defschema/1 but also defines a module:
defmodule MyApp.Schemas do
use JSV.Schema
defschema User,
name: string(),
age: integer(default: 0)
defschema Admin,
"""
With a schema description and @moduledoc
""",
user: User,
privileges: array_of(string())
end
I think it’s nice to have this when defining some responses schemas directly in controllers or message queue consumers, à la Pydantic.
And that’s it Thanks for reading!
[0.10.0] - 2025-07-10
Features
Define and expect schema modules to export json_schema/0 instead of schema/0
Allow to call defschema with a list of properties
Added the defschema/3 macro to define schemas as submodules
Bug Fixes
Ensure defschema with keyword syntax supports module-based properties
Looking at you files it appears those are LLM generated right? I think it did a pretty good job (though it should have stopped at some point in that test guide, at the end it’s making things up). What did you use?
I tried to use Exdantic with JSV but we have an incompatibility. JSV expects raw data with binary keys while Exdantic seem to expect atom keys only:
Mix.install([
{:jsv, "~> 0.10"},
{:exdantic, "~> 0.0.2"}
])
defmodule UserSchema do
use Exdantic, define_struct: true
schema "User account information" do
field :name, :string do
required()
min_length(2)
description("User's full name")
end
field :email, :string do
required()
format(~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
description("Primary email address")
end
field :age, :integer do
optional()
gt(0)
lt(150)
description("User's age in years")
end
field :active, :boolean do
default(true)
description("Whether the account is active")
end
# Cross-field validation
model_validator(:validate_adult_email)
# Computed field derived from other fields
computed_field(:display_name, :string, :generate_display_name)
config do
title("User Schema")
strict(true)
end
end
def validate_adult_email(input) do
if input.age && input.age >= 18 && String.contains?(input.email, "example.com") do
{:error, "Adult users cannot use example.com emails"}
else
{:ok, input}
end
end
def generate_display_name(input) do
display =
if input.age do
"#{input.name} (#{input.age})"
else
input.name
end
{:ok, display}
end
use JSV.Schema
def json_schema do
JSV.Schema.with_cast([__MODULE__, :from_jsv])
end
defcast from_jsv(data) do
validate(data)
end
def format_error("from_jsv", [exdantic_error | _], _) do
Exdantic.Error.format(exdantic_error)
end
end
root = JSV.build!(UserSchema) |> dbg()
data = %{"name" => "alice", "email" => "foo@bar.com"}
JSV.validate!(data, root)
I just used Claude Code. Yeah, Claude likes being “helpful”, whether hallucinating ideas or running git reset --hard after you ask it to not lose uncommitted work! (!!)
Thanks for looking at that integration doc with JSV. It was just brainstorms, really. Will look when time allows.