JSON decode to one of possible Structs

How would you idiomatically call each of A module’s functions until you get a first {:ok, ...} return?

defmodule A do
  def a(), do: {:error, 1}
  def b(), do: {:ok, 2}
  # ...
end

Is this the best one can do?

case {A.a, A.b} do
  {{:ok, a}, _} -> a
  {_, {:ok, b}} -> b
  _ -> {:error, ""}
end

To avoid the XY-problem, I’m Jason.decodeing an incoming JSON and then, in parse/1 below, trying to build one of predefined structs from it - StructN presents one of N possible events.

defmodule StructN do
  embedded_schema do
    field :uuid, :string
    # ...
  end
  # def changeset ...
  def new(params) do
    %__MODULE__{}
    |> changeset(params)
    |> apply_action(:build)
  end
end

def parse(input) do
  with {:ok, json} <- Jason.decode(input) do
    case {StructA.new(json), StructB.new(json), ...} do
      {{:ok, a}, _, ...} -> a
      {_, {:ok, b}, ...} -> b
      _ -> {:error, ""}
    end
  end
end

Could anything be improved with this approach?

Enum.reduce_while with list of functions.

1 Like

or Enum.find, with a list of modules :slight_smile:

You should consider using parse, and parse!

Thank you both. Let me know if this can be improved further … say, the ad hoc nil as a starting (and unused) “accumulator”.

def parse(input) do
  with {:ok, json} <- Jason.decode(input) do
      Enum.reduce_while([&StructA.new/1, &StructB.new/1, ...], nil, fn new, _acc ->
        case new.(json) do
          {:ok, event} -> {:halt, event}
          _ -> {:cont, "Unknown event"}
        end
      end)
  end
end

If you have a field with the type of the event coming you may select the struct’s module in advance and decode only into it. Something like:

module = 
  case map["event_type"] do
    "struct_a_event" -> StructA
    "struct_b_event" -> StructB
    ...
  end

case module.new(json) do
  ...
end