Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 33 additions & 12 deletions lib/atomic_map.ex
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
defmodule AtomicMap.Opts do
@moduledoc ~S"""
Set any value to `false` to disable checking for that kind of key.
"""
defstruct safe: true,
underscore: true
end

defmodule AtomicMap do
def convert(v, opts \\ %{})

def convert(v, opts \\ [])
def convert(struct=%{__struct__: type}, opts) do
def convert(struct=%{__struct__: type}, opts=%AtomicMap.Opts{}) do
struct
|> Map.from_struct
|> convert(opts)
|> Map.put(:__struct__, type)
end
def convert(map, opts) when is_map(map) do
safe = Keyword.get(opts, :safe, true)
def convert(map, opts=%AtomicMap.Opts{}) when is_map(map) do
map |> Enum.reduce(%{}, fn({k,v}, acc)->
k = as_atom(k, safe)
v = convert(v, opts)
k = k |> convert_key(opts)
v = v |> convert(opts)
acc |> Map.put(k, v)
end)
end
def convert(list, opts) when is_list(list) do
def convert(list, opts=%AtomicMap.Opts{}) when is_list(list) do
list |> Enum.map(fn(x)-> convert(x, opts) end)
end
def convert(tuple, opts) when is_tuple(tuple) do
def convert(tuple, opts=%AtomicMap.Opts{}) when is_tuple(tuple) do
tuple |> Tuple.to_list |> convert(opts) |> List.to_tuple
end
def convert(v, _opts), do: v
def convert(v, _opts=%AtomicMap.Opts{}), do: v

# if you pass a plain map or keyword list as opts, those will match and convert it to struct
def convert(v, opts=%{}), do: convert(v, struct(AtomicMap.Opts, opts))
def convert(v, opts) when is_list(opts), do: convert(v, Enum.into(opts, %{}))

defp convert_key(k, opts) do
k
|> as_underscore(opts.underscore)
|> as_atom(opts.safe)
end

defp as_atom(s, true) when is_binary(s), do: s |> String.to_existing_atom
defp as_atom(s, false) when is_binary(s), do: s |> String.to_atom
defp as_atom(s, _), do: s

defp as_atom(s, true) when is_binary(s), do: s |> String.to_existing_atom
defp as_atom(s, false) when is_binary(s), do: s |> String.to_atom
defp as_atom(s, _), do: s
def as_underscore(s, true) when is_binary(s), do: s |> Macro.underscore
def as_underscore(s, true) when is_atom(s), do: s |> Atom.to_string |> as_underscore(true)
def as_underscore(s, false), do: s
end
8 changes: 7 additions & 1 deletion test/atomic_map_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule AtomicMapTest do
test "works with maps" do
input = %{"a" => 2, "b" => %{"c" => 4}}
expected = %{a: 2, b: %{c: 4}}
assert AtomicMap.convert(input, safe: true) == expected
assert AtomicMap.convert(input, %{safe: true}) == expected
end

test "works with maps with lists" do
Expand Down Expand Up @@ -60,6 +60,12 @@ defmodule AtomicMapTest do
assert AtomicMap.convert(input) == expected
end

test "convertes keys to underscore by default (attention: safe: false needed here)" do
input = %{ "firstKey" => {1,2}, :secondKey => 4}
expected = %{first_key: {1, 2}, second_key: 4}
assert AtomicMap.convert(input, safe: false) == expected
end

test "raises for not existing atoms" do
assert_raise ArgumentError, fn ->
input = %{"a" => 2, "b" => %{"c" => 4}, "__not___existing__" => 5}
Expand Down