Currently I’m trying to use the Ecto changesets for validating a map, or actually HTTP response body as JSON decoded into a map.
According to the docs it is possible to use schemaless changesets in order to do the normalization/validation of this data. However, there is no mention or examples on how to use this when JSON objects are nested (or nested maps, after decoding the incoming JSON).
A trivial example would be this book
map
%{
"author": "B. Tate, S. DeBenedetto",
"title": "Programming Phoenix LiveView"
}
for which a setup like below works perfectly fine:
defmodule Book do
defstruct [:tile, :author]
@types %{title: :string, author: :string}
import Ecto.Changeset
def changeset(%__MODULE__{} = book, params) do
{book, @types}
|> cast(params, Map.keys(@types))
|> validate_required([:title, :author])
end
end
However, for the case of a nested structure, where a person
has a collection of books (library
), e.g.:
%{
name: "Sil van Brummen"
library: [
%{
author: "B. Tate, S. DeBenedetto",
title: "Programming Phoenix LiveView"
},
%{
author: "C. McCord, B. Tate and J. Valim",
title: "Programming Phoenix 1.4"
}
]
}
I’ve extended the code snippet to include a person
struct with a name
and library
(note the library
being of type {:array, :map}
).
defmodule Person do
defstruct [:name, :library]
@types %{name: :string, library: {:array, :map}}
import Ecto.Changeset
def changeset(%__MODULE__{} = person, params) do
{person, @types}
|> cast(params, Map.keys(@types))
|> validate_required([:name, :library])
end
end
defmodule Book do
defstruct [:tile, :author]
@types %{title: :string, author: :string}
import Ecto.Changeset
def changeset(%__MODULE__{} = book, params) do
{book, @types}
|> cast(params, Map.keys(@types))
|> validate_required([:title, :author])
end
end
Running below code in iex
with my setup does not validate the books, it only checks if the books are of type map.
Person.changeset(%Person{}, %{name: "Sil van Brummen", library: [%{title: "Programming Phoenix LiveView", author: "B. Tate, S. DeBenedetto"}, %{title: "Programming Phoenix 1.4", author: "C. McCord, B. Tate and J. Valim"}]})
Is it possible to define a nested schema structure where the library
is of type {:array, Book}
such that both the person
and all books
will be validated? And if it is possible, how would this nested schemaless setup look like?
For context; my ultimate goal is to be able to normalize/validate a nested map where the library
consists of various books, identified by type
and each book type has their own structure.
%{
name: "Sil van Brummen"
library: [
%{
type: "coding",
author: "B. Tate, S. DeBenedetto",
title: "Programming Phoenix LiveView",
language: "elixir"
},
%{
type: "coding",
author: "C. McCord, B. Tate and J. Valim",
title: "Programming Phoenix 1.4",
language: "elixir"
},
%{
type: "cooking",
author: "Marcella Hazan",
title: "The Essentials of Classic Italian Cooking",
kitchen: "Italian"
}
]
}
Cheers,
Sil