Introduction
When building an Elixir-powered application, you may need to get some data from the outside and create a proper Elixir Struct from it. Structs are more convenient1 than maps2. If your application grows bigger than a couple of .ex
files, you might consider using Structs as they provide a guarantee that only defined fields will be allowed in a Struct. In other words, it's easier to mess up key-value usage in a map, than in a Struct.
Problem
When using the Poison library to parse JSON, you get a map, which looks like this: my_map = %{"my_key" => "my_value"}
.
If you want to convert that map to a Struct, this will NOT work:
1 2 3 |
my_struct = %MyStruct{} my_struct["my_key"] = my_map["my_key"] |
You will get a ...does not implement the Access behaviour
error. Structs in Elixir do not allow the Access behaviour3.
Solutions: options
Option 1
Use String.to_existing_atom/1
and Kernel.struct/2
to convert each key in the map. This requires writing some code:
1 2 3 4 5 6 7 8 |
user_with_atom_keys = for {key, val} <- user, into: %{} do {String.to_existing_atom(key), val} end user_struct = struct(UserInfo, user_with_atom_keys) # %UserInfo{basic_auth: "Basic Ym1hOmphYnJhMTc=", firstname: "foo", lastname: "boo"} |
Code snippet author: Patrick Oscity4.
Option 2
Use the as: [%MyStruct{}]
option to Poison.decode!
5 as recommended by David Tang6. This option does not require any additional coding. However, it also requires some naming discipline: all the fields of your Struct shall exactly match the fields in the incoming JSON. This will give you an error if the JSON has myKey
and your Struct has my_key
.
Option 3.
If you get more than one JSON object coming in as input, you can use the %{keys: :atoms!}
option to Poison.Parser.parse!
. With this you will get all JSON parsed into respective Structs. This requires the same naming discipline as Option 2.
A problem with Options 2 and 3
Poison
does not respect7 the @enforce_keys
attribute of Elixir's defstruct
. That is, it will not check for you if the incoming JSON contains all keys that you need in your Struct.
Option 4.
Use Option 1 with @enforce_keys
enabled in your Structs. This will guarantee that JSON data with missing keys will not end up in your Structs.
Not an option:
The ExConstructor library8 seems to be abandonded, I don't recommend using it.
4: Elixir - Convert maps to struct - Stackoverflow ↩
6: Decoding JSON into Elixir Structs ↩
7: Elixir Trickery: Cheating on Structs, And Why It Pays Off ↩