Reputation: 418
I need to refactor my program to take a JSON obj, store it somewhere, make a bunch of changes, then compare the two objects to see what has been changed, deleted, and added.
I'm not sure of a way to do this in JS, so could anyone advise a way to do this in Angular (the object comparison part)? Otherwise, I'm going to have to make a ton of changes to the way my program runs / try it from the backend. Appreciate any help.
Upvotes: 3
Views: 10801
Reputation: 418
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, '');
Upvotes: 0
Reputation: 29989
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.
Upvotes: 2