1module.exports = { 2 diffApply: diffApply, 3 jsonPatchPathConverter: jsonPatchPathConverter, 4}; 5 6/* 7 const obj1 = {a: 3, b: 5}; 8 diffApply(obj1, 9 [ 10 { "op": "remove", "path": ['b'] }, 11 { "op": "replace", "path": ['a'], "value": 4 }, 12 { "op": "add", "path": ['c'], "value": 5 } 13 ] 14 ); 15 obj1; // {a: 4, c: 5} 16 17 // using converter to apply jsPatch standard paths 18 // see http://jsonpatch.com 19 import {diff, jsonPatchPathConverter} from 'just-diff' 20 const obj2 = {a: 3, b: 5}; 21 diffApply(obj2, [ 22 { "op": "remove", "path": '/b' }, 23 { "op": "replace", "path": '/a', "value": 4 } 24 { "op": "add", "path": '/c', "value": 5 } 25 ], jsonPatchPathConverter); 26 obj2; // {a: 4, c: 5} 27 28 // arrays 29 const obj3 = {a: 4, b: [1, 2, 3]}; 30 diffApply(obj3, [ 31 { "op": "replace", "path": ['a'], "value": 3 } 32 { "op": "replace", "path": ['b', 2], "value": 4 } 33 { "op": "add", "path": ['b', 3], "value": 9 } 34 ]); 35 obj3; // {a: 3, b: [1, 2, 4, 9]} 36 37 // nested paths 38 const obj4 = {a: 4, b: {c: 3}}; 39 diffApply(obj4, [ 40 { "op": "replace", "path": ['a'], "value": 5 } 41 { "op": "remove", "path": ['b', 'c']} 42 { "op": "add", "path": ['b', 'd'], "value": 4 } 43 ]); 44 obj4; // {a: 5, b: {d: 4}} 45*/ 46 47var REMOVE = 'remove'; 48var REPLACE = 'replace'; 49var ADD = 'add'; 50var MOVE = 'move'; 51 52function diffApply(obj, diff, pathConverter) { 53 if (!obj || typeof obj != 'object') { 54 throw new Error('base object must be an object or an array'); 55 } 56 57 if (!Array.isArray(diff)) { 58 throw new Error('diff must be an array'); 59 } 60 61 var diffLength = diff.length; 62 for (var i = 0; i < diffLength; i++) { 63 var thisDiff = diff[i]; 64 var subObject = obj; 65 var thisOp = thisDiff.op; 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; 81 } 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]; 90 } 91 } 92 93 var pathCopy = thisPath.slice(); 94 var lastProp = pathCopy.pop(); 95 prototypeCheck(lastProp); 96 if (lastProp == null) { 97 return false; 98 } 99 100 var thisProp; 101 while (((thisProp = pathCopy.shift())) != null) { 102 prototypeCheck(thisProp); 103 if (!(thisProp in subObject)) { 104 subObject[thisProp] = {}; 105 } 106 subObject = subObject[thisProp]; 107 } 108 if (thisOp === REMOVE || thisOp === REPLACE || thisOp === MOVE) { 109 var path = thisOp === MOVE ? thisDiff.from : thisDiff.path; 110 if (!subObject.hasOwnProperty(lastProp)) { 111 throw new Error(['expected to find property', path, 'in object', obj].join(' ')); 112 } 113 } 114 if (thisOp === REMOVE || thisOp === MOVE) { 115 if (thisOp === MOVE) { 116 valueToMove = subObject[lastProp]; 117 } 118 Array.isArray(subObject) ? subObject.splice(lastProp, 1) : delete subObject[lastProp]; 119 } 120 if (thisOp === REPLACE || thisOp === ADD) { 121 subObject[lastProp] = thisDiff.value; 122 } 123 124 if (thisOp === MOVE) { 125 subObject[lastToProp] = valueToMove; 126 } 127 } 128 return subObject; 129} 130 131function 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 152function jsonPatchPathConverter(stringPath) { 153 return stringPath.split('/').slice(1); 154} 155 156function 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} 162