» npm install json-diff-kit
I am wondering if there's something that allows you to easily display differences between two json like on diffchecker.com. Is there a library that allows you to easily do that?
The only built in operation for object comparison is the ==/=== equality operators, which use reference equality: A is B, rather than A is equal to B.
What you want is a list of change descriptors describing the difference between two objects.
As identified in the comments, this is going to need a recursive traversal of the objects, using a mixture of reference and existence checks.
The following algorithm is a quick implementation of an idea. The objects are traversed and their changes are described with a list of objects. Just like the objects themselves, the changes are nested, but have a unique id, based on their location within the object (so it could be flattened).
function diff(a, b, namespace) {
namespace = (namespace || '') + '.';
var keysInA = Object.keys(a),
keysInB = Object.keys(b);
var diffA = keysInA.reduce(function(changes, key) {
var ns = namespace + key;
if(typeof b[key] == 'undefined') {
return changes.concat([{ type: 'DELETED', id: ns }]);
}
if(a[key] !== b[key]) {
return changes.concat([{ type: 'CHANGED', id: ns }]);
}
if(typeof a[key] == b[key] == 'object') {
return diff(a[key], b[key], ns);
}
return changes;
}, []);
var diffB = keysInB.reduce(function(changes, key) {
var ns = namespace + key;
if(typeof a[key] == 'undefined') {
return changes.concat([{ type: 'ADDED', id: ns }]);
}
return changes;
}, []);
return diffA.concat(diffB);
}
For example we take the original state of an object.
var a = { a: 1, b: 2, c: 3 };
And the new state.
var b = { a: 2, c: 3, d: 5 };
Then run them with the diff function.
diff(a, b);
It returns a list of the changes.
[
{ id: '.a', type: 'CHANGED' },
{ id: '.b', type: 'DELETED' },
{ id: '.d', type: 'ADDED' }
]
Obviously, you would have to adapt this algorithm to make it fit your criteria for what constitutes a change. You might want to look at deep equality, rather than comparing references the whole way down.
I'll add my implementation of Dan's suggestion here in case it will help someone who wants to see an actual implementation:
var propertiesToIgnore = ['.indexesTracked', '.notInSyncWithDb', '.date', '.details.updatedAt', '.autoLoadLocalStorage', '.deletionQueue']; //these are extraneous properties added to project that should not be represented in interface (and not tracked)
var keysToIgnore = ['addAllDatacuts', 'datacutNames']; // this just looks at the property rather than the above which matches from the project root
function diff(a, b, namespace, firstCall) {
namespace = firstCall ? (namespace || '') : (namespace || '') + '.';
var keysInA = Object.keys(a),
keysInB = Object.keys(b);
var diffA = keysInA.reduce(function(changes, key) {
var ns = namespace + key;
if (propertiesToIgnore.indexOf(ns) !== -1 || keysToIgnore.indexOf(key) !== -1) {
return changes;
}
if (key == '$$hashKey') {
return changes;
}
if (angular.equals(a[key], b[key])) { // whole chain is equal so I do not need to iterate over this branch
return changes;
}
if (typeof b[key] == 'undefined') {
return changes.concat([{ type: 'DELETED', id: ns }]);
}
if (a[key] !== b[key] && (typeof b[key] !== 'object' || typeof a[key] !== 'object')) {
return changes.concat([{ type: 'UPDATED', id: ns }]);
}
if (typeof b[key] === 'object' && typeof a[key] === 'object') {
return changes.concat(diff(a[key], b[key], ns));
}
if (a[key] === null || b[key] === null) { // avoids values that are null as js considers null an object
return changes;
}
if(typeof a[key] == 'object' && typeof b[key] == 'object' && typeof a[key].getMonth !== 'function' && typeof b[key].getMonth !== 'function') { // last part necessary to make sure it is not a date object
return diff(a[key], b[key], ns);
}
return changes;
}, []);
var diffB = keysInB.reduce(function(changes, key) {
var ns = namespace + key;
if (propertiesToIgnore.indexOf(ns) !== -1 || keysToIgnore.indexOf(key) !== -1) {
return changes;
}
if (key == '$$hashKey') {
return changes;
}
if (typeof a[key] == 'undefined') {
return changes.concat([{ type: 'ADDED', id: ns }]);
}
return changes;
}, []);
return diffA.concat(diffB);
}
$scope.changes = diff(dbData, $scope.project, '');
» npm install angular-object-diff
» npm install json-diff-ts
» npm install jsondiffpatch