What is the way to convert %{\"foo\" => \"bar\"}
to %{foo: \"bar\"}
in Elixir?
There's a library for this, https://hex.pm/packages/morphix. It also has a recursive function for embedded keys.
Most of the work is done in this function:
defp atomog (map) do
atomkeys = fn({k, v}, acc) ->
Map.put_new(acc, atomize_binary(k), v)
end
Enum.reduce(map, %{}, atomkeys)
end
defp atomize_binary(value) do
if is_binary(value), do: String.to_atom(value), else: value
end
Which is called recursively. After reading @Galzer's answer I'll probably convert this to use String.to_existing_atom
soon.
To build on @emaillenin's answer, you can check to see if the keys are already atoms, to avoid the ArgumentError
that is raised by String.to_atom when it gets a key that is already an atom.
for {key, val} <- string_key_map, into: %{} do
cond do
is_atom(key) -> {key, val}
true -> {String.to_atom(key), val}
end
end
You can use a combination of Enum.reduce/3 and String.to_atom/1
%{"foo" => "bar"}
|> Enum.reduce(%{}, fn ({key, val}, acc) -> Map.put(acc, String.to_atom(key), val) end)
However you should be wary of converting to atoms based in user input as they will not be garbage collected which can lead to a memory leak. See this issue.
You can use String.to_existing_atom/1 to prevent this if the atom already exists.
Snippet below converts keys of nested json-like map to existing atoms:
iex(2)> keys_to_atoms(%{"a" => %{"b" => [%{"c" => "d"}]}})
%{a: %{b: [%{c: "d"}]}}
def keys_to_atoms(json) when is_map(json) do
Map.new(json, &reduce_keys_to_atoms/1)
end
def reduce_keys_to_atoms({key, val}) when is_map(val), do: {String.to_existing_atom(key), keys_to_atoms(val)}
def reduce_keys_to_atoms({key, val}) when is_list(val), do: {String.to_existing_atom(key), Enum.map(val, &keys_to_atoms(&1))}
def reduce_keys_to_atoms({key, val}), do: {String.to_existing_atom(key), val}
defmodule Service.MiscScripts do
@doc """
Changes String Map to Map of Atoms e.g. %{"c"=> "d", "x" => %{"yy" => "zz"}} to
%{c: "d", x: %{yy: "zz"}}, i.e changes even the nested maps.
"""
def convert_to_atom_map(map), do: to_atom_map(map)
defp to_atom_map(map) when is_map(map), do: Map.new(map, fn {k,v} -> {String.to_atom(k),to_atom_map(v)} end)
defp to_atom_map(v), do: v
end
m = %{"key" => "value", "another_key" => "another_value"}
k = Map.keys(m)|> Enum.map(&(String.to_atom(&1)))
v = Map.values(m)
result = Enum.zip(k, v) |> Enum.into(%{})