Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert a map into a keyword list in Elixir

Tags:

elixir

I have a map of the form:

%{"browser_name" => "Chrome", "platform" => "linux"}

and I need to convert it to a keyword list:

[browser_name: "Chrome", platform: "linux"]

What's the best way of achieving this?

like image 234
cjm2671 Avatar asked Feb 10 '19 12:02

cjm2671


4 Answers

Wouldn't this work:

 def convert_to_klist(map) do
   Enum.map(map, fn({key, value}) -> {String.to_existing_atom(key), value} end)
 end
like image 54
NiteRain Avatar answered Nov 12 '22 07:11

NiteRain


I would put it here for the sake of future readers.

While one surely might blindly call String.to_atom/1 here and there, that approach is strongly discouraged due to its vulnerability to atom DoS attacks.

Here is the excerpt from Erlang docs:

Atoms are not garbage-collected. Once an atom is created, it is never removed. The emulator terminates if the limit for the number of atoms (1,048,576 by default) is reached.

Therefore, converting arbitrary input strings to atoms can be dangerous in a system that runs continuously. If only certain well-defined atoms are allowed as input, list_to_existing_atom/1 can be used to guard against a denial-of-service attack. (All atoms that are allowed must have been created earlier, for example, by simply using all of them in a module and loading that module.)

— http://erlang.org/doc/efficiency_guide/commoncaveats.html#list_to_atom-1

That said, whether you receive the map from the external untrusted source (e. g. it’s the parameters in the call to your API or something,) you must not use String.to_atom/1 and should use String.to_existing_atom/1 instead.

Otherwise, the intruder with a simple random generator for keys would blow up your ErlangVM without any issue.

like image 36
Aleksei Matiushkin Avatar answered Nov 12 '22 07:11

Aleksei Matiushkin


You can use the Keyword.new function and pass in a transform function as the second argument.

Keyword.new(%{"browser_name" => "Chrome", "platform" => "linux"}, fn {k,v} -> {String.to_existing_atom(k), v} end)
> [browser_name: "Chrome", platform: "linux"]

Although this is basically equivalent to the accepted answer from @NiteRain, I think it's a bit more explicit and shows future readers of your code your intentions.

See: https://hexdocs.pm/elixir/Keyword.html#new/2

like image 8
Eric Goodwin Avatar answered Nov 12 '22 07:11

Eric Goodwin


For future reference. You can also use Enum.into/3

%{"browser_name" => "Chrome", "platform" => "linux"}
|> Enum.into([], fn {k, v} -> {String.to_existing_atom(k), v} end)

or in its shorter version:

%{"browser_name" => "Chrome", "platform" => "linux"}
|> Enum.into([], &{String.to_existing_atom(elem(&1,0)), elem(&1,1)})
like image 4
Euen Avatar answered Nov 12 '22 07:11

Euen