如何使用 lodash 在 2 个对象之间进行深入比较?

IT技术 javascript lodash
2021-01-15 23:01:57

我有 2 个不同的嵌套对象,我需要知道它们的嵌套属性之一是否存在差异。

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

具有更多嵌套属性的对象可能会复杂得多。但这是一个很好的例子。我可以选择使用递归函数或 lodash 的东西......

6个回答

一个简单而优雅的解决方案是使用_.isEqual,它执行深度比较:

var a = {};
var b = {};

a.prop1 = 2;
a.prop2 = { prop3: 2 };

b.prop1 = 2;
b.prop2 = { prop3: 3 };

console.log(_.isEqual(a, b)); // returns false if different
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

但是,此解决方案并未显示哪个属性不同。

@oruckdeschel 如果引用相同,则它是同一个对象。因此它是平等的。这是一个棘手的指针,而不是 lodash。lodash 很棒。
2021-04-09 23:01:57
我知道答案很旧,但我想补充一点,这_.isEqual可能非常棘手。如果您复制对象并更改其中的一些值,它仍然会显示为真,因为引用是相同的。所以使用这个功能要小心。
2021-04-11 23:01:57

如果您需要知道哪些属性不同,请使用reduce()

_.reduce(a, function(result, value, key) {
    return _.isEqual(value, b[key]) ?
        result : result.concat(key);
}, []);
// → [ "prop2" ]
_.reduce(a, (result, value, key) => _.isEqual(value, b[key]) ? result : result.concat(key), [])单行 ES6 解决方案
2021-03-13 23:01:57
连接键:值的版本 let edited = _.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat( { [key]: value } ); }, []);
2021-03-17 23:01:57
此外,这不会选择 b 中不在 a 中的属性。
2021-03-24 23:01:57
请注意,这只会输出第一级不同的属性。(所以它在输出不同属性的意义上并不是很深。)
2021-03-26 23:01:57

对于任何在此线程上绊倒的人,这里有一个更完整的解决方案。它将比较两个对象,并为您提供仅在 object1 中仅在 object2中或同时在 object1 和 object2 中但具有不同值的所有属性的键

/*
 * Compare two objects by reducing an array of keys in obj1, having the
 * keys in obj2 as the intial value of the result. Key points:
 *
 * - All keys of obj2 are initially in the result.
 *
 * - If the loop finds a key (from obj1, remember) not in obj2, it adds
 *   it to the result.
 *
 * - If the loop finds a key that are both in obj1 and obj2, it compares
 *   the value. If it's the same value, the key is removed from the result.
 */
function getObjectDiff(obj1, obj2) {
    const diff = Object.keys(obj1).reduce((result, key) => {
        if (!obj2.hasOwnProperty(key)) {
            result.push(key);
        } else if (_.isEqual(obj1[key], obj2[key])) {
            const resultKeyIndex = result.indexOf(key);
            result.splice(resultKeyIndex, 1);
        }
        return result;
    }, Object.keys(obj2));

    return diff;
}

这是一个示例输出:

// Test
let obj1 = {
    a: 1,
    b: 2,
    c: { foo: 1, bar: 2},
    d: { baz: 1, bat: 2 }
}

let obj2 = {
    b: 2, 
    c: { foo: 1, bar: 'monkey'}, 
    d: { baz: 1, bat: 2 }
    e: 1
}
getObjectDiff(obj1, obj2)
// ["c", "e", "a"]

如果你不关心嵌套对象并想跳过 lodash,你可以用 代替_.isEqual一个正常的值比较,例如obj1[key] === obj2[key].

这个选择的答案对于测试相等性是正确的。如果您需要知道差异是什么,没有明显的方法来列出它们,但是这个答案非常好,只是给出了存在差异的顶级属性键的列表。(它以函数的形式给出答案,这使得它可用。)
2021-03-15 23:01:57
这样做和只使用 _.isEqual(obj1, obj2) 有什么区别?添加对 hasOwnProperty 的检查会做什么而 _.isEqual 没有?我假设如果 obj1 有一个 obj2 没有的属性,_.isEqual 不会返回 true..?
2021-03-24 23:01:57
@Jaked222 - 不同之处在于 isEqual 返回一个布尔值,告诉您对象是否相等,而上面的函数则告诉您两个对象之间有什么不同(如果它们不同)。如果您只想知道两个对象是否相同,那么 isEqual 就足够了。但在许多情况下,您想知道两个对象之间的区别是什么。例如,如果您想检测某事前后的更改,然后根据更改分派事件。
2021-04-11 23:01:57

根据Adam Boduch 的回答,我编写了这个函数,它在最深的意义上比较两个对象,返回具有不同值的路径以及一个或另一个对象缺少的路径。

编写代码时没有考虑到效率,在这方面的改进是最受欢迎的,但这里是基本形式:

var compare = function (a, b) {

  var result = {
    different: [],
    missing_from_first: [],
    missing_from_second: []
  };

  _.reduce(a, function (result, value, key) {
    if (b.hasOwnProperty(key)) {
      if (_.isEqual(value, b[key])) {
        return result;
      } else {
        if (typeof (a[key]) != typeof ({}) || typeof (b[key]) != typeof ({})) {
          //dead end.
          result.different.push(key);
          return result;
        } else {
          var deeper = compare(a[key], b[key]);
          result.different = result.different.concat(_.map(deeper.different, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_second = result.missing_from_second.concat(_.map(deeper.missing_from_second, (sub_path) => {
            return key + "." + sub_path;
          }));

          result.missing_from_first = result.missing_from_first.concat(_.map(deeper.missing_from_first, (sub_path) => {
            return key + "." + sub_path;
          }));
          return result;
        }
      }
    } else {
      result.missing_from_second.push(key);
      return result;
    }
  }, result);

  _.reduce(b, function (result, value, key) {
    if (a.hasOwnProperty(key)) {
      return result;
    } else {
      result.missing_from_first.push(key);
      return result;
    }
  }, result);

  return result;
}

您可以尝试使用此代码段的代码(建议在整页模式下运行):

我刚刚修复了这个错误,但为了让您知道,您应该b 使用b.hasOwnProperty(key)orkey in b来检查对象中的密钥是否存在,而不是使用b[key] != undefined对于使用的旧版本b[key] != undefined,该函数为包含 的对象返回了不正确的差异undefined,如compare({disabled: undefined}, {disabled: undefined}). 事实上,旧版本也有问题null您可以通过始终使用===and!==而不是==and来避免此类问题!=
2021-03-16 23:01:57

这是使用Lodash的简洁解决方案

_.differenceWith(a, b, _.isEqual);

请注意,两个输入都必须是数组(可能是一个对象的数组)。

@Brendon、@THughes、@aristidesfl 抱歉,我混淆了一些东西,它适用于对象数组,但不适用于深度对象比较。事实证明,如果两个参数都不是数组,lodash 只会返回[].
2021-03-14 23:01:57
还使用 Lodash 4.17.4 获取空数组
2021-03-15 23:01:57
看起来它只适用于数组。但是将对象放入数组没有问题。_.differenceWith([object1], [object2], _.isEqual);如果返回的数组为空 - 这意味着 - 如果数组不为空则没有区别 - 有区别
2021-03-20 23:01:57
@Z.Khullah 如果它确实以这种方式工作,则没有记录。
2021-03-28 23:01:57
似乎不适用于我的对象。而是返回一个空数组。
2021-04-03 23:01:57