Last active
October 5, 2022 14:13
-
-
Save moroz/8b5361f033f8c9fe57b2930c1d32b218 to your computer and use it in GitHub Desktop.
Flatten and unflatten a nested map
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule UnnestHelpers do | |
@doc """ | |
Converts an arbitrarily nested map of translations to a flat map | |
with all key levels combined to period-separated strings. | |
Useful when you want to convert a nested structure like Rails | |
i18n YAML translation files into a flat structure, like an Excel | |
spreadsheet. | |
## Examples | |
iex> nested = %{ | |
...> views: %{ | |
...> index: %{ | |
...> title: "Home page", | |
...> section: "Recent news" | |
...> }, | |
...> show: %{ | |
...> title: "Showing post: %{title}", | |
...> test: "Test key" | |
...> } | |
...> } | |
...> } | |
iex> UnnestHelpers.unnest_map(nested) | |
%{ | |
"views.index.section" => "Recent news", | |
"views.index.title" => "Home page", | |
"views.show.test" => "Test key", | |
"views.show.title" => "Showing post: %{title}" | |
} | |
""" | |
def unnest_map(map, prefix \\ nil) | |
def unnest_map(map, prefix) when is_map(map) do | |
Enum.reduce(map, %{}, fn tuple, acc -> | |
unnest(tuple, acc, prefix) | |
end) | |
end | |
def unnest_map(value, _prefix), do: value | |
defp unnest({key, value}, acc, prefix) when is_map(value) do | |
Map.merge(acc, unnest_map(value, join_prefix(prefix, key))) | |
end | |
defp unnest({key, value}, acc, prefix) do | |
Map.put(acc, join_prefix(prefix, key), value) | |
end | |
defp join_prefix(nil, key), do: to_string(key) | |
defp join_prefix(prefix, key), do: "#{prefix}.#{key}" | |
@doc """ | |
Converts a flat map of period-separated strings to other terms to | |
a deeply nested map, with each period-separated segment corresponding to one map | |
level. Reverse of `unnest_map/2`. Does not preserve key data types -- all keys | |
are converted to strings. | |
## Examples | |
iex> flat = %{ | |
...> "views.index.section" => "Recent news", | |
...> "views.index.title" => "Home page", | |
...> "views.show.test" => "Test key", | |
...> "views.show.title" => "Showing post: %{title}" | |
...> } | |
iex> UnnestHelpers.nest_map(flat) | |
%{ | |
"views" => %{ | |
"index" => %{"section" => "Recent news", "title" => "Home page"}, | |
"show" => %{"test" => "Test key", "title" => "Showing post: %{title}"} | |
} | |
} | |
""" | |
def nest_map(input) do | |
Enum.reduce(input, %{}, fn {key, value}, intermediate_map -> | |
merge(intermediate_map, String.split(key, "."), value) | |
end) | |
end | |
defp merge(map, [leaf], value), do: Map.put(map, leaf, value) | |
defp merge(map, [node | remaining_keys], value) do | |
inner_map = merge(Map.get(map, node, %{}), remaining_keys, value) | |
Map.put(map, node, inner_map) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule UnnestHelpers.Test do | |
use ExUnit.Case, async: true | |
doctest UnnestHelpers | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment