Skip to content

Instantly share code, notes, and snippets.

@aikomastboom
Forked from rcdilorenzo/json_map_builder.ex
Last active October 16, 2021 19:49
Show Gist options
  • Save aikomastboom/34d3dbc2cfec70e141f0722ab9eba3a6 to your computer and use it in GitHub Desktop.
Save aikomastboom/34d3dbc2cfec70e141f0722ab9eba3a6 to your computer and use it in GitHub Desktop.
Convert a elixir json-decoded object to a map no matter how deep
defmodule JSONMapBuilder do
def to_map(list) when is_list(list) and length(list) > 0 do
case list
|> List.first do
{_, _} ->
Enum.reduce(
list,
%{},
fn (tuple, acc) ->
{key, value} = tuple_to_key_value(tuple)
Map.put(acc, key_to_atom(key), to_map(value))
end
)
_ ->
list
end
end
def to_map(tuple) when is_tuple(tuple) do
case tuple_to_key_value(tuple) do
{nil, nil} -> %{}
{key, value} ->
Enum.into([{key_to_atom(key), to_map(value)}], %{})
end
end
def to_map(value), do: value
defp tuple_to_key_value(tuple) when is_tuple(tuple) do
case tuple_size(tuple) do
0 -> {nil, nil}
1 -> {elem(tuple, 0), nil}
2 -> tuple
_ -> {elem(tuple, 0), Tuple.delete_at(tuple, 0)}
end
end
defp key_to_atom(key) do
cond do
is_atom(key) -> key
is_binary(key) ->
try do
String.to_existing_atom(key)
rescue
ArgumentError -> key
end
end
end
end
defmodule JSONMapTest do
use ExUnit.Case
alias JSONMapBuilder, as: JS
@simple_tuple [{"key", "value"}]
@simple_map %{key: "value"}
@deep_tuple [{"key1", {"key2", "value2"}}]
@deep_map %{
key1: %{
key2: "value2"
}
}
@multiple_deep_tuple [
{
"key1",
[
{"key2", "value2"},
{"key3", "value3"}
]
},
{
"another_key",
{
"another_key2",
{"another_value", "another_values_value"}
}
}
]
@multiple_deep_map %{
key1: %{
key2: "value2",
key3: "value3"
},
another_key: %{
another_key2: %{
another_value: "another_values_value"
}
}
}
test "converts tuple to map with one level deep" do
assert JS.to_map(@simple_tuple) == @simple_map
end
test "converts tuple to map with two levels" do
assert JS.to_map(@deep_tuple) == @deep_map
end
test "converts tuple with multiple values and keys with multiple levels" do
assert JS.to_map(@multiple_deep_tuple) == @multiple_deep_map
end
test "doesn't convert an empty list into a map when converting" do
assert JS.to_map([{"key", []}]) == %{key: []}
end
test "doesn't convert an list of non-tuples into a map when converting" do
assert JS.to_map([{"key", [1, 2, 3]}]) == %{key: [1, 2, 3]}
end
test "convert empty tuple" do
assert JS.to_map({}) == %{}
end
test "convert tuple of one" do
assert JS.to_map({"key"}) == %{key: nil}
end
test "convert tuple of three" do
assert JS.to_map({"key", "value", "default"}) == %{
key: %{
value: "default"
}
}
end
test "ecto.changeset errors" do
assert JS.to_map(
[
type: {"can't be blank", [validation: :required]},
hmac: {"can't be blank", [validation: :required]},
app_id: {"One of these fields must be present: [:app_id, :subscription_id, :endpoint_id]", []}
]
) == %{
type: %{
"can't be blank": %{validation: :required}},
app_id: %{"One of these fields must be present: [:app_id, :subscription_id, :endpoint_id]" => []},
hmac: %{"can't be blank": %{validation: :required}}
}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment