Packages

A library to transform between two map structures

Current section

Files

Jump to
map_transform lib map_transform.ex
Raw

lib/map_transform.ex

defmodule MapTransform do
@moduledoc """
This is a simple library that can transform one map into another through
mapping rules.
"""
@type path :: nonempty_list(term)
@type mapping :: {path, path} | {path, path, (term -> term)}
@doc """
Transform one map into another.
## Format for mappings
For the paths is the standard that `get_in/2` and `put_in/2` use.
For example to get data from `c` in `%{a: %{b: %{c: 1}}}` you would provide
`[:a, :b, :c]` as the path.
Then we'll use these paths in the mapping in a list of tuples where:
{from_path, to_path}
{from_path, to_path, &transform_function/1}
## Example
Basic
iex> mapping = [
...> {[:a, :b, :c], [:abc]}
...> ]
...> source = %{a: %{b: %{c: 1}}}
...> transform(source, mapping)
%{abc: 1}
String keys and using a transform function
iex> mapping = [
...> {["a", "b", "c"], [:abc], &String.to_integer/1}
...> ]
...> source = %{"a" => %{"b" => %{"c" => "1"}}}
...> transform(source, mapping)
%{abc: 1}
Any nesting
iex> mapping = [
...> {[:a, :b, :c], [:foo, :bar]}
...> ]
...> source = %{a: %{b: %{c: 1}}}
...> transform(source, mapping)
%{foo: %{bar: 1}}
"""
@spec transform(map, [mapping]) :: map
def transform(source, mapping) do
base_map = base_map_from_mapping(mapping)
mapping
|> Enum.reduce(base_map, &do_transform(&1, &2, source))
end
defp do_transform({from_path, to_path}, acc, source) do
do_transform({from_path, to_path, & &1}, acc, source)
end
defp do_transform({from_path, to_path, function}, acc, source) do
put_in(acc, to_path, source |> get_in(from_path) |> function.())
end
defp base_map_from_mapping(mapping) do
mapping
|> Enum.map(&elem(&1, 1))
|> base_map()
end
defp base_map(paths) do
paths
|> Enum.reduce(%{}, &do_base_map/2)
end
defp do_base_map([], _acc) do
nil
end
defp do_base_map([last], acc) do
Map.put(acc, last, nil)
end
defp do_base_map([key | rest], acc) do
Map.put(acc, key, do_base_map(rest, %{}))
end
end