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