@@ -8,20 +8,20 @@ import (
8
8
"strings"
9
9
)
10
10
11
- var errBadJSONDoc = fmt .Errorf ("Invalid JSON Document" )
11
+ var errBadJSONDoc = fmt .Errorf ("invalid JSON Document" )
12
12
13
- type JsonPatchOperation struct {
13
+ type Operation struct {
14
14
Operation string `json:"op"`
15
15
Path string `json:"path"`
16
16
Value interface {} `json:"value,omitempty"`
17
17
}
18
18
19
- func (j * JsonPatchOperation ) Json () string {
19
+ func (j * Operation ) Json () string {
20
20
b , _ := json .Marshal (j )
21
21
return string (b )
22
22
}
23
23
24
- func (j * JsonPatchOperation ) MarshalJSON () ([]byte , error ) {
24
+ func (j * Operation ) MarshalJSON () ([]byte , error ) {
25
25
var b bytes.Buffer
26
26
b .WriteString ("{" )
27
27
b .WriteString (fmt .Sprintf (`"op":"%s"` , j .Operation ))
@@ -39,14 +39,14 @@ func (j *JsonPatchOperation) MarshalJSON() ([]byte, error) {
39
39
return b .Bytes (), nil
40
40
}
41
41
42
- type ByPath []JsonPatchOperation
42
+ type ByPath []Operation
43
43
44
44
func (a ByPath ) Len () int { return len (a ) }
45
45
func (a ByPath ) Swap (i , j int ) { a [i ], a [j ] = a [j ], a [i ] }
46
46
func (a ByPath ) Less (i , j int ) bool { return a [i ].Path < a [j ].Path }
47
47
48
- func NewPatch (operation , path string , value interface {}) JsonPatchOperation {
49
- return JsonPatchOperation {Operation : operation , Path : path , Value : value }
48
+ func NewPatch (operation , path string , value interface {}) Operation {
49
+ return Operation {Operation : operation , Path : path , Value : value }
50
50
}
51
51
52
52
// CreatePatch creates a patch as specified in http://jsonpatch.com/
@@ -55,7 +55,7 @@ func NewPatch(operation, path string, value interface{}) JsonPatchOperation {
55
55
// The function will return an array of JsonPatchOperations
56
56
//
57
57
// An error will be returned if any of the two documents are invalid.
58
- func CreatePatch (a , b []byte ) ([]JsonPatchOperation , error ) {
58
+ func CreatePatch (a , b []byte ) ([]Operation , error ) {
59
59
aI := map [string ]interface {}{}
60
60
bI := map [string ]interface {}{}
61
61
err := json .Unmarshal (a , & aI )
@@ -66,7 +66,7 @@ func CreatePatch(a, b []byte) ([]JsonPatchOperation, error) {
66
66
if err != nil {
67
67
return nil , errBadJSONDoc
68
68
}
69
- return diff (aI , bI , "" , []JsonPatchOperation {})
69
+ return diff (aI , bI , "" , []Operation {})
70
70
}
71
71
72
72
// Returns true if the values matches (must be json types)
@@ -78,22 +78,25 @@ func matchesValue(av, bv interface{}) bool {
78
78
}
79
79
switch at := av .(type ) {
80
80
case string :
81
- bt := bv .(string )
82
- if bt == at {
81
+ bt , ok := bv .(string )
82
+ if ok && bt == at {
83
83
return true
84
84
}
85
85
case float64 :
86
- bt := bv .(float64 )
87
- if bt == at {
86
+ bt , ok := bv .(float64 )
87
+ if ok && bt == at {
88
88
return true
89
89
}
90
90
case bool :
91
- bt := bv .(bool )
92
- if bt == at {
91
+ bt , ok := bv .(bool )
92
+ if ok && bt == at {
93
93
return true
94
94
}
95
95
case map [string ]interface {}:
96
- bt := bv .(map [string ]interface {})
96
+ bt , ok := bv .(map [string ]interface {})
97
+ if ! ok {
98
+ return false
99
+ }
97
100
for key := range at {
98
101
if ! matchesValue (at [key ], bt [key ]) {
99
102
return false
@@ -106,7 +109,10 @@ func matchesValue(av, bv interface{}) bool {
106
109
}
107
110
return true
108
111
case []interface {}:
109
- bt := bv .([]interface {})
112
+ bt , ok := bv .([]interface {})
113
+ if ! ok {
114
+ return false
115
+ }
110
116
if len (bt ) != len (at ) {
111
117
return false
112
118
}
@@ -148,7 +154,7 @@ func makePath(path string, newPart interface{}) string {
148
154
}
149
155
150
156
// diff returns the (recursive) difference between a and b as an array of JsonPatchOperations.
151
- func diff (a , b map [string ]interface {}, path string , patch []JsonPatchOperation ) ([]JsonPatchOperation , error ) {
157
+ func diff (a , b map [string ]interface {}, path string , patch []Operation ) ([]Operation , error ) {
152
158
for key , bv := range b {
153
159
p := makePath (path , key )
154
160
av , ok := a [key ]
@@ -157,11 +163,6 @@ func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation)
157
163
patch = append (patch , NewPatch ("add" , p , bv ))
158
164
continue
159
165
}
160
- // If types have changed, replace completely
161
- if reflect .TypeOf (av ) != reflect .TypeOf (bv ) {
162
- patch = append (patch , NewPatch ("replace" , p , bv ))
163
- continue
164
- }
165
166
// Types are the same, compare values
166
167
var err error
167
168
patch , err = handleValues (av , bv , p , patch )
@@ -181,7 +182,12 @@ func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation)
181
182
return patch , nil
182
183
}
183
184
184
- func handleValues (av , bv interface {}, p string , patch []JsonPatchOperation ) ([]JsonPatchOperation , error ) {
185
+ func handleValues (av , bv interface {}, p string , patch []Operation ) ([]Operation , error ) {
186
+ // If types have changed, replace completely
187
+ if reflect .TypeOf (av ) != reflect .TypeOf (bv ) {
188
+ return append (patch , NewPatch ("replace" , p , bv )), nil
189
+ }
190
+
185
191
var err error
186
192
switch at := av .(type ) {
187
193
case map [string ]interface {}:
@@ -195,16 +201,19 @@ func handleValues(av, bv interface{}, p string, patch []JsonPatchOperation) ([]J
195
201
patch = append (patch , NewPatch ("replace" , p , bv ))
196
202
}
197
203
case []interface {}:
198
- bt , ok := bv .([]interface {})
199
- if ! ok {
200
- // array replaced by non-array
201
- patch = append (patch , NewPatch ("replace" , p , bv ))
202
- } else if len (at ) != len (bt ) {
203
- // arrays are not the same length
204
- patch = append (patch , compareArray (at , bt , p )... )
205
-
204
+ bt := bv .([]interface {})
205
+ if isSimpleArray (at ) && isSimpleArray (bt ) {
206
+ patch = append (patch , compareEditDistance (at , bt , p )... )
206
207
} else {
207
- for i := range bt {
208
+ n := min (len (at ), len (bt ))
209
+ for i := len (at ) - 1 ; i >= n ; i -- {
210
+ patch = append (patch , NewPatch ("remove" , makePath (p , i ), nil ))
211
+ }
212
+ for i := n ; i < len (bt ); i ++ {
213
+ patch = append (patch , NewPatch ("add" , makePath (p , i ), bt [i ]))
214
+ }
215
+ for i := 0 ; i < n ; i ++ {
216
+ var err error
208
217
patch , err = handleValues (at [i ], bt [i ], makePath (p , i ), patch )
209
218
if err != nil {
210
219
return nil , err
@@ -224,34 +233,97 @@ func handleValues(av, bv interface{}, p string, patch []JsonPatchOperation) ([]J
224
233
return patch , nil
225
234
}
226
235
227
- func compareArray (av , bv []interface {}, p string ) []JsonPatchOperation {
228
- retval := []JsonPatchOperation {}
229
- // var err error
230
- for i , v := range av {
231
- found := false
232
- for _ , v2 := range bv {
233
- if reflect .DeepEqual (v , v2 ) {
234
- found = true
235
- break
236
+ func isBasicType (a interface {}) bool {
237
+ switch a .(type ) {
238
+ case string , float64 , bool :
239
+ default :
240
+ return false
241
+ }
242
+ return true
243
+ }
244
+
245
+ func isSimpleArray (a []interface {}) bool {
246
+ for i := range a {
247
+ switch a [i ].(type ) {
248
+ case string , float64 , bool :
249
+ default :
250
+ val := reflect .ValueOf (a [i ])
251
+ if val .Kind () == reflect .Map {
252
+ for _ , k := range val .MapKeys () {
253
+ av := val .MapIndex (k )
254
+ if av .Kind () == reflect .Ptr || av .Kind () == reflect .Interface {
255
+ if av .IsNil () {
256
+ continue
257
+ }
258
+ av = av .Elem ()
259
+ }
260
+ if av .Kind () != reflect .String && av .Kind () != reflect .Float64 && av .Kind () != reflect .Bool {
261
+ return false
262
+ }
263
+ }
264
+ return true
236
265
}
237
- }
238
- if ! found {
239
- retval = append (retval , NewPatch ("remove" , makePath (p , i ), nil ))
266
+ return false
240
267
}
241
268
}
269
+ return true
270
+ }
271
+
272
+ // https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
273
+ // Adapted from https://github.com/texttheater/golang-levenshtein
274
+ func compareEditDistance (s , t []interface {}, p string ) []Operation {
275
+ m := len (s )
276
+ n := len (t )
277
+
278
+ d := make ([][]int , m + 1 )
279
+ for i := 0 ; i <= m ; i ++ {
280
+ d [i ] = make ([]int , n + 1 )
281
+ d [i ][0 ] = i
282
+ }
283
+ for j := 0 ; j <= n ; j ++ {
284
+ d [0 ][j ] = j
285
+ }
242
286
243
- for i , v := range bv {
244
- found := false
245
- for _ , v2 := range av {
246
- if reflect .DeepEqual (v , v2 ) {
247
- found = true
248
- break
287
+ for j := 1 ; j <= n ; j ++ {
288
+ for i := 1 ; i <= m ; i ++ {
289
+ if reflect .DeepEqual (s [i - 1 ], t [j - 1 ]) {
290
+ d [i ][j ] = d [i - 1 ][j - 1 ] // no op required
291
+ } else {
292
+ del := d [i - 1 ][j ] + 1
293
+ add := d [i ][j - 1 ] + 1
294
+ rep := d [i - 1 ][j - 1 ] + 1
295
+ d [i ][j ] = min (rep , min (add , del ))
249
296
}
250
297
}
251
- if ! found {
252
- retval = append (retval , NewPatch ("add" , makePath (p , i ), v ))
253
- }
254
298
}
255
299
256
- return retval
300
+ return backtrace (s , t , p , m , n , d )
301
+ }
302
+
303
+ func min (x int , y int ) int {
304
+ if y < x {
305
+ return y
306
+ }
307
+ return x
308
+ }
309
+
310
+ func backtrace (s , t []interface {}, p string , i int , j int , matrix [][]int ) []Operation {
311
+ if i > 0 && matrix [i - 1 ][j ]+ 1 == matrix [i ][j ] {
312
+ return append (backtrace (s , t , p , i - 1 , j , matrix ), NewPatch ("remove" , makePath (p , i - 1 ), nil ))
313
+ }
314
+ if j > 0 && matrix [i ][j - 1 ]+ 1 == matrix [i ][j ] {
315
+ return append (backtrace (s , t , p , i , j - 1 , matrix ), NewPatch ("add" , makePath (p , i ), t [j - 1 ]))
316
+ }
317
+ if i > 0 && j > 0 && matrix [i - 1 ][j - 1 ]+ 1 == matrix [i ][j ] {
318
+ if isBasicType (s [0 ]) {
319
+ return append (backtrace (s , t , p , i - 1 , j - 1 , matrix ), NewPatch ("replace" , makePath (p , i - 1 ), t [j - 1 ]))
320
+ }
321
+
322
+ p2 , _ := handleValues (s [j - 1 ], t [j - 1 ], makePath (p , i - 1 ), []Operation {})
323
+ return append (backtrace (s , t , p , i - 1 , j - 1 , matrix ), p2 ... )
324
+ }
325
+ if i > 0 && j > 0 && matrix [i - 1 ][j - 1 ] == matrix [i ][j ] {
326
+ return backtrace (s , t , p , i - 1 , j - 1 , matrix )
327
+ }
328
+ return []Operation {}
257
329
}
0 commit comments