Comparing Arrays of Objects in JavaScript

Viewed 324308

I want to compare 2 arrays of objects in JavaScript code. The objects have 8 total properties, but each object will not have a value for each, and the arrays are never going to be any larger than 8 items each, so maybe the brute force method of traversing each and then looking at the values of the 8 properties is the easiest way to do what I want to do, but before implementing, I wanted to see if anyone had a more elegant solution. Any thoughts?

18 Answers

As serialization doesn't work generally (only when the order of properties matches: JSON.stringify({a:1,b:2}) !== JSON.stringify({b:2,a:1})) you have to check the count of properties and compare each property as well:

const objectsEqual = (o1, o2) =>
    Object.keys(o1).length === Object.keys(o2).length 
        && Object.keys(o1).every(p => o1[p] === o2[p]);

const obj1 = { name: 'John', age: 33};
const obj2 = { age: 33, name: 'John' };
const obj3 = { name: 'John', age: 45 };
        
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false

If you need a deep comparison, you can call the function recursively:

const obj1 = { name: 'John', age: 33, info: { married: true, hobbies: ['sport', 'art'] } };
const obj2 = { age: 33, name: 'John', info: { hobbies: ['sport', 'art'], married: true } };
const obj3 = { name: 'John', age: 33 };

const objectsEqual = (o1, o2) => 
    typeof o1 === 'object' && Object.keys(o1).length > 0 
        ? Object.keys(o1).length === Object.keys(o2).length 
            && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
        : o1 === o2;
        
console.log(objectsEqual(obj1, obj2)); // true
console.log(objectsEqual(obj1, obj3)); // false

Then it's easy to use this function to compare objects in arrays:

const arr1 = [obj1, obj1];
const arr2 = [obj1, obj2];
const arr3 = [obj1, obj3];

const arraysEqual = (a1, a2) => 
   a1.length === a2.length && a1.every((o, idx) => objectsEqual(o, a2[idx]));

console.log(arraysEqual(arr1, arr2)); // true
console.log(arraysEqual(arr1, arr3)); // false

EDIT: You cannot overload operators in current, common browser-based implementations of JavaScript interpreters.

To answer the original question, one way you could do this, and mind you, this is a bit of a hack, simply serialize the two arrays to JSON and then compare the two JSON strings. That would simply tell you if the arrays are different, obviously you could do this to each of the objects within the arrays as well to see which ones were different.

Another option is to use a library which has some nice facilities for comparing objects - I use and recommend MochiKit.


EDIT: The answer kamens gave deserves consideration as well, since a single function to compare two given objects would be much smaller than any library to do what I suggest (although my suggestion would certainly work well enough).

Here is a naïve implemenation that may do just enough for you - be aware that there are potential problems with this implementation:

function objectsAreSame(x, y) {
   var objectsAreSame = true;
   for(var propertyName in x) {
      if(x[propertyName] !== y[propertyName]) {
         objectsAreSame = false;
         break;
      }
   }
   return objectsAreSame;
}

The assumption is that both objects have the same exact list of properties.

Oh, and it is probably obvious that, for better or worse, I belong to the only-one-return-point camp. :)

Honestly, with 8 objects max and 8 properties max per object, your best bet is to just traverse each object and make the comparisons directly. It'll be fast and it'll be easy.

If you're going to be using these types of comparisons often, then I agree with Jason about JSON serialization...but otherwise there's no need to slow down your app with a new library or JSON serialization code.

I tried JSON.stringify() and worked for me.

let array1 = [1,2,{value:'alpha'}] , array2 = [{value:'alpha'},'music',3,4];

JSON.stringify(array1) // "[1,2,{"value":"alpha"}]"

JSON.stringify(array2) // "[{"value":"alpha"},"music",3,4]"

JSON.stringify(array1) === JSON.stringify(array2); // false

There is a optimized code for case when function needs to equals to empty arrays (and returning false in that case)

const objectsEqual = (o1, o2) => {
    if (o2 === null && o1 !== null) return false;
    return o1 !== null && typeof o1 === 'object' && Object.keys(o1).length > 0 ?
        Object.keys(o1).length === Object.keys(o2).length && 
        Object.keys(o1).every(p => objectsEqual(o1[p], o2[p]))
        : (o1 !== null && Array.isArray(o1) && Array.isArray(o2) && !o1.length && 
        !o2.length) ? true : o1 === o2;
}

My practice implementation with sorting, tested and working.

const obj1 = { name: 'John', age: 33};
const obj2 = { age: 33, name: 'John' };
const obj3 = { name: 'John', age: 45 };

const equalObjs = ( obj1, obj2 ) => {
let keyExist = false;
for ( const [key, value] of Object.entries(obj1) ) {
     // Search each key in reference object and attach a callback function to 
     // compare the two object keys
    if( Object.keys(obj2).some( ( e ) => e == key ) ) {
        keyExist = true;
    }
}

return keyExist;

}


console.info( equalObjs( obj1, obj2 ) );

Compare your arrays

// Sort Arrays
    var arr1 = arr1.sort(( a, b ) => {
    var fa = Object.keys(a);
    var fb = Object.keys(b);

    if (fa < fb) {
        return -1;
    }
    if (fa > fb) {
        return 1;
    }
    return 0;
});

var arr2 = arr2.sort(( a, b ) => {
    var fa = Object.keys(a);
    var fb = Object.keys(b);

    if (fa < fb) {
        return -1;
    }
    if (fa > fb) {
        return 1;
    }
    return 0;
});

const equalArrays = ( arr1, arr2 ) => {
    // If the arrays are different length we an eliminate immediately
    if( arr1.length !== arr2.length ) {
        return false;
    } else if ( arr1.every(( obj, index ) => equalObjs( obj, arr2[index] ) ) ) {
        return true;
      } else { 
        return false;
      }
    }

    console.info( equalArrays( arr1, arr2 ) );

The objectsAreSame function mentioned in @JasonBunting's answer works fine for me. However, there's a little problem: If x[propertyName] and y[propertyName] are objects (typeof x[propertyName] == 'object'), you'll need to call the function recursively in order to compare them.

I am sharing my compare function implementation as it might be helpful for others:

 /*
  null AND null // true
  undefined AND undefined // true
  null AND undefined // false
  [] AND [] // true
  [1, 2, 'test'] AND ['test', 2, 1] // true
  [1, 2, 'test'] AND ['test', 2, 3] // false
  [undefined, 2, 'test'] AND ['test', 2, 1] // false
  [undefined, 2, 'test'] AND ['test', 2, undefined] // true
  [[1, 2], 'test'] AND ['test', [2, 1]] // true
  [1, 'test'] AND ['test', [2, 1]] // false
  [[2, 1], 'test'] AND ['test', [2, 1]] // true
  [[2, 1], 'test'] AND ['test', [2, 3]] // false
  [[[3, 4], 2], 'test'] AND ['test', [2, [3, 4]]] // true
  [[[3, 4], 2], 'test'] AND ['test', [2, [5, 4]]] // false
  [{x: 1, y: 2}, 'test'] AND ['test', {x: 1, y: 2}] // true
  1 AND 1 // true
  {test: 1} AND ['test', 2, 1] // false
  {test: 1} AND {test: 1} // true
  {test: 1} AND {test: 2} // false
  {test: [1, 2]} AND {test: [1, 2]} // true
  {test: [1, 2]} AND {test: [1]} // false
  {test: [1, 2], x: 1} AND {test: [1, 2], x: 2} // false
  {test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 5}]} // true
  {test: [1, { z: 5 }], x: 1} AND {x: 1, test: [1, { z: 6}]} // false
   */
  function is_equal(x, y) {
      const
          arr1 = x,
          arr2 = y,
          is_objects_equal = function (obj_x, obj_y) {
              if (!(
                  typeof obj_x === 'object' &&
                  Object.keys(obj_x).length > 0
              ))
                  return obj_x === obj_y;

              return Object.keys(obj_x).length === Object.keys(obj_y).length &&
                  Object.keys(obj_x).every(p => is_objects_equal(obj_x[p], obj_y[p]));
          }
          ;

      if (!( Array.isArray(arr1) && Array.isArray(arr2) ))
          return (
              arr1 && typeof arr1 === 'object' &&
              arr2 && typeof arr2 === 'object'
          )
              ? is_objects_equal(arr1, arr2)
              : arr1 === arr2;

      if (arr1.length !== arr2.length)
          return false;

      for (const idx_1 of arr1.keys())
          for (const idx_2 of arr2.keys())
              if (
                  (
                      Array.isArray(arr1[idx_1]) &&
                      this.is_equal(arr1[idx_1], arr2[idx_2])
                  ) ||
                  is_objects_equal(arr1[idx_1], arr2[idx_2])
              )
              {
                  arr2.splice(idx_2, 1);
                  break;
              }

      return !arr2.length;
  }

using _.some from lodash: https://lodash.com/docs/4.17.11#some

const array1AndArray2NotEqual = 
          _.some(array1, (a1, idx) => a1.key1 !== array2[idx].key1 
                                     || a1.key2 !== array2[idx].key2 
                                     || a1.key3 !== array2[idx].key3);

There`s my solution. It will compare arrays which also have objects and arrays. Elements can be stay in any positions. Example:

const array1 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];
const array2 = [{a: 1}, {b: 2}, { c: 0, d: { e: 1, f: 2, } }, [1,2,3,54]];

const arraysCompare = (a1, a2) => {
  if (a1.length !== a2.length) return false;
  const objectIteration = (object) => {
    const result = [];
    const objectReduce = (obj) => {
      for (let i in obj) {
        if (typeof obj[i] !== 'object') {
          result.push(`${i}${obj[i]}`);
        } else {
          objectReduce(obj[i]);
        }
      }
    };
    objectReduce(object);
    return result;
  };
  const reduceArray1 = a1.map(item => {
    if (typeof item !== 'object') return item;
    return objectIteration(item).join('');
  });
  const reduceArray2 = a2.map(item => {
    if (typeof item !== 'object') return item;
    return objectIteration(item).join('');
  });
  const compare =  reduceArray1.map(item => reduceArray2.includes(item));
  return compare.reduce((acc, item) => acc + Number(item)) === a1.length;
};

console.log(arraysCompare(array1, array2));

not sure about the performance ... will have to test on big objects .. however, this works great for me.. the advantage it has compared to the other solutions is, the objects/array do not have to be in the same order ....

it practically takes the first object in the first array, and scans the second array for every objects .. if it's a match, it will proceed to another

there is absolutely a way for optimization but it's working :)

thx to @ttulka I got inspired by his work ... just worked on it a little bit

const objectsEqual = (o1, o2) => {
  let match = false
    if(typeof o1 === 'object' && Object.keys(o1).length > 0) {
     match = (Object.keys(o1).length === Object.keys(o2).length && Object.keys(o1).every(p => objectsEqual(o1[p], o2[p])))
    }else {
     match = (o1 === o2)
    }
    return match
}

const arraysEqual = (a1, a2) => {
  let finalMatch = []
  let itemFound = []
  
  if(a1.length === a2.length) {
    finalMatch = []
    a1.forEach( i1 => {
      itemFound = []
      a2.forEach( i2 => { 
        itemFound.push(objectsEqual(i1, i2)) 
      })
        finalMatch.push(itemFound.some( i => i === true))  
    }) 
  } 
  return finalMatch.every(i => i === true)
}

const ar1 = [
  { id: 1, name: "Johnny", data: { body: "Some text"}},
  { id: 2, name: "Jimmy"}
]
const ar2 = [
  {name: "Jimmy", id: 2},
  {name: "Johnny", data: { body: "Some text"}, id: 1}
]


console.log("Match:",arraysEqual(ar1, ar2))

jsfiddle: https://jsfiddle.net/x1pubs6q/

or just use lodash :))))

const _ = require('lodash')

const isArrayEqual = (x, y) => {
  return _.isEmpty(_.xorWith(x, y, _.isEqual));
};

This is work for me to compare two array of objects without taking into consideration the order of the items

const collection1 =  [
  { id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" },
  { id: "2", name: "item 2", parentId: "1" },
  { id: "3", name: "item 3", parentId: "1" },
]
const collection2 =  [
  { id: "3", name: "item 3", parentId: "1" },
  { id: "2", name: "item 2", parentId: "1" },
  { id: "1", name: "item 1", subtitle: "This is a subtitle", parentId: "1" },
]


const contains = (arr, obj) => {
  let i = arr.length;
  while (i--) {
     if (JSON.stringify(arr[i]) === JSON.stringify(obj)) {
         return true;
     }
  }
  return false;
}

const isEqual = (obj1, obj2) => {
  let n = 0
  if (obj1.length !== obj2.length) {
      return false;
  }
  for (let i = 0; i < obj1.length; i++) {
      if (contains(obj2, obj1[i])) {
        n++
      }
  }
  return n === obj1.length
}

console.log(isEqual(collection1,collection2))

if you take into consideration the order of the items use built in function in lodash isEqual

If you stringify them...

type AB = {
  nome: string;
}

const a: AB[] = [{ nome: 'Célio' }];
const b: AB[] = [{ nome: 'Célio' }];

console.log(a === b); // false
console.log(JSON.stringify(a) === JSON.stringify(b)); // true

Related