在JavaScript中比较嵌套对象并返回相同的key

12
我有两个嵌套对象obj1obj2,我想比较它们并递归返回一个对象,对于每个嵌套键,该对象具有类似相等的布尔标志。
因此,对于给定的obj1,例如:
obj1 = {
  prop1: 1,
  prop2: "foo",
  prop3: {
    prop4: 2,
    prop5: "bar" 
  }
}

并且像obj2一样

obj2 = {
      prop1: 3,
      prop2: "foo",
      prop3: {
        prop4: 2,
        prop5: "foobar" 
      },
      prop6: "new"
    }

它应该返回

equality = {
     prop1: false,
     prop2: true,
     prop3 : {
       prop4: true,
       prop5: false
     },
     prop6: false
   }

如果一个对象有一个新的属性,比如obj2.prop6,那么平等性将会是equality.prop6 = false

对于非嵌套对象,这里提供了一个简单的键比较解决方案获取JavaScript中两个对象之间差异的属性。而对于递归比较嵌套对象,则可以查看这里JavaScript:深度递归比较:对象和属性


1
这两个对象的属性总是完全匹配吗? - holydragon
好的观点。不对,所以相等性可以设置一个新键为"false"。根据这个观点更新。谢谢。 - loretoparisi
1
一位贡献者在他的问题中提到了这个链接,他有一个深度嵌套对象的数组作为输入。因此,我想我应该引用我的答案 - Nur
5个回答

10
你可以遍历所有键,并检查嵌套对象是否都是对象。
const isObject = v => v && typeof v === 'object';

function getDifference(a, b) {
    return Object.assign(...Array.from(
        new Set([...Object.keys(a), ...Object.keys(b)]),
        k => ({ [k]: isObject(a[k]) && isObject(b[k])
            ? getDifference(a[k], b[k])
            : a[k] === b[k]
        })
    ));
}

var obj1 = { prop1: 1, prop2: "foo", prop3: { prop4: 2, prop5: "bar" } },
    obj2 = { prop1: 3, prop2: "foo", prop3: { prop4: 2, prop5: "foobar" }, prop6: "new" };

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


谢谢!这个解决方案适用于任何情况,即使属性是一个空对象,比如 prop7 = {}。也许避免使用ECMA6的Spread符号可以提高兼容性。 - loretoparisi
可能像 Object.assign(Array.from(new Set([].concat(Object.keys(a)).concat(Object.keys(b))) 这样。 - loretoparisi

4

您可以使用reduce来构建新对象,另外使用get方法从其他对象中获取嵌套属性,通过string与第一个对象中的当前属性值进行比较。

const obj1 = { prop1: 1, prop2: "foo", prop3: { prop4: 2, prop5: "bar" } }
const obj2 = { prop1: 3, prop2: "foo", prop3: { prop4: 2, prop5: "foobar" } }

function get(obj, path) {
  return path.split('.').reduce((r, e) => {
    if (!r) return r
    else return r[e] || undefined
  }, obj)
}

function compare(a, b, prev = "") {
  return Object.keys(a).reduce((r, e) => {
    const path = prev + (prev ? '.' + e : e);
    const value = a[e] === get(b, path);
    r[e] = typeof a[e] === 'object' ? compare(a[e], b, path) : value
    return r;
  }, {})
}

const result = compare(obj1, obj2);
console.log(result)

为了比较两个对象的所有属性,您可以创建一个额外的函数来通过这两个对象执行循环。

const obj1 = {"prop1":1,"prop2":"foo","prop3":{"prop4":2,"prop5":"bar"},"prop7":{"prop9":{"prop10":"foo"}}}
const obj2 = {"prop1":3,"prop2":"foo","prop3":{"prop4":2,"prop5":"foobar"},"prop6":"new","prop7":{"foo":"foo","bar":{"baz":"baz"}}}

function get(obj, path) {
  return path.split('.').reduce((r, e) => {
    if (!r) return r;
    else return r[e] || undefined;
  }, obj);
}

function isEmpty(o) {
  if (typeof o !== 'object') return true;
  else return !Object.keys(o).length;
}

function build(a, b, o = null, prev = '') {
  return Object.keys(a).reduce(
    (r, e) => {
      const path = prev + (prev ? '.' + e : e);
      const bObj = get(b, path);
      const value = a[e] === bObj;

      if (typeof a[e] === 'object') {
        if (isEmpty(a[e]) && isEmpty(bObj)) {
          if (e in r) r[e] = r[e];
          else r[e] = true;
        } else if (!bObj && isEmpty(a[e])) {
          r[e] = value;
        } else {
          r[e] = build(a[e], b, r[e], path);
        }
      } else {
        r[e] = value;
      }
      return r;
    },
    o ? o : {}
  );
}

function compare(a, b) {
  const o = build(a, b);
  return build(b, a, o);
}

const result = compare(obj1, obj2);
console.log(result)


1
我认为这是最佳解决方案,因为它支持所有最近的ECMAScript版本。 - loretoparisi
只是一件事,假设属性是一个空对象,如{},它不会进行比较。 - loretoparisi
好的,现在它处理了一个嵌套的孤立的 prop11={} 对象,但是如果你在两个中都有相同的 prop12={},你将会得到 {} 作为 prop12 关键字的结果,而不是 bool。请参见这里 https://jsfiddle.net/gpu20nwy/ - loretoparisi
这个可以,麻烦更新一下解决方案,我会接受它的! - loretoparisi

3
你可以创建一个“merged”对象,该对象将具有两个对象的键。循环遍历此对象并比较每个键的obj1obj2的值。如果属性是对象,请递归比较属性。这对于任何层次的嵌套都有效。由于属性可能在任一对象中缺失,因此添加了默认参数= {}

const obj1={prop1:1,prop2:"foo",prop3:{prop4:2,prop5:"bar"},prop7:{pro8:"only in 1"}},
      obj2={prop1:3,prop2:"foo",prop3:{prop4:2,prop5:"foobar"}, prop6: "only in 2"};
    
const isObject = val => typeof val === 'object' && val // required for "null" comparison

function compare(obj1 = {}, obj2 = {}) {
  const output = {},
        merged = { ...obj1, ...obj2 }; // has properties of both

  for (const key in merged) {
    const value1 = obj1[key],
          value2 = obj2[key];

    if (isObject(value1) || isObject(value2))
      output[key] = compare(value1, value2) // recursively call
    else
      output[key] = value1 === value2
  }
  
  return output;
}

console.log(compare(obj1, obj2))


谢谢,我刚刚更新了代码,假设你也可以有新的属性。 - loretoparisi
如果 obj1 有一些键的值是一个子对象,而 obj2 没有相同的键,则该方法将使用 compare(obj1, undefined) 调用,这将在 obj2[key] 处抛出错误。 - AZ_
@adiga 不确定,但问题中的评论和更新后的问题都提到了这一点,不能假设额外的键总是具有字符串值。 - AZ_
@AZ_更新。不确定是否会在任何情况下失败。 - adiga

1

一个递归的例子,

var obj1 = {
        prop1: 1,
        prop2: "foo",
        prop3: {
            prop4: 2,
            prop5: "bar"
        },
        prop7: {},
    }

    var obj2 = {
        prop1: 3,
        prop2: "foo",
        prop3: {
            prop4: 2,
            prop5: "foobar"
        },
        prop6: "new",
        prop7: {},
        prop8: {},
    }

    var result = {};

    function compare(obj1, obj2, obj_) {
        for (let k in obj1) {
            var type = typeof obj1[k];
            if (type === 'object') {
                obj_[k] = {};
                if (!obj2[k]){
                    obj_[k] = false;
                }else if ((Object.entries(obj1[k]).length === 0 && obj1[k].constructor === Object) && (Object.entries(obj2[k]).length === 0 && obj2[k].constructor === Object)) {
                    obj_[k] = true;
                } else {
                    compare(obj1[k], obj2[k], obj_[k]);
                }
            } else {
                obj_[k] = (obj1[k] === obj2[k]);
            }

        }
    }

    if (Object.keys(obj1).length < Object.keys(obj2).length) { //check if both objects vary in length.
        var tmp = obj1;
        obj1 = obj2;
        obj2 = tmp;
    }

    compare(obj1, obj2, result);

    console.log(result);


谢谢。如果您尝试此案例 - https://jsfiddle.net/47fcqtom/,您将在类型为“object”的属性的结果中获得`{}`,这些属性属于两个对象且都为`{}`。 - loretoparisi
1
@loretoparisi,我做了一个更改,请现在尝试。 - Shoyeb Sheikh

1

最近我做了一个解决方案,可以处理相同的问题,并且它需要一个可选的密钥来设置比较的严格性。当你的后端将一个值作为数字发送,但期望该值以字符串形式返回时,这个方案非常有用。我们一直在使用JSON.stringify比较,但它有点粗糙,并且不能解决对象相同但键不同的情况。

public objectsAreTheSame(objA: {[key: string]: any}, objB: {[key: string]: any}, isStrict = false): boolean {
        let areTheSame = true;

        const strictLevel = isStrict ? 'isStrict' : 'isNotStrict';

        const valuesDoNotMatch = {
            'isStrict': (a, b) => a !== b,
            'isNotStrict': (a, b) => a != b
        };

        const isObject = (a, b) => typeof a === 'object' && !Array.isArray(a) && (!!a && !!b);

        const compareArrays = (a, b) => {
            if (a.length === b.length) {
                a.sort();
                b.sort();

                a.forEach((ele, idx) => compareValues(ele, b[idx]));
            } else {
                areTheSame = false;
            }
        };

        const compareValues = (a, b) => {
            if (Array.isArray(a)) {
                compareArrays(a, b);
            } else if (!isObject(a, b) && valuesDoNotMatch[strictLevel](a, b)) {
                areTheSame = false;
            } else if (isObject(a, b) && !this.objectsAreTheSame(a, b, isStrict)) {
                areTheSame = false;
            }
        };

        const keysA = Object.keys(objA);
        const keysB = Object.keys(objB);

        if (keysA.length !== keysB.length) return false;

        for (let key of keysA) compareValues(objA[key], objB[key]);

        return areTheSame;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接