如何用lodash对2个对象进行深度比较?

JLa*_*oie 254 javascript lodash

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

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

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

b.prop1 = 2;
b.prop2 = { prop3: 3 };
Run Code Online (Sandbox Code Playgroud)

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

JLa*_*oie 410

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

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

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

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

_.isEqual(a, b); // returns false if different
Run Code Online (Sandbox Code Playgroud)

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

http://jsfiddle.net/bdkeyn0h/

  • @oruckdeschel如果引用相同,则它是同一个对象.因此它是平等的.这是一个棘手的指针而不是lodash.洛达什太棒了. (10认同)
  • @WalterMonecke 不,不应该。`hasSameReference` 只是普通的 `===`,这不是它的作用。`_.isEqual` 对于相同的引用返回 true,因为这是值和属性相等的对象的非严格子集,但它也返回两个_非引用的不同对象_在值和属性上是否(深度)相等。 (3认同)
  • 我知道答案很旧,但是我想补充一下,_.isEqual可能非常棘手。如果复制对象并在其中更改一些值,由于引用相同,它仍将显示true。因此,应谨慎使用此功能。 (2认同)

Ada*_*uch 256

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

_.reduce(a, function(result, value, key) {
    return _.isEqual(value, b[key]) ?
        result : result.concat(key);
}, []);
// ? [ "prop2" ]
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这只会输出第一级不同的属性.(因此,在输出不同的属性时,它并不是真正的*深*.) (36认同)
  • 此外,这不会获取b中不属于a的属性. (15认同)
  • 和`_.reduce(a,(result,value,key)=> _.isEqual(value,b [key])?result:result.concat(key),[])`用于单行ES6解决方案 (3认同)
  • 一个版本连接 key:value `letedited = _.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat( { [key]:值 } ); }, []);` (2认同)

Joh*_*son 42

对于任何绊到这个线程的人来说,这是一个更完整的解决方案.它将比较两个对象,并为您提供所有属性的键,这些属性仅在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;
}
Run Code Online (Sandbox Code Playgroud)

这是一个示例输出:

// 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"]
Run Code Online (Sandbox Code Playgroud)

如果您不关心嵌套对象并想要跳过lodash,则可以替换_.isEqual正常的值比较,例如obj1[key] === obj2[key].

  • @Jaked222 - 区别在于isEqual返回一个布尔值,告诉你对象是否相等,而上面的函数告诉你_what_在两个对象之间是不同的(如果它们是不同的).如果你只想知道两个对象是否相同,那么isEqual就足够了.但在许多情况下,您想知道两个对象之间的区别.例如,如果要检测某些事物之前和之后的更改,然后根据更改调度事件. (2认同)

Ras*_*leh 26

根据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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

var a_editor = new JSONEditor($('#a')[0], {
  name: 'a',
  mode: 'code'
});
var b_editor = new JSONEditor($('#b')[0], {
  name: 'b',
  mode: 'code'
});

var a = {
  same: 1,
  different: 2,
  missing_from_b: 3,
  missing_nested_from_b: {
    x: 1,
    y: 2
  },
  nested: {
    same: 1,
    different: 2,
    missing_from_b: 3
  }
}

var b = {
  same: 1,
  different: 99,
  missing_from_a: 3,
  missing_nested_from_a: {
    x: 1,
    y: 2
  },
  nested: {
    same: 1,
    different: 99,
    missing_from_a: 3
  }
}

a_editor.set(a);
b_editor.set(b);

var result_editor = new JSONEditor($('#result')[0], {
  name: 'result',
  mode: 'view'
});

var do_compare = function() {
  var a = a_editor.get();
  var b = b_editor.get();
  result_editor.set(compare(a, b));
}
Run Code Online (Sandbox Code Playgroud)
#objects {} #objects section {
  margin-bottom: 10px;
}
#objects section h1 {
  background: #444;
  color: white;
  font-family: monospace;
  display: inline-block;
  margin: 0;
  padding: 5px;
}
.jsoneditor-outer, .ace_editor {
min-height: 230px !important;
}
button:hover {
  background: orangered;
}
button {
  cursor: pointer;
  background: red;
  color: white;
  text-align: left;
  font-weight: bold;
  border: 5px solid crimson;
  outline: 0;
  padding: 10px;
  margin: 10px 0px;
}
Run Code Online (Sandbox Code Playgroud)
<link href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.5.10/jsoneditor.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="objects">
  <section>
    <h1>a (first object)</h1>
    <div id="a"></div>
  </section>
  <section>
    <h1>b (second object)</h1>
    <div id="b"></div>
  </section>
  <button onClick="do_compare()">compare</button>
  <section>
    <h1>result</h1>
    <div id="result"></div>
  </section>
</div>
Run Code Online (Sandbox Code Playgroud)

  • 我刚修复了这个bug,但是为了让你知道,你应该检查一个对象`b`中的键存在[使用`b.hasOwnProperty(key)`或`b中的键'](http://stackoverflow.com/q/1098040/578288),而不是`b [key]!= undefined`.对于使用`b [key]!= undefined`的旧版本,函数为包含`undefined`的对象返回了一个不正确的diff,如`compare({disabled:undefined},{disabled:undefined})`.事实上,旧版本也存在"null"的问题; 你可以通过总是使用[`===`和`!==`](http://stackoverflow.com/a/359509/578288)来代替`==`和`!=`来避免这样的问题. (2认同)

小智 23

这是一个简洁的解决方案:

_.differenceWith(a, b, _.isEqual);
Run Code Online (Sandbox Code Playgroud)

  • 仅适用于数组,不适用于对象 (56认同)
  • 似乎不适合我的对象.而是返回一个空数组. (7认同)
  • Lodash 4.17.4可以在这里使用对象 (3认同)
  • 同样用Lodash 4.17.4获取空数组 (2认同)
  • @Brendon、@THughes、@aristidesfl 抱歉,我混淆了一些东西,它适用于对象数组,但不适用于深度对象比较。事实证明,如果两个参数都不是数组,lodash 只会返回 `[]`。 (2认同)
  • 看起来它只适用于数组。但是将对象放入数组中没有问题。`_.differenceWith([object1], [object2], _.isEqual);` 如果返回的数组为空 - 这意味着 - 没有区别 如果数组不为空 - 有区别 (2认同)
  • `_.differenceWith([object1], [object2], _.isEqual);` 等价于 `_.isEqual(object1, object2)` 所以使用 `differenceWith` 是没有用的 (2认同)

San*_*vid 7

要递归显示对象与其他对象的区别,可以将_.reduce_.isEqual_.isPlainObject结合使用。在这种情况下,您可以比较a与b的不同之处或b与a的不同之处:

var a = {prop1: {prop1_1: 'text 1', prop1_2: 'text 2', prop1_3: [1, 2, 3]}, prop2: 2, prop3: 3};
var b = {prop1: {prop1_1: 'text 1', prop1_3: [1, 2]}, prop2: 2, prop3: 4};

var diff = function(obj1, obj2) {
  return _.reduce(obj1, function(result, value, key) {
    if (_.isPlainObject(value)) {
      result[key] = diff(value, obj2[key]);
    } else if (!_.isEqual(value, obj2[key])) {
      result[key] = value;
    }
    return result;
  }, {});
};

var res1 = diff(a, b);
var res2 = diff(b, a);
console.log(res1);
console.log(res2);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>
Run Code Online (Sandbox Code Playgroud)


Ali*_*eza 7

简单的使用_.isEqual方法,它将适用于所有比较......

  • 注意:此方法支持比较数组、数组缓冲区、布尔值、* 日期对象、错误对象、映射、数字、Object对象、正则表达式、* 集、字符串、符号和类型化数组。Object对象通过它们自己的而非继承的可枚举属性进行比较 *。支持函数和 DOM * 节点。

所以如果你有以下:

 const firstName = {name: "Alireza"};
 const otherName = {name: "Alireza"};
Run Code Online (Sandbox Code Playgroud)

如果你这样做: _.isEqual(firstName, otherName);,

它会返回

而如果 const fullName = {firstName: "Alireza", familyName: "Dezfoolian"};

如果你这样做: _.isEqual(firstName, fullName);,

会返回


use*_*748 6

正如所问,这是一个递归对象比较函数。还有一点。假设这个函数的主要用途是对象检查,我有话要说。当某些差异无关紧要时,完全深度比较是一个坏主意。例如,TDD 断言中的盲目深度比较会使测试变得不必要的脆弱。因此,我想介绍一个更有价值的部分 diff。它是对该线程先前贡献的递归模拟。它忽略不存在

var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => key + '.' + x) 
            : (!b || val != b[key] ? [key] : [])),
        []);
Run Code Online (Sandbox Code Playgroud)

BDiff 允许检查预期值,同时容忍其他属性,这正是您想要的自动检查。这允许构建各种高级断言。例如:

var diff = bdiff(expected, actual);
// all expected properties match
console.assert(diff.length == 0, "Objects differ", diff, expected, actual);
// controlled inequality
console.assert(diff.length < 3, "Too many differences", diff, expected, actual);
Run Code Online (Sandbox Code Playgroud)

回到完整的解决方案。使用 bdiff 构建完整的传统 diff 很简单:

function diff(a, b) {
    var u = bdiff(a, b), v = bdiff(b, a);
    return u.filter(x=>!v.includes(x)).map(x=>' < ' + x)
    .concat(u.filter(x=>v.includes(x)).map(x=>' | ' + x))
    .concat(v.filter(x=>!u.includes(x)).map(x=>' > ' + x));
};
Run Code Online (Sandbox Code Playgroud)

在两个复杂对象上运行上述函数将输出类似于以下内容的内容:

 [
  " < components.0.components.1.components.1.isNew",
  " < components.0.cryptoKey",
  " | components.0.components.2.components.2.components.2.FFT.min",
  " | components.0.components.2.components.2.components.2.FFT.max",
  " > components.0.components.1.components.1.merkleTree",
  " > components.0.components.2.components.2.components.2.merkleTree",
  " > components.0.components.3.FFTResult"
 ]
Run Code Online (Sandbox Code Playgroud)

最后,为了了解这些值有何不同,我们可能需要直接eval() diff 输出。为此,我们需要一个更丑陋的bdiff版本,它输出语法上正确的路径:

// provides syntactically correct output
var bdiff = (a, b) =>
    _.reduce(a, (res, val, key) =>
        res.concat((_.isPlainObject(val) || _.isArray(val)) && b
            ? bdiff(val, b[key]).map(x => 
                key + (key.trim ? '':']') + (x.search(/^\d/)? '.':'[') + x)
            : (!b || val != b[key] ? [key + (key.trim ? '':']')] : [])),
        []);

// now we can eval output of the diff fuction that we left unchanged
diff(a, b).filter(x=>x[1] == '|').map(x=>[x].concat([a, b].map(y=>((z) =>eval('z.' + x.substr(3))).call(this, y)))));
Run Code Online (Sandbox Code Playgroud)

这将输出类似这样的内容:

[" | components[0].components[2].components[2].components[2].FFT.min", 0, 3]
[" | components[0].components[2].components[2].components[2].FFT.max", 100, 50]
Run Code Online (Sandbox Code Playgroud)

麻省理工学院许可证;)


Nab*_*hah 6

已经发布了很多答案,但对于那些好奇避免编写任何代码来计算具有任何类型结构的两个对象之间的差异的人来说,实际上有一个库可以做到这一点。LodashisEqual只返回 true 或 false,不返回有关更改属性的任何信息。https://www.npmjs.com/package/deep-diff

它返回两个对象之间差异的完整详细信息

import DeepDiff from 'deep-diff';
let a = {...} //some object
let b = {...} //some object 
var differences = DeepDiff.diff(a, b);
Run Code Online (Sandbox Code Playgroud)

在这个线程中也提出了类似的问题 Getting the Difference Between 2 JSON object


bli*_*lum 5

此代码返回一个对象,该对象的所有属性都具有不同的值,并且两个对象的值也不同。有助于记录差异。

var allkeys = _.union(_.keys(obj1), _.keys(obj2));
var difference = _.reduce(allkeys, function (result, key) {
  if ( !_.isEqual(obj1[key], obj2[key]) ) {
    result[key] = {obj1: obj1[key], obj2: obj2[key]}
  }
  return result;
}, {});
Run Code Online (Sandbox Code Playgroud)