1/* 2 const obj1 = {a: 3, b: 5}; 3 diffApply(obj1, 4 [ 5 { "op": "remove", "path": ['b'] }, 6 { "op": "replace", "path": ['a'], "value": 4 }, 7 { "op": "add", "path": ['c'], "value": 5 } 8 ] 9 ); 10 obj1; // {a: 4, c: 5} 11 12 // using converter to apply jsPatch standard paths 13 // see http://jsonpatch.com 14 import {diff, jsonPatchPathConverter} from 'just-diff' 15 const obj2 = {a: 3, b: 5}; 16 diffApply(obj2, [ 17 { "op": "remove", "path": '/b' }, 18 { "op": "replace", "path": '/a', "value": 4 } 19 { "op": "add", "path": '/c', "value": 5 } 20 ], jsonPatchPathConverter); 21 obj2; // {a: 4, c: 5} 22 23 // arrays 24 const obj3 = {a: 4, b: [1, 2, 3]}; 25 diffApply(obj3, [ 26 { "op": "replace", "path": ['a'], "value": 3 } 27 { "op": "replace", "path": ['b', 2], "value": 4 } 28 { "op": "add", "path": ['b', 3], "value": 9 } 29 ]); 30 obj3; // {a: 3, b: [1, 2, 4, 9]} 31 32 // nested paths 33 const obj4 = {a: 4, b: {c: 3}}; 34 diffApply(obj4, [ 35 { "op": "replace", "path": ['a'], "value": 5 } 36 { "op": "remove", "path": ['b', 'c']} 37 { "op": "add", "path": ['b', 'd'], "value": 4 } 38 ]); 39 obj4; // {a: 5, b: {d: 4}} 40*/ 41 42var REMOVE = 'remove'; 43var REPLACE = 'replace'; 44var ADD = 'add'; 45var MOVE = 'move'; 46 47function diffApply(obj, diff, pathConverter) { 48 if (!obj || typeof obj != 'object') { 49 throw new Error('base object must be an object or an array'); 50 } 51 52 if (!Array.isArray(diff)) { 53 throw new Error('diff must be an array'); 54 } 55 56 var diffLength = diff.length; 57 for (var i = 0; i < diffLength; i++) { 58 var thisDiff = diff[i]; 59 var subObject = obj; 60 var thisOp = thisDiff.op; 61 62 var thisPath = transformPath(pathConverter, thisDiff.path); 63 var thisFromPath = thisDiff.from && transformPath(pathConverter, thisDiff.from); 64 var toPath, toPathCopy, lastToProp, subToObject, valueToMove; 65 66 if (thisFromPath) { 67 // MOVE only, "fromPath" is effectively path and "path" is toPath 68 toPath = thisPath; 69 thisPath = thisFromPath; 70 71 toPathCopy = toPath.slice(); 72 lastToProp = toPathCopy.pop(); 73 prototypeCheck(lastToProp); 74 if (lastToProp == null) { 75 return false; 76 } 77 78 var thisToProp; 79 while (((thisToProp = toPathCopy.shift())) != null) { 80 prototypeCheck(thisToProp); 81 if (!(thisToProp in subToObject)) { 82 subToObject[thisToProp] = {}; 83 } 84 subToObject = subToObject[thisToProp]; 85 } 86 } 87 88 var pathCopy = thisPath.slice(); 89 var lastProp = pathCopy.pop(); 90 prototypeCheck(lastProp); 91 if (lastProp == null) { 92 return false; 93 } 94 95 var thisProp; 96 while (((thisProp = pathCopy.shift())) != null) { 97 prototypeCheck(thisProp); 98 if (!(thisProp in subObject)) { 99 subObject[thisProp] = {}; 100 } 101 subObject = subObject[thisProp]; 102 } 103 if (thisOp === REMOVE || thisOp === REPLACE || thisOp === MOVE) { 104 var path = thisOp === MOVE ? thisDiff.from : thisDiff.path; 105 if (!subObject.hasOwnProperty(lastProp)) { 106 throw new Error(['expected to find property', path, 'in object', obj].join(' ')); 107 } 108 } 109 if (thisOp === REMOVE || thisOp === MOVE) { 110 if (thisOp === MOVE) { 111 valueToMove = subObject[lastProp]; 112 } 113 Array.isArray(subObject) ? subObject.splice(lastProp, 1) : delete subObject[lastProp]; 114 } 115 if (thisOp === REPLACE || thisOp === ADD) { 116 subObject[lastProp] = thisDiff.value; 117 } 118 119 if (thisOp === MOVE) { 120 subObject[lastToProp] = valueToMove; 121 } 122 } 123 return subObject; 124} 125 126function transformPath(pathConverter, thisPath) { 127 if(pathConverter) { 128 thisPath = pathConverter(thisPath); 129 if(!Array.isArray(thisPath)) { 130 throw new Error([ 131 'pathConverter must return an array, returned:', 132 thisPath, 133 ].join(' ')); 134 } 135 } else { 136 if(!Array.isArray(thisPath)) { 137 throw new Error([ 138 'diff path', 139 thisPath, 140 'must be an array, consider supplying a path converter'] 141 .join(' ')); 142 } 143 } 144 return thisPath; 145} 146 147function jsonPatchPathConverter(stringPath) { 148 return stringPath.split('/').slice(1); 149} 150 151function prototypeCheck(prop) { 152 // coercion is intentional to catch prop values like `['__proto__']` 153 if (prop == '__proto__' || prop == 'constructor' || prop == 'prototype') { 154 throw new Error('setting of prototype values not supported'); 155 } 156} 157 158export {diffApply, jsonPatchPathConverter}; 159