Transform list into nested map



I have a list of key value maps like so:

%{k: "vacancy.state.draft", v: "concept"},
%{k: "vacancy.state.expired", v: "gone"},
%{k: "", v: "ready"},
%{k: "", v: "some name"},
%{k: "vacancy.title", v: "jadada"}

Now I’d like transform this into a map, that should look like this:

  vacancy: %{
    state: %{
      draft: "concept",
      expired: "gone",
      online: "ready"
    name: "some name",
    title: "jadada"

I can use String.split to get an array of keys, but looking them up and creating them when not present in the result map is a bit hard for me to wrap my head around.

I ttried using but I;m stuck, being very new to Elixir and all


Here’s my take with plain recursion:

defmodule Converter do
  def to_map(input) do
    Enum.reduce(input, %{},
      fn(%{k: key, v: value}, intermediate_map) ->
        merge(intermediate_map, String.split(key, "."), value)

  defp merge(map, [leaf], value), do: Map.put(map, leaf, value)
  defp merge(map, [node | remaining_keys], value) do
    inner_map = merge(Map.get(map, node, %{}), remaining_keys, value)
    Map.put(map, node, inner_map)

Testing it:

  %{k: "vacancy.state.draft", v: "concept"},
  %{k: "vacancy.state.expired", v: "gone"},
  %{k: "", v: "ready"},
  %{k: "", v: "some name"},
  %{k: "vacancy.title", v: "jadada"}
|> Converter.to_map
|> IO.inspect


%{"vacancy" => %{"name" => "some name",
    "state" => %{"draft" => "concept", "expired" => "gone",
      "online" => "ready"}, "title" => "jadada"}}

Perhaps it can be done with update_in and friends, but I didn’t try it :slight_smile:

Convert map with nested string keys to nested map

This works, thanks Saša!

So Elixir’s pattern matching can enforce a list with a certain number of elements? 1 in this case?

That’s very cool :slight_smile:


Yeah, that matches a one-element list. Here are some list patterns I use frequently:

[]     # empty list
[_]    # one-element list
[_|_]  # non-empty list


There is also some sugar for lists of a minimum length: [_, _, _|_]. A list matching this has at least 3 elements. It is equivalent to matching on [_|[_|[_|_]]] but quite a lot more readable.


Here’s a solution that uses Access.key/2 and put_in/3 inside a single Enum.reduce/3:

def to_nested_map(list) do
  Enum.reduce list, %{}, fn %{k: key, v: value}, acc ->
    key_path = key |> String.split(".") |>, %{}))
    put_in(acc, key_path, value)

The trick to making it work is to use Access.key/2 for each element of the key path, passing %{} as the 2nd arg to Access.key/2 so it is used as the default.