@@ -111,11 +111,11 @@ defmodule Jsonpatch do
111111 iex> destination = %{"name" => "Bob", "married" => true, "hobbies" => ["Elixir!"], "age" => 33}
112112 iex> Jsonpatch.diff(source, destination)
113113 [
114- %Add {path: "/age ", value: 33 },
115- %Replace {path: "/hobbies/0", value: "Elixir! "},
116- %Replace {path: "/married", value: true },
117- %Remove {path: "/hobbies/1 "},
118- %Remove {path: "/hobbies/2" }
114+ %Jsonpatch.Operation.Replace {path: "/married ", value: true },
115+ %Jsonpatch.Operation.Remove {path: "/hobbies/2 "},
116+ %Jsonpatch.Operation.Remove {path: "/hobbies/1" },
117+ %Jsonpatch.Operation.Replace {path: "/hobbies/0", value: "Elixir! "},
118+ %Jsonpatch.Operation.Add {path: "/age", value: 33 }
119119 ]
120120 """
121121 @ spec diff ( maybe_improper_list | map , maybe_improper_list | map ) :: list ( Jsonpatch . t ( ) )
@@ -144,15 +144,26 @@ defmodule Jsonpatch do
144144 defguardp are_unequal_lists ( val1 , val2 )
145145 when val1 != val2 and is_list ( val2 ) and is_list ( val1 )
146146
147- defp do_diff ( destination , source , current_path , acc \\ [ ] )
147+ defp do_diff ( destination , source , ancestor_path , acc \\ [ ] , checked_keys \\ [ ] )
148+
149+ defp do_diff ( [ ] , source , ancestor_path , acc , checked_keys ) do
150+ # The complete desination was check. Every key that is not in the list of
151+ # checked keys, must be removed.
152+ acc =
153+ source
154+ |> flat ( )
155+ |> Stream . map ( fn { k , _ } -> k end )
156+ |> Stream . filter ( fn k -> k not in checked_keys end )
157+ |> Stream . map ( fn k -> % Remove { path: "#{ ancestor_path } /#{ k } " } end )
158+ |> Enum . reduce ( acc , fn r , acc -> [ r | acc ] end )
148159
149- defp do_diff ( [ ] , _ , _ , acc ) do
150160 acc
151161 end
152162
153- defp do_diff ( [ { key , val } | tail ] , source , ancestor_path , acc )
163+ defp do_diff ( [ { key , val } | tail ] , source , ancestor_path , acc , checked_keys )
154164 when is_list ( source ) or is_map ( source ) do
155165 current_path = "#{ ancestor_path } /#{ escape ( key ) } "
166+ checked_keys = [ escape ( key ) | checked_keys ]
156167
157168 from_source =
158169 cond do
@@ -169,8 +180,8 @@ defmodule Jsonpatch do
169180 # Source has a different value but both (destination and source) value are lists or a maps
170181 source_val
171182 when are_unequal_lists ( source_val , val ) or are_unequal_maps ( source_val , val ) ->
172- # Enter next level
173- do_diff ( next_level ( val ) , source_val , current_path , acc )
183+ # Enter next level - set check_keys to empty list because it is a different level
184+ do_diff ( flat ( val ) , source_val , current_path , acc , [ ] )
174185
175186 # Scalar source val that is not equal
176187 source_val when source_val != val ->
@@ -181,13 +192,13 @@ defmodule Jsonpatch do
181192 end
182193
183194 # Diff next value of same level
184- do_diff ( tail , source , ancestor_path , acc )
195+ do_diff ( tail , source , ancestor_path , acc , checked_keys )
185196 end
186197
187198 # Transforms a map into a tuple list and a list also into a tuple list with indizes
188- defp next_level ( val ) do
199+ defp flat ( val ) do
189200 cond do
190- is_list ( val ) -> Enum . with_index ( val ) |> Enum . map ( fn { v , k } -> { k , v } end )
201+ is_list ( val ) -> Stream . with_index ( val ) |> Enum . map ( fn { v , k } -> { k , v } end )
191202 is_map ( val ) -> Map . to_list ( val )
192203 true -> [ ]
193204 end
0 commit comments