@@ -74,18 +74,32 @@ function diff(obj1, obj2, pathConverter) {
7474 return arr ;
7575 } ) ;
7676
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} ) {
7881 var obj1Keys = Object . keys ( obj1 ) ;
7982 var obj1KeysLength = obj1Keys . length ;
8083 var obj2Keys = Object . keys ( obj2 ) ;
8184 var obj2KeysLength = obj2Keys . length ;
8285 var path ;
8386
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
8498 for ( var i = 0 ; i < obj1KeysLength ; i ++ ) {
8599 var key = Array . isArray ( obj1 ) ? Number ( obj1Keys [ i ] ) : obj1Keys [ i ] ;
86100 if ( ! ( key in obj2 ) ) {
87- path = basePath . concat ( key ) ;
88- diffs . remove . push ( {
101+ path = basePathForRemoves . concat ( key ) ;
102+ permutation . remove . push ( {
89103 op : 'remove' ,
90104 path : pathConverter ( path ) ,
91105 } ) ;
@@ -94,57 +108,121 @@ function diff(obj1, obj2, pathConverter) {
94108
95109 for ( var i = 0 ; i < obj2KeysLength ; i ++ ) {
96110 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' ,
104127 path : pathConverter ( path ) ,
105- value : obj2Value ,
106128 } ) ;
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- }
124129 }
125- }
126130
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+ }
128145 }
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
130161 return finalDiffs . remove
131162 . reverse ( )
132163 . concat ( finalDiffs . replace )
133164 . 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+ }
134206}
135207
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 ;
144218}
145219
146220function jsonPatchPathConverter ( arrayPath ) {
147221 return [ '' ] . concat ( arrayPath ) . join ( '/' ) ;
148222}
149223
224+ function differentTypes ( a , b ) {
225+ return Object . prototype . toString . call ( a ) != Object . prototype . toString . call ( b ) ;
226+ }
227+
150228export { diff , jsonPatchPathConverter } ;
0 commit comments