Smelter 🔥⚗️

JSON Schema to Elixir code generator. Extracts pure Elixir types from raw JSON Schema ore.

Generates Ecto.Schema modules with embedded_schema and changeset/2 for validation.

Features

Installation

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

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

Usage

Single Schema

# Parse and resolve a schema
{:ok, schema} = Smelter.parse("path/to/schema.json")

# Generate Elixir code
code = Smelter.generate(schema, module: "MyApp.Schemas.User")

# Or do both in one step
{:ok, code} = Smelter.compile("path/to/schema.json", module: "MyApp.Schemas.User")

Batch Generation

Smelter can process entire directories of JSON Schemas, preserving the folder structure:

Smelter.Batch.generate(
  schema_dir: "priv/schemas/2026-01-11",
  output_dir: "lib/my_app/schemas",
  module_prefix: "MyApp.Schemas"
)

This processes all .json files in the directory, including:

Generated Code

Given a JSON Schema like:

{
  "title": "User",
  "type": "object",
  "required": ["name", "email"],
  "properties": {
    "name": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "age": { "type": "integer", "minimum": 0 }
  }
}

Smelter generates:

defmodule MyApp.Schemas.User do
  @moduledoc """
  User
  """
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key false
  embedded_schema do
    field :age, :integer
    field :email, :string
    field :name, :string
  end

  def changeset(struct \\ %__MODULE__{}, params) do
    struct
    |> cast(params, [:age, :email, :name])
    |> validate_required([:email, :name])
  end
end

Schema Composition

Smelter handles JSON Schema composition keywords:

allOf

Properties from all schemas are merged together:

{
  "allOf": [
    { "$ref": "base.json" },
    { "properties": { "extra": { "type": "string" } } }
  ]
}

oneOf / anyOf

Generates union type modules that delegate to the appropriate variant:

{
  "oneOf": [
    { "$ref": "error.json" },
    { "$ref": "warning.json" }
  ]
}

Generated code uses a discriminator field (commonly type) to route to the correct schema module.

$defs

Entries in $defs are extracted and generated as separate modules. A schema like:

{
  "$defs": {
    "Address": {
      "type": "object",
      "properties": { "city": { "type": "string" } }
    }
  }
}

Generates MyApp.Schemas.ContainerName.Address module.

Real-World Usage: Bazaar

Bazaar uses Smelter to generate UCP schemas:

mix bazaar.gen.schemas priv/ucp_schemas/2026-01-11

This generates all Elixir modules in lib/bazaar/schemas/ from the official UCP JSON Schemas.

License

Apache-2.0