-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdeep_merge.ex
103 lines (79 loc) · 3.59 KB
/
deep_merge.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
defmodule OldDeepMerge do
@doc """
`deep_merge` implementation that uses its own custom resolver implementation
to pass to `Map.merge/2`
iex> OldDeepMerge.deep_merge(%{a: 1, b: 2}, %{b: 3, c: 4})
%{a: 1, b: 3, c: 4}
iex> OldDeepMerge.deep_merge(%{a: 1, b: %{x: 10, y: 9}}, %{b: %{y: 20, z: 30}, c: 4})
%{a: 1, b: %{x: 10, y: 20, z: 30}, c: 4}
iex> OldDeepMerge.deep_merge(%{a: 1, b: %{x: 10, y: 9}}, %{b: 5, c: 4})
%{a: 1, b: 5, c: 4}
iex> OldDeepMerge.deep_merge(%{a: %{b: %{c: %{d: "foo", e: 2}}}}, %{a: %{b: %{c: %{d: "bar"}}}})
%{a: %{b: %{c: %{d: "bar", e: 2}}}}
"""
def deep_merge_specific(base_map, override) do
Map.merge(base_map, override, &deep_merge_resolver/3)
end
@doc """
`deep_merge` implementation that builds upon the more general
`OldDeepMerge.deep_merge/3` to provide its merging capability.
Merges two maps similar to `Map.merge/2`, but with the important difference
that if keys exist in both maps and their value is also a map it will
recursively also merge these maps. If a value for a duplicated key is not a
map it will prefer the value present in `override`.
For maps without maps as possible values it behaves exactly like `Map.merge/2`
iex> OldDeepMerge.deep_merge(%{a: 1, b: %{x: 10, y: 9}}, %{b: %{y: 20, z: 30}, c: 4})
%{a: 1, b: %{x: 10, y: 20, z: 30}, c: 4}
iex> OldDeepMerge.deep_merge(%{a: 1, b: 2}, %{b: 3, c: 4})
%{a: 1, b: 3, c: 4}
iex> OldDeepMerge.deep_merge(%{a: 1, b: %{x: 10, y: 9}}, %{b: 5, c: 4})
%{a: 1, b: 5, c: 4}
iex> OldDeepMerge.deep_merge(%{a: 1, b: 5}, %{b: %{x: 10, y: 9}, c: 4})
%{a: 1, b: %{x: 10, y: 9}, c: 4}
iex> OldDeepMerge.deep_merge(%{a: %{b: %{c: %{d: "foo", e: 2}}}}, %{a: %{b: %{c: %{d: "bar"}}}})
%{a: %{b: %{c: %{d: "bar", e: 2}}}}
"""
def deep_merge(base_map, override) do
deep_merge(base_map, override, fn _key, _base, override -> override end)
end
@doc """
And adjustable version of `deep_merge/2` where one can specify the resolver
function akin to `Map.merge/3` which gets called if a key exists in both maps
and is not a map.
This can also be practical if you want to merge further values like lists.
iex> OldDeepMerge.deep_merge(%{a: %{y: "bar", z: "bar"}, b: 2}, %{a: %{y: "foo"}, b: 3, c: 4}, fn(_, _, _) -> :conflict end)
%{a: %{y: :conflict, z: "bar"}, b: :conflict, c: 4}
iex> simple_resolver = fn(_key, base, _override) -> base end
iex> OldDeepMerge.deep_merge(%{a: 1, b: %{x: 10, y: 9}}, %{b: 5, c: 4}, simple_resolver)
%{a: 1, b: %{x: 10, y: 9}, c: 4}
iex> list_merger = fn
...> _, list1, list2 when is_list(list1) and is_list(list2) ->
...> list1 ++ list2
...> _, _, override ->
...> override
...> end
iex> OldDeepMerge.deep_merge(%{a: %{b: [1]}, c: 2}, %{a: %{b: [2]}, c: 100}, list_merger)
%{a: %{b: [1, 2]}, c: 100}
"""
def deep_merge(base_map, override_map, fun) do
# build a function to resolve the merges here and then use an actual
# do_deep_merge function to do the merges and refer to that one
# when really merging within the built functions
Map.merge(base_map, override_map, build_deep_merge_resolver(fun))
end
defp build_deep_merge_resolver(fun) do
fn
_key, base_map, override_map when is_map(base_map) and is_map(override_map) ->
deep_merge(base_map, override_map, fun)
key, base, override ->
fun.(key, base, override)
end
end
defp deep_merge_resolver(_key, base_map, override_map)
when is_map(base_map) and is_map(override_map) do
deep_merge_specific(base_map, override_map)
end
defp deep_merge_resolver(_key, _base, override) do
override
end
end