From 2b2557ba05c2eb7ce66870f6060f19b2875252d5 Mon Sep 17 00:00:00 2001 From: Andreas Lind Petersen Date: Thu, 3 Apr 2014 23:10:16 +0200 Subject: [PATCH] Implemented diffJson. It takes two objects, serializes them as canonical JSON, then does a line-based diff that ignores differences in trailing commas. See discussion here: https://github.com/visionmedia/mocha/pull/1182 --- diff.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++-- test/diffTest.js | 16 +++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/diff.js b/diff.js index c084609a..e63ab21b 100644 --- a/diff.js +++ b/diff.js @@ -127,8 +127,11 @@ var JsDiff = (function() { while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { newPos++; oldPos++; - - this.pushComponent(basePath.components, newString[newPos], undefined, undefined); + var value = newString[newPos]; + if (this.useLongestToken && oldString[oldPos].length > value.length) { + value = oldString[oldPos]; + } + this.pushComponent(basePath.components, value, undefined, undefined); } basePath.newPos = newPos; return oldPos; @@ -183,6 +186,52 @@ var JsDiff = (function() { return retLines; }; + JsonDiff = new Diff(); + JsonDiff.useLongestToken = true; + JsonDiff.tokenize = LineDiff.tokenize; + JsonDiff.equals = function(left, right) { + return LineDiff.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); + }; + + function canonicalize(obj, stack) { + stack = stack || []; + + var i; + + for (var i = 0 ; i < stack.length ; i += 1) { + if (stack[i] === obj) { + return obj; + } + } + + var canonicalizedObj; + + if ('[object Array]' == {}.toString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + for (i = 0 ; i < obj.length ; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack); + } + stack.pop(); + } else if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + 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); + } + stack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; + } + return { Diff: Diff, @@ -191,6 +240,13 @@ var JsDiff = (function() { diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); }, diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, + diffJson: function(oldObj, newObj) { + return JsonDiff.diff( + JSON.stringify(canonicalize(oldObj), undefined, " "), + JSON.stringify(canonicalize(newObj), undefined, " ") + ); + }, + diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { diff --git a/test/diffTest.js b/test/diffTest.js index e2aa8eab..d4d009dc 100644 --- a/test/diffTest.js +++ b/test/diffTest.js @@ -112,6 +112,22 @@ describe('#diffLines', function() { }); }); +describe('#diffJson', function () { + it('should ignore trailing comma on the previous line when the property has been removed', function() { + var diffResult = diff.diffJson( + {a: 123, b: 456, c: 789}, + {a: 123, b: 456}); + diff.convertChangesToXML(diffResult).should.equal('{\n "a": 123,\n "b": 456,\n "c": 789\n}'); + }); + + it('should ignore the missing trailing comma on the last line when a property has been added after it', function() { + var diffResult = diff.diffJson( + {a: 123, b: 456}, + {a: 123, b: 456, c: 789}); + diff.convertChangesToXML(diffResult).should.equal('{\n "a": 123,\n "b": 456,\n "c": 789\n}'); + }); +}); + describe('convertToDMP', function() { it('should output diff-match-patch format', function() { var diffResult = diff.diffWords('New Value ', 'New ValueMoreData ');