@@ -74,18 +74,32 @@ function diff(obj1, obj2, pathConverter) {
74
74
return arr ;
75
75
} ) ;
76
76
77
- function getDiff ( obj1 , obj2 , basePath , diffs ) {
77
+ // we will gather all permutations and return the one with the fewest diffs
78
+ var permutations = [ { remove : [ ] , replace : [ ] , add : [ ] } ] ;
79
+
80
+ function getDiff ( { obj1, obj2, basePath, basePathForRemoves, permutation} ) {
78
81
var obj1Keys = Object . keys ( obj1 ) ;
79
82
var obj1KeysLength = obj1Keys . length ;
80
83
var obj2Keys = Object . keys ( obj2 ) ;
81
84
var obj2KeysLength = obj2Keys . length ;
82
85
var path ;
83
86
87
+ var newPermutation ;
88
+
89
+ var lengthDelta = obj1 . length - obj2 . length ;
90
+ // if both objects are arrays and obj1 length > obj2 length
91
+ // we create an additional permutation that trims obj1 from left
92
+ if ( Array . isArray ( obj1 ) && Array . isArray ( obj2 ) && lengthDelta > 0 ) {
93
+ newPermutation = clonePermutation ( permutation ) ;
94
+ permutations . push ( newPermutation ) ;
95
+ }
96
+
97
+ // trim from right
84
98
for ( var i = 0 ; i < obj1KeysLength ; i ++ ) {
85
99
var key = Array . isArray ( obj1 ) ? Number ( obj1Keys [ i ] ) : obj1Keys [ i ] ;
86
100
if ( ! ( key in obj2 ) ) {
87
- path = basePath . concat ( key ) ;
88
- diffs . remove . push ( {
101
+ path = basePathForRemoves . concat ( key ) ;
102
+ permutation . remove . push ( {
89
103
op : 'remove' ,
90
104
path : pathConverter ( path ) ,
91
105
} ) ;
@@ -94,57 +108,121 @@ function diff(obj1, obj2, pathConverter) {
94
108
95
109
for ( var i = 0 ; i < obj2KeysLength ; i ++ ) {
96
110
var key = Array . isArray ( obj2 ) ? Number ( obj2Keys [ i ] ) : obj2Keys [ i ] ;
97
- var obj1AtKey = obj1 [ key ] ;
98
- var obj2AtKey = obj2 [ key ] ;
99
- if ( ! ( key in obj1 ) ) {
100
- path = basePath . concat ( key ) ;
101
- var obj2Value = obj2 [ key ] ;
102
- diffs . add . push ( {
103
- op : 'add' ,
111
+ pushReplaces ( {
112
+ key,
113
+ obj1,
114
+ obj2,
115
+ path : basePath . concat ( key ) ,
116
+ pathForRemoves : basePath . concat ( key ) ,
117
+ permutation,
118
+ } ) ;
119
+ }
120
+
121
+ // if we created a new permutation above it means we should also try trimming from left
122
+ if ( newPermutation ) {
123
+ for ( var i = 0 ; i < lengthDelta ; i ++ ) {
124
+ path = basePathForRemoves . concat ( i ) ;
125
+ newPermutation . remove . push ( {
126
+ op : 'remove' ,
104
127
path : pathConverter ( path ) ,
105
- value : obj2Value ,
106
128
} ) ;
107
- } else if ( obj1AtKey !== obj2AtKey ) {
108
- if (
109
- Object ( obj1AtKey ) !== obj1AtKey ||
110
- Object ( obj2AtKey ) !== obj2AtKey
111
- ) {
112
- path = pushReplace ( path , basePath , key , diffs , pathConverter , obj2 ) ;
113
- } else {
114
- if (
115
- ! Object . keys ( obj1AtKey ) . length &&
116
- ! Object . keys ( obj2AtKey ) . length &&
117
- String ( obj1AtKey ) != String ( obj2AtKey )
118
- ) {
119
- path = pushReplace ( path , basePath , key , diffs , pathConverter , obj2 ) ;
120
- } else {
121
- getDiff ( obj1 [ key ] , obj2 [ key ] , basePath . concat ( key ) , diffs ) ;
122
- }
123
- }
124
129
}
125
- }
126
130
127
- return diffs ;
131
+ // now make a copy of obj1 with excess elements left trimmed and see if there any replaces
132
+ var obj1Trimmed = obj1 . slice ( lengthDelta ) ; for ( var i = 0 ; i < obj2KeysLength ; i ++ ) {
133
+ pushReplaces ( {
134
+ key : i ,
135
+ obj1 : obj1Trimmed ,
136
+ obj2,
137
+ path : basePath . concat ( i ) ,
138
+ // since list of removes are reversed before presenting result,
139
+ // we need to ignore existing parent removes when doing nested removes
140
+ pathForRemoves : basePath . concat ( i + lengthDelta ) ,
141
+ permutation : newPermutation ,
142
+ } ) ;
143
+ }
144
+ }
128
145
}
129
- const finalDiffs = getDiff ( obj1 , obj2 , [ ] , { remove : [ ] , replace : [ ] , add : [ ] } ) ;
146
+
147
+ getDiff ( {
148
+ obj1,
149
+ obj2,
150
+ basePath : [ ] ,
151
+ basePathForRemoves : [ ] ,
152
+ permutation : permutations [ 0 ] ,
153
+ } ) ;
154
+
155
+ // find the shortest permutation
156
+ var finalDiffs = permutations . sort (
157
+ ( a , b ) => diffStepCount ( a ) > diffStepCount ( b ) ? 1 : - 1
158
+ ) [ 0 ] ;
159
+
160
+ // reverse removes since we want to maintain indexes
130
161
return finalDiffs . remove
131
162
. reverse ( )
132
163
. concat ( finalDiffs . replace )
133
164
. concat ( finalDiffs . add ) ;
165
+
166
+ function pushReplaces ( { key, obj1, obj2, path, pathForRemoves, permutation} ) {
167
+ var obj1AtKey = obj1 [ key ] ;
168
+ var obj2AtKey = obj2 [ key ] ;
169
+
170
+ if ( ! ( key in obj1 ) && obj2AtKey ) {
171
+ var obj2Value = obj2AtKey ;
172
+ permutation . add . push ( {
173
+ op : 'add' ,
174
+ path : pathConverter ( path ) ,
175
+ value : obj2Value ,
176
+ } ) ;
177
+ } else if ( obj1AtKey !== obj2AtKey ) {
178
+ if ( Object ( obj1AtKey ) !== obj1AtKey ||
179
+ Object ( obj2AtKey ) !== obj2AtKey || differentTypes ( obj1AtKey , obj2AtKey )
180
+ ) {
181
+ pushReplace ( path , permutation , obj2AtKey ) ;
182
+ } else {
183
+ if ( ! Object . keys ( obj1AtKey ) . length &&
184
+ ! Object . keys ( obj2AtKey ) . length &&
185
+ String ( obj1AtKey ) != String ( obj2AtKey ) ) {
186
+ pushReplace ( path , permutation , obj2AtKey ) ;
187
+ } else {
188
+ getDiff ( {
189
+ obj1 : obj1 [ key ] ,
190
+ obj2 : obj2 [ key ] ,
191
+ basePath : path ,
192
+ basePathForRemoves : pathForRemoves ,
193
+ permutation} ) ;
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ function pushReplace ( path , diffs , newValue ) {
200
+ diffs . replace . push ( {
201
+ op : 'replace' ,
202
+ path : pathConverter ( path ) ,
203
+ value : newValue ,
204
+ } ) ;
205
+ }
134
206
}
135
207
136
- function pushReplace ( path , basePath , key , diffs , pathConverter , obj2 ) {
137
- path = basePath . concat ( key ) ;
138
- diffs . replace . push ( {
139
- op : 'replace' ,
140
- path : pathConverter ( path ) ,
141
- value : obj2 [ key ] ,
142
- } ) ;
143
- return path ;
208
+ function clonePermutation ( permutation ) {
209
+ return {
210
+ remove : permutation . remove . slice ( 0 ) ,
211
+ replace : permutation . replace . slice ( 0 ) ,
212
+ add : permutation . add . slice ( 0 ) ,
213
+ } ;
214
+ }
215
+
216
+ function diffStepCount ( permutation ) {
217
+ return permutation . remove . length + permutation . replace . length + permutation . add . length ;
144
218
}
145
219
146
220
function jsonPatchPathConverter ( arrayPath ) {
147
221
return [ '' ] . concat ( arrayPath ) . join ( '/' ) ;
148
222
}
149
223
224
+ function differentTypes ( a , b ) {
225
+ return Object . prototype . toString . call ( a ) != Object . prototype . toString . call ( b ) ;
226
+ }
227
+
150
228
export { diff , jsonPatchPathConverter } ;
0 commit comments