@@ -47,6 +47,7 @@ module.exports = {
4747var REMOVE = 'remove' ;
4848var REPLACE = 'replace' ;
4949var ADD = 'add' ;
50+ var MOVE = 'move' ;
5051
5152function diffApply ( obj , diff , pathConverter ) {
5253 if ( ! obj || typeof obj != 'object' ) {
@@ -62,44 +63,99 @@ function diffApply(obj, diff, pathConverter) {
6263 var thisDiff = diff [ i ] ;
6364 var subObject = obj ;
6465 var thisOp = thisDiff . op ;
65- var thisPath = thisDiff . path ;
66- if ( pathConverter ) {
67- thisPath = pathConverter ( thisPath ) ;
68- if ( ! Array . isArray ( thisPath ) ) {
69- throw new Error ( 'pathConverter must return an array' ) ;
66+
67+ var thisPath = transformPath ( pathConverter , thisDiff . path ) ;
68+ var thisFromPath = thisDiff . from && transformPath ( pathConverter , thisDiff . from ) ;
69+ var toPath , toPathCopy , lastToProp , subToObject , valueToMove ;
70+
71+ if ( thisFromPath ) {
72+ // MOVE only, "fromPath" is effectively path and "path" is toPath
73+ toPath = thisPath ;
74+ thisPath = thisFromPath ;
75+
76+ toPathCopy = toPath . slice ( ) ;
77+ lastToProp = toPathCopy . pop ( ) ;
78+ prototypeCheck ( lastToProp ) ;
79+ if ( lastToProp == null ) {
80+ return false ;
7081 }
71- } else {
72- if ( ! Array . isArray ( thisPath ) ) {
73- throw new Error ( 'diff path must be an array, consider supplying a path converter' ) ;
82+
83+ var thisToProp ;
84+ while ( ( ( thisToProp = toPathCopy . shift ( ) ) ) != null ) {
85+ prototypeCheck ( thisToProp ) ;
86+ if ( ! ( thisToProp in subToObject ) ) {
87+ subToObject [ thisToProp ] = { } ;
88+ }
89+ subToObject = subToObject [ thisToProp ] ;
7490 }
7591 }
92+
7693 var pathCopy = thisPath . slice ( ) ;
7794 var lastProp = pathCopy . pop ( ) ;
95+ prototypeCheck ( lastProp ) ;
7896 if ( lastProp == null ) {
7997 return false ;
8098 }
99+
81100 var thisProp ;
82101 while ( ( ( thisProp = pathCopy . shift ( ) ) ) != null ) {
102+ prototypeCheck ( thisProp ) ;
83103 if ( ! ( thisProp in subObject ) ) {
84104 subObject [ thisProp ] = { } ;
85105 }
86106 subObject = subObject [ thisProp ] ;
87107 }
88- if ( thisOp === REMOVE || thisOp === REPLACE ) {
108+ if ( thisOp === REMOVE || thisOp === REPLACE || thisOp === MOVE ) {
109+ var path = thisOp === MOVE ? thisDiff . from : thisDiff . path ;
89110 if ( ! subObject . hasOwnProperty ( lastProp ) ) {
90- throw new Error ( [ 'expected to find property' , thisDiff . path , 'in object' , obj ] . join ( ' ' ) ) ;
111+ throw new Error ( [ 'expected to find property' , path , 'in object' , obj ] . join ( ' ' ) ) ;
91112 }
92113 }
93- if ( thisOp === REMOVE ) {
114+ if ( thisOp === REMOVE || thisOp === MOVE ) {
115+ if ( thisOp === MOVE ) {
116+ valueToMove = subObject [ lastProp ] ;
117+ }
94118 Array . isArray ( subObject ) ? subObject . splice ( lastProp , 1 ) : delete subObject [ lastProp ] ;
95119 }
96120 if ( thisOp === REPLACE || thisOp === ADD ) {
97121 subObject [ lastProp ] = thisDiff . value ;
98122 }
123+
124+ if ( thisOp === MOVE ) {
125+ subObject [ lastToProp ] = valueToMove ;
126+ }
99127 }
100128 return subObject ;
101129}
102130
131+ function transformPath ( pathConverter , thisPath ) {
132+ if ( pathConverter ) {
133+ thisPath = pathConverter ( thisPath ) ;
134+ if ( ! Array . isArray ( thisPath ) ) {
135+ throw new Error ( [
136+ 'pathConverter must return an array, returned:' ,
137+ thisPath ,
138+ ] . join ( ' ' ) ) ;
139+ }
140+ } else {
141+ if ( ! Array . isArray ( thisPath ) ) {
142+ throw new Error ( [
143+ 'diff path' ,
144+ thisPath ,
145+ 'must be an array, consider supplying a path converter' ]
146+ . join ( ' ' ) ) ;
147+ }
148+ }
149+ return thisPath ;
150+ }
151+
103152function jsonPatchPathConverter ( stringPath ) {
104153 return stringPath . split ( '/' ) . slice ( 1 ) ;
105154}
155+
156+ function prototypeCheck ( prop ) {
157+ // coercion is intentional to catch prop values like `['__proto__']`
158+ if ( prop == '__proto__' || prop == 'constructor' || prop == 'prototype' ) {
159+ throw new Error ( 'setting of prototype values not supported' ) ;
160+ }
161+ }
0 commit comments