A small utility to convert Elixir maps with mixed string/atom keys to atom-only keyed maps. Optionally with a safe option, to prevent atom space exhaustion of the Erlang VM. Since v0.8 it also supports conversion of keys from CamelCase to under_score format.
AtomicMap can convert simple maps or nested structures such as lists of maps; see Nested Structures below for examples.
With no options, it safely converts string keys in maps to atoms, using String.to_existing_atom/1.
iex>AtomicMap.convert(%{"a"=>"a","b"=>"b",c: "c"})%{a: "a",b: "b",c: "c"}Because safe conversion uses String.to_existing_atom/1, it will raise when the target atom does not exist.
iex>AtomicMap.convert(%{"abcdefg"=>"a","b"=>"b"})**(ArgumentError)argumenterror:erlang.binary_to_existing_atom("abcdefg",:utf8)To have safe conversion can ignore unsafe keys, leaving them as strings, pass true for the ignore option.
iex>AtomicMap.convert(%{"abcdefg"=>"a","b"=>"b"},%{ignore: true})%{:b=>"b","abcdefg"=>"a"}To disable safe conversion and allow new atoms to be created, pass false for the safe: option.
(This makes the ignore option irrelevant.)
If the input is user-generated, converting only expected keys will prevent excessive atom creation.
iex>map=%{"expected_key"=>"a","b"=>"b","unexpected_key"=>"c"}%{"expected_key"=>"a","b"=>"b","unexpected_key"=>"c"}iex>filtered_map=Map.take(map,["expected_key","b"])%{"b"=>"b","expected_key"=>"a"}iex>AtomicMap.convert(filtered_map,%{safe: false})%{b: "b",expected_key: "a"}By default, "CamelCase" string keys will be converted to under_score atom keys.
iex> AtomicMap.convert(%{ "CamelCase" => "hi" })
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("camel_case", :utf8)
Note that "camel_case" was the string that failed conversion.
If that atom is explicitly created first, the conversion will succeed.
iex>:camel_case:camel_caseiex>AtomicMap.convert(%{"CamelCase"=>"hi"})%{camel_case: "hi"}Allowing unsafe conversions will also work. If the input is user-generated, converting only expected keys will prevent excessive atom creation.
iex>map=%{"CamelCase"=>"a","b"=>"b","AnotherCamelCase"=>"c"}%{"CamelCase"=>"a","b"=>"b","AnotherCamelCase"=>"c"}iex>filtered_map=Map.take(map,["CamelCase","b"])%{"b"=>"b","CamelCase"=>"a"}iex>AtomicMap.convert(filtered_map,%{safe: false})%{b: "b",camel_case: "a"}"hyphenated-string" keys will always be converted to under_score atom keys.
There is currently no way to disable this behavior.
iex>AtomicMap.convert(%{"some-key"=>"a","b"=>"c"})**(ArgumentError)argumenterror:erlang.binary_to_existing_atom("some_key",:utf8)Note that "some_key" was the string that failed conversion.
If that atom is explicitly created first, the conversion will succeed.
iex>:some_key:some_keyiex>AtomicMap.convert(%{"some-key"=>"a","b"=>"c"})%{b: "c",some_key: "a"}Allowing unsafe conversions will also work. If the input is user-generated, converting only expected keys will prevent excessive atom creation.
iex>map=%{"some-key"=>"a","b"=>"b","another-key"=>"c"}%{"some-key"=>"a","b"=>"b","another-key"=>"c"}iex>filtered_map=Map.take(map,["some-key","b"])%{"b"=>"b","some-key"=>"a"}iex>AtomicMap.convert(filtered_map,%{safe: false})%{b: "b",some_key: "a"}# works with nested mapsiex>AtomicMap.convert(%{"a"=>2,"b"=>%{"c"=>4}})%{a: 2,b: %{c: 4}}# works with nested maps + lists + mixed key types (atoms + binaries)iex>AtomicMap.convert([%{"c"=>1},%{:c=>2},%{"c"=>%{:b=>4}}],%{safe: true})[%{c: 1},%{c: 2},%{c: %{b: 4}}]Add atomic_map to your list of dependencies in
mix.exs:def deps do [{:atomic_map, "~> 0.8"}] end
- maybe allow direct conversion to a struct, like Poison does it: as: %SomeStruct{}...
$ mix bench