Ecto: Serialising a jsonb array column into a list of possible types

I have a jsonb array column in a (postgres) db. It is a list that can be only a limited number of maps, is there a way to represent this in Ecto?

I’ve tried implementing a custom Ecto Type, with cast functions that correctly figure out which struct to serialize / deserialize :

  schema "table_name" do
    embeds_many(:things, DB.Things)

    timestamps()
  end

Then in a custom type:

  def cast(thing) do
    case thing.type do
      :foo -> {:ok, Foo.new(thing)}
      _ -> :error
    end
  end

  def load(data) when is_map(data) do
    data =
      for {key, val} <- data do
        {String.to_existing_atom(key), val}
      end

    {:ok, struct!(Foo, data)}
  end

  def dump(thing = %Foo{}), do: {:ok, Map.from_struct(thing)}
  def dump(_), do: :error

But this doesn’t work as the last argument to the embeds_many needs to be an Ecto schema.

I know I can use embeds_many and give a schema for an array of ONE type of thing, but I want to be able to say “This can be an array of any of these types of things” and have those things be embeded schemas. An embeds_one_of

2 Likes

Can you make the thing into a wrapper struct like %Thing{inner: %Foo{}}?

  schema "table_name" do
    embeds_many(:things, Thing)

    timestamps()
  end
  def cast(%Think{inner: inner}) do
    inner
  end

  def load(data) when is_map(data) do
    data =
      for {key, val} <- data do
        {String.to_existing_atom(key), val}
      end

    {:ok, %Thing{inner: struct!(Foo, data)}}
  end

  def dump(%Think{inner: %Foo{} = foo} ), do: {:ok, Map.from_struct(foo)}
  def dump(_), do: :error

or something like that.

2 Likes