Create a struct from a map within Elixir

With Elixir, we can create a struct which is a data type similar to a map except it ensures only a given set of key / value pairs are present.

Converting from a struct to a map is fairly simple.

Map.from_struct(%MyStruct{})

But doing the reverse programmatically isn’t possible with the standard library. Personally, I feel its a good design decision because:

  • Structs can only use atoms as keys. Maps can use any data type including processes identifiers, references, functions, and other complex data types. Within an application its easy to say a string key “hello” is same as the a atom key :hello, but a language creator may not want to make the same assumptions about application internals.
  • If a map contains keys not present in the struct, the behavior of a converter is undefinable by a language creator. Should it ignore extra keys? Should it throw an error?

That said….

When designing an application its a good practice to handle data from outside sources as Maps, then immediately convert it to a Struct within your own code.

I wanted to convert a map to a given struct while:

  • Ignoring keys not found
  • Treating string keys within the map as if they were atoms. So %{“hello” => “world”} is treated the same as %{:hello => “world”}
  • Merging the valid keys into the struct, which preserves default values if a key isn’t present.

Here’s a code.

def struct_from_map(a_map, as: a_struct) do
# Find the keys within the map
keys = Map.keys(a_struct)
|> Enum.filter(fn x -> x != :__struct__ end)
  # Process map, checking for both string / atom keys
processed_map =
for key <- keys, into: %{} do
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
{key, value}
end
  a_struct = Map.merge(a_struct, processed_map)
a_struct
end

Given a struct defined as:

defmodule User do
defstruct [
username: ""
  ]
end

Convert it with:

struct_from_map(%{"username": "Me!"}, as: %User{})

Hopefully, someone finds this useful enough to use.

The above code is MIT Licenced.

— —

Edit: 3/30/2016.

For a more general solution, please check out the beautiful ExConstructor library. https://github.com/appcues/exconstructor