如何检查两个对象是否具有相同的属性名称集?

IT技术 javascript node.js mocha.js chai
2021-02-12 17:16:45

我正在为我的应用程序使用 node、mocha 和 chai。我想测试我返回的结果数据属性是否与我的模型对象之一具有相同的“对象类型”(非常类似于 chai 的实例)。我只想确认这两个对象具有相同的属性名称集。我对属性的实际值特别不感兴趣。

假设我有像下面这样的模型 Person 。我想检查我的 results.data 是否具有与预期模型相同的所有属性。所以在这种情况下,Person 有一个 firstName 和 lastName。

所以如果results.data.lastNameresults.data.firstName两者都存在,那么它应该返回真。如果其中一个不存在,则应返回 false。如果results.data 有任何额外的属性,如results.data.surname,那么它会返回false,因为surname 在Person 中不存在。

这个型号

function Person(data) {
  var self = this;
  self.firstName = "unknown";
  self.lastName = "unknown";

  if (typeof data != "undefined") {
     self.firstName = data.firstName;
     self.lastName = data.lastName;
  }
}
6个回答

您可以序列化简单数据以检查相等性:

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

这会给你类似的东西

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

作为函数...

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

编辑

如果您像您说的那样使用 chai,请查看http://chaijs.com/api/bdd/#equal-section

编辑 2

如果您只想检查密钥...

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

应该这样做。

它今天有效,因为大多数浏览器维护某种对象键的排序,但 ecma 规范不需要它,因此此代码可能会失败。
2021-03-23 17:16:45
这正是我正在寻找的……JS 新手并且不确定如何进行属性反射。谢谢!
2021-03-26 17:16:45
我不想检查属性的实际值,只想检查属性名称。对困惑感到抱歉
2021-03-28 17:16:45
+ 1 表示想法,但要注意陷阱 -参数顺序在您的方法中很重要JSON.stringify({b:1, a:1}) 不同于 JSON.stringify({a:1, b:1})
2021-04-05 17:16:45
如果您需要深入检查/嵌套对象stackoverflow.com/questions/41802259 /...
2021-04-14 17:16:45

2 这是一个简短的ES6可变参数版本:

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);
}

一点性能测试(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;
}

结果:

objectsHaveSameKeys(x, y) // took  4996 milliseconds
compareKeys(x, y)               // took 14880 milliseconds
hasSameProps(x,y)               // after 10 minutes I stopped execution
厉害的对比!
2021-03-15 17:16:45
为了返回不同键的数量: return objects.reduce((res, object) => res += union.size - Object.keys(object).length, 0);
2021-03-20 17:16:45
为什么我得到了反对票?请写评论,以便我可以改进我的答案:)
2021-03-25 17:16:45

如果要检查两个对象是否具有相同的属性名称,可以执行以下操作:

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));

通过这种方式,您可以确保只检查两个对象的可迭代和可访问属性。

编辑 - 2013.04.26:

前面的函数可以用以下方式重写:

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;
}

这样我们检查两个对象是否具有相同数量的属性(否则对象没有相同的属性,我们必须返回一个逻辑假)然后,如果数量匹配,我们去检查它们是否具有相同的属性特性。

奖金

一个可能的增强可能是引入类型检查来强制匹配每个属性。

这不只是检查obj2hasobj1的属性,反之亦然吗?
2021-03-27 17:16:45
看起来它只检查第一级属性,对吗?
2021-04-07 17:16:45
我认为这也会奏效。与凯西的非常相似。谢谢
2021-04-10 17:16:45
@Mirko 是的。请注意,检查是通过在对象中查找相同的键来完成的。它不是基于它们的有效值。(例如,我可以将两个name键分配给一个字符串,一个分配给数字,并且检查仍然会返回真实性)。但是,您可以通过在对象键的情况下实现某种可记录性来调整它,但它需要扩展对数据类型的检查。
2021-04-10 17:16:45
该函数检查是否所有属性obj1都存在于obj2,所以它们具有相同的属性。但反之则不然。如果您想跳过具有不同属性数量的对象的迭代,则必须添加对两个对象中属性数量的检查,并在它们不匹配的情况下返回逻辑假。
2021-04-14 17:16:45

如果你想要像@speculees 这样的深度验证,这里有一个使用的答案deep-keys(披露:我是这个小包的维护者)

// 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);

或与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));

这是shirrmacher上面提供的函数的深度检查版本下面是我的尝试。请注意:

  • 解决方案不检查 null 并且不是防弹的
  • 我还没有对其进行性能测试。也许 shirrmacher 或 OP 可以做到这一点并为社区分享。
  • 我不是 JS 专家:)。
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
}

更新 1

在递归深检查版本A 90%的改善被跳过我的电脑上实现concat(),并直接添加的钥匙Set()shirrmacher 对原始单级版本的相同优化也实现了约 40% 的改进。

优化的深度检查现在在性能上与优化的单级版本非常相似!

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
}

性能比较

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)

结果

A:递归/深度检查版本*

  1. objectsHaveSameKeys = 5.185
  2. objectsHaveSameKeysOptimized = 0.415

B:原始非深度版本

  1. objectsHaveSameKeysOriginalNonDeep = 0.517
  2. objectsHaveSameKeysOriginalNonDeepOptimized = 0.342
我喜欢这个,我能看到的唯一改进是在递归if (!res[0]) continue之前检查虚假:if (!objectsHaveSameKeys(...res)) return false
2021-03-26 17:16:45
@AlbertoSadoc,感谢您的建议!该条件if(!res[0])在代码中永远不会为真。但是,如果我们filter()res,那么它应该可以工作,即res = res.filter((e) => (Object.keys(e).length !== 0))但是 filter() 和 Object.keys() 的成本是不合理的,因为我们在递归调用上执行另一个 Object.keys() 无论如何使大多数调用的成本增加一倍,只是为了节省一个退出场景的成本。额外的代码也不值得。
2021-04-07 17:16:45