From 1a271bf4cc0520ae938c3ba9acfef8e5b5490498 Mon Sep 17 00:00:00 2001 From: caohanyang Date: Tue, 22 Mar 2016 09:51:02 +0100 Subject: [PATCH] Update README.md --- README.md | 6 +- json-diff-rfc6902.js | 275 ++++++++++++++++++++++++++----------------- 2 files changed, 168 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 2d244e5..23bf79c 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,12 @@ _**Array**_ ## API ```js -var jpn = require('./JSON-Diff'); +var jdr = require('json-diff-rfc6902'); //diff the two JSON objects to get the pathes -var jpn_patch = jpn.diff(f_old, f_new); +var jdr_patch = jdr.diff(f_old, f_new); //apply the patches to the f_old object -jpn.apply(f_old, jpn_patch); +jdr.apply(f_old, jdr_patch); ``` ## TEST diff --git a/json-diff-rfc6902.js b/json-diff-rfc6902.js index 0c354f9..45c5e73 100644 --- a/json-diff-rfc6902.js +++ b/json-diff-rfc6902.js @@ -3,6 +3,8 @@ var applyPatches = require('./applyPatches'); var lcs = require('./LCS.js'); var unchangedArea = require('./unchangedArea.js'); var patchArea = require('./patchArea.js'); +var hashObject = require('./hashObject.js'); +// var hash = require('string-hash'); exports.diff = diff; exports.apply = apply; @@ -50,17 +52,15 @@ function generateValueDiff(oldJson, newJson, unchanged, patches, path) { // the endpoint if (newJson !== oldJson) { // console.log({ op: "replace", path: path, value: copy.clone(newJson)}); - patches.push({ op: "replace", path: path, value: newJson}); + patches.push(hashObject.hash({ op: "replace", path: path, value: newJson})); } } function generateArrayDiff(oldJson, newJson, unchanged, patches, path) { // console.log("--------This is Array-------------"); - console.time("mapSpeed"); - var x = map(hashArray, oldJson); - var y = map(hashArray, newJson); - console.timeEnd("mapSpeed"); + var x = hashObject.map(hashObject.hash, oldJson); + var y = hashObject.map(hashObject.hash, newJson); // Use LCS var tmpPatches = []; lcs.LCS(x, y, unchanged, tmpPatches, path); @@ -70,10 +70,6 @@ function generateArrayDiff(oldJson, newJson, unchanged, patches, path) { // console.log("--------Array complete-------"); } -function hashArray(obj) { - //Default hash - return JSON.stringify(obj); -} function generateObjectDiff(oldJson, newJson, unchanged, patches, path) { var oldKeys = Object.keys(oldJson); var newKeys = Object.keys(newJson); @@ -82,18 +78,16 @@ function generateObjectDiff(oldJson, newJson, unchanged, patches, path) { // console.log("oldKeys: " + oldKeys); // console.log("newKeys: " + newKeys); + var oldKey, oldValue; // Loop from the old; from lengths -1 to 0 for (var i = oldKeys.length -1; i >= 0; i--) { - var oldKey = oldKeys[i]; - var oldValue = oldJson[oldKey]; + oldKey = oldKeys[i]; + oldValue = oldJson[oldKey]; // console.log("oldKey: " + oldKey); // console.log("oldValue: " + JSON.stringify(oldValue)); if (newJson.hasOwnProperty(oldKey)) { - var newValue = newJson[oldKey]; - - // console.log("newValue: " + JSON.stringify(newValue)); // go deeper generateDiff(oldJson[oldKey], newJson[oldKey], unchanged, patches, path + "/" + oldKey ); @@ -103,9 +97,7 @@ function generateObjectDiff(oldJson, newJson, unchanged, patches, path) { // Remove // console.log({ op: "remove", path: path + "/" + patchPointString(oldKey), value: copy.clone(oldValue) }); removed = true; - console.time("Object-remove"); - patches.push({ op: "remove", path: path + "/" + patchPointString(oldKey), value: oldValue }); - console.timeEnd("Object-remove"); + patches.push(hashObject.hash({ op: "remove", path: path + "/" + patchPointString(oldKey), value: oldValue })); } } @@ -124,29 +116,31 @@ function generateObjectDiff(oldJson, newJson, unchanged, patches, path) { if (!oldJson.hasOwnProperty(newKey)) { //Try to find the value in the unchanged area // change JSON.stringify() - console.time("Object-find"); var pointer = unchangedArea.findValueInUnchanged(JSON.stringify(newVal), unchanged); - console.timeEnd("Object-find"); // console.log("pointer: " + pointer); if (pointer) { //COPY // console.log({ op: "copy", path: path + "/" + patchPointString(newKey), from: pointer}); - patches.push({ op: "copy", path: path + "/" + patchPointString(newKey), from: pointer}); + patches.push(hashObject.hash({ op: "copy", path: path + "/" + patchPointString(newKey), from: pointer})); } else { // no json.stringnify + if (typeof newVal === "string") { + // Ajust 333 => "333" + newVal = "\"" + newVal + "\""; + } var previousIndex = patchArea.findValueInPatch(newVal, patches); // console.log("previousIndex: " + previousIndex); if (previousIndex !== -1) { // MOVE - var oldPath = patches[previousIndex].path; + var oldPath = JSON.parse(patches[previousIndex]).path; patches.splice(previousIndex, 1); // console.log({ op: "move", from: oldPath, path: path + "/" + patchPointString(newKey)}); - patches.push({ op: "move", from: oldPath, path: path + "/" + patchPointString(newKey)}); + patches.push(hashObject.hash({ op: "move", from: oldPath, path: path + "/" + patchPointString(newKey)})); } else { //ADD // console.log({ op: "add", path: path + "/" + patchPointString(newKey), value: copy.clone(newVal)}); - patches.push({ op: "add", path: path + "/" + patchPointString(newKey), value: newVal}); + patches.push(hashObject.hash({ op: "add", path: path + "/" + patchPointString(newKey), value: newVal})); } } @@ -165,27 +159,10 @@ function patchPointString(str) { return str.replace(/~/g, '~0').replace(/\//g, '~1'); } - - -/** - * map the array. Faster than Array.prototype.map - * - * @param {function} f function - * @param {Array} a array-like - * @return {Array} new Array mapped by f - */ -function map(f, a) { - - var b = new Array(a.length); - for (var i = 0; i < a.length; i++) { - b[i] = f(a[i]); - } - return b; -} - -},{"./LCS.js":2,"./applyPatches":3,"./patchArea.js":5,"./unchangedArea.js":6}],2:[function(require,module,exports){ +},{"./LCS.js":2,"./applyPatches":3,"./hashObject.js":5,"./patchArea.js":6,"./unchangedArea.js":7}],2:[function(require,module,exports){ var unchangedArea = require('./unchangedArea.js'); var patchArea = require('./patchArea.js'); +var hashObject = require('./hashObject.js'); exports.LCS = LCS; @@ -208,9 +185,7 @@ function LCS (x, y, unchanged, patches, path) { var newX = x.slice(start, x_end + 1); var newY = y.slice(start, y_end + 1); - console.time("matrix"); var matrix = lcsMatrix(newX, newY); - console.timeEnd("matrix"); //backtrack // var result = lcsResult(newX, newY, matrix); // var finalResult = x.slice(0, start) + result + x.slice(x_end + 1, x.length); @@ -221,9 +196,7 @@ function LCS (x, y, unchanged, patches, path) { var offset = {}; offset.value = 1; // pass offset reference - console.time("printDiff"); printDiff(newX, newY, matrix, newX.length - 1, newY.length -1, start, offset, unchanged, patches, path); - console.timeEnd("printDiff"); } function lcsMatrix(x, y) { @@ -281,6 +254,7 @@ function backtrack(x, y, matrix, i, j) { } } + function printDiff(x, y, matrix, i, j, start, offset, unchanged, patches, path) { if (i > -1 && j > -1 && x[i] === y[j]) { @@ -293,61 +267,20 @@ function printDiff(x, y, matrix, i, j, start, offset, unchanged, patches, path) // First Add or Replace var lastElement = patches[patches.length - 1]; var tmpPath = path + "/" + (i + start + offset.value); - if (lastElement !== void 0 && lastElement.op === "remove" && lastElement.path === tmpPath) { - //First Replace - patches[patches.length - 1].op = "replace"; - patches[patches.length - 1].value = JSON.parse(y[j]); - } else { - // First MOVE or ADD or COPY - var previousIndex = patchArea.findValueInPatch(y[j], patches); - // var previousIndex = -1; //save 4ms - // // - // ********Need to be fiexed***************** - // only move when the previousIndex is 0 and patchLength is 1 - // if (previousIndex !== -1) - if (previousIndex === 0 && patches.length === 1) { - // MOVE - var oldPath = patches[previousIndex].path; - // console.log({ op: "move", from: oldPath, path: tmpPath}); - patches.splice(previousIndex, 1); - patches.push({ op: "move", from: oldPath, path: tmpPath}); + if (lastElement !== void 0) { + lastElement = JSON.parse(lastElement); + if (lastElement !== void 0 && lastElement.op === "remove" && lastElement.path === tmpPath) { + //First Replace + lastElement.op = "replace"; + lastElement.value = JSON.parse(y[j]); + patches.splice(patches.length - 1, 1, JSON.stringify(lastElement)); + } else { - // ADD OR COPY - //Try to find the value in the unchanged area - var pointer = unchangedArea.findValueInUnchanged(y[j], unchanged); - // var pointer = null; // save 5ms - if (pointer) { - // COPY - // Adjust the index in the unchanged area - var newPointerArr = pointer.split('/'); - var initIndex = parseInt(newPointerArr[newPointerArr.length - 1]); - - var newIndex; - // change index - if (initIndex < start) { - newIndex = initIndex; - } else { - newIndex = initIndex + offset.value - 1; - } - // console.log("newIndex: " + newIndex); - if (newIndex >= 0) { - // newIndex >= 0, hence the element exists in the array, copy - var index = pointer.lastIndexOf('/'); - if (index !== -1) { - var newPointer = pointer.slice(0, index + 1) + newIndex; - // console.log({ op: "copy", path: tmpPath, from: newPointer }); - patches.push({ op: "copy", path: tmpPath, from: newPointer }); - } - } else { - // newIndex < 0, hence the element doesn't exist in the array, add - patches.push({ op: "add", path: tmpPath, value: JSON.parse(y[j]) }); - } - } else { - // ADD - patches.push({ op: "add", path: tmpPath, value: JSON.parse(y[j]) }); - } + addCopyMove(x, y, matrix, i, j, start, offset, unchanged, patches, path, tmpPath); } + } else { + addCopyMove(x, y, matrix, i, j, start, offset, unchanged, patches, path, tmpPath); } //Then change offset offset.value++; @@ -359,13 +292,70 @@ function printDiff(x, y, matrix, i, j, start, offset, unchanged, patches, path) offset.value--; //Then remove // console.log({ op: "remove", path: path + "/" + (i + start + offset.value), value: x[i] }); - patches.push({ op: "remove", path: path + "/" + (i + start + offset.value), value: JSON.parse(x[i]) }); + patches.push(hashObject.hash({ op: "remove", path: path + "/" + (i + start + offset.value), value: JSON.parse(x[i]) })); + // patches.push({ op: "remove", path: path + "/" + (i + start + offset.value), value: oldJson[i] }); } else { // console.log("reach the end i = " + i); } } -},{"./patchArea.js":5,"./unchangedArea.js":6}],3:[function(require,module,exports){ +function addCopyMove(x, y, matrix, i, j, start, offset, unchanged, patches, path, tmpPath) { + // First MOVE or ADD or COPY + // var templeOps = hashObject({ op: "remove", path: tmpPath, value: JSON.parse(y[j])}); + var previousIndex = patchArea.findValueInPatch(y[j], patches); + // var previousIndex = -1; //save 4ms + // // + // ********Need to be fiexed***************** + // only move when the previousIndex is 0 and patchLength is 1 + // if (previousIndex !== -1) + if (previousIndex === 0 && patches.length === 1) { + // MOVE + var oldPath = JSON.parse(patches[previousIndex]).path; + // console.log({ op: "move", from: oldPath, path: tmpPath}); + patches.splice(previousIndex, 1); + patches.push(hashObject.hash({ op: "move", from: oldPath, path: tmpPath})); + } else { + // ADD OR COPY + //Try to find the value in the unchanged area + var pointer = unchangedArea.findValueInUnchanged(y[j], unchanged); + // var pointer = null; // save 5ms + if (pointer) { + // COPY + // Adjust the index in the unchanged area + var newPointerArr = pointer.split('/'); + var initIndex = parseInt(newPointerArr[newPointerArr.length - 1]); + + var newIndex; + // change index + if (initIndex < start) { + newIndex = initIndex; + } else { + newIndex = initIndex + offset.value - 1; + } + // console.log("newIndex: " + newIndex); + if (newIndex >= 0) { + // newIndex >= 0, hence the element exists in the array, copy + var index = pointer.lastIndexOf('/'); + if (index !== -1) { + var newPointer = pointer.slice(0, index + 1) + newIndex; + // console.log({ op: "copy", path: tmpPath, from: newPointer }); + patches.push(hashObject.hash({ op: "copy", path: tmpPath, from: newPointer })); + } + } else { + // newIndex < 0, hence the element doesn't exist in the array, add + patches.push(hashObject.hash({ op: "add", path: tmpPath, value: JSON.parse(y[j]) })); + // patches.push({ op: "add", path: tmpPath, value: newJson[j] }); + } + } else { + // ADD + patches.push(hashObject.hash({ op: "add", path: tmpPath, value: JSON.parse(y[j]) })); + // patches.push({ op: "add", path: tmpPath, value: newJson[j] }); + + } + } +} + +},{"./hashObject.js":5,"./patchArea.js":6,"./unchangedArea.js":7}],3:[function(require,module,exports){ exports.apply = apply; var objectOps = { @@ -591,6 +581,36 @@ module.exports._equals = _equals; } },{}],5:[function(require,module,exports){ + +exports.hash = hash; +exports.map = map; + +function hash(obj) { + //Default hash + return JSON.stringify(obj); + + //String-hash + // return hash(obj); +} + +/** + * map the array. Faster than Array.prototype.map + * + * @param {function} f function + * @param {Array} a array-like + * @return {Array} new Array mapped by f + */ +function map(f, a) { + + var b = new Array(a.length); + for (var i = 0; i < a.length; i++) { + // b[i] = f(typeof a[i] === "string"? a[i]: JSON.stringify(a[i])); + b[i] = f(a[i]); + } + return b; +} + +},{}],6:[function(require,module,exports){ var deepEqual = require('./deepEquals.js'); exports.findValueInPatch = findValueInPatch; exports.handlePatch = handlePatch; @@ -598,9 +618,23 @@ exports.handlePatch = handlePatch; function findValueInPatch(newValue, patches) { var patchValue; + // for (var i = 0; i < patches.length; i++) { + // patchValue = patches[i].value; + // if (deepEqual._equals(newValue, typeof patchValue === "string"? patchValue: JSON.stringify(patchValue)) && patches[i].op === 'remove') { + // return i; + // } + // } + for (var i = 0; i < patches.length; i++) { - patchValue = patches[i].value; - if (deepEqual._equals(newValue, typeof patchValue === "string"? patchValue: JSON.stringify(patchValue)) && patches[i].op === 'remove') { + patchValue = patches[i]; + patchValue = patchValue.substring(patchValue.indexOf('value') + 7, patchValue.length - 1); + + if (patchValue.length !== newValue.length) { + // Speed up ????? + continue; + } + + if (deepEqual._equals(newValue, patchValue)) { return i; } } @@ -611,14 +645,17 @@ function findValueInPatch(newValue, patches) { function handlePatch(patches) { // Delete the value in 'remove' option for (var i = 0; i < patches.length; i++) { + patches[i] = JSON.parse(patches[i]); if (patches[i].op === 'remove') { delete patches[i].value; } } } -},{"./deepEquals.js":4}],6:[function(require,module,exports){ +},{"./deepEquals.js":4}],7:[function(require,module,exports){ var deepEqual = require('./deepEquals.js'); +var hashObject = require('./hashObject.js'); + exports.generateUnchanged = generateUnchanged; exports.findValueInUnchanged = findValueInUnchanged; @@ -651,12 +688,29 @@ function generateUnchanged(oldJson, newJson, unchanged, path) { } } +function arrayCompare(oldArr, newArr, unchanged, path) { + // Check if two array element (string) is the same + // Equal + if (oldArr === newArr) { + // if (equal(oldJson, newJson)) { + // console.log({path: path, value: copy.clone(newJson)}); + unchanged.push( path + "=" + newArr); + return; + } + +} + //********************Need to be changed ******************** function generateUnchangedArray(oldJson, newJson, unchanged, path) { - var miniLength = Math.min(oldJson.length, newJson.length); + //When is the Array, stop to find leaf node + var x = hashObject.map(hashObject.hash, oldJson); + var y = hashObject.map(hashObject.hash, newJson); + + var miniLength = Math.min(x.length, y.length); // console.log("miniLength is " + miniLength); for (var i = 0; i < miniLength; i++) { - generateUnchanged(oldJson[i], newJson[i], unchanged, path + "/" + i); + // generateUnchanged(x[i], y[i], unchanged, path + "/" + i); + arrayCompare(x[i], y[i], unchanged, path + "/" + i); } } @@ -675,15 +729,16 @@ function generateUnchangedObject(oldJson, newJson, unchanged, path) { function findValueInUnchanged(newValue, unchanged) { for (var i = 0; i < unchanged.length; i++) { var value = unchanged[i].split("=")[1]; - // console.log("Value = " + value); - // console.log("ValueType = " + Array.isArray(value)); - // console.log("newValue = " + newValue); - // console.log("newValueType = " + typeof newValue); + if (value.length !== newValue.length) { + // Speed up ????? + continue; + } + if (deepEqual._equals(newValue, value)) { return unchanged[i].split("=")[0]; } } } -},{"./deepEquals.js":4}]},{},[1])(1) +},{"./deepEquals.js":4,"./hashObject.js":5}]},{},[1])(1) }); \ No newline at end of file