Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Unified schema definition, validation, and JSON generation for Elixir

License

Notifications You must be signed in to change notification settings

nshkrdotcom/sinter

Repository files navigation

Sinter

Unified schema definition, validation, and JSON Schema for Elixir

Sinter is a runtime-first schema library for JSON-shaped data. Schemas are defined once and used for validation, coercion, and JSON Schema generation. By default, schema fields are string-keyed to avoid atom leaks and to match JSON wire formats.

Highlights

  • String-keyed schema fields by default (safe for untrusted input)
  • Nested object schemas with Schema.object/1 and {:object, ...}
  • Dual JSON Schema drafts (2020-12 default, Draft 7 for providers)
  • JSON encode/decode helpers with aliasing and omit/nil handling
  • JSV-backed JSON Schema validation

Installation

Add sinter to your list of dependencies in mix.exs:

def deps do
[
{:sinter, "~> 0.1.0"}
]
end

Quick Start

schema = Sinter.Schema.define([
{:name, :string, [required: true, min_length: 2]},
{:age, :integer, [optional: true, gteq: 0]},
{:profile,
{:object,
[
{:nickname, :string, [optional: true]},
{:joined_at, :datetime, [optional: true]}
]}, [optional: true]}
], strict: true)

{:ok, validated} =
Sinter.Validator.validate(schema, %{
"name" => "Ada",
"age" => "36",
"profile" => %{"joined_at" => "2024-01-01T12:00:00Z"}
}, coerce: true)

validated["name"]
# => "Ada"

Schema Definition

Sinter accepts atom or string field names but stores them internally as strings.

# Runtime schema definition
schema = Sinter.Schema.define([
{:title, :string, [required: true]},
{:tags, {:array, :string}, [optional: true, min_items: 1]}
])

# Compile-time schema definition (same engine under the hood)
defmodule PostSchema do
use Sinter.Schema

use_schema do
option :title, "Post"
option :strict, true

field :title, :string, required: true
field :tags, {:array, :string}, optional: true, min_items: 1
end
end

Nested Objects

Use Schema.object/1 (or {:object, field_specs}) to model structured data.

address = Sinter.Schema.object([
{:street, :string, [required: true]},
{:zip, :string, [required: true]}
])

schema = Sinter.Schema.define([
{:name, :string, [required: true]},
{:address, address, [required: true]}
])

Validation

{:ok, data} = Sinter.Validator.validate(schema, %{
"name" => "Ada",
"address" => %{"street" => "Main", "zip" => "12345"}
})

JSON Encode/Decode Helpers

Sinter.JSON combines the transform pipeline with JSON encoding/decoding.

payload = %{
name: "Ada",
profile: %{
nickname: Sinter.NotGiven.omit(),
joined_at: ~N[2024-01-01 12:00:00]
}
}

{:ok, json} = Sinter.JSON.encode(payload, formats: %{joined_at: :iso8601})
{:ok, decoded} = Sinter.JSON.decode(json, schema, coerce: true)

# Aliases are applied for outbound payloads
{:ok, json} =
Sinter.JSON.encode(payload,
aliases: %{name: "full_name"},
formats: %{joined_at: :iso8601}
)

JSON Schema Generation

json_schema = Sinter.JsonSchema.generate(schema)
# Draft 2020-12 by default

openai_schema = Sinter.JsonSchema.for_provider(schema, :openai)
# Draft 7 + recursive strictness for provider expectations

json_schema = Sinter.JsonSchema.generate(schema, draft: :draft7)
:ok = Sinter.JsonSchema.validate_schema(json_schema)

Convenience Helpers

{:ok, 42} = Sinter.validate_type(:integer, "42", coerce: true)
{:ok, "user@example.com"} =
Sinter.validate_value(:email, :string, "user@example.com", format: ~r/@/)

{:ok, values} =
Sinter.validate_many([
{:string, "hello"},
{:integer, 42},
{:email, :string, "test@example.com", [format: ~r/@/]}
])

Dynamic Schema Creation

examples = [
%{"name" => "Alice", "age" => 30},
%{"name" => "Bob", "age" => 25}
]

schema = Sinter.infer_schema(examples)

input_schema = Sinter.Schema.define([{:query, :string, [required: true]}])
output_schema = Sinter.Schema.define([{:answer, :string, [required: true]}])
program_schema = Sinter.merge_schemas([input_schema, output_schema])

Examples

Run everything at once:

examples/run_all.sh

Or run individual scripts from examples/:

  • basic_usage.exs
  • readme_comprehensive.exs
  • json_schema_generation.exs
  • advanced_validation.exs
  • dspy_integration.exs

License

MIT

Packages

Contributors

Languages