Decoding Json with nested lists of dissimilar structs

Is there a built-in way to decode json that has a list of objects that are not decoded to the same struct, and the list is not at the root of the json?
Its a large, deeply nested json payload from a 3rd party, and I would prefer not to recursively decode and replace each list, but thats the only solution I have so far. Any suggestions would be appreciated.

Poison can do this if the list is the root,

defmodule TypeA do
  defstruct shared: "", only_a: ""
end

defmodule TypeB do
  defstruct shared: "", only_b: ""
end

options = fn
  %{only_a: _a} -> %TypeA{}
  %{only_b: _b} -> %TypeB{}
end

"""
[
  {
    "shared": "value",
    "only_a": "A!"
  }, 
  {
    "shared": "value",
    "only_b": "B!"
  }
]
"""
|> Poison.decode!([keys: :atoms!, as: [options]])
[
  %TypeA{shared: "value", only_a: "A!"}, 
  %TypeB{shared: "value", only_b: "B!"}
]

but if there is a parent struct, the children objects just decode to plain maps.

defmodule Parent do
  defstruct children: []
end

"""
{ 
  "children": [
    {
      "shared": "value",
      "only_a": "A!"
    }, 
    {
      "shared": "value",
      "only_b": "B!"
    }
  ]
}
"""
|> Poison.decode!( as: %Parent{})
%Parent{
  children: [
    %{"only_a" => "A!", "shared" => "value"}, 
    %{"only_b" => "B!", "shared" => "value"}
  ]
}

I’ve tried adding the as: function to the %Parent{} struct definition, but its not a valid value.

defmodule Parent do
  children = fn
    %{only_a: _a} -> %TypeA{}
    %{only_b: _b} -> %TypeB{}
  end

  defstruct children: children # <- invalid value
end

Another option would be a custom Poison’s encoder implementation for structs like Parent.

I would nevertheless stick with straight parsing into maps and then upgrading them to structs with update_in/3 through Access. That’d be more explicit. Like

%{root: %{lvl1: [%{only_a: :a}, %{only_b: :b}]}}
|> update_in([:root, :lvl1, Access.all()], options)

%{
  root: %{
    lvl1: [%TypeA{shared: "", only_a: ""}, %TypeB{shared: "", only_b: ""}]
  }
}```
1 Like