diff --git a/commonjs/duplex.js b/commonjs/duplex.js index 1603bace..e8f5e3d8 100644 --- a/commonjs/duplex.js +++ b/commonjs/duplex.js @@ -1,3 +1,14 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; Object.defineProperty(exports, "__esModule", { value: true }); /*! * https://github.com/Starcounter-Jack/JSON-Patch @@ -5,6 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); * MIT license */ var helpers_js_1 = require("./helpers.js"); +var lodash_difference_js_1 = require("./lodash-difference.js"); var core_js_1 = require("./core.js"); var beforeDict = new WeakMap(); var Mirror = /** @class */ (function () { @@ -113,6 +125,77 @@ function generate(observer, invertible) { return temp; } exports.generate = generate; +/* + * 'compareArrays' doesnt do a good job with ordering, + * if bad ordering was detected, or bad job, return false, and revert + * to old code. its most likely that there isnt much added value anyway + * + */ +function testOrder(arr1, arr2, patches) { + // we dont want to mess up with arr1 + // the patches are just remove / add so need to clone deep + var clonedArr1 = Array.from(arr1); + core_js_1.applyPatch(clonedArr1, patches); + if (clonedArr1.length !== arr2.length) { + return false; + } + for (var index = 0; index < arr2.length; index++) { + if (clonedArr1[index] !== arr2[index]) { + return false; + } + } + return true; +} +/* + * return array efficient array patches when possible. + * in frequenct cases of arrays additions or removals, where an element was removed, or added. + * and thats the only difference between the arrays, and all other elements are the exact same (===) + * then the code bellow can do a great job and having a very small number of patches. + * in some cases it will revert back to the old behaviour. + * + */ +function compareArrays(arr1, arr2, path, invertible) { + if (arr1.length === arr2.length) { + return []; + } + var diff = lodash_difference_js_1.default(arr1, arr2); + if (diff.length === arr1.length) { + // this means that the the arrays are completly different + // and there is no added value in this function - revert to old behaviour + return []; + } + var removePatches = []; + diff.forEach(function (value) { + var index = arr1.indexOf(value); + var op = 'remove'; + removePatches.push({ + op: op, + path: "/" + index + }); + if (invertible) { + removePatches.push({ + op: 'test', + path: "/" + index, + value: value + }); + } + }); + diff = lodash_difference_js_1.default(arr2, arr1); + var addPatches = diff.map(function (value) { + var index = arr2.indexOf(value); + var op = 'add'; + return { + op: op, + value: value, + path: "/" + index + }; + }); + var finalPatches = removePatches.reverse().concat(addPatches); + if (testOrder(arr1, arr2, finalPatches)) { + return finalPatches.map(function (p) { return (__assign({}, p, { path: path + p.path })); }); + } + return []; +} // Dirty check if obj is different from mirror, generate patches and update mirror function _generate(mirror, obj, patches, path, invertible) { if (obj === mirror) { @@ -125,6 +208,12 @@ function _generate(mirror, obj, patches, path, invertible) { var oldKeys = helpers_js_1._objectKeys(mirror); var changed = false; var deleted = false; + if (Array.isArray(mirror) && Array.isArray(obj)) { + var newPatches = compareArrays(mirror, obj, path, invertible); + if (newPatches.length) { + return newPatches.forEach(function (p) { return patches.push(p); }); + } + } //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" for (var t = oldKeys.length - 1; t >= 0; t--) { var key = oldKeys[t]; diff --git a/commonjs/lodash-difference.js b/commonjs/lodash-difference.js new file mode 100644 index 00000000..3301e941 --- /dev/null +++ b/commonjs/lodash-difference.js @@ -0,0 +1,61 @@ +Object.defineProperty(exports, "__esModule", { value: true }); +function baseIsNaN(value) { + return value !== value; +} +function strictIndexOf(array, value, fromIndex) { + var index = fromIndex - 1; + var length = array.length; + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; +} +function baseFindIndex(array, predicate, fromIndex, fromRight) { + var length = array.length; + var index = fromIndex + (fromRight ? 1 : -1); + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index; + } + } + return -1; +} +function baseIndexOf(array, value, fromIndex) { + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex, false); +} +function arrayIncludes(array, value) { + var length = array == null ? 0 : array.length; + return !!length && baseIndexOf(array, value, 0) > -1; +} +function lodashDifference(array, values) { + var includes = arrayIncludes; + var isCommon = true; + var result = []; + var valuesLength = values.length; + if (!array.length) { + return result; + } + outer: for (var _i = 0, array_1 = array; _i < array_1.length; _i++) { + var value = array_1[_i]; + var computed = value; + value = (value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var valuesIndex = valuesLength; + while (valuesIndex--) { + if (values[valuesIndex] === computed) { + continue outer; + } + } + result.push(value); + } + else if (!includes(values, computed)) { + result.push(value); + } + } + return result; +} +exports.default = lodashDifference; diff --git a/dist/fast-json-patch.js b/dist/fast-json-patch.js index 6641831c..bdaf2520 100644 --- a/dist/fast-json-patch.js +++ b/dist/fast-json-patch.js @@ -1,4 +1,4 @@ -/*! fast-json-patch, version: 3.0.0-1 */ +/*! fast-json-patch, version: 3.0.2 */ var jsonpatch = /******/ (function(modules) { // webpackBootstrap /******/ // The module cache @@ -464,6 +464,9 @@ function applyOperation(document, operation, validateOperation, mutateDocument, } while (true) { key = keys[t]; + if (key && key.indexOf('~') != -1) { + key = helpers_js_1.unescapePathComponent(key); + } if (banPrototypeModifications && key == '__proto__') { throw new TypeError('JSON-Patch: modifying `__proto__` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README'); } @@ -505,9 +508,6 @@ function applyOperation(document, operation, validateOperation, mutateDocument, } } else { - if (key && key.indexOf('~') != -1) { - key = helpers_js_1.unescapePathComponent(key); - } if (t >= len) { var returnValue = objOps[operation.op].call(operation, obj, key, document); // Apply patch if (returnValue.test === false) { @@ -519,7 +519,7 @@ function applyOperation(document, operation, validateOperation, mutateDocument, obj = obj[key]; // If we have more keys in the path, but the next value isn't a non-null object, // throw an OPERATION_PATH_UNRESOLVABLE error instead of iterating again. - if (t < len && (!obj || typeof obj !== "object")) { + if (validateOperation && t < len && (!obj || typeof obj !== "object")) { throw new exports.JsonPatchError('Cannot perform operation at the desired path', 'OPERATION_PATH_UNRESOLVABLE', index, operation, document); } } @@ -739,6 +739,17 @@ exports.unescapePathComponent = helpers.unescapePathComponent; /* 3 */ /***/ (function(module, exports, __webpack_require__) { +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; Object.defineProperty(exports, "__esModule", { value: true }); /*! * https://github.com/Starcounter-Jack/JSON-Patch @@ -746,6 +757,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); * MIT license */ var helpers_js_1 = __webpack_require__(0); +var lodash_difference_js_1 = __webpack_require__(4); var core_js_1 = __webpack_require__(1); var beforeDict = new WeakMap(); var Mirror = /** @class */ (function () { @@ -854,6 +866,77 @@ function generate(observer, invertible) { return temp; } exports.generate = generate; +/* + * 'compareArrays' doesnt do a good job with ordering, + * if bad ordering was detected, or bad job, return false, and revert + * to old code. its most likely that there isnt much added value anyway + * + */ +function testOrder(arr1, arr2, patches) { + // we dont want to mess up with arr1 + // the patches are just remove / add so need to clone deep + var clonedArr1 = Array.from(arr1); + core_js_1.applyPatch(clonedArr1, patches); + if (clonedArr1.length !== arr2.length) { + return false; + } + for (var index = 0; index < arr2.length; index++) { + if (clonedArr1[index] !== arr2[index]) { + return false; + } + } + return true; +} +/* + * return array efficient array patches when possible. + * in frequenct cases of arrays additions or removals, where an element was removed, or added. + * and thats the only difference between the arrays, and all other elements are the exact same (===) + * then the code bellow can do a great job and having a very small number of patches. + * in some cases it will revert back to the old behaviour. + * + */ +function compareArrays(arr1, arr2, path, invertible) { + if (arr1.length === arr2.length) { + return []; + } + var diff = lodash_difference_js_1.default(arr1, arr2); + if (diff.length === arr1.length) { + // this means that the the arrays are completly different + // and there is no added value in this function - revert to old behaviour + return []; + } + var removePatches = []; + diff.forEach(function (value) { + var index = arr1.indexOf(value); + var op = 'remove'; + removePatches.push({ + op: op, + path: "/" + index + }); + if (invertible) { + removePatches.push({ + op: 'test', + path: "/" + index, + value: value + }); + } + }); + diff = lodash_difference_js_1.default(arr2, arr1); + var addPatches = diff.map(function (value) { + var index = arr2.indexOf(value); + var op = 'add'; + return { + op: op, + value: value, + path: "/" + index + }; + }); + var finalPatches = removePatches.reverse().concat(addPatches); + if (testOrder(arr1, arr2, finalPatches)) { + return finalPatches.map(function (p) { return (__assign({}, p, { path: path + p.path })); }); + } + return []; +} // Dirty check if obj is different from mirror, generate patches and update mirror function _generate(mirror, obj, patches, path, invertible) { if (obj === mirror) { @@ -866,13 +949,19 @@ function _generate(mirror, obj, patches, path, invertible) { var oldKeys = helpers_js_1._objectKeys(mirror); var changed = false; var deleted = false; + if (Array.isArray(mirror) && Array.isArray(obj)) { + var newPatches = compareArrays(mirror, obj, path, invertible); + if (newPatches.length) { + return newPatches.forEach(function (p) { return patches.push(p); }); + } + } //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" for (var t = oldKeys.length - 1; t >= 0; t--) { var key = oldKeys[t]; var oldVal = mirror[key]; if (helpers_js_1.hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) { var newVal = obj[key]; - if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) { + if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null && Array.isArray(oldVal) === Array.isArray(newVal)) { _generate(oldVal, newVal, patches, path + "/" + helpers_js_1.escapePathComponent(key), invertible); } else { @@ -922,5 +1011,72 @@ function compare(tree1, tree2, invertible) { exports.compare = compare; +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + +Object.defineProperty(exports, "__esModule", { value: true }); +function baseIsNaN(value) { + return value !== value; +} +function strictIndexOf(array, value, fromIndex) { + var index = fromIndex - 1; + var length = array.length; + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; +} +function baseFindIndex(array, predicate, fromIndex, fromRight) { + var length = array.length; + var index = fromIndex + (fromRight ? 1 : -1); + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index; + } + } + return -1; +} +function baseIndexOf(array, value, fromIndex) { + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex, false); +} +function arrayIncludes(array, value) { + var length = array == null ? 0 : array.length; + return !!length && baseIndexOf(array, value, 0) > -1; +} +function lodashDifference(array, values) { + var includes = arrayIncludes; + var isCommon = true; + var result = []; + var valuesLength = values.length; + if (!array.length) { + return result; + } + outer: for (var _i = 0, array_1 = array; _i < array_1.length; _i++) { + var value = array_1[_i]; + var computed = value; + value = (value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var valuesIndex = valuesLength; + while (valuesIndex--) { + if (values[valuesIndex] === computed) { + continue outer; + } + } + result.push(value); + } + else if (!includes(values, computed)) { + result.push(value); + } + } + return result; +} +exports.default = lodashDifference; + + /***/ }) /******/ ]); \ No newline at end of file diff --git a/dist/fast-json-patch.min.js b/dist/fast-json-patch.min.js index cb63eca8..10e31580 100644 --- a/dist/fast-json-patch.min.js +++ b/dist/fast-json-patch.min.js @@ -1,14 +1,14 @@ -/*! fast-json-patch, version: 3.0.0-1 */ +/*! fast-json-patch, version: 3.0.2 */ var jsonpatch=function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=2)}([function(e,t){ /*! * https://github.com/Starcounter-Jack/JSON-Patch * (c) 2017 Joachim Wester * MIT license */ -var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0});var o=Object.prototype.hasOwnProperty;function a(e,t){return o.call(e,t)}function i(e){if(Array.isArray(e)){for(var t=new Array(e.length),r=0;r=48&&t<=57))return!1;r++}return!0},t.escapePathComponent=p,t.unescapePathComponent=function(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")},t._getPathRecursive=u,t.getPath=function(e,t){if(e===t)return"/";var r=u(e,t);if(""===r)throw new Error("Object not found in root");return"/"+r},t.hasUndefined=function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var r=0,n=t.length;r=y){if(p&&"add"===r.op&&O>v.length)throw new t.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",h,r,e);if(!1===(l=a[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",h,r,e);return l}}else if(O&&-1!=O.indexOf("~")&&(O=n.unescapePathComponent(O)),w>=y){if(!1===(l=o[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",h,r,e);return l}if(v=v[O],w0)throw new t.JsonPatchError('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",r,e,a);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new t.JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&n.hasUndefined(e.value))throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",r,e,a);if(a)if("add"==e.op){var p=e.path.split("/").length,u=i.split("/").length;if(p!==u+1&&p!==u)throw new t.JsonPatchError("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",r,e,a)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==i)throw new t.JsonPatchError("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",r,e,a)}else if("move"===e.op||"copy"===e.op){var s=c([{op:"_get",path:e.from,value:void 0}],a);if(s&&"OPERATION_PATH_UNRESOLVABLE"===s.name)throw new t.JsonPatchError("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",r,e,a)}}function c(e,r,o){try{if(!Array.isArray(e))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(r)u(n._deepClone(r),n._deepClone(e),o||!0);else{o=o||s;for(var a=0;a=48&&t<=57))return!1;r++}return!0},t.escapePathComponent=p,t.unescapePathComponent=function(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")},t._getPathRecursive=u,t.getPath=function(e,t){if(e===t)return"/";var r=u(e,t);if(""===r)throw new Error("Object not found in root");return"/"+r},t.hasUndefined=function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var r=0,n=t.length;r=w){if(p&&"add"===r.op&&O>v.length)throw new t.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",h,r,e);if(!1===(l=a[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",h,r,e);return l}}else if(y>=w){if(!1===(l=o[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",h,r,e);return l}if(v=v[O],p&&y0)throw new t.JsonPatchError('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",r,e,a);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new t.JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&n.hasUndefined(e.value))throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",r,e,a);if(a)if("add"==e.op){var p=e.path.split("/").length,u=i.split("/").length;if(p!==u+1&&p!==u)throw new t.JsonPatchError("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",r,e,a)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==i)throw new t.JsonPatchError("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",r,e,a)}else if("move"===e.op||"copy"===e.op){var s=f([{op:"_get",path:e.from,value:void 0}],a);if(s&&"OPERATION_PATH_UNRESOLVABLE"===s.name)throw new t.JsonPatchError("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",r,e,a)}}function f(e,r,o){try{if(!Array.isArray(e))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(r)u(n._deepClone(r),n._deepClone(e),o||!0);else{o=o||s;for(var a=0;a0&&(e.patches=[],e.callback&&e.callback(n)),n}function s(e,t,r,o,a){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=n._objectKeys(t),p=n._objectKeys(e),u=!1,c=p.length-1;c>=0;c--){var f=e[l=p[c]];if(!n.hasOwnProperty(t,l)||void 0===t[l]&&void 0!==f&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(a&&r.push({op:"test",path:o+"/"+n.escapePathComponent(l),value:n._deepClone(f)}),r.push({op:"remove",path:o+"/"+n.escapePathComponent(l)}),u=!0):(a&&r.push({op:"test",path:o,value:e}),r.push({op:"replace",path:o,value:t}),!0);else{var h=t[l];"object"==typeof f&&null!=f&&"object"==typeof h&&null!=h?s(f,h,r,o+"/"+n.escapePathComponent(l),a):f!==h&&(!0,a&&r.push({op:"test",path:o+"/"+n.escapePathComponent(l),value:n._deepClone(f)}),r.push({op:"replace",path:o+"/"+n.escapePathComponent(l),value:n._deepClone(h)}))}}if(u||i.length!=p.length)for(c=0;c0&&(e.patches=[],e.callback&&e.callback(n)),n}function c(e,t,r,o){if(e.length===t.length)return[];var p=a.default(e,t);if(p.length===e.length)return[];var u=[];p.forEach(function(t){var r=e.indexOf(t);u.push({op:"remove",path:"/"+r}),o&&u.push({op:"test",path:"/"+r,value:t})});var s=(p=a.default(t,e)).map(function(e){return{op:"add",value:e,path:"/"+t.indexOf(e)}}),f=u.reverse().concat(s);return function(e,t,r){var n=Array.from(e);if(i.applyPatch(n,r),n.length!==t.length)return!1;for(var o=0;o=0;f--){var l=e[v=p[f]];if(!o.hasOwnProperty(t,v)||void 0===t[v]&&void 0!==l&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(a&&r.push({op:"test",path:n+"/"+o.escapePathComponent(v),value:o._deepClone(l)}),r.push({op:"remove",path:n+"/"+o.escapePathComponent(v)}),u=!0):(a&&r.push({op:"test",path:n,value:e}),r.push({op:"replace",path:n,value:t}),!0);else{var d=t[v];"object"==typeof l&&null!=l&&"object"==typeof d&&null!=d&&Array.isArray(l)===Array.isArray(d)?h(l,d,r,n+"/"+o.escapePathComponent(v),a):l!==d&&(!0,a&&r.push({op:"test",path:n+"/"+o.escapePathComponent(v),value:o._deepClone(l)}),r.push({op:"replace",path:n+"/"+o.escapePathComponent(v),value:o._deepClone(d)}))}}if(u||i.length!=p.length)for(f=0;f-1}Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){var r=n,o=[],a=t.length;if(!e.length)return o;e:for(var i=0,p=e;i= 0; t--) { var key = oldKeys[t]; diff --git a/module/lodash-difference.d.ts b/module/lodash-difference.d.ts new file mode 100644 index 00000000..21bae97c --- /dev/null +++ b/module/lodash-difference.d.ts @@ -0,0 +1,2 @@ +declare function lodashDifference(array: any, values: any): any[]; +export default lodashDifference; diff --git a/module/lodash-difference.mjs b/module/lodash-difference.mjs new file mode 100644 index 00000000..f8979363 --- /dev/null +++ b/module/lodash-difference.mjs @@ -0,0 +1,60 @@ +function baseIsNaN(value) { + return value !== value; +} +function strictIndexOf(array, value, fromIndex) { + var index = fromIndex - 1; + var length = array.length; + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; +} +function baseFindIndex(array, predicate, fromIndex, fromRight) { + var length = array.length; + var index = fromIndex + (fromRight ? 1 : -1); + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index; + } + } + return -1; +} +function baseIndexOf(array, value, fromIndex) { + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex, false); +} +function arrayIncludes(array, value) { + var length = array == null ? 0 : array.length; + return !!length && baseIndexOf(array, value, 0) > -1; +} +function lodashDifference(array, values) { + var includes = arrayIncludes; + var isCommon = true; + var result = []; + var valuesLength = values.length; + if (!array.length) { + return result; + } + outer: for (var _i = 0, array_1 = array; _i < array_1.length; _i++) { + var value = array_1[_i]; + var computed = value; + value = (value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var valuesIndex = valuesLength; + while (valuesIndex--) { + if (values[valuesIndex] === computed) { + continue outer; + } + } + result.push(value); + } + else if (!includes(values, computed)) { + result.push(value); + } + } + return result; +} +export default lodashDifference; diff --git a/src/duplex.ts b/src/duplex.ts index 093001ab..cb1ab816 100644 --- a/src/duplex.ts +++ b/src/duplex.ts @@ -4,6 +4,7 @@ * MIT license */ import { _deepClone, _objectKeys, escapePathComponent, hasOwnProperty } from './helpers.js'; +import lodashDifference from './lodash-difference.js' import { applyPatch, Operation } from './core.js'; export interface Observer { @@ -139,6 +140,84 @@ export function generate(observer: Observer, invertible = false): Ope return temp; } +/* + * 'compareArrays' doesnt do a good job with ordering, + * if bad ordering was detected, or bad job, return false, and revert + * to old code. its most likely that there isnt much added value anyway + * + */ + +function testOrder(arr1, arr2, patches) { + // we dont want to mess up with arr1 + // the patches are just remove / add so need to clone deep + let clonedArr1 = Array.from(arr1); + + applyPatch(clonedArr1, patches); + if (clonedArr1.length !== arr2.length) { + return false; + } + for (let index = 0; index < arr2.length; index++) { + if (clonedArr1[index] !== arr2[index]) { + return false; + } + } + return true; + +} + +/* + * return array efficient array patches when possible. + * in frequenct cases of arrays additions or removals, where an element was removed, or added. + * and thats the only difference between the arrays, and all other elements are the exact same (===) + * then the code bellow can do a great job and having a very small number of patches. + * in some cases it will revert back to the old behaviour. + * + */ + +function compareArrays(arr1, arr2, path, invertible) { + if (arr1.length === arr2.length) { + return []; + } + let diff = lodashDifference(arr1, arr2); + if (diff.length === arr1.length) { + // this means that the the arrays are completly different + // and there is no added value in this function - revert to old behaviour + return []; + } + let removePatches = []; + diff.forEach(value => { + const index = arr1.indexOf(value); + + const op = 'remove'; + removePatches.push({ + op, + path: "/" + index + }) + if (invertible) { + removePatches.push({ + op: 'test', + path: "/" + index, + value + }); + } + }); + diff = lodashDifference(arr2, arr1); + const addPatches = diff.map(value => { + const index = arr2.indexOf(value); + const op = 'add'; + return { + op, + value, + path: "/" + index + } + }); + const finalPatches = removePatches.reverse().concat(addPatches); + if (testOrder(arr1, arr2, finalPatches)) { + return finalPatches.map(p => ({ ...p, path: path + p.path })); + } + return [] +} + // Dirty check if obj is different from mirror, generate patches and update mirror function _generate(mirror, obj, patches, path, invertible) { if (obj === mirror) { @@ -154,6 +233,13 @@ function _generate(mirror, obj, patches, path, invertible) { var changed = false; var deleted = false; + if(Array.isArray(mirror) && Array.isArray(obj)) { + const newPatches = compareArrays(mirror, obj, path, invertible) + if (newPatches.length) { + return newPatches.forEach(p => patches.push(p)); + } + } + //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" for (var t = oldKeys.length - 1; t >= 0; t--) { diff --git a/src/lodash-difference.ts b/src/lodash-difference.ts new file mode 100644 index 00000000..81c66acb --- /dev/null +++ b/src/lodash-difference.ts @@ -0,0 +1,71 @@ + +function baseIsNaN(value) { + return value !== value +} + +function strictIndexOf(array, value, fromIndex) { + let index = fromIndex - 1 + const { length } = array + + while (++index < length) { + if (array[index] === value) { + return index + } + } + return -1 +} + +function baseFindIndex(array, predicate, fromIndex, fromRight) { + const { length } = array + let index = fromIndex + (fromRight ? 1 : -1) + + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index + } + } + return -1 +} + +function baseIndexOf(array, value, fromIndex) { + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex, false) +} + +function arrayIncludes(array, value) { + const length = array == null ? 0 : array.length + return !!length && baseIndexOf(array, value, 0) > -1 +} + +function lodashDifference(array, values ) { + let includes = arrayIncludes + let isCommon = true + const result = [] + const valuesLength = values.length + + if (!array.length) { + return result + } + outer: + for (let value of array) { + const computed = value + + value = ( value !== 0) ? value : 0 + if (isCommon && computed === computed) { + let valuesIndex = valuesLength + while (valuesIndex--) { + if (values[valuesIndex] === computed) { + continue outer + } + } + result.push(value) + } + else if (!includes(values, computed)) { + result.push(value) + } + } + return result +} + +export default lodashDifference; diff --git a/test/spec/duplexSpec.mjs b/test/spec/duplexSpec.mjs index 130389b6..642206ea 100644 --- a/test/spec/duplexSpec.mjs +++ b/test/spec/duplexSpec.mjs @@ -64,6 +64,36 @@ function variantIt(name, variants, fn) { }); } + +function randomArray(maxSize, maxValue) { + const size = Math.round(Math.random() * maxSize) + const arr = []; + for (let i = 0; i < size; i++) { + arr.push(Math.round(Math.random() * maxValue)) + } + return arr; +} + +function createTest(counter) { + // create a random arrays with random sizes and max value. + const arr1 = randomArray(20, 20); + const arr2 = randomArray(20, 20); + const testName = JSON.stringify(arr1) + " VS " + JSON.stringify(arr2); + it(counter + ". " + testName, () => { + const arr3 = JSON.parse(JSON.stringify(arr1)); + const patches = jsonpatch.compare(arr1, arr2); + jsonpatch.applyPatch(arr1, patches); + expect(arr2).toReallyEqual(arr1); + }) +} + +function nIt(n) { + for (let i = 1; i <= n; i++) { + createTest(i); + } + +} + function insertIf(condition, ...elements) { return condition ? elements : []; } @@ -1658,6 +1688,24 @@ describe('duplex', function() { ]); } }); + + + it('Diff array should remove not replace', function () { + const obj1 = { id: 1 }; + const obj2 = { id: 2 }; + const obj3 = { id: 3 }; + const objA = { + arr: [obj1, obj2, obj3] + }; + const objB = { arr: objA.arr.filter(o => o.id !== 1) } + const patches = jsonpatch.compare(objA, objB); + expect(patches).toEqual([ + { + op: "remove", path: '/arr/0' + } + ]) + }) + variantIt('Replacing an array with an object should be handled well', [ ['invertible = FALSE', false], ['invertible = TRUE', true] @@ -1993,6 +2041,18 @@ describe('duplex', function() { }); }); + describe('random', function() { + it('found a', () => { + const arr1 = [ 2, 10, 14, 2, 13]; + const arr2 = [ 8, 2, 19]; + const arr3 = JSON.parse(JSON.stringify(arr1)); + const patches = jsonpatch.compare(arr1, arr2); + jsonpatch.applyPatch(arr1, patches); + expect(arr2).toReallyEqual(arr1); + }); + nIt(1000); + }); + describe('compare', function() { it('should return patch difference between objects', function() { const obj = { diff --git a/test/spec/webpack/importSpec.build.js b/test/spec/webpack/importSpec.build.js index 55e8d38c..d91c9569 100644 --- a/test/spec/webpack/importSpec.build.js +++ b/test/spec/webpack/importSpec.build.js @@ -1,13 +1,7 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);var o={};n.r(o),n.d(o,"JsonPatchError",function(){return m}),n.d(o,"deepClone",function(){return y}),n.d(o,"getValueByPointer",function(){return A}),n.d(o,"applyOperation",function(){return _}),n.d(o,"applyPatch",function(){return E}),n.d(o,"applyReducer",function(){return g}),n.d(o,"validator",function(){return P}),n.d(o,"validate",function(){return x}),n.d(o,"_areEquals",function(){return T});var r={};n.r(r),n.d(r,"unobserve",function(){return C}),n.d(r,"observe",function(){return R}),n.d(r,"generate",function(){return I}),n.d(r,"compare",function(){return B});var i={};n.r(i),n.d(i,"JsonPatchError",function(){return w}),n.d(i,"deepClone",function(){return s}),n.d(i,"escapePathComponent",function(){return h}),n.d(i,"unescapePathComponent",function(){return l}),n.d(i,"default",function(){return S}),n.d(i,"getValueByPointer",function(){return A}),n.d(i,"applyOperation",function(){return _}),n.d(i,"applyPatch",function(){return E}),n.d(i,"applyReducer",function(){return g}),n.d(i,"validator",function(){return P}),n.d(i,"validate",function(){return x}),n.d(i,"_areEquals",function(){return T}),n.d(i,"unobserve",function(){return C}),n.d(i,"observe",function(){return R}),n.d(i,"generate",function(){return I}),n.d(i,"compare",function(){return B}); +!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);var r={};n.r(r),n.d(r,"JsonPatchError",function(){return y}),n.d(r,"deepClone",function(){return m}),n.d(r,"getValueByPointer",function(){return A}),n.d(r,"applyOperation",function(){return _}),n.d(r,"applyPatch",function(){return g}),n.d(r,"applyReducer",function(){return E}),n.d(r,"validator",function(){return P}),n.d(r,"validate",function(){return x}),n.d(r,"_areEquals",function(){return T});var o={};n.r(o),n.d(o,"unobserve",function(){return B}),n.d(o,"observe",function(){return S}),n.d(o,"generate",function(){return k}),n.d(o,"compare",function(){return J});var i={};n.r(i),n.d(i,"JsonPatchError",function(){return w}),n.d(i,"deepClone",function(){return h}),n.d(i,"escapePathComponent",function(){return d}),n.d(i,"unescapePathComponent",function(){return l}),n.d(i,"default",function(){return M}),n.d(i,"getValueByPointer",function(){return A}),n.d(i,"applyOperation",function(){return _}),n.d(i,"applyPatch",function(){return g}),n.d(i,"applyReducer",function(){return E}),n.d(i,"validator",function(){return P}),n.d(i,"validate",function(){return x}),n.d(i,"_areEquals",function(){return T}),n.d(i,"unobserve",function(){return B}),n.d(i,"observe",function(){return S}),n.d(i,"generate",function(){return k}),n.d(i,"compare",function(){return J}); /*! * https://github.com/Starcounter-Jack/JSON-Patch * (c) 2017 Joachim Wester * MIT license */ -var a,u=(a=function(e,t){return(a=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}a(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),p=Object.prototype.hasOwnProperty;function c(e,t){return p.call(e,t)}function f(e){if(Array.isArray(e)){for(var t=new Array(e.length),n=0;n=48&&t<=57))return!1;n++}return!0}function h(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function l(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function v(e,t){var n=[e];for(var o in t){var r="object"==typeof t[o]?JSON.stringify(t[o],null,2):t[o];void 0!==r&&n.push(o+": "+r)}return n.join("\n")}var w=function(e){function t(t,n,o,r,i){var a=this.constructor,u=e.call(this,v(t,{name:n,index:o,operation:r,tree:i}))||this;return u.name=n,u.index=o,u.operation=r,u.tree=i,Object.setPrototypeOf(u,a.prototype),u.message=v(t,{name:n,index:o,operation:r,tree:i}),u}return u(t,e),t}(Error),m=w,y=s,b={add:function(e,t,n){return e[t]=this.value,{newDocument:n}},remove:function(e,t,n){var o=e[t];return delete e[t],{newDocument:n,removed:o}},replace:function(e,t,n){var o=e[t];return e[t]=this.value,{newDocument:n,removed:o}},move:function(e,t,n){var o=A(n,this.path);o&&(o=s(o));var r=_(n,{op:"remove",path:this.from}).removed;return _(n,{op:"add",path:this.path,value:r}),{newDocument:n,removed:o}},copy:function(e,t,n){var o=A(n,this.from);return _(n,{op:"add",path:this.path,value:s(o)}),{newDocument:n}},test:function(e,t,n){return{newDocument:n,test:T(e[t],this.value)}},_get:function(e,t,n){return this.value=e[t],{newDocument:n}}},O={add:function(e,t,n){return d(t)?e.splice(t,0,this.value):e[t]=this.value,{newDocument:n,index:t}},remove:function(e,t,n){return{newDocument:n,removed:e.splice(t,1)[0]}},replace:function(e,t,n){var o=e[t];return e[t]=this.value,{newDocument:n,removed:o}},move:b.move,copy:b.copy,test:b.test,_get:b._get};function A(e,t){if(""==t)return e;var n={op:"_get",path:t};return _(e,n),n.value}function _(e,t,n,o,r,i){if(void 0===n&&(n=!1),void 0===o&&(o=!0),void 0===r&&(r=!0),void 0===i&&(i=0),n&&("function"==typeof n?n(t,0,e,t.path):P(t,0)),""===t.path){var a={newDocument:e};if("add"===t.op)return a.newDocument=t.value,a;if("replace"===t.op)return a.newDocument=t.value,a.removed=e,a;if("move"===t.op||"copy"===t.op)return a.newDocument=A(e,t.from),"move"===t.op&&(a.removed=e),a;if("test"===t.op){if(a.test=T(e,t.value),!1===a.test)throw new m("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a.newDocument=e,a}if("remove"===t.op)return a.removed=e,a.newDocument=null,a;if("_get"===t.op)return t.value=e,a;if(n)throw new m("Operation `op` property is not one of operations defined in RFC-6902","OPERATION_OP_INVALID",i,t,e);return a}o||(e=s(e));var u=(t.path||"").split("/"),p=e,c=1,f=u.length,h=void 0,v=void 0,w=void 0;for(w="function"==typeof n?n:P;;){if((v=u[c])&&-1!=v.indexOf("~")&&(v=l(v)),r&&"__proto__"==v)throw new TypeError("JSON-Patch: modifying `__proto__` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===h&&(void 0===p[v]?h=u.slice(0,c).join("/"):c==f-1&&(h=t.path),void 0!==h&&w(t,0,e,h)),c++,Array.isArray(p)){if("-"===v)v=p.length;else{if(n&&!d(v))throw new m("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",i,t,e);d(v)&&(v=~~v)}if(c>=f){if(n&&"add"===t.op&&v>p.length)throw new m("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",i,t,e);if(!1===(a=O[t.op].call(t,p,v,e)).test)throw new m("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}}else if(c>=f){if(!1===(a=b[t.op].call(t,p,v,e)).test)throw new m("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}if(p=p[v],n&&c0)throw new m('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new m("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new m("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var n=0,o=t.length;n0&&(e.patches=[],e.callback&&e.callback(o)),o}function L(e,t,n,o,r){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=f(t),a=f(e),u=!1,p=a.length-1;p>=0;p--){var d=e[v=a[p]];if(!c(t,v)||void 0===t[v]&&void 0!==d&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(r&&n.push({op:"test",path:o+"/"+h(v),value:s(d)}),n.push({op:"remove",path:o+"/"+h(v)}),u=!0):(r&&n.push({op:"test",path:o,value:e}),n.push({op:"replace",path:o,value:t}),!0);else{var l=t[v];"object"==typeof d&&null!=d&&"object"==typeof l&&null!=l&&Array.isArray(d)===Array.isArray(l)?L(d,l,n,o+"/"+h(v),r):d!==l&&(!0,r&&n.push({op:"test",path:o+"/"+h(v),value:s(d)}),n.push({op:"replace",path:o+"/"+h(v),value:s(l)}))}}if(u||i.length!=a.length)for(p=0;p=48&&t<=57))return!1;n++}return!0}function d(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function l(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function v(e,t){var n=[e];for(var r in t){var o="object"==typeof t[r]?JSON.stringify(t[r],null,2):t[r];void 0!==o&&n.push(r+": "+o)}return n.join("\n")}var w=function(e){function t(t,n,r,o,i){var a=this.constructor,u=e.call(this,v(t,{name:n,index:r,operation:o,tree:i}))||this;return u.name=n,u.index=r,u.operation=o,u.tree=i,Object.setPrototypeOf(u,a.prototype),u.message=v(t,{name:n,index:r,operation:o,tree:i}),u}return u(t,e),t}(Error),y=w,m=h,b={add:function(e,t,n){return e[t]=this.value,{newDocument:n}},remove:function(e,t,n){var r=e[t];return delete e[t],{newDocument:n,removed:r}},replace:function(e,t,n){var r=e[t];return e[t]=this.value,{newDocument:n,removed:r}},move:function(e,t,n){var r=A(n,this.path);r&&(r=h(r));var o=_(n,{op:"remove",path:this.from}).removed;return _(n,{op:"add",path:this.path,value:o}),{newDocument:n,removed:r}},copy:function(e,t,n){var r=A(n,this.from);return _(n,{op:"add",path:this.path,value:h(r)}),{newDocument:n}},test:function(e,t,n){return{newDocument:n,test:T(e[t],this.value)}},_get:function(e,t,n){return this.value=e[t],{newDocument:n}}},O={add:function(e,t,n){return s(t)?e.splice(t,0,this.value):e[t]=this.value,{newDocument:n,index:t}},remove:function(e,t,n){return{newDocument:n,removed:e.splice(t,1)[0]}},replace:function(e,t,n){var r=e[t];return e[t]=this.value,{newDocument:n,removed:r}},move:b.move,copy:b.copy,test:b.test,_get:b._get};function A(e,t){if(""==t)return e;var n={op:"_get",path:t};return _(e,n),n.value}function _(e,t,n,r,o,i){if(void 0===n&&(n=!1),void 0===r&&(r=!0),void 0===o&&(o=!0),void 0===i&&(i=0),n&&("function"==typeof n?n(t,0,e,t.path):P(t,0)),""===t.path){var a={newDocument:e};if("add"===t.op)return a.newDocument=t.value,a;if("replace"===t.op)return a.newDocument=t.value,a.removed=e,a;if("move"===t.op||"copy"===t.op)return a.newDocument=A(e,t.from),"move"===t.op&&(a.removed=e),a;if("test"===t.op){if(a.test=T(e,t.value),!1===a.test)throw new y("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a.newDocument=e,a}if("remove"===t.op)return a.removed=e,a.newDocument=null,a;if("_get"===t.op)return t.value=e,a;if(n)throw new y("Operation `op` property is not one of operations defined in RFC-6902","OPERATION_OP_INVALID",i,t,e);return a}r||(e=h(e));var u=(t.path||"").split("/"),p=e,c=1,f=u.length,d=void 0,v=void 0,w=void 0;for(w="function"==typeof n?n:P;;){if((v=u[c])&&-1!=v.indexOf("~")&&(v=l(v)),o&&"__proto__"==v)throw new TypeError("JSON-Patch: modifying `__proto__` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===d&&(void 0===p[v]?d=u.slice(0,c).join("/"):c==f-1&&(d=t.path),void 0!==d&&w(t,0,e,d)),c++,Array.isArray(p)){if("-"===v)v=p.length;else{if(n&&!s(v))throw new y("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",i,t,e);s(v)&&(v=~~v)}if(c>=f){if(n&&"add"===t.op&&v>p.length)throw new y("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",i,t,e);if(!1===(a=O[t.op].call(t,p,v,e)).test)throw new y("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}}else if(c>=f){if(!1===(a=b[t.op].call(t,p,v,e)).test)throw new y("Test operation failed","TEST_OPERATION_FAILED",i,t,e);return a}if(p=p[v],n&&c0)throw new y('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new y("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new y("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var n=0,r=t.length;n-1}var N=function(e,t){var n=D,r=[],o=t.length;if(!e.length)return r;e:for(var i=0,a=e;i0&&(e.patches=[],e.callback&&e.callback(r)),r}function U(e,t,n,r){var o=N(e,t);if(o.length===e.length)return[];var i=[];o.forEach(function(t){var n=e.indexOf(t);i.push({op:"remove",path:"/"+n}),r&&i.push({op:"test",path:"/"+n,value:t})});var a=(o=N(t,e)).map(function(e){return{op:"add",value:e,path:"/"+t.indexOf(e)}}),u=i.reverse().concat(a);return function(e,t,n){var r=e.map(function(e){return e});if(g(r,n),r.length!==t.length)return!1;for(var o=0;o=0;s--){var l=e[w=a[s]];if(!c(t,w)||void 0===t[w]&&void 0!==l&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(o&&n.push({op:"test",path:r+"/"+d(w),value:h(l)}),n.push({op:"remove",path:r+"/"+d(w)}),u=!0):(o&&n.push({op:"test",path:r,value:e}),n.push({op:"replace",path:r,value:t}),!0);else{var v=t[w];"object"==typeof l&&null!=l&&"object"==typeof v&&null!=v&&Array.isArray(l)===Array.isArray(v)?V(l,v,n,r+"/"+d(w),o):l!==v&&(!0,o&&n.push({op:"test",path:r+"/"+d(w),value:h(l)}),n.push({op:"replace",path:r+"/"+d(w),value:h(v)}))}}if(u||i.length!=a.length)for(s=0;s