Javascript - deepEqual Comparison
As you suspect, you're returning the match of the first property seen. You should return false
if that property doesn't match, but keep looking otherwise.
Also, return false
if there's no prop
property found on y
(that is, the counts match, but not the actual properties).
If all properties have matched, return true
:
var deepEqual = function (x, y) {
if (x === y) {
return true;
}
else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
if (Object.keys(x).length != Object.keys(y).length)
return false;
for (var prop in x) {
if (y.hasOwnProperty(prop))
{
if (! deepEqual(x[prop], y[prop]))
return false;
}
else
return false;
}
return true;
}
else
return false;
}
var deepEqual = function (x, y) {
if (x === y) {
return true;
}
else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
if (Object.keys(x).length != Object.keys(y).length)
return false;
for (var prop in x) {
if (y.hasOwnProperty(prop))
{
if (! deepEqual(x[prop], y[prop]))
return false;
}
else
return false;
}
return true;
}
else
return false;
}
var obj = {here: {is: "an", other: "3"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2}));
// → true
Feel that this version is a bit more readable (easier to comprehend). The logic is very similar with the top answer though. (ES6 this time)
function deepEqual(obj1, obj2) {
if(obj1 === obj2) // it's just the same object. No need to compare.
return true;
if(isPrimitive(obj1) && isPrimitive(obj2)) // compare primitives
return obj1 === obj2;
if(Object.keys(obj1).length !== Object.keys(obj2).length)
return false;
// compare objects with same number of keys
for(let key in obj1)
{
if(!(key in obj2)) return false; //other object doesn't have this prop
if(!deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
//check if value is primitive
function isPrimitive(obj)
{
return (obj !== Object(obj));
}
By the way, there is a cheater version of deep equal which works like a charm)) However, it's approximately 1.6 times slower.
As noticed by zero298, this approach is sensitive to the properties ordering and shouldn't be taken seriously
function cheatDeepEqual(obj1, obj2)
{
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
You can use a variable outside the for loop to keep track of the comparison:
var allPropertiesEqual = true;
for (var prop in x) {
if (y.hasOwnProperty(prop)) {
allPropertiesEqual = deepEqual(x[prop], y[prop]) && allPropertiesEqual;
} else {
allPropertiesEqual = false;
}
}
return allPropertiesEqual;
The previous example is not optimized on purpose. Because you're comparing objects, you know that you can return false
as soon as you find an inequality, and you can keep looping while all the previous checked properties are equal:
for (var prop in x) {
if (y.hasOwnProperty(prop)) {
if (! deepEqual(x[prop], y[prop]) )
return false; //first inequality found, return false
} else {
return false; //different properties, so inequality, so return false
}
}
return true;
I am quite new to JS but this is the way I solved it:
function deepEqual(obj1, obj2) {
if (typeof obj1 === "object" && typeof obj2 === "object") {
let isObjectMatch = false;
for (let property1 in obj1) {
let isPropertyMatch = false;
for (let property2 in obj2) {
if (property1 === property2) {
isPropertyMatch = deepEqual(obj1[property1], obj2[property2])
}
if(isPropertyMatch){
break;
}
}
isObjectMatch = isPropertyMatch;
if (!isObjectMatch) {
break;
}
}
return isObjectMatch;
} else {
return obj1 === obj2;
}
}
And here are my tests:
var obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}))
// → true
console.log(deepEqual(obj, {object: 2, here: {is: "an"}}));
// → true
console.log(deepEqual(obj, {object: 1, here: {is: "an"}}));
// → false
console.log(deepEqual(obj, {objectt: 2, here: {is: "an"}}));
// → false
console.log(deepEqual(2, 2));
// → true
console.log(deepEqual(2, 3));
// → false
console.log(deepEqual(2, null));
// → false
console.log(deepEqual(null, null));
// → false
console.log(deepEqual(obj, null));
// → false
Based on the accepted answer by Paul Roub, I needed it to also match function values, and I wanted it to be a lot more concise, so I've refactored it.
function deepEqual(x, y, z) {
return x === y || typeof x == "function" && y && x.toString() == y.toString()
|| x && y && typeof x == "object" && x.constructor == y.constructor
&& (z = Object.keys(y)) && z.length == Object.keys(x).length
&& !z.find(v => !deepEqual(x[v], y[v]));
}
var myFunc = (x) => { return x*2; }
var obj = {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc}));
// → true
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*2; }}));
// → true
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*999; }}));
// → false
notes:
- You pass in only 2 args: x and y (z is for internal use).
- If one of the variables is
null
orundefined
it returns that value instead offalse
, but that result is still "falsey" so I'm OK with it. To fix that you could change all occurrences ofy &&
to(y || !1) &&
andx &&
to(x || !1) &&
- If you definitely don't expect that functions/callbacks would be supplied in
your objects then remove
|| typeof x == "function" && y && x.toString() == y.toString()