Skip to content

Commit d719706

Browse files
committed
Expose a cheaper diffing option.
1 parent 8aac58c commit d719706

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

src/Json/Diff.elm

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Json.Diff exposing (diff, invertibleDiff, diffWithCustomWeight)
44
55
This has been implemented rather simply, and is probably not very optimised, but it should work for a lot of use cases.
66
7-
@docs diff, invertibleDiff, diffWithCustomWeight
7+
@docs diff, invertibleDiff, diffWithCustomWeight, cheapDiff
88
99
-}
1010

@@ -62,6 +62,18 @@ diffWithCustomWeight weight =
6262
internalDiff weight []
6363

6464

65+
{-| Does a diff without using any testing of multiple options. This makes computing the diff much cheaper, but is much
66+
more likely to produce less concise (but still correct) patches.
67+
68+
In particular, removing elements from the start of lists will produce changes for future elements, and lots of small
69+
changes may be created rather than doing a single replace more efficiently.
70+
71+
-}
72+
cheapDiff : Json.Value -> Json.Value -> Invertible.Patch
73+
cheapDiff =
74+
internalCheapDiff []
75+
76+
6577

6678
{- Private -}
6779

@@ -86,7 +98,7 @@ internalDiff weight root a b =
8698
d
8799

88100
Nothing ->
89-
case try (Json.dict Json.value) a b |> Maybe.andThen (diffObject weight root) of
101+
case try (Json.dict Json.value) a b |> Maybe.andThen (diffObject (internalDiff weight) root) of
90102
Just modify ->
91103
if weight modify > weight replace then
92104
replace
@@ -98,6 +110,50 @@ internalDiff weight root a b =
98110
replace
99111

100112

113+
internalCheapDiff : Json.Pointer -> Json.Value -> Json.Value -> Invertible.Patch
114+
internalCheapDiff root a b =
115+
let
116+
replace =
117+
[ Invertible.Replace root a b ]
118+
in
119+
case try primitiveDecoder a b |> Maybe.map primitiveEquals of
120+
Just equal ->
121+
if equal then
122+
[]
123+
124+
else
125+
replace
126+
127+
Nothing ->
128+
case try (Json.list Json.value) a b |> Maybe.map (cheapDiffList root) of
129+
Just d ->
130+
d
131+
132+
Nothing ->
133+
case try (Json.dict Json.value) a b |> Maybe.andThen (diffObject internalCheapDiff root) of
134+
Just modify ->
135+
modify
136+
137+
Nothing ->
138+
replace
139+
140+
141+
cheapDiffList : Json.Pointer -> ( List Json.Value, List Json.Value ) -> Invertible.Patch
142+
cheapDiffList root ( a, b ) =
143+
List.range 0 (max (List.length a) (List.length b))
144+
|> List.reverse
145+
|> List.concatMap (\i -> diffField internalCheapDiff root (String.fromInt i) (get i a) (get i b))
146+
147+
148+
get : Int -> List a -> Maybe a
149+
get i values =
150+
if i > 0 then
151+
values |> List.drop i |> List.head
152+
153+
else
154+
Nothing
155+
156+
101157
defaultPatchWeight : Invertible.Patch -> Int
102158
defaultPatchWeight patch =
103159
patch |> Invertible.toMinimalPatch |> JsonP.encoder |> jsonWeight
@@ -191,8 +247,8 @@ diffList weight root ( a, b ) =
191247
|> List.map (\( op, _ ) -> op)
192248

193249

194-
diffObject : (Invertible.Patch -> Int) -> Json.Pointer -> ( Dict String Json.Value, Dict String Json.Value ) -> Maybe Invertible.Patch
195-
diffObject weight root ( a, b ) =
250+
diffObject : (Json.Pointer -> Json.Value -> Json.Value -> Invertible.Patch) -> Json.Pointer -> ( Dict String Json.Value, Dict String Json.Value ) -> Maybe Invertible.Patch
251+
diffObject parentDiff root ( a, b ) =
196252
let
197253
aKeys =
198254
a |> Dict.keys |> Set.fromList
@@ -212,12 +268,12 @@ diffObject weight root ( a, b ) =
212268
else
213269
Set.union aKeys bKeys
214270
|> Set.toList
215-
|> List.concatMap (\k -> diffField weight root k (Dict.get k a) (Dict.get k b))
271+
|> List.concatMap (\k -> diffField parentDiff root k (Dict.get k a) (Dict.get k b))
216272
|> Just
217273

218274

219-
diffField : (Invertible.Patch -> Int) -> Json.Pointer -> String -> Maybe Json.Value -> Maybe Json.Value -> Invertible.Patch
220-
diffField weight root key a b =
275+
diffField : (Json.Pointer -> Json.Value -> Json.Value -> Invertible.Patch) -> Json.Pointer -> String -> Maybe Json.Value -> Maybe Json.Value -> Invertible.Patch
276+
diffField parentDiff root key a b =
221277
let
222278
pointer =
223279
root ++ [ key ]
@@ -226,7 +282,7 @@ diffField weight root key a b =
226282
Just ja ->
227283
case b of
228284
Just jb ->
229-
internalDiff weight pointer ja jb
285+
parentDiff pointer ja jb
230286

231287
Nothing ->
232288
[ Invertible.Remove pointer ja ]

tests/Cases.elm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,12 @@ cases =
338338
{ "op": "replace", "path": "/a/2", "value": 13 },
339339
{ "op": "replace", "path": "/a/3", "value": 14 }
340340
]
341+
},
342+
{
343+
"description": "pick between lots of small changes vs a reasonable replacement",
344+
"a": {"a": { "b" : { "c" : { "1" : 1, "2": 2, "3": 3 } } } },
345+
"b": {"a": { "b" : { "c" : { "x" : 1, "y": 2, "z": 3 } } } },
346+
"patch": [{"op": "replace", "path": "/a/b/c", "value": { "x" : 1, "y": 2, "z": 3 }}]
341347
}
342348
]
343349
"""

tests/Invertible.elm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import Expect exposing (Expectation)
55
import Json.Decode as Json
66
import Json.Diff as Diff
77
import Json.Encode as JsonE
8-
import Json.Patch as Json
98
import Json.Patch.Invertible as Invertable
109
import Test exposing (..)
1110

0 commit comments

Comments
 (0)