Way to find out changed value or potentially a bug!
collegeimprovements opened this issue · 4 comments
iex(4)> MapDiff.diff(%{a: 1, b: 2, c: %{ d: 5, e: 6 }}, %{a: 4, b: 2, c: %{d: 9, e: 6}})
%{ added: %{a: 4, c: %{d: 9, e: 6}},
changed: :map_change,
removed: %{a: 1, c: %{d: 5, e: 6}}, value: %{ a: %{added: 4, changed: :primitive_change, removed: 1},
b: %{changed: :equal, value: 2}, c: %{
added: %{d: 9}, changed: :map_change, removed: %{d: 5},
value: %{ d: %{added: 9, changed: :primitive_change, removed: 5}, e: %{changed: :equal, value: 6}
} } }
}iex(5)> MapDiff.diff(%{a: 1, b: 2, c: %{ d: 5, e: 6 }}, %{a: 4, b: 2, c: %{d: 9, e: 6}}) |> Map.get(:added)%{a: 4, c: %{d: 9, e: 6}}
iex(6)>
Since e: 6
is unchanged, is there any way to get only changed values easily ?
I thought :added
does that. But maybe i'm misunderstanding something.
The way the library works on your example input is correct.
However, depending on what you are using MapDiff for, you might like to perform some post-processing.
A change might be one of two things:
changed: :primitive_change
. In this case it is a simple value that was changed. You can see the previous and current value using the:added
and:removed
fields in that case.changed: :map_change
. In this case the change is a recursive change where one or multiple fields of a nested map are changed. Here,:value
can be used to recurse deeper.
To for instance only get a list of (deeply nested) keys, consider the following recursive function:
defmodule Changes do
def run(map_diff_output = %{changed: :map_change}) do
map_diff_output[:value]
|> Enum.flat_map(fn {key, val} ->
# Prefix all nested changes with the key they appear under in a map
run(val)
|> Enum.map(fn nested_key -> [key | nested_key] end)
end)
end
# A primitive change ends up in our list iff it was nested under a key
def run(map_diff_output = %{changed: :primitive_change}) do
[[]]
end
# Things that are equal are discarded:
def run(_), do: []
end
If you want to get a map with only the (deeply nested) changed fields in there, you can do so as follows:
defmodule NestedChanges do
def run(map_diff_output) do
{:ok, res} = do_run(map_diff_output)
res
end
defp do_run(map_diff_output = %{changed: :primitive_change}) do
{:ok, map_diff_output[:added]}
end
defp do_run(map_diff_output = %{changed: :map_change}) do
IO.inspect(map_diff_output)
res =
map_diff_output[:value]
|> Enum.map(fn {key, val} -> {key, do_run(val)} end) # Recur on all keys
|> Enum.reject(fn {key, val} -> val == :error end) # Remove all keys that do not contain changes
|> Enum.map(fn {key, {:ok, val}} -> {key, val} end)
|> Enum.into(%{})
{:ok, res}
end
defp do_run(_), do: :error
end
There might be cleaner ways of writing these snippets, (it's a bit late here right now 😴 ) but hopefully you get the general idea.
What are you trying to do, exactly? What do you need the output of MapDiff
for?
Maybe it makes sense to add some common ways of transforming the output of MapDiff
like these two examples above to the library itself, if it turns out that they are useful in a variety of contexts.
I've a very big nested query (graphql) that returns a map. I need to return only the diff
of those two maps. And then i send that diff to user via mail to indicate the changes he has made.
NestedChanges
is breaking when two maps are equal.
Maybe It should return empty map or empty list.
defmodule Utils.MapDiff.NestedMapDiff do
@moduledoc """
NestedMapDiff
"""
def run(map_diff_output) do
{:ok, res} = do_run(map_diff_output)
res
end
defp do_run(_map_diff_output = %{changed: :equal}) do
{:ok, %{}}
end
defp do_run(map_diff_output = %{changed: :primitive_change}) do
{:ok, map_diff_output[:added]}
end
defp do_run(map_diff_output = %{changed: :map_change}) do
IO.inspect(map_diff_output)
res =
map_diff_output[:value]
# Recur on all keys
|> Enum.map(fn {key, val} -> {key, do_run(val)} end)
# Remove all keys that do not contain changes
|> Enum.reject(fn {_key, val} -> val == :error end)
|> Enum.map(fn {key, {:ok, val}} -> {key, val} end)
|> Enum.into(%{})
{:ok, res}
end
defp do_run(_), do: :error
end
Ops..the better way is to use your NestedMapDiff
and before that need to check Map.equal?(a,b)
. That did what i wanted.
Thanks @Qqwy I'm closing this issue as i found the way to do what i want from your comment.