Search code examples
postgresqlelixirectojsonb

ecto jsonb array and map cast issue


I want to store data using jsonb that could be either an array or a map. The source data arrives as either an array or a map, and we want to unify this on the server so the front-end can work with a single type. Is there a way/workaround that I can use, store and retrieve both json/map and array types which are valid for the jsonb postgres type?

I have tried to use source: to copy to array type sourced field before calling cast pipeline if I get an array in params:

condition: Map.has_key?(params, "value") && is_list(value)

I am successfully able to save data, but when retrieving from the DB I am getting a cast error

field(:value, :map)
field(:value_array, {:array, :integer}, source: :value)

Note: Ecto version > 3 and postgres 10


Solution

  • Solution was to define custom ecto types which would accept and load both arrays and maps

    defmodule HaiData.Jsonb do
      @behaviour Ecto.Type
      def type, do: :map
    
      # Provide custom casting rules.
      def cast(data) when is_list(data) or is_map(data) do
        {:ok, data}
      end
    
    
      # Everything else is a failure though
      def cast(_), do: :error
    
      # When loading data from the database, we are guaranteed to
      # receive a map or list
      def load(data) when is_list(data) or is_map(data) do
        {:ok, data}
      end
    
      # When dumping data to the database, we *expect* a map or list
      # so we need to guard against them.
      def dump(data)  when is_list(data) or is_map(data), do: {:ok, data}
      def dump(_), do: :error
    end
    

    Credit goes to idiot - an elixirforum user