@@ -208,44 +208,38 @@ defmodule Jsonpatch do
208208 |> Keyword . update ( :object_hash , nil , & make_safe_hash_fn / 1 )
209209 |> Keyword . validate! (
210210 ancestor_path: "" ,
211- prepare_map: fn struct -> struct end ,
211+ prepare_map: & Function . identity / 1 ,
212212 object_hash: nil
213213 )
214214
215- cond do
216- is_map ( source ) and is_map ( destination ) ->
217- do_map_diff ( destination , source , opts [ :ancestor_path ] , [ ] , opts )
218-
219- is_list ( source ) and is_list ( destination ) ->
220- do_list_diff ( destination , source , opts [ :ancestor_path ] , [ ] , 0 , opts )
221-
222- # type of value changed, eg set to nil
223- source != destination ->
224- destination = maybe_prepare_map ( destination , opts )
225- [ % { op: "replace" , path: opts [ :ancestor_path ] , value: destination } ]
226-
227- true ->
228- [ ]
229- end
215+ do_diff ( destination , source , opts [ :ancestor_path ] , nil , [ ] , opts )
230216 end
231217
232218 defguardp are_unequal_maps ( val1 , val2 ) when val1 != val2 and is_map ( val2 ) and is_map ( val1 )
233219 defguardp are_unequal_lists ( val1 , val2 ) when val1 != val2 and is_list ( val2 ) and is_list ( val1 )
234220
235221 defp do_diff ( dest , source , path , key , patches , opts ) when are_unequal_lists ( dest , source ) do
236222 # uneqal lists, let's use a specialized function for that
237- do_list_diff ( dest , source , " #{ path } / #{ escape ( key ) } " , patches , 0 , opts )
223+ do_list_diff ( dest , source , join_key ( path , key ) , patches , opts )
238224 end
239225
240226 defp do_diff ( dest , source , path , key , patches , opts ) when are_unequal_maps ( dest , source ) do
241- # uneqal maps, let's use a specialized function for that
242- do_map_diff ( dest , source , "#{ path } /#{ escape ( key ) } " , patches , opts )
227+ # Convert structs to maps if prepare_map function is provided
228+ dest = maybe_prepare_map ( dest , opts )
229+ source = maybe_prepare_map ( source , opts )
230+
231+ if not is_map ( dest ) or not is_map ( source ) do
232+ # type changed, let's process it again
233+ do_diff ( dest , source , path , key , patches , opts )
234+ else
235+ # uneqal maps, let's use a specialized function for that
236+ do_map_diff ( dest , source , join_key ( path , key ) , patches , opts )
237+ end
243238 end
244239
245240 defp do_diff ( dest , source , path , key , patches , opts ) when dest != source do
246241 # scalar values or change of type (map -> list etc), let's just make a replace patch
247- value = maybe_prepare_map ( dest , opts )
248- [ % { op: "replace" , path: "#{ path } /#{ escape ( key ) } " , value: value } | patches ]
242+ [ % { op: "replace" , path: join_key ( path , key ) , value: maybe_prepare_map ( dest , opts ) } | patches ]
249243 end
250244
251245 defp do_diff ( _dest , _source , _path , _key , patches , _opts ) do
@@ -254,10 +248,6 @@ defmodule Jsonpatch do
254248 end
255249
256250 defp do_map_diff ( % { } = destination , % { } = source , ancestor_path , patches , opts ) do
257- # Convert structs to maps if prepare_map function is provided
258- destination = maybe_prepare_map ( destination , opts )
259- source = maybe_prepare_map ( source , opts )
260-
261251 # entrypoint for map diff, let's convert the map to a list of {k, v} tuples
262252 destination
263253 |> Map . to_list ( )
@@ -271,7 +261,7 @@ defmodule Jsonpatch do
271261 if k in checked_keys do
272262 patches
273263 else
274- [ % { op: "remove" , path: " #{ ancestor_path } / #{ escape ( k ) } " } | patches ]
264+ [ % { op: "remove" , path: join_key ( ancestor_path , k ) } | patches ]
275265 end
276266 end )
277267 end
@@ -285,23 +275,23 @@ defmodule Jsonpatch do
285275
286276 :error ->
287277 value = maybe_prepare_map ( val , opts )
288- [ % { op: "add" , path: " #{ ancestor_path } / #{ escape ( key ) } " , value: value } | patches ]
278+ [ % { op: "add" , path: join_key ( ancestor_path , key ) , value: value } | patches ]
289279 end
290280
291281 # Diff next value of same level
292282 do_map_diff ( rest , source , ancestor_path , patches , [ key | checked_keys ] , opts )
293283 end
294284
295- defp do_list_diff ( destination , source , ancestor_path , patches , idx , opts ) do
285+ defp do_list_diff ( destination , source , ancestor_path , patches , opts ) do
296286 if opts [ :object_hash ] do
297287 do_hash_list_diff ( destination , source , ancestor_path , patches , opts )
298288 else
299- do_pairwise_list_diff ( destination , source , ancestor_path , patches , idx , opts )
289+ do_pairwise_list_diff ( destination , source , ancestor_path , patches , 0 , opts )
300290 end
301291 catch
302292 # happens if we've got a nil hash or we tried to hash a non-map
303293 :hash_not_implemented ->
304- do_pairwise_list_diff ( destination , source , ancestor_path , patches , idx , opts )
294+ do_pairwise_list_diff ( destination , source , ancestor_path , patches , 0 , opts )
305295 end
306296
307297 defp do_pairwise_list_diff ( destination , source , ancestor_path , patches , idx , opts )
@@ -310,15 +300,15 @@ defmodule Jsonpatch do
310300
311301 defp do_pairwise_list_diff ( [ ] , [ _item | source_rest ] , ancestor_path , patches , idx , opts ) do
312302 # if we find any leftover items in source, we have to remove them
313- patches = [ % { op: "remove" , path: " #{ ancestor_path } / #{ idx } " } | patches ]
303+ patches = [ % { op: "remove" , path: join_key ( ancestor_path , idx ) } | patches ]
314304 do_pairwise_list_diff ( [ ] , source_rest , ancestor_path , patches , idx + 1 , opts )
315305 end
316306
317307 defp do_pairwise_list_diff ( items , [ ] , ancestor_path , patches , idx , opts ) do
318308 # we have to do it without recursion, because we have to keep the order of the items
319309 items
320310 |> Enum . map_reduce ( idx , fn val , idx ->
321- { % { op: "add" , path: " #{ ancestor_path } / #{ idx } " , value: maybe_prepare_map ( val , opts ) } ,
311+ { % { op: "add" , path: join_key ( ancestor_path , idx ) , value: maybe_prepare_map ( val , opts ) } ,
322312 idx + 1 }
323313 end )
324314 |> elem ( 0 )
@@ -469,15 +459,15 @@ defmodule Jsonpatch do
469459 @ compile { :inline , add_removals: 4 }
470460 defp add_removals ( from_idx , to_idx , path , removals ) do
471461 Enum . reduce ( from_idx .. to_idx // 1 , removals , fn idx , removals ->
472- [ % { op: "remove" , path: " #{ path } / #{ idx } " } | removals ]
462+ [ % { op: "remove" , path: join_key ( path , idx ) } | removals ]
473463 end )
474464 end
475465
476466 @ compile { :inline , add_additions: 6 }
477467 defp add_additions ( from_idx , to_idx , path , dest_tuple , additions , opts ) do
478468 Enum . reduce ( from_idx .. to_idx // 1 , additions , fn idx , additions ->
479469 value = dest_tuple |> elem ( idx ) |> maybe_prepare_map ( opts )
480- [ % { op: "add" , path: " #{ path } / #{ idx } " , value: value } | additions ]
470+ [ % { op: "add" , path: join_key ( path , idx ) , value: value } | additions ]
481471 end )
482472 end
483473
@@ -503,6 +493,10 @@ defmodule Jsonpatch do
503493
504494 defp escape ( fragment ) , do: fragment
505495
496+ @ compile { :inline , join_key: 2 }
497+ defp join_key ( path , nil ) , do: path
498+ defp join_key ( path , key ) , do: "#{ path } /#{ escape ( key ) } "
499+
506500 defp make_safe_hash_fn ( hash_fn ) do
507501 # we want to compare only maps, and returning nil should mean
508502 # we should compare lists pairwise instead
0 commit comments