Get a map instead of a list from embeds_many

I’m building an Ecto schema to cast a JSON into a struct.

The data looks like this:

%{
  name: "Bob", 
  ...
  body_parts: [
    %{id: "head", count: 1},
    %{id: "hands", count: 2},
  ]
}

I’d like to have the body parts in a map (indexed by their ID) like:

%Person{
  body_parts: %{
    "head" => %BodyPart{count: 1, id: "head"},
    "hands" => %BodyPart{count: 2, id: "hands"}
  },
  ...
  name: "Bob"
}

but embeds_many(:body_parts, BodyPart) (obviously) gives me a list.
Is there a clean way to do that? I could just post-process the Person but my schema would be lying then.
If that helps I could change the input to a map.

defmodule BodyPart do
  use Ecto.Schema

  @primary_key false
  embedded_schema do
    field(:id, :string)
    field(:count, :integer)
  end

  def changeset(body_part, params) do
    Ecto.Changeset.cast(body_part, params, [:id, :count])
  end
end

defmodule Person do
  use Ecto.Schema

  @primary_key false
  embedded_schema do
    field(:id, :integer)
    ...
    embeds_many(:body_parts, BodyPart)
  end

  def changeset(params) do
    %Person{}
    |> Ecto.Changeset.cast(params, [:id, :name, :age, :hobbies, :friends])
    |> Ecto.Changeset.cast_embed(:body_parts)
  end
end

This got me curious: why do you need this connection as a map? Why is the normal embedding not good enough?

I’m not using a database but use Ecto.Schema to load JSON into Elixir Structs.
The IDs are used by other objects to reference objects in another collection. So I need the collections indexed by ID.

Maybe use embeds_one and do your transformation in the changeset function.

You’ll probably have to create a custom ecto type for this as the keys look dynamic.

What I currently do is use embeds_many and transform the list to a map (pull out the ID).
The only problem is, that the schema does not reflect reality now. Don’t see how this would get better by using embeds_one.

I thought about that, but after implementing Ecto.Enum because I didn’t understand it I’d rather ask if there is a builtin way. I’m not sure if its worth the effort for just to have a clean schema. Actually I’m not sure if I shouldnt give up the DB-free path and just use Ecto and postgres.

The transformation of the list to map indicates a core domain model requirement, where the map has a meaning for indexing. And from the JSON’s model perspective represented by schema, it’s ok to have a list. So, It’s an option to have two models and make a transformation between them on the controller level.