I’m happy to announce Spectral, a library that lets your Elixir structs and @type specs become the single source of truth for validation, encoding/decoding (primarily JSON), and OpenAPI schema generation. If you’re familiar with Pydantic in the Python world, the idea is similar.
Who is this for?
Spectral is aimed at developers building and consuming JSON who want to avoid keeping multiple representations of the same information in sync — a type definition here, validation logic there, a JSON schema somewhere else. If your types already express the shape of your data, Spectral lets them do more of the work.
Example
defmodule Person do
defstruct [:name, :age, :role]
@type role :: :user | :admin
@type t :: %Person{
name: String.t(),
age: non_neg_integer(),
role: role()
}
@spec from_json(binary()) :: {:ok, t()} | {:error, [Spectral.Error.t()]}
def from_json(json), do: Spectral.decode(json, __MODULE__, :t, :json)
@spec to_json(t()) :: {:ok, iodata()} | {:error, [Spectral.Error.t()]}
def to_json(person), do: Spectral.encode(person, __MODULE__, :t, :json)
end
{:ok, person} = Person.from_json(~s({"name": "Alice", "age": 30, "role": "admin"}))
#=> {:ok, %Person{name: "Alice", age: 30, role: :admin}}
Person.to_json(person)
#=> {:ok, ...}
Person.from_json(~s({"name": "Alice", "age": -1, "role": "admin"}))
#=> {:error, [%Spectral.Error{location: ["age"], type: :type_mismatch, ...}]}
# Generate OpenAPI schema
Spectral.schema(Person, :t)
Hex: spectral | Hex
Docs: Spectral v0.9.2 — Documentation






















