Javascript Objects deep comparison

Javascript Objects deep comparison

This is one of the most frequently asked interview questions for a Javascript developer role. Sit back, and understand while I try my best to explain the problem and its solution.

Problem

In Javascript, the primitive data types are compared by values that they have, when compared by the strict equality operator.

const str1 = "a string"
const str2 = "a string"

str1 === str2 //true

const num1 = 5
const num2 = 5

num1 === num2 //true

const bool1 = true
const bool2 = true

bool1 === bool2 //true

const a = undefined
const b = undefined

a === b //true

But when it comes to Objects, they are compared by reference

const obj1 = {
    a: 1,
    b: "some string",
    c: { d: 2 }
}

const obj2 = {
    a: 1,
    b: "some string",
    c: { d: 2 }
}

obj1 === obj2 //false, as both references point to different objects even though the objects contain the same value.

In Javascript, arrays and functions are also a type of object, so they too are compared by reference when compared using the strict equality operator.

And this type of comparison is often called shallow comparison, when only the references of the objects are compared but not the values that they contain.

Solution

There is one quick and dirty way to compare two objects,

const obj1 = {
    a: 1,
    b: "some string",
    c: { d: 2 }
}

const obj2 = {
    a: 1,
    b: "some string",
    c: { d: 2 }
}

JSON.stringify(obj1) === JSON.stringify(obj2) //now that we have converted the objects to a string representation we are basically comparing two strings, hence this works.

But the above comparison will fail in a certain condition, can you guess?

const obj1 = {
    b: "some string",
    a: 1,
    c: { d: 2 }
}

const obj2 = {
    a: 1,
    b: "some string",
    c: { d: 2 }
}

JSON.stringify(obj1) === JSON.stringfy(obj2) //false, as we changed the order of the keys in obj1

So now we build the deepCompare function, but before that there's just one more gotcha. (I promise this is the last one, and then we start coding the function)

typeof null //"object"

null === null //true, wtf???

Ya well, that's how javascript works. So we first create a function that checks that the arguments that we pass to the deepCompare function are actually an object.

const isObject = (obj) => obj !== null && typeof obj === "object"

By default we'll assume that the objects passed to the deepCompare are equal and hence we return true. And the rest of the code in the function will find reasons to prove that they are not equal.

const deepCompare = (obj1, obj2) => {
     //code to find reasons to prove that these objects are not equal

    return true
}

Now, we first check that the arguments are objects and also that they have the same number of keys.

const deepCompare = (obj1, obj2) => {
    if( !isObject(obj1) || !isObject(obj2) || Object.keys(obj1).length !== Object.keys(obj2).length )
        return false

    return true
}
//Object.keys() returns an array of all the keys of a given object

Now we iterate over the keys and compare their respective values. But, what if the values are objects, so we first check that.

const deepCompare = (obj1, obj2) => {
    if( !isObject(obj1) || !isObject(obj2) || Object.keys(obj1).length !== Object.keys(obj2).length )
       return false

    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    for (const key of keys1) {
       const val1 = obj1[key];
       const val2 = obj2[key];
       const areObjects = isObject(val1) && isObject(val2);

       if(areObjects){
           if(!deepCompare(val1, val2))
               return false
       }else{
            if(val1 !== val2)
               return false
       }
    }

    return true
}

We check if the values of the keys are objects, if so then we recursively call deepCompare to check if these are equal. And we return false if they are not equal.

If the values are not objects then we return false if the values are not equal by using strict equality operator.

As we are returning false for both if and else condition we can shorten the code as follows

const deepCompare = (obj1, obj2) => {
    if( !isObject(obj1) || !isObject(obj2) || Object.keys(obj1).length !== Object.keys(obj2).length )
       return false

    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    for (const key of keys1) {
       const val1 = obj1[key];
       const val2 = obj2[key];
       const areObjects = isObject(val1) && isObject(val2);

       if ( areObjects && !deepCompare(val1, val2) || !areObjects && val1 !== val2 ) {
           return false;
       }
    }

    return true
}

So, this is how we deep compare objects in Javascript. If you didn't understand the code by reading once, read it again and traverse the function with the given objects as the arguments.