How to decode a JSON into a struct safely?

Conceptually, you can’t determine if a field like friends has valid values in it without a larger context than the single Person.

When persisting things to the database, the database acts as that context.

If you aren’t persisting the data, then that context is the whole group of Person structs being decoded.

That’s not exactly a “second run”, but it’s similar:

  • decode each Person
  • pass the whole list to a function that checks friends for internal consistency: refers to people that exist, is properly reflexive (if desired)

Re: EctoAtom - String.to_atom still makes people pretty worried. What about specific types for the various things:

defmodule Job do
  @type id_t() :: :job_mechanic | :job_doc | :job_programmer

  use Ecto.Type

  def type, do: :atom

  def cast("job_mechanic"), do: {:ok, :job_mechanic}
  def cast("job_doc"), do: {:ok, :job_doc}
  def cast("job_programmer"), do: {:ok, :job_programmer}
  def cast(_), do: :error

  def load("job_mechanic"), do: {:ok, :job_mechanic}
  def load("job_doc"), do: {:ok, :job_doc}
  def load("job_programmer"), do: {:ok, :job_programmer}
  def load(_), do: :error

  def dump(:job_mechanic), do: {:ok, "job_mechanic"}
  def dump(:job_doc), do: {:ok, "job_doc"}
  def dump(:job_programmer), do: {:ok, "job_programmer"}
  def dump(_}, do: :error
end

Then the fields are more specific about what they contain:

    field(:job, Job) :: Job.id_t()
    field(:hobbies, {:array, Hobby}) :: [Hobby.id_t()]