Javascript: Deep Comparison

I was checking this question Javascript Deep Comparison
The solution of the question asker did not convince me so I tried to analyze the problem and came up with that

var obj = {here: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1}));
// → false
console.log(deepEqual(obj, {here: 2}));
// → true
function deepEqual(a,b)
{
  if( (typeof a == 'object' && a != null) &&
      (typeof b == 'object' && b != null) )
  {
     var count = [0,0];
     for( var key in a) count[0]++;
     for( var key in b) count[1]++;
     if( count[0]-count[1] != 0) {console.log('1');return false;}
     for( var key in a)
     {
       if(!(key in b) || !deepEqual(a[key],b[key])) {console.log('2');return false;}
     }
     for( var key in b)
     {
       if(!(key in a) || !deepEqual(b[key],a[key])) {console.log('3');return false;}
     }  
  }
  console.log('a:'+a+' b:'+b);
  return a===b;
}
obj === { here:2 }

This code fails the last test ( console.log(deepEqual(obj, {here: 2})) ) but the logic of considering objects deeply equal if they have respectively equal keys and values despite being different instances in memory does not convince me. Is there’s a problem with my ‘solution’ or the mistake lies in the presumptions of the exercise? Is the code mentioned in the question I linked valid?

Resources that hikinthru forgot to mention ( http://eloquentjavascript.net/04_data.html#exercise_deep_compare )

20 thoughts on “Javascript: Deep Comparison”

  1. “Deep equality”, which is what the question you linked to is talking about, and “strict equality” are two different things. “Deep equality” means, as you said, “equal keys and equal values.” “Strict equality” for objects means “same instance.” Strict equality implies deep equality, but objects can be deeply equal without being strictly equal.

    Your code is somewhat inefficient because you only need one loop, but it will behave correctly if you check a === b in an else block and return true after the for loops. This is because you should be handling objects separately from primitive values like strings and numbers. I’ve removed some logging for the sake of clarity, and I tried to keep your style.

    var obj = {here: 2};
    console.log(deepEqual(obj, obj));
    // → true
    console.log(deepEqual(obj, {here: 1}));
    // → false
    console.log(deepEqual(obj, {here: 2}));
    // → true
    console.log(obj === { here:2 });
    // → false
    function deepEqual(a,b)
    {
      if( (typeof a == 'object' && a != null) &&
          (typeof b == 'object' && b != null) )
      {
         var count = [0,0];
         for( var key in a) count[0]++;
         for( var key in b) count[1]++;
         if( count[0]-count[1] != 0) {return false;}
         for( var key in a)
         {
           if(!(key in b) || !deepEqual(a[key],b[key])) {return false;}
         }
         for( var key in b)
         {
           if(!(key in a) || !deepEqual(b[key],a[key])) {return false;}
         }
         return true;
      }
      else
      {
         return a === b;
      }
    }
    Reply
  2. Here is a deep comparison that in addition to Object and Array also handles Date, Set, Map, RegExp:

    export function deepEquals<T>(a: T, b: T, ignoreKeys: (keyof T)[] = []): boolean {
      if (typeof a !== 'object' || b === null) {
        return a === b;
      }
    
      if (a instanceof Set && b instanceof Set) {
        return deepEquals(Array.from(a), Array.from(b));
      }
    
      if (a instanceof Map && b instanceof Map) {
        return (
          deepEquals(Array.from(a.keys()), Array.from(b.keys())) &&
          deepEquals(Array.from(a.values()), Array.from(b.values()))
        );
      }
    
      if (a instanceof Date && b instanceof Date) {
        return a.getTime() === b.getTime();
      }
    
      if (a instanceof RegExp && b instanceof RegExp) {
        return a.toString() === b.toString();
      }
    
      return Object.keys(a).every(key => {
        if (ignoreKeys.length > 0 && ignoreKeys.includes(key as keyof T)) {
          return true;
        }
    
        return deepEquals(a, b);
      });
    }
    
    Reply

Leave a Comment