DEV Community

Cover image for Deeply Compare 2 Objects with Recursive Function in JavaScript
Rasaf Ibrahim
Rasaf Ibrahim

Posted on • Updated on

Deeply Compare 2 Objects with Recursive Function in JavaScript

In this article, we will compare two objects deeply. The objects that we will compare can not only have strings, numbers, and boolean as properties but also nested objects & arrays. Also, the property of these objects can not only be enumerable but also nonenumerable.

 

Table of Contents

 

Why it's a little bit tough to compare two objects

 

In JavaScript, objects are stored by reference. That means one object is strictly equal to another only if they both point to the same object in memory.

pass by reference vs pass by value

This GIF is collected from panjee.com and modified afterward.

 

const obj_1 = { score: 12 }
const obj_2 = obj_1
const obj_3 = { score: 12 }

obj_1 === obj_2; // true, same reference
obj_1 === obj_3 // false, different reference but same keys and values
Enter fullscreen mode Exit fullscreen mode

 

Let's Compare Two Objects

 

We need to check multiple things to compare two objects. So, we will break down the comparison process into multiple steps:

 

Step 1

 

Let's first create a function (deepComparison) that has two parameters (first, second).

function deepComparison (first, second) {

 ...

}
Enter fullscreen mode Exit fullscreen mode

If we pass two objects as the arguments of this function, this function will return a boolean value after comparing them.

 

Step 2

 

Our real work is beginning from this step. In this step, we will check if the types and values of the two arguments are the same. If they are the same, we will return true. Otherwise, we will move forward to the next step.

function deepComparison (first, second) {

    /* Using the strict equality operator (===) */
    if(first === second) return true

}

Enter fullscreen mode Exit fullscreen mode

 

Step 3

 

In this step, we will check whether any of the arguments is null. If any argument is null, we will return false. But if none of them are null, we will move forward to the next step.

function deepComparison (first, second) {

    ...

   if(first === null || second === null) return false

}

Enter fullscreen mode Exit fullscreen mode

 

Step 4

 

Now, we will make sure that both arguments are objects. If any of the arguments is not an object, we will return false.

function deepComparison (first, second) {

    ...

   if(typeof first !== 'object'  || typeof second !== 'object') return false

}

Enter fullscreen mode Exit fullscreen mode

 

Step 5

 

In the last step, we have made sure that both arguments are objects. In this step, we will check whether both objects have the same number of properties or not. If they don't have the same number of properties, we will return false.

function deepComparison (first, second) {

    ...

   /* Using Object.getOwnPropertyNames() method to return the list of the objects’ properties*/
 let first_keys = Object.getOwnPropertyNames(first)

 let second_keys = Object.getOwnPropertyNames(second)


  /* Using array.length() method to count the number of total property */
  if(first_keys.length !== second_keys.length) return false

}

Enter fullscreen mode Exit fullscreen mode

Could we use Object.keys() instead of Object.getOwnPropertyNames()?

No. Because Object.keys() can't return Nonenumerable
property's name but Object.getOwnPropertyNames() can.

 

Step 6

 

In the last step, we made sure that both objects have the same number of properties. Now, we need to make sure that the properties' names of both objects are the same. If they are not the same, we will return false. But if they are the same, we will move forward to the next step.

function deepComparison (first, second) {

    ...


  /* Iterating through all the properties of the first object with for..of method */
 for(let key of first_keys) {


    /* Making sure that every property in the first object also exists in the second object */ 
    if(!Object.hasOwn(second, key)) return false


 }


}

Enter fullscreen mode Exit fullscreen mode

Could we use Object.hasOwnProperty() instead of Object.hasOwn()?

No. Because Object.hasOwnProperty() can't check a Nonenumerable property's name but Object.hasOwnProperty() can. Also, Object.hasOwn() is intended as a replacement for Object.hasOwnProperty(), so we are using Object.hasOwn() here.

 

Step 7

 

In the last step, we made sure that the property names of both objects are the same. Now, we will check whether the property values of both objects are the same or not.

 

This step is a little bit tricky because:

 

  • The property values can not only be strings, numbers, or boolean but also nested objects.
  • Just checking with strict equality operator (===) would be enough if the values were string or number or boolean but it wouldn't be enough for an object because the nested object will have its own properties.
  • Let's suppose that the nested object has another object inside it as a property! What will we do? Will we check one by one!

 

Solution:

 

  • In the step 1, we created this deepComparison function, and this function not only checks the strict equality operator (===), but also it checks null or not, object or not, properties of objects are same or not. So, actually, we need a function like this deepComparison to check any nested object's properties.
  • Fortunately, we don't need to create any new function because there is a term known as the recursive function. A recursive function is a function that calls itself during its execution. So, actually, we can use this deepComparision function as a recursive function.

Now, let's use this deepComparision function to check whether the property values of both objects are the same or not. If they are not the same, we will return false.

function deepComparison (first, second) {

    ...



 for(let key of first_keys) {



     ...


   /* Using the deepComparison function recursively and passing the values of each property into it to check if they are equal. */

  if (deepComparison(first[key], second[key]) === false) return false



}

Enter fullscreen mode Exit fullscreen mode

Note

As the deepComparision is inside a loop. So, it will continue to execute itself till it finishes checking all the nested objects.

 

Step 8

 

If we are in this step 8, that means no other step's condition matched. As we have checked almost all possible ways in which the objects are not the same and none of those conditions matched, so now we can surely say that the objects are the same. So, we will simply return true in this step.

function deepComparison (first, second) {

    ...


  for(let key of first_keys) {



     ...


   }

 return true

}

Enter fullscreen mode Exit fullscreen mode

 

Full Code

 

function deepComparison (first, second) {


  /* Checking if the types and values of the two arguments are the same. */
  if(first === second) return true


  /* Checking if any arguments are null */
  if(first === null || second === null) return false

  /* Checking if any argument is none object */
  if(typeof first !== 'object'  || typeof second !== 'object') return false



  /* Using Object.getOwnPropertyNames() method to return the list of the objects’ properties */
  let first_keys = Object.getOwnPropertyNames(first)

  let second_keys = Object.getOwnPropertyNames(second)



  /* Checking if the objects' length are same*/
  if(first_keys.length !== second_keys.length) return false




  /* Iterating through all the properties of the first object with the for of method*/
  for(let key of first_keys) {


      /* Making sure that every property in the first object also exists in second object. */ 
      if(!Object.hasOwn(second, key)) return false


      /* Using the deepComparison function recursively (calling itself) and passing the values of each property into it to check if they are equal. */
      if (deepComparison(first[key], second[key]) === false) return false

  }



  /* if no case matches, returning true */ 
  return true


}


Enter fullscreen mode Exit fullscreen mode

 

Let's test

 



let obj1 = {
  a: 1,
  b: 2,
  c: { foo: 1, bar: 2},
  d: { baz: 1, bat: 2, arr: [2,3] }
}

let obj2 = {
  a: 1,
  b: 2,
  c: { foo: 1, bar: 2},
  d: { baz: 1, bat: 2, arr: [2,3] }
}


let obj3 = {
  name: "Rahim",
  additionalData: {
      instructor: true,
      favoriteHobbies: ["Playing Cricket", "Tennis", "Coding"],
      citiesLivedIn: ["Rajshahi", "Rangpur", "Joypurhat"]
      }
  }


let obj4 = {
    name: "Rahim",
    additionalData: {
        instructor: true,
        favoriteHobbies: ["Playing Cricket", "Tennis", "Coding"],
        citiesLivedIn: ["Rajshahi", "Rangpur", "Joypurhat"]
        }
    }

Enter fullscreen mode Exit fullscreen mode

console.log(deepComparison(obj1, obj2)) //true
console.log(deepComparison(obj1, obj3)) //false
console.log(deepComparison(obj2, obj3)) // false
console.log(deepComparison(obj3, obj4)) //true

Enter fullscreen mode Exit fullscreen mode

That's it. 😃 Thanks for reading. 🎉 If you find any typos or errors, or if you want to add something, please write it down in the comment section.

Top comments (4)

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

This is a pretty good example, and a well written article, however, the includes on the second_keys is going to cause it to start running very slowly if there are many properties, you'd be much better off using Object.hasOwnProperty rather than reinventing it with a linear array search.

Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim

Very useful information, thank you. I've modified the article.

Collapse
 
zlm11 profile image
zlm11 • Edited

Good article,however there is one problem, as Object.keys not include unenumerable properties, so if one obj has unenumerable property but the other not, the result expected false but true.

let obj3 = {
  name: "Rahim",
  additionalData: {
    instructor: true,
    favoriteHobbies: ["Playing Cricket", "Tennis", "Coding"],
    citiesLivedIn: ["Rajshahi", "Rangpur", "Joypurhat"]
  }
};
Object.defineProperty(obj3, 'noneEnumble', {
  value: 123,
  enumerable: false
});

let obj4 = {
  name: "Rahim",
  additionalData: {
    instructor: true,
    favoriteHobbies: ["Playing Cricket", "Tennis", "Coding"],
    citiesLivedIn: ["Rajshahi", "Rangpur", "Joypurhat"]
  }
};
console.log(deepComparison(obj3, obj4));
console.log(obj3.noneEnumble);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rasaf_ibrahim profile image
Rasaf Ibrahim • Edited

Thank you. Now, I am using Object.getOwnPropertyNames() instead of Object.keys() to solve the problem.