Skip to content

Commit

Permalink
canonicalize: Keep track of the replacement objects so the correct ob…
Browse files Browse the repository at this point in the history
…ject can be substituted when a circular reference is detected.
  • Loading branch information
papandreou committed Sep 4, 2014
1 parent d786a63 commit d12c46d
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 6 deletions.
21 changes: 15 additions & 6 deletions diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,17 @@ var JsDiff = (function() {

var objectPrototypeToString = Object.prototype.toString;

function canonicalize(obj, stack) {
// This function handles the presence of circular references by bailing out when encountering an
// object that is already on the "stack" of items being processed.
function canonicalize(obj, stack, replacementStack) {
stack = stack || [];
replacementStack = replacementStack || [];

var i;

for (var i = 0 ; i < stack.length ; i += 1) {
if (stack[i] === obj) {
return obj;
return replacementStack[i];
}
}

Expand All @@ -213,28 +216,32 @@ var JsDiff = (function() {
if ('[object Array]' === objectPrototypeToString.call(obj)) {
stack.push(obj);
canonicalizedObj = new Array(obj.length);
replacementStack.push(canonicalizedObj);
for (i = 0 ; i < obj.length ; i += 1) {
canonicalizedObj[i] = canonicalize(obj[i], stack);
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack);
}
stack.pop();
replacementStack.pop();
} else if (typeof obj === 'object' && obj !== null) {
stack.push(obj);
canonicalizedObj = {};
replacementStack.push(canonicalizedObj);
var sortedKeys = [];
for (var key in obj) {
sortedKeys.push(key);
}
sortedKeys.sort();
for (i = 0 ; i < sortedKeys.length ; i += 1) {
var key = sortedKeys[i];
canonicalizedObj[key] = canonicalize(obj[key], stack);
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack);
}
stack.pop();
replacementStack.pop();
} else {
canonicalizedObj = obj;
}
return canonicalizedObj;
}
};

return {
Diff: Diff,
Expand Down Expand Up @@ -420,7 +427,9 @@ var JsDiff = (function() {
ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]);
}
return ret;
}
},

canonicalize: canonicalize
};
})();

Expand Down
37 changes: 37 additions & 0 deletions test/canonicalize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const VERBOSE = false;

var diff = require('../diff');

function getKeys(obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys;
}

describe('#canonicalize', function() {
it('should put the keys in canonical order', function() {
getKeys(diff.canonicalize({b: 456, a: 123})).should.eql(['a', 'b']);
});

it('should dive into nested objects', function() {
var canonicalObj = diff.canonicalize({b: 456, a: {d: 123, c: 456}});
getKeys(canonicalObj.a).should.eql(['c', 'd']);
});

it('should dive into nested arrays', function() {
var canonicalObj = diff.canonicalize({b: 456, a: [789, {d: 123, c: 456}]});
getKeys(canonicalObj.a[1]).should.eql(['c', 'd']);
});

it('should handle circular references correctly', function() {
var obj = {b: 456};
obj.a = obj;
var canonicalObj = diff.canonicalize(obj);
getKeys(canonicalObj).should.eql(['a', 'b']);
getKeys(canonicalObj.a).should.eql(['a', 'b']);
});
});

0 comments on commit d12c46d

Please sign in to comment.