<p align="center">
<img src="https://raw.githubusercontent.com/pauldemarco/auto_struct/main/assets/logo.png" alt="AutoStruct logo" width="420">
</p>
# AutoStruct
Generated structs for Elixir from JSON Schema.
AutoStruct is a thin code generation layer over [Exonerate](https://hexdocs.pm/exonerate/).
Exonerate validates JSON-shaped Elixir terms generated from [JSON Schema](https://json-schema.org/);
AutoStruct generates structs, conversion helpers, and encoder implementations around that validator.
## Installation
The package can be installed by adding `auto_struct` to your list of
dependencies in `mix.exs`:
```elixir
def deps do
[
{:auto_struct, "~> 0.3.0"}
]
end
```
Documentation is available on [HexDocs](https://hexdocs.pm/auto_struct).
## Usage
```elixir
defmodule Person do
use AutoStruct.JsonSchema,
schema: """
{
"$id": "https://example.com/person.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Person",
"type": "object",
"properties": {
"first_name": {
"type": "string",
"description": "The person's first name."
},
"age": {
"description": "Age in years which must be equal to or greater than zero.",
"type": "integer",
"minimum": 0
},
"happy": {
"type": "boolean",
"default": true,
"description": "Whether the person is happy."
},
"attrs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional array of attributes.",
"nullable": true
}
},
"required": ["first_name"]
}
"""
end
# `new/1` returns {:ok, value} | {:error, reason}
iex> Person.new(first_name: "George", age: 31)
{:ok, %Person{first_name: "George", age: 31, happy: true, attrs: nil}}
# `new!/1` raises on error
iex> Person.new!(first_name: "George", age: 31, attrs: %{phone: 123})
** Some Error Message from the underlying validation library
# `from_json/1` returns {:ok, value} | {:error, reason}
iex> Person.from_json(%{"first_name" => "George", "age" => 31})
{:ok, %Person{first_name: "George", age: 31, happy: true, attrs: nil}}
iex> Person.from_json(%{"first_name" => "George", "age" => 31, "attrs" => %{"phone" => 123}})
** Some Error Message from the underlying validation library
# Generated structs implement Elixir's built-in JSON.Encoder.
iex> JSON.encode!(Person.new!(first_name: "George"))
"{\"age\":null,\"attrs\":null,\"first_name\":\"George\",\"happy\":true}"
# If you would like to build the struct directly, and avoid the internal cast to json map
# and back again, you can skip validation but still enforce the keys:
iex> %Person{first_name: "Hello"}
```
## Examples
Runnable examples are available in the `examples/` directory:
```bash
mix run examples/inline_schema.exs
mix run examples/file_schema.exs
```
## Mix Tasks
### mix auto_struct.gen.modules
Generate the Module stubs from a folder of schema files.
```
mix auto_struct.gen.modules <path_to_schemas> <output_path_modules>
```
## How it works
Under the hood the `use AutoStruct.JsonSchema` macro uses Exonerate to generate the validation function needed
by `new/1`, `from_json/1`, and `validate/1`.
AutoStruct creates a `defstruct` with an atom key for every top-level property in the JSON Schema.
Property names are preserved as-is. For properties marked as `required`, AutoStruct also includes
them in `@enforce_keys`.
`new/1` and `validate/1` recursively normalize structs, maps, and lists into JSON-shaped terms before
calling Exonerate. Nested maps and arrays are validated by Exonerate, but `from_json/1` only casts the
top-level object into a struct; nested objects remain JSON-shaped maps unless you transform them yourself.
Generated modules expose schema metadata:
```elixir
Person.__schema__(:json)
Person.__schema__(:fields)
Person.__schema__(:required)
```
Elixir's built-in `JSON.Encoder` is the primary encoder. When Jason is available, AutoStruct also emits
a compatible `Jason.Encoder` implementation.
## Limitations
Nested JSON Schema objects and arrays are validated by Exonerate, but AutoStruct only casts the top-level
schema object into a struct. Nested objects returned from `from_json/1` remain string-keyed maps unless you
transform them yourself.
AutoStruct currently generates fields from top-level schema properties. Advanced JSON Schema composition
features are delegated to Exonerate for validation and are not expanded into additional struct fields.