Poison decode nested objects

Hi…

I am using Poison.decode to decode my JSON to structs and I am hitting a snag when it comes to decoding JSON data with 2nd level named keys… I have seen that the Poison examples here that shows nested decoding, but that only works if my second level is an JSON array, not an object with named keys…

Here is some example JSON based on what I am trying to do. My application JSON is more complex, so this is the smallest example I could think of to highlight what I am trying to do.

JSON:

{
	"name":"nathan"
	"states":{
		"first_state":{
			"arbitrary_property":10
		},
		"second_state":{
			"arbitrary_property":20
		}
	}
}

Top level module:

defmodule TopLevel

defstruct [name: "", states: %{}]

end

Second level module:

defmodule State 

	defstruct [:arbitrary_property]

end

I have used this line of code to successfully decode the top level to my struct:

{:ok,data} = Poison.decode(encoded_json, as: %TopLevel{})

But with this, the TopLevel.states is a map. But each of the items in the Map are only Maps too… I want them to be %State{}… It makes sense that they are only Maps, as I have only told the decode function about %TopLevel{}… but whats the syntax (if there is one) that allows me to tell Posion to decode each state as %State{}.

As I mentioned earlier, I have seen the post where the Posion author gives an example of using collections (he means Lists right?), but I want to use a Map so that I can easily retrieve a state from the states map when my application is running.

I don’t believe there’s an option in Poison to decode into arbitrary "first_state", "second_state", etc. keys like you want (rather, you have to tell it the type of each key, which is either a struct or list of structs).

In any case, it’s trivial to do the transformation yourself. Here’s one way to do it:

json
|> Poison.decode!(as: %TopLevel{})
|> Map.update!(:states, fn states ->
  for {state, props} <- states, into: %{} do
    props = for {k, v} <- props, into: %{}, do: {String.to_atom(k), v}
    {state, struct(State, props)}
  end
end)

%TopLevel{
  name: "nathan",
  states: %{
    "first_state" => %State{arbitrary_property: 10},
    "second_state" => %State{arbitrary_property: 20}
  }
}

Thanks… I was leaning towards some form of manual manipulation… but had no idea how I would do that.

I did think that I could also just take the List approach and use Enum.find to get the element that I am after, but I figure thats more inefficient than being able to pull it from the map via the key_name. Or is that a negligable concern?

Also, should I be wary of String.to_atom as I have come across a number of articles talking about atoms and garbage collection, but still don’t know enough to be sure of when to use it or not.

Thanks for pushing me in the right direction.

one reason I potentially thinking that I should avoid the String.to_atom is that my final application my JSON will be user generated and the names of the states will be defined via another web application (think level editor).

For example, in my editor, they may have entered a name of “First state” into the editor (a friendly name that they know of) which is then converted to something more programming language friendly (first_state)…

You have to cast the "arbitrary_property" key to an atom, in order to create a struct from it (that’s what the above code does). You could use String.to_existing_atom/1 instead, and discard any keys that raise an error.

I see - thanks…