DEV Community

Je We
Je We

Posted on

Deep Equality

Alt Text

My warmest greetings to all the spiritually awakened coders out there. Are you sick of shallow equality that barely scratches the surface? Looking to take the next step in your journey towards a deep equality that reveals the underlying cosmic truth about JavaScript objects? You've come to the right place.

When it comes to primitive data types (numbers, strings, etc.), JavaScript's strict equality operator (===) is plenty for comparing two values. However, you'll know if you've ever tried to compare two objects, not one of those three equal signs is going to do you any good. The strict equality operator, applied to objects and other complex data, only compares references to determine equality. The actual content of the two objects means diddlysquat.

In this example, the strict equality operator will tell us that yes, these objects are equal, because they both have the same reference i.e. they occupy the same place in memory.

  const meMyselfMe = {
    firstName: 'Je',
    lastName: 'We'
  }

  const myClone = meMyselfMe;

  console.log(meMyselfMe === myClone) // => true

However, if we create two objects separately, with coincidentally identical key-value pairs, when it comes time to compare these two objects, strict equality will betray us.

  const anotherClone = {
    firstName: 'Je',
    lastName: 'We'
  }

  console.log(meMyselfMe === anotherClone) // => false

Now there are definitely scenarios in which this behavior might be useful, so we won't take it personally. However, wouldn't it be nice to be able to compare objects based on their contents? Let's go ahead and write a deep equality function then!

How to get started? Well, we'd expect that two object's whose keys are associated with the same values would be deeply equal. So let's write some code which will return whether every key-value on one object is the same on the other.

  const deepEquals = (obj1, obj2) => {
    return Object.keys(obj1).every(key => obj1[key] === obj2[key]);
  }

OK, this seems like a good start for a very simple scenario. However, imagine that obj2 has equal values for all of obj1's keys, but it has some keys of its own that aren't present on obj1. We definitely wouldn't call these objects deeply equal, but our code as written would still return true. So how about we add some code that will check if our objects have the same amount of keys. Objects with different numbers of keys can never be deeply equal, so this code will let us stop when this is the case and return false right away, without wasting any more time.

  const deepEquals = (obj1, obj2) => {
    if(Object.keys(obj1).length !== Object.keys(obj2).length) return false;
    return Object.keys(obj1).every(key => obj1[key] === obj2[key]);
  }

Looking pretty good. This will work...except in the case of nested objects. We're using strict equality in our callback function, so if our objects have values that are themselves objects, even if those values are deeply equal, we'll still return false. So we're going to need to refactor our code to deal with nesting. We're going to have to be set up to deal with indeterminate nested objects, which should get some bells ringing for recursion. So how about we have a little faith that our deepEquals function will do what we want it to do, and check if every key-value pair is DEEPLY equal rather than strictly equal?

  const deepEquals = (obj1, obj2) => {
    if(Object.keys(obj1).length !== Object.keys(obj2).length) return false;
    return Object.keys(obj1).every(key => deepEquals(obj1[key], obj2[key]));
  }

The only problem here is that we have no stopping condition for our recursion. It's safe to say that, whenever the endless nightmare of nested objects come to a close, we'll be back to comparing primitive data types, for which the strict equality operator is a great tool! So, whenever deepEquals is called on two non-objects, it's safe to go ahead and return their strict equality. These simple comparisons will serve as our stopping condition.

  const deepEquals = (obj1, obj2) => {
    if(typeof obj1 !== 'object' && typeof obj2 !== 'object') return obj1 === obj2
    if(Object.keys(obj1).length !== Object.keys(obj2).length) return false;
    return Object.keys(obj1).every(key => deepEquals(obj1[key], obj2[key]));
  }

And now we have it! We can use this function to determine the deep equality of two objects. It's been fun. See you at the next planetary alignment!

Top comments (0)