flitbit / diff

Javascript utility for calculating deep difference, capturing changes, and applying changes across objects; for nodejs and the browser.
MIT License
2.99k stars 213 forks source link

Incorrectly rejects identical structure when encountering circularity #113

Open cpcallen opened 7 years ago

cpcallen commented 7 years ago

In this case there is no difference between o1 and o2, but diff incorrectly reports that there is:

var o1 = {};
var o2 = {};
o1.foo = o1;
o2.foo = o2;
diff(o1, o2)

By comparison, in this case there is actually a structural difference (which is correctly detected, albeit with less than entirely illuminating output):

var o1 = {};
var o2 = {};
o1.foo = o1;
o2.foo = {foo: o2};
diff(o1, o2)
flitbit commented 6 years ago

I've left this issue here for about a year. Seems like a theoretical question... here's why I say that:

const { log, inspect } = require('util');
const assert = require('assert');
const diff = require('../');

const o1 = {};
const o2 = {};
o1.foo = o1;
o2.foo = o2;

assert.notEqual(o1, o2, 'not same object');
assert.notEqual(o1.foo, o2.foo, 'not same object');

const differences = diff(o1, o2);
log(inspect(differences, false, 9));

Produces:

[ DiffEdit {
    kind: 'E',
    path: [ 'foo' ],
    lhs: { foo: [Circular] },
    rhs: { foo: [Circular] } } ]

Seems correct to me. I suppose the README

for determining the structural differences between objects

may be the counter argument, after all, the structures are similar. I'm inclined to close this.

cpcallen commented 6 years ago

The use case I would have for diff, absent this bug, is testing serialisation / deserialisation round-tripping or other deep-copy routines. My test code consists of creating various datastructures, serialising and then deserialising them, and then diffing the deserialised result against the original input. The data I am serialising is arbitrary JavaScript data, so the serialisation routines need to handle cyclic data—and therefore the tests must too.

Having already had to write my own diff routines for the golang version of this code, I will observe that the algorithm is actually pretty easy:

(Object prototypes, set members, and map keys and values ought to be treated just like properties: i.e., recursed upon and with the same two-way map tests. WeakMaps and WeakSets present some difficulty, though there are other possible workarounds if we can constrain the set of possible keys.)

flitbit commented 6 years ago

Thanks for the feedback. Now I'm inclined to fix it.

robertdumitrescu commented 5 years ago

Any update on this? Thanks!