How can I check that two objects have the same set of property names?

129,976

Solution 1

You can serialize simple data to check for equality:

data1 = {firstName: 'John', lastName: 'Smith'};
data2 = {firstName: 'Jane', lastName: 'Smith'};
JSON.stringify(data1) === JSON.stringify(data2)

This will give you something like

'{firstName:"John",lastName:"Smith"}' === '{firstName:"Jane",lastName:"Smith"}'

As a function...

function compare(a, b) {
  return JSON.stringify(a) === JSON.stringify(b);
}
compare(data1, data2);

EDIT

If you're using chai like you say, check out http://chaijs.com/api/bdd/#equal-section

EDIT 2

If you just want to check keys...

function compareKeys(a, b) {
  var aKeys = Object.keys(a).sort();
  var bKeys = Object.keys(b).sort();
  return JSON.stringify(aKeys) === JSON.stringify(bKeys);
}

should do it.

Solution 2

2 Here a short ES6 variadic version:

function objectsHaveSameKeys(...objects) {
   const allKeys = objects.reduce((keys, object) => keys.concat(Object.keys(object)), []);
   const union = new Set(allKeys);
   return objects.every(object => union.size === Object.keys(object).length);
}

A little performance test (MacBook Pro - 2,8 GHz Intel Core i7, Node 5.5.0):

var x = {};
var y = {};

for (var i = 0; i < 5000000; ++i) {
    x[i] = i;
    y[i] = i;
}

Results:

objectsHaveSameKeys(x, y) // took  4996 milliseconds
compareKeys(x, y)               // took 14880 milliseconds
hasSameProps(x,y)               // after 10 minutes I stopped execution

Solution 3

If you want to check if both objects have the same properties name, you can do this:

function hasSameProps( obj1, obj2 ) {
  return Object.keys( obj1 ).every( function( prop ) {
    return obj2.hasOwnProperty( prop );
  });
}

var obj1 = { prop1: 'hello', prop2: 'world', prop3: [1,2,3,4,5] },
    obj2 = { prop1: 'hello', prop2: 'world', prop3: [1,2,3,4,5] };

console.log(hasSameProps(obj1, obj2));

In this way you are sure to check only iterable and accessible properties of both the objects.

EDIT - 2013.04.26:

The previous function can be rewritten in the following way:

function hasSameProps( obj1, obj2 ) {
    var obj1Props = Object.keys( obj1 ),
        obj2Props = Object.keys( obj2 );

    if ( obj1Props.length == obj2Props.length ) {
        return obj1Props.every( function( prop ) {
          return obj2Props.indexOf( prop ) >= 0;
        });
    }

    return false;
}

In this way we check that both the objects have the same number of properties (otherwise the objects haven't the same properties, and we must return a logical false) then, if the number matches, we go to check if they have the same properties.

Bonus

A possible enhancement could be to introduce also a type checking to enforce the match on every property.

Solution 4

If you want deep validation like @speculees, here's an answer using deep-keys (disclosure: I'm sort of a maintainer of this small package)

// obj1 should have all of obj2's properties
var deepKeys = require('deep-keys');
var _ = require('underscore');
assert(0 === _.difference(deepKeys(obj2), deepKeys(obj1)).length);

// obj1 should have exactly obj2's properties
var deepKeys = require('deep-keys');
var _ = require('lodash');
assert(0 === _.xor(deepKeys(obj2), deepKeys(obj1)).length);

or with chai:

var expect = require('chai').expect;
var deepKeys = require('deep-keys');
// obj1 should have all of obj2's properties
expect(deepKeys(obj1)).to.include.members(deepKeys(obj2));
// obj1 should have exactly obj2's properties
expect(deepKeys(obj1)).to.have.members(deepKeys(obj2));

Solution 5

Here's a deep-check version of the function provided above by schirrmacher. Below is my attempt. Please note:

  • Solution does not check for null and is not bullet proof
  • I haven't performance tested it. Maybe schirrmacher or OP can do that and share for the community.
  • I'm not a JS expert :).
function objectsHaveSameKeys(...objects) {
  const allKeys = objects.reduce((keys, object) => keys.concat(Object.keys(object)), [])
  const union = new Set(allKeys)
  if (union.size === 0) return true
  if (!objects.every((object) => union.size === Object.keys(object).length)) return false

  for (let key of union.keys()) {
    let res = objects.map((o) => (typeof o[key] === 'object' ? o[key] : {}))
    if (!objectsHaveSameKeys(...res)) return false
  }
  return true
}

Update 1

A 90% improvement on the recursive deep-check version is achieved on my computer by skipping the concat() and adding the keys directly to the Set(). The same optimization to the original single level version by schirrmacher also achieves ~40% improvement.

The optimized deep-check is now very similar in performance to the optimized single level version!

function objectsHaveSameKeysOptimized(...objects) {
  let union = new Set();
  union = objects.reduce((keys, object) => keys.add(Object.keys(object)), union);
  if (union.size === 0) return true
  if (!objects.every((object) => union.size === Object.keys(object).length)) return false

  for (let key of union.keys()) {
    let res = objects.map((o) => (typeof o[key] === 'object' ? o[key] : {}))
    if (!objectsHaveSameKeys(...res)) return false
  }
  return true
}

Performance Comparison

var x = {}
var y = {}
var a = {}
for (var j = 0; j < 10; ++j){
  a[j] = j
}

for (var i = 0; i < 500000; ++i) {
  x[i] = JSON.parse(JSON.stringify(a))
  y[i] = JSON.parse(JSON.stringify(a))
}

let startTs = new Date()
let result = objectsHaveSameKeys(x, y)
let endTs = new Date()
console.log('objectsHaveSameKeys = ' + (endTs - startTs)/1000)

Results

A: Recursive/deep-check versions*

  1. objectsHaveSameKeys = 5.185
  2. objectsHaveSameKeysOptimized = 0.415

B: Original non-deep versions

  1. objectsHaveSameKeysOriginalNonDeep = 0.517
  2. objectsHaveSameKeysOriginalNonDeepOptimized = 0.342
Share:
129,976
dan27
Author by

dan27

Updated on July 05, 2022

Comments

  • dan27
    dan27 almost 2 years

    I am using node, mocha, and chai for my application. I want to test that my returned results data property is the same "type of object" as one of my model objects (Very similar to chai's instance). I just want to confirm that the two objects have the same sets of property names. I am specifically not interested in the actual values of the properties.

    Let's say I have the model Person like below. I want to check that my results.data has all the same properties as the expected model does. So in this case, Person which has a firstName and lastName.

    So if results.data.lastName and results.data.firstName both exist, then it should return true. If either one doesn't exist, it should return false. A bonus would be if results.data has any additional properties like results.data.surname, then it would return false because surname doesn't exist in Person.

    This model

    function Person(data) {
      var self = this;
      self.firstName = "unknown";
      self.lastName = "unknown";
    
      if (typeof data != "undefined") {
         self.firstName = data.firstName;
         self.lastName = data.lastName;
      }
    }