@@ -20,73 +20,45 @@ package jsonpatch
20
20
import lcs ._
21
21
import jsonpointer ._
22
22
23
- import cats .implicits ._
23
+ import cats .syntax .all ._
24
+ import cats .data .Chain
25
+ import cats .Eval
24
26
25
27
import scala .annotation .tailrec
26
28
27
29
class JsonDiff [Json ](diffArray : Boolean , rememberOld : Boolean )(implicit J : Jsony [Json ], Lcs : Lcs [Json ])
28
30
extends Diff [Json , JsonPatch [Json ]] {
29
31
def diff (json1 : Json , json2 : Json ): JsonPatch [Json ] =
30
- JsonPatch (diff(json1, json2, Pointer .Root ))
32
+ JsonPatch (diff(json1, json2, Pointer .Root ).value.toList )
31
33
32
- private def diff (json1 : Json , json2 : Json , pointer : Pointer ): List [Operation [Json ]] =
33
- if (json1 === json2)
34
- // if they are equal, this one is easy...
35
- Nil
36
- else
37
- (json1, json2) match {
38
- case (JsObject (fields1), JsObject (fields2)) => fieldsDiff(fields1.toList, fields2.toList, pointer)
39
- case (JsArray (arr1), JsArray (arr2)) if diffArray => arraysDiff(arr1.toList, arr2.toList, pointer)
40
- case (_, _) => List (Replace (pointer, json2, if (rememberOld) Some (json1) else None ))
41
- }
34
+ private def diff (json1 : Json , json2 : Json , pointer : Pointer ): Eval [Chain [Operation [Json ]]] =
35
+ (json1, json2) match {
36
+ case (JsObject (fields1), JsObject (fields2)) => fieldsDiff(fields1.toList, fields2, pointer)
37
+ case (JsArray (arr1), JsArray (arr2)) if diffArray => arraysDiff(arr1.toList, arr2.toList, pointer)
38
+ case _ if json1 === json2 =>
39
+ // if they are equal, this one is easy...
40
+ Eval .now(Chain .empty)
41
+ case _ => Eval .now(Chain .one(Replace (pointer, json2, if (rememberOld) Some (json1) else None )))
42
+ }
42
43
43
44
private def fieldsDiff (fields1 : List [(String , Json )],
44
- fields2 : List [(String , Json )],
45
- path : Pointer ): List [Operation [Json ]] = {
46
- // sort fields by name in both objects
47
- val sorted1 = fields1.sortBy(_._1)
48
- val sorted2 = fields2.sortBy(_._1)
49
- @ tailrec
50
- def associate (fields1 : List [(String , Json )],
51
- fields2 : List [(String , Json )],
52
- acc : List [(Option [(String , Json )], Option [(String , Json )])])
53
- : List [(Option [(String , Json )], Option [(String , Json )])] = (fields1, fields2) match {
54
- case (f1 :: t1, f2 :: t2) if f1._1 == f2._1 =>
55
- // same name, associate both
56
- associate(t1, t2, (Some (f1), Some (f2)) :: acc)
57
- case (f1 :: t1, f2 :: _) if f1._1 < f2._1 =>
58
- // the first field is not present in the second object
59
- associate(t1, fields2, (Some (f1), None ) :: acc)
60
- case (_ :: _, f2 :: t2) =>
61
- // the second field is not present in the first object
62
- associate(fields1, t2, (None , Some (f2)) :: acc)
63
- case (_, Nil ) =>
64
- fields1.map(Some (_) -> None ) ::: acc
65
- case (Nil , _) =>
66
- fields2.map(None -> Some (_)) ::: acc
45
+ fields2 : Map [String , Json ],
46
+ path : Pointer ): Eval [Chain [Operation [Json ]]] =
47
+ fields1 match {
48
+ case (fld, value1) :: fields1 =>
49
+ fields2.get(fld) match {
50
+ case Some (value2) =>
51
+ fieldsDiff(fields1, fields2 - fld, path).flatMap(d => diff(value1, value2, path / fld).map(_ ++ d))
52
+ case None =>
53
+ // field is not in the second object, delete it
54
+ fieldsDiff(fields1, fields2, path).map(
55
+ _.prepend(Remove (path / fld, if (rememberOld) Some (value1) else None )))
56
+ }
57
+ case Nil =>
58
+ Eval .now(Chain .fromSeq(fields2.toList).map { case (fld, value) => Add (path / fld, value) })
67
59
}
68
- @ tailrec
69
- def fields (fs : List [(Option [(String , Json )], Option [(String , Json )])],
70
- acc : List [Operation [Json ]]): List [Operation [Json ]] = fs match {
71
- case (Some (f1), Some (f2)) :: tl if f1 == f2 =>
72
- // all right, nothing changed
73
- fields(tl, acc)
74
- case (Some (f1), Some (f2)) :: tl =>
75
- // same field name, different values
76
- fields(tl, diff(f1._2, f2._2, path / f1._1) ::: acc)
77
- case (Some (f1), None ) :: tl =>
78
- // the field was deleted
79
- fields(tl, Remove [Json ](path / f1._1, if (rememberOld) Some (f1._2) else None ) :: acc)
80
- case (None , Some (f2)) :: tl =>
81
- // the field was added
82
- fields(tl, Add (path / f2._1, f2._2) :: acc)
83
- case _ =>
84
- acc
85
- }
86
- fields(associate(sorted1, sorted2, Nil ), Nil )
87
- }
88
60
89
- private def arraysDiff (arr1 : List [Json ], arr2 : List [Json ], path : Pointer ): List [ Operation [Json ]] = {
61
+ private def arraysDiff (arr1 : List [Json ], arr2 : List [Json ], path : Pointer ): Eval [ Chain [ Operation [Json ] ]] = {
90
62
// get the longest common subsequence in the array
91
63
val lcs = Lcs .lcs(arr1, arr2)
92
64
@@ -104,27 +76,27 @@ class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony
104
76
105
77
// add a bunch of values to an array starting at the specified index
106
78
@ tailrec
107
- def add (arr : List [Json ], idx : Int , acc : List [Operation [Json ]]): List [Operation [Json ]] = arr match {
108
- case v :: tl => add(tl, idx + 1 , Add (path / idx, v) :: acc )
109
- case Nil => acc.reverse
79
+ def add (arr : List [Json ], idx : Int , acc : Chain [Operation [Json ]]): Chain [Operation [Json ]] = arr match {
80
+ case v :: tl => add(tl, idx + 1 , acc.append( Add (path / idx, v)) )
81
+ case Nil => acc
110
82
}
111
83
112
84
// remove a bunch of array elements starting by the last one in the range
113
- def remove (from : Int , until : Int , shift : Int , arr : List [Json ]): List [Operation [Json ]] =
114
- (for (idx <- until to from by - 1 )
115
- yield Remove [Json ](path / idx, if (rememberOld) Some (arr(idx - shift)) else None )).toList
85
+ def remove (from : Int , until : Int , shift : Int , arr : List [Json ]): Chain [Operation [Json ]] =
86
+ Chain .fromSeq(
87
+ for (idx <- until to from by - 1 )
88
+ yield Remove [Json ](path / idx, if (rememberOld) Some (arr(idx - shift)) else None ))
116
89
117
90
// now iterate over the first array to computes what was added, what was removed and what was modified
118
- @ tailrec
119
91
def loop (
120
92
arr1 : List [Json ], // the first array
121
93
arr2 : List [Json ], // the second array
122
94
idx1 : Int , // current index in the first array
123
95
shift1 : Int , // current index shift in the first array (due to elements being add or removed)
124
96
idx2 : Int , // current index in the second array
125
97
lcs : List [(Int , Int )], // the list of remaining matching indices
126
- acc : List [Operation [Json ]] // the already accumulated result
127
- ): List [ Operation [Json ]] = (arr1, arr2) match {
98
+ acc : Chain [Operation [Json ]] // the already accumulated result
99
+ ): Eval [ Chain [ Operation [Json ] ]] = (arr1, arr2) match {
128
100
case (_ :: tl1, _) if isCommon1(idx1, lcs) =>
129
101
// all values in arr2 were added until the index of common value
130
102
val until = lcs.head._2
@@ -134,7 +106,7 @@ class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony
134
106
shift1 + until - idx2,
135
107
until + 1 ,
136
108
lcs.tail,
137
- add(arr2.take(until - idx2), idx1 + shift1, Nil ) reverse_:: : acc )
109
+ acc ++ add(arr2.take(until - idx2), idx1 + shift1, Chain .empty) )
138
110
case (_, _ :: tl2) if isCommon2(idx2, lcs) =>
139
111
// all values in arr1 were removed until the index of common value
140
112
val until = lcs.head._1
@@ -144,18 +116,18 @@ class JsonDiff[Json](diffArray: Boolean, rememberOld: Boolean)(implicit J: Jsony
144
116
shift1 - (until - idx1),
145
117
idx2 + 1 ,
146
118
lcs.tail,
147
- remove(idx1 + shift1, until - 1 + shift1, idx1 + shift1, arr1) reverse_:: : acc )
119
+ acc ++ remove(idx1 + shift1, until - 1 + shift1, idx1 + shift1, arr1))
148
120
case (v1 :: tl1, v2 :: tl2) =>
149
121
// values are different, recursively compute the diff of these values
150
- loop(tl1, tl2, idx1 + 1 , shift1, idx2 + 1 , lcs, diff(v1, v2, path / (idx1 + shift1)) reverse_:: : acc )
122
+ diff(v1, v2, path / (idx1 + shift1)).flatMap(d => loop(tl1, tl2, idx1 + 1 , shift1, idx2 + 1 , lcs, acc ++ d) )
151
123
case (_, Nil ) =>
152
124
// all subsequent values in arr1 were removed
153
- remove(idx1 + shift1, idx1 + arr1.size - 1 + shift1, idx1 + shift1, arr1) reverse_:: : acc
125
+ Eval .now(acc ++ remove(idx1 + shift1, idx1 + arr1.size - 1 + shift1, idx1 + shift1, arr1))
154
126
case (Nil , _) =>
155
127
// all subsequent value in arr2 were added
156
- arr2.map(Add (path / " -" , _)) reverse_:: : acc
128
+ Eval .now(acc ++ Chain .fromSeq( arr2.map(Add (path / " -" , _))))
157
129
}
158
130
159
- loop(arr1, arr2, 0 , 0 , 0 , lcs, Nil ).reverse
131
+ loop(arr1, arr2, 0 , 0 , 0 , lcs, Chain .empty)
160
132
}
161
133
}
0 commit comments