How to decode a JSON into a struct safely?

Thanks guys, works like a charm.
Am I doing it right?

In my data I have static stuff (here: hobbies and jobs which can be referenced in a list or as a single atom, this works). Also I have data (here person) that has an integer ID. These objects may

  1. reference each other (here friends)
  2. reference other objects (say we’d have a pets field that references pets by [Pet.id_t()]

I see how I could first load all pets and then check in a person-changeset-validation if the referenced pets exist. But I can’t do that with persons referencing other persons, because they may not be loaded yet. So I’d need a second run, right?

defmodule Person do
  use TypedEctoSchema

  @type id_t() :: non_neg_integer()

  @primary_key false
  typed_schema "person" do
    field(:id, :integer)
    field(:name, :string, null: false)
    field(:age, :integer) :: non_neg_integer()
    field(:job, EctoAtom) :: Job.id_t()
    field(:hobbies, {:array, EctoAtom}) :: [Hobby.id_t()]
    field(:friends, {:array, :integer}) :: [Person.id_t()]
  end
end
defmodule EctoAtom do
  use Ecto.Type

  def type, do: :atom

  def cast(data), do: {:ok, String.to_atom(data)}
  def load(data), do: {:ok, String.to_atom(data)}
  def dump(atom), do: {:ok, Atom.to_string(atom)}
end
defmodule Job do
  @type id_t() :: :job_mechanic | :job_doc | :job_programmer
end
defmodule Hobby do
  @type id_t() :: :hobby_painting | :hobby_freeclimbing | :hobby_stampcollecting
end
iex> data = %{id: 1, name: "Bob", age: "18", job: "job_programmer", friends: [2, 4711], hobbies: ["hobby_freeclimbing", "hobby_painting"]}
...
iex> p = 
...>  Ecto.Changeset.cast(%Person{}, data, Map.keys(data)) 
...>  |> Ecto.Changeset.apply_changes()  
%Person{
  __meta__: #Ecto.Schema.Metadata<:built, "person">,
  age: 18,
  friends: [2, 4711],
  hobbies: [:hobby_freeclimbing, :hobby_painting],
  id: 1,
  job: :job_programmer,
  name: "Bob"
}

EDIT: I created a behaviour for the atom-types and I like it. I think I’ll use this.