Patching an array using the objectHash
It looks like currently when patching an array, only the index is used but not the object hash. I have run into the following scenario which can be resumed by the following steps
- my instance of jsondiffpatch has a custom objectHash function looking for object 'code' property
- create a diff on 2 arrays :
let d = jsondiffpatch.diff([{ code: '1' }, { code: '2' }], [{ code: '3' }, { code: '1' }, { code: '2' }]) //res { '0': [{ code: '3' }], _t: 'a' } - apply this diff in reverse to the same array but which has been retrieved (from a db) and is now not in the same order :
jsondiffpatch.unpatch([{ code: '1' }, { code: '2' }, { code: '3' }], { '0': [{ code: '3' }], _t: 'a' }) //res [ { code: '2' }, { code: '3' } ]As we can see the 1st element of the array got removed in the operation. I would've liked for the object with {code:3} to get removed following the usage of the objectHash as was done during the diffing.
Is this me misusing the library or is there any way to achieve this ? Thanks
Ok so I've managed to implement a new patcher that takes care of the scenario above.
I've basically copied the initial patchFilter$2 method and changed the relevant part so that it removes objects from arrays using the provided hash function.
One thing I need is to access PatchContext as it does in the original function :
child = new PatchContext(context.left[modification.index], modification.delta);
Unfortunately, it is not exposed in the exports. Any idea how I could go around this ?
For those that want something similary here is my code. Happy to provide a PR if needed:
var arraysByHashPatchFilter = function (context) {
// if (context.delta && context.delta._t Array.isArray(context.delta) && context.delta[2] === NUMERIC_DIFFERENCE) {
// context.setResult(context.left + context.delta[1]).exit();
// }
if (!context.nested) {
return;
}
if (context.delta._t !== 'a') {
return;
}
var index = void 0;
var index1 = void 0;
var delta = context.delta;
var array = context.left;
// first, separate removals, insertions and modifications
var toRemoveOverridenByHash = [];
var toRemove = [];
var toInsert = [];
var toModify = [];
for (index in delta) {
if (index !== '_t') {
if (index[0] === '_') {
// removed item from original array
if (delta[index][2] === 0 || delta[index][2] === ARRAY_MOVE) {
//find the object by hash instead of by index
//if we find an index to remove by hash we create an entry in toRemoveOverridenByHash to map that index to the original one
//this is used later in the case of moves to know in which position to reinsert the item if necessary
if (context.options.objectHash) {
let indexOfObjectToRemove = array.findIndex(e => context.options.objectHash(e) === context.options.objectHash(delta[index][0]))
if (indexOfObjectToRemove !== -1) {
toRemove.push(indexOfObjectToRemove);
toRemoveOverridenByHash.push({
indexToRemoveCalculatedByIndex: parseInt(index.slice(1), 10),
indexToRemoveCalculatedByHash: indexOfObjectToRemove
})
}
} else {
toRemove.push(parseInt(index.slice(1), 10));
}
} else {
throw new Error('only removal or move can be applied at original array indices,' + (' invalid diff type: ' + delta[index][2]));
}
} else {
if (delta[index].length === 1) {
// added item at new array
toInsert.push({
index: parseInt(index, 10),
value: delta[index][0]
});
} else {
// modified item at new array
toModify.push({
index: parseInt(index, 10),
delta: delta[index]
});
}
}
}
}
// remove items, in reverse order to avoid sawing our own floor
toRemove = toRemove.sort(compare.numerically);
for (index = toRemove.length - 1; index >= 0; index--) {
let indexToRemove = toRemove[index];
let overridenByHashIndex = toRemoveOverridenByHash.find(o => o.indexToRemoveCalculatedByHash === indexToRemove);
var indexDiff = delta[`_${overridenByHashIndex ? overridenByHashIndex.indexToRemoveCalculatedByIndex : indexToRemove}`];
var removedValue = array.splice(indexToRemove, 1)[0];
if (indexDiff[2] === ARRAY_MOVE) {
// reinsert later
toInsert.push({
index: indexDiff[1],
value: removedValue
});
}
}
// insert items, in reverse order to avoid moving our own floor
toInsert = toInsert.sort(compare.numericallyBy('index'));
var toInsertLength = toInsert.length;
for (index = 0; index < toInsertLength; index++) {
var insertion = toInsert[index];
array.splice(insertion.index, 0, insertion.value);
}
// apply modifications
var toModifyLength = toModify.length;
var child = void 0;
if (toModifyLength > 0) {
for (index = 0; index < toModifyLength; index++) {
var modification = toModify[index];
child = new PatchContext(context.left[modification.index], modification.delta);
context.push(child, modification.index);
}
}
if (!context.children) {
context.setResult(context.left).exit();
return;
}
context.exit();
};`
Ok so for the PatchContext issue I have done
child = new context.constructor(context.left[modification.index], modification.delta) which seems to solve it.
Would it solve #269?
Would be nice to have a pull request as well as more details on how to implement ourselves in the meantime :) Like, where do you use your arraysByHashPatchFilter()?
Thanks!
I cant see a real problem with #269 actually. This issue is about unpatching once a delta has already been generated.
To come back to your question about how to use this, you need to plug the arraysByHashPatchFilter function as explained in the plugins doc here : https://github.com/benjamine/jsondiffpatch/blob/master/docs/plugins.md
In summary the following should make it work :
(<any>arraysByHashPatchFilter).filterName = 'arraysByHash'; (<any>jsondiffpatch).processor.pipes.patch.before('arrays', arraysByHashPatchFilter);