Reputation: 24572
I am using the following AngularJS code:
if (angular.equals(rowData, $scope.grid.backup[i])) {
console.log('equal')
}
Please note that AngularJS has an equals function that compares each element inside the object.
The two objects look equal when I debug but Angular does not agree. I just cannot see what is not equal. Is there some other way I can do a comparison?
Upvotes: 4
Views: 8154
Reputation: 17473
jfriend00's function is a good start, but isn't very descriptive when the issue is a ways down the object model, as instead of acting recursively, it punts when both objects have the same properties pointing to objects. Instead of walking the object model, it falls back using angular.equals
.
That is, jfriend's solution only defers angular.equals
limitations one level of the object hierarchy.
For instance, it fails mightily on this test:
var obj1 = {
a: 1,
b: {
c: 2,
d: {
e: 3,
f: 4,
},
g: 5,
},
h: 6,
};
var obj2 = {
a: 1,
b: {
c: 2,
d: {
e2: 3, // DIFFERENT PROPERTY
f: 'different', // DIFFERENT TYPE AND VALUE
},
g: 'again', // DIFFERENT TYPE AND VALUE
},
h: 7, // DIFFERENT VALUE
};
It tells us only:
That's not wrong, but it's also not particularly descriptive, and didn't help me find differences between two complex objects when angular.equals
came up false.
We can improve on that.
If we wanted to know specifically that [obj].b.d.f
are different values, we'd need to recurse down the object model. We'd also need to recurse down both object models to see when obj2
has something obj1
did not. (To do that efficiently, a different function is used for the second comparison.)
Here's a cut that does that, I believe:
// The unicorn: https://stackoverflow.com/a/18234317/1028230
// prettier-ignore
String.prototype.formatUnicorn = String.prototype.formatUnicorn || function () {
var str = this.toString(); if (arguments.length) { var t = typeof arguments[0]; var key; var args = 'string' === t || 'number' === t ? Array.prototype.slice.call(arguments) : arguments[0]; for (key in args) { str = str.replace(new RegExp('\\{' + key + '\\}', 'gi'), args[key]); } }
return str;
};
function compareObjects(s, t, propsToIgnore1) {
return compareFirstToSecond(s, t, propsToIgnore1, '/').concat(
compareSecondToFirst(t, s, propsToIgnore1, '/')
);
function compareFirstToSecond(first, second, propsToIgnore, path) {
var msgs = [];
propsToIgnore = propsToIgnore || [];
// Check type
if (typeof first !== typeof second) {
msgs.push(
path +
' -- two objects not the same type $${0}$${1}$$'.formatUnicorn(
typeof first,
typeof second
)
);
return msgs;
}
// Check value
// Keep in mind that typeof null is 'object'
// https://stackoverflow.com/a/18808270/1028230
if (typeof first !== 'object' || first === null) {
if (first !== second) {
msgs.push(
'{2} -- Unequal (null and not null) or (two unequal non-objects): {0}-{1} '.formatUnicorn(
first,
second,
path
)
);
}
return msgs;
}
// Check properties
for (var prop in first) {
if (propsToIgnore.indexOf(prop) === -1) {
if (first.hasOwnProperty(prop) && first[prop] !== undefined) {
if (second.hasOwnProperty(prop) && second[prop] !== undefined) {
msgs = msgs.concat(
compareFirstToSecond(
first[prop],
second[prop],
propsToIgnore,
path + prop + '/'
)
);
} else {
msgs.push(path + prop + ' -- second object does not have property ' + prop);
}
}
}
}
return msgs;
}
// now verify that t doesn't have any properties
// that are missing from s.
// To recurse this properly, let's set up another function.
function compareSecondToFirst(second, first, propsToIgnore, path) {
var msgs = [];
propsToIgnore = propsToIgnore || [];
for (var prop in second) {
if (propsToIgnore.indexOf(prop) === -1) {
if (second.hasOwnProperty(prop) && second[prop] !== undefined) {
if (!first.hasOwnProperty(prop) || first[prop] === undefined) {
msgs.push(path + prop + ' -- first object does not have property ' + prop);
} else if (
typeof first[prop] === 'object' &&
typeof second[prop] === 'object'
) {
// NOTE: To make this proceed down the object tree, even though we've already
// checked equality for each match, we need to keep recursively calling
// a check to see if the second object's object model has a prop the first's
// model does not.
//
// That is, we don't know what "props of props" are missing all the way
// down the object model without this call. But we're recursively calling this
// inner function so we don't do any extra comparision work.
msgs = msgs.concat(
compareSecondToFirst(
second[prop],
first[prop],
propsToIgnore,
path + prop + '/'
)
);
} // else they aren't objects and we already know the props values match, as they've already been checked.
}
}
}
return msgs;
}
}
This will tell us not just what's different but the path down the object model to where each difference lives. It also has something that's important to me: The ability to skip properties with the propsToIgnore
array parameter. Your use case may differ, of course.
That's getting closer to helping us know what's different, I believe. As you can imagine, as the objects become more complex, having this drill down reporting becomes more useful.
Here's the requisite fiddle of reasonable success with the two compareObjects
methods and results:
https://jsfiddle.net/t4oh7qu9/1/
Upvotes: 0
Reputation: 707736
The doc for angular.equals()
says:
Two objects or values are considered equivalent if at least one of the following is true:
Both objects or values pass === comparison.
Both objects or values are of the same type and all of their properties are equal by comparing them with angular.equals.
Both values are NaN. (In JavaScript, NaN ==> NaN => false. But we consider two NaN as equal)
Both values represent the same regular expression (In JavasScript, /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual representation matches).
So, if you're getting a false for .equals()
, then we can conclude the following:
===
So, that only leaves the 2nd item in the documentation which means that either the objects are not the same type or some of their properties are not the same. For anyone to help you further on what exactly is different between them, we'd have to see the actual objects or the code that creates them.
If you have the non-minimized version of angular installed in your page, you could also just step through your call to angular.equals()
and see which step in the code it is finding the difference.
Or, if there are a lot of properties or a lot of objects so stepping in the debugger is difficult, you could write your own little debug routine to tell you which property was different. That would look something like this:
function compareObjects(s, t) {
if (typeof s !== typeof t) {
console.log("two objects not the same type");
return;
}
if (typeof s !== "object") {
console.log('arguments are not typeof === "object"');
return;
}
for (var prop in s) {
if (s.hasOwnProperty(prop)) {
if (t.hasOwnProperty(prop)) {
if (!angular.equals(s[prop], t[prop])) {
console.log("property " + prop + " does not match");
}
} else {
console.log("second object does not have property " + prop);
}
}
}
// now verify that t doesn't have any properties
// that are missing from s
for (prop in t) {
if (t.hasOwnProperty(prop)) {
if (!s.hasOwnProperty(prop)) {
console.log("first object does not have property " + prop);
}
}
}
}
// then call this function on your two objects
// and then look in the debug console to see what it reports
compareObjects(rowData, $scope.grid.backup[i]);
Upvotes: 9
Reputation: 15566
I would suggest you to try debugging.
Open developer bar in Chrome, place a break-point
at the equals function in angular code. Now when the comparison happens Go line by line by stepping over line by line. Check at which point its returning false and you will probably get the reason why.
Or get the equals function from angular source:
Modify it with console logs at each return false
, and use this function to compare your objects to debug.
Upvotes: 3