json-diff-rfc6902
Version:
This framework is to compare two JSON data and generate the Patch
236 lines (201 loc) • 6.2 kB
JavaScript
var unchangedArea = require('./unchangedArea.js');
var patchArea = require('./patchArea.js');
var hashObject = require('./hashObject.js');
var applyPatches = require('./applyPatches');
exports.transformArray = transformArray;
function transformIndex(element, m, array) {
var finalIndex;
switch(element.op) {
case 'add':
case 'replace':
case 'copy':
// When add, replace and copy, add directly
return element.index;
case 'remove':
finalIndex = element.index;
break;
case 'move':
finalIndex = element.from;
break;
}
for (var i = 0; i < m; i++) {
switch (array[i].op) {
case 'remove':
if(finalIndex > array[i].index) {
// when equal, don't --
finalIndex --;
}
break;
case 'add':
case 'copy':
if(finalIndex >= array[i].index) {
// when equal, do ++
finalIndex ++;
}
break;
case 'replace':
// When equal, don't change
break;
case 'move':
if (array[i].from !== array[i].index) {
var min = Math.min(array[i].from, array[i].index);
var max = Math.max(array[i].from, array[i].index);
if (finalIndex >= min && finalIndex <= max) {
if (array[i].from > array[i].index) {
finalIndex ++;
} else {
finalIndex --;
}
}
}
break;
}
}
return finalIndex;
}
function operationValue (op) {
switch (op) {
case "move" : return 0;
case "remove" : return 1;
case "add" : return 2;
case "replace": return 3;
case "copy" : return 4;
}
}
function compare(a, b) {
if (a.index === b.index) {
// Order: move < remove < add
var a_value = operationValue(a.op);
var b_value = operationValue(b.op);
if (a_value > b_value) {
return 1;
} else {
return -1;
}
} else {
return a.index - b.index;
}
}
function findCopyInArray(element, m, array, arrUnchanged) {
var copyIndex = -1;
for (var i = 0; i < m; i++) {
switch (element.op) {
case 'remove':
case 'copy':
break;
default:
// Move Replace Add
if (element.hash === array[i].hash) {
return array[i].index;
}
break;
}
}
//Find value in arrUnchanged
for (var j= 0; j< arrUnchanged.length; j++) {
if (element.hash === arrUnchanged[j].hash) {
return arrUnchanged[j].index;
}
}
return copyIndex;
}
function transformArray(oldJson, newJson, unchanged, patches, patchHashes, path) {
//When is the Array, stop to find leaf node
// (hash, value, index)
var x = hashObject.mapArray(hashObject.hash, oldJson);
var y = hashObject.mapArray(hashObject.hash, newJson);
// Reserve the origin index
// COPY ARRAY
var x_sorted = x.slice();
var y_sorted = y.slice();
x_sorted.sort(function(a, b) {return a.hash - b.hash;});
y_sorted.sort(function(a, b) {return a.hash - b.hash;});
//Diff
var arrPatch = [], arrUnchanged = [];
var i= 0, j = 0;
while (i < x_sorted.length) {
while( j < y_sorted.length) {
if(x_sorted[i] !== void 0) {
if (x_sorted[i].hash > y_sorted[j].hash) {
arrPatch.push({op: "add", value: y_sorted[j].value, index: y_sorted[j].index, hash: y_sorted[j].hash });
j++;
} else if (x_sorted[i].hash === y_sorted[j].hash) {
// Unchanged push
unchanged.push( path + '/' + y_sorted[j].index + "=" + JSON.stringify(x_sorted[i].hash));
arrPatch.push({op: "move", value: y_sorted[j].value, from: x_sorted[i].index , index: y_sorted[j].index, hash: y_sorted[j].hash });
i++;
j++;
} else {
arrPatch.push({op: "remove", index: x_sorted[i].index});
i++;
}
} else {
arrPatch.push({op: "add", value: y_sorted[j].value, index: y_sorted[j].index, hash: y_sorted[j].hash });
j++;
}
}
if (i < x_sorted.length) {
// Remove the rest elements of the x_sorted
arrPatch.push({op: "remove", index: x_sorted[i].index });
i++;
}
}
//Get the patch to make all the elements are the same, but index is random
arrPatch = arrPatch.sort(compare);
// console.log(arrPatch);
var m = 0;
while(arrPatch[m] !== void 0) {
// f_index = transformIndex(arrPatch[m], arrPatch);
switch(arrPatch[m].op) {
case 'add':
arrPatch[m].index = transformIndex(arrPatch[m], m, arrPatch);
// replace
if (arrPatch[m-1] !== void 0) {
if (arrPatch[m-1].op === 'remove' && arrPatch[m-1].index === arrPatch[m].index) {
arrPatch[m].op = 'replace';
arrPatch.splice(m-1,1);
continue;
}
}
// COPY
var copyIndex = findCopyInArray(arrPatch[m], m, arrPatch, arrUnchanged);
if (copyIndex !== -1) {
arrPatch[m].op = 'copy';
arrPatch[m].from = copyIndex;
if (arrPatch[m].index === arrPatch[m].from) {
arrPatch.splice(m, 1);
continue;
}
}
break;
case 'remove':
arrPatch[m].index = transformIndex(arrPatch[m], m, arrPatch);
break;
case 'move':
arrPatch[m].from = transformIndex(arrPatch[m], m, arrPatch);
if (arrPatch[m].index === arrPatch[m].from) {
arrUnchanged.push(arrPatch[m]);
arrPatch.splice(m, 1);
continue;
}
break;
}
m++;
}
// console.log("==========================");
// console.log(arrPatch);
//
// console.log("========Final===============");
arrPatch = arrPatch.map(function(obj) {
obj.path = path + '/' + obj.index;
delete obj.hash;
delete obj.index;
if (obj.op === 'move' || obj.op === 'copy') {
obj.from = path + '/' + obj.from;
delete obj.value;
}
return obj;
});
// console.log(arrPatch);
return arrPatch;
}