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?
Wouldn't this work:
def convert_to_klist(map) do
Enum.map(map, fn({key, value}) -> {String.to_existing_atom(key), value} end)
end
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.
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
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)})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With