【问题标题】:How to do a deep comparison between 2 objects with lodash?如何用 lodash 对 2 个对象进行深度比较?
【发布时间】:2015-10-19 09:48:10
【问题描述】:

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

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

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

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

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

【问题讨论】:

标签: javascript lodash


【解决方案1】:

一个简单而优雅的解决方案是使用_.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>

但是,此解决方案没有显示不同的属性。

【讨论】:

  • 我知道答案已经很老了,但我想补充一点,_.isEqual 可能非常棘手。如果您复制对象并更改其中的一些值,它仍然会显示为 true,因为引用是相同的。所以使用这个功能要小心。
  • @oruckdeschel 如果引用相同,则它是相同的对象。因此它是平等的。这是一个棘手的指针而不是 lodash。 lodash 很棒。
【解决方案2】:

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

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

【讨论】:

  • 注意这只会输出第一级不同的属性。 (所以在输出不同的属性的意义上,它并不是真正的deep。)
  • 另外,这不会获取 b 中不在 a 中的属性。
  • _.reduce(a, (result, value, key) => _.isEqual(value, b[key]) ? result : result.concat(key), []) 用于单行 ES6 解决方案
  • 连接key:value的版本let edited = _.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat( { [key]: value } ); }, []);
【解决方案3】:

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

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

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

【讨论】:

  • 似乎不适用于我的对象。而是返回一个空数组。
  • Lodash 4.17.4 也得到空数组
  • @Z.Khullah 如果确实以这种方式工作,则没有记录。
  • @Brendon , @THughes, @aristidesfl 抱歉,我已经混合了一些东西,它适用于对象数组,但不适用于深度对象比较。事实证明,如果两个参数都不是数组,lodash 将只返回[]
  • 看起来它只适用于数组。但是将对象放入数组中没有问题。 _.differenceWith([object1], [object2], _.isEqual); 如果返回的数组为空 - 这意味着 - 如果数组不为空,则没有区别 - 有区别
【解决方案4】:

完成Adam Boduch的回答,这个考虑了属性的差异

const differenceOfKeys = (...objects) =>
  _.difference(...objects.map(obj => Object.keys(obj)));
const differenceObj = (a, b) => 
  _.reduce(a, (result, value, key) => (
    _.isEqual(value, b[key]) ? result : [...result, key]
  ), differenceOfKeys(b, a));

【讨论】:

    【解决方案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;
    }, {});
    

    【讨论】:

      【解决方案6】:

      如果您只需要键比较:

       _.reduce(a, function(result, value, key) {
           return b[key] === undefined ? key : []
        }, []);
      

      【讨论】:

        【解决方案7】:

        对于偶然发现此线程的任何人,这里有一个更完整的解决方案。它将比较两个对象,并为您提供仅在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].

        【讨论】:

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

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

        编写代码时没有考虑到效率,欢迎在这方面进行改进,但基本形式如下:

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

        你可以试试这个sn-p的代码(推荐全页模式运行):

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

        【讨论】:

        • 我刚刚修复了这个错误,但是为了让您知道,您应该检查对象 b using b.hasOwnProperty(key) or key in b 中的键是否存在,而不是使用 b[key] != undefined。对于使用 b[key] != undefined 的旧版本,该函数为包含 undefined 的对象返回了不正确的差异,如 compare({disabled: undefined}, {disabled: undefined})。其实老版本null也有问题;您可以通过始终使用=== and !== 而不是==!= 来避免此类问题。
        【解决方案9】:

        我对 Adam Boduch 的代码进行了测试以输出深度差异 - 这完全未经测试,但碎片就在那里:

        function diff (obj1, obj2, path) {
            obj1 = obj1 || {};
            obj2 = obj2 || {};
        
            return _.reduce(obj1, function(result, value, key) {
                var p = path ? path + '.' + key : key;
                if (_.isObject(value)) {
                    var d = diff(value, obj2[key], p);
                    return d.length ? result.concat(d) : result;
                }
                return _.isEqual(value, obj2[key]) ? result : result.concat(p);
            }, []);
        }
        
        diff({ foo: 'lol', bar: { baz: true }}, {}) // returns ["foo", "bar.baz"]
        

        【讨论】:

        • 就像一个魅力,只是 obj1 和 obj2 的顺序很重要。例如:diff({}, { foo: 'lol', bar: { baz: true }}) // returns []
        【解决方案10】:

        在不使用 lodash/underscore 的情况下,我编写了这段代码,并且可以很好地对 object1 和 object2 进行深入比较

        function getObjectDiff(a, b) {
            var diffObj = {};
            if (Array.isArray(a)) {
                a.forEach(function(elem, index) {
                    if (!Array.isArray(diffObj)) {
                        diffObj = [];
                    }
                    diffObj[index] = getObjectDiff(elem, (b || [])[index]);
                });
            } else if (a != null && typeof a == 'object') {
                Object.keys(a).forEach(function(key) {
                    if (Array.isArray(a[key])) {
                        var arr = getObjectDiff(a[key], b[key]);
                        if (!Array.isArray(arr)) {
                            arr = [];
                        }
                        arr.forEach(function(elem, index) {
                            if (!Array.isArray(diffObj[key])) {
                                diffObj[key] = [];
                            }
                            diffObj[key][index] = elem;
                        });
                    } else if (typeof a[key] == 'object') {
                        diffObj[key] = getObjectDiff(a[key], b[key]);
                    } else if (a[key] != (b || {})[key]) {
                        diffObj[key] = a[key];
                    } else if (a[key] == (b || {})[key]) {
                        delete a[key];
                    }
                });
            }
            Object.keys(diffObj).forEach(function(key) {
                if (typeof diffObj[key] == 'object' && JSON.stringify(diffObj[key]) == '{}') {
                    delete diffObj[key];
                }
            });
            return diffObj;
        }
        

        【讨论】:

          【解决方案11】:
          var isEqual = function(f,s) {
            if (f === s) return true;
          
            if (Array.isArray(f)&&Array.isArray(s)) {
              return isEqual(f.sort(), s.sort());
            }
            if (_.isObject(f)) {
              return isEqual(f, s);
            }
            return _.isEqual(f, s);
          };
          

          【讨论】:

          • 这是无效的。您不能直接将对象与=== 进行比较,{ a: 20 } === { a: 20 } 将返回 false,因为它比较原型。主要比较对象的更正确方法是将它们包装到JSON.stringify()
          • 如果 (f === s) 返回真; - 仅用于递归。是 a: 20 } === { a: 20 } 将返回 false 并转到下一个条件
          • 为什么不只是_.isEqual(f, s)? :)
          • 这将导致无限递归循环,因为如果f 是一个对象并且您到达if (_.isObject(f)),您只需返回函数并再次点击该点。 f (Array.isArray(f)&amp;&amp;Array.isArray(s)) 也是如此
          【解决方案12】:

          使用(嵌套)属性的模板进行深度比较来检查

          function objetcsDeepEqualByTemplate(objectA, objectB, comparisonTemplate) {
            if (!objectA || !objectB) return false
          
            let areDifferent = false
            Object.keys(comparisonTemplate).some((key) => {
              if (typeof comparisonTemplate[key] === 'object') {
                areDifferent = !objetcsDeepEqualByTemplate(objectA[key], objectB[key], comparisonTemplate[key])
                return areDifferent
              } else if (comparisonTemplate[key] === true) {
                areDifferent = objectA[key] !== objectB[key]
                return areDifferent
              } else {
                return false
              }
            })
          
            return !areDifferent
          }
          
          const objA = { 
            a: 1,
            b: {
              a: 21,
              b: 22,
            },
            c: 3,
          }
          
          const objB = { 
            a: 1,
            b: {
              a: 21,
              b: 25,
            },
            c: true,
          }
          
          // template tells which props to compare
          const comparisonTemplateA = {
            a: true,
            b: {
              a: true
            }
          }
          objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateA)
          // returns true
          
          const comparisonTemplateB = {
            a: true,
            c: true
          }
          // returns false
          objetcsDeepEqualByTemplate(objA, objB, comparisonTemplateB)
          

          这将在控制台中运行。如果需要,可以添加数组支持

          【讨论】:

            【解决方案13】:

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

            const objectA = {
              a: {
                1: "SAME WILL BE MISSING IN RESULT", 
                2: "BBB", 
                3: [1, 2, 3]
              }, 
              b: "not", 
              c: "foo bar"
            };
            const objectB = {
              a: {
                1: "SAME WILL BE MISSING IN RESULT",
                2: [1, 2]
              }, 
              b: "foo", 
              c: "bar"
            };
            
            const 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;
              }, {});
            };
            
            const diffAOverB = diff(objectA, objectB);
            const diffBOverA = diff(objectA, objectB);
            console.log(diffAOverB);
            console.log(diffBOverA);
            &lt;script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"&gt;&lt;/script&gt;

            【讨论】:

            • 我无法编辑,因为它的变化太小了,但diffBOverA diff 应该交换了参数
            【解决方案14】:

            这是基于@JLavoie,使用lodash

            let differences = function (newObj, oldObj) {
                  return _.reduce(newObj, function (result, value, key) {
                    if (!_.isEqual(value, oldObj[key])) {
                      if (_.isArray(value)) {
                        result[key] = []
                        _.forEach(value, function (innerObjFrom1, index) {
                          if (_.isNil(oldObj[key][index])) {
                            result[key].push(innerObjFrom1)
                          } else {
                            let changes = differences(innerObjFrom1, oldObj[key][index])
                            if (!_.isEmpty(changes)) {
                              result[key].push(changes)
                            }
                          }
                        })
                      } else if (_.isObject(value)) {
                        result[key] = differences(value, oldObj[key])
                      } else {
                        result[key] = value
                      }
                    }
                    return result
                  }, {})
                }
            

            https://jsfiddle.net/EmilianoBarboza/0g0sn3b9/8/

            【讨论】:

              【解决方案15】:

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

              中不存在的键
              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] : [])),
                      []);
              

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

              回到完整的解决方案。使用 bdiff 构建完整的传统差异是微不足道的:

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

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

               [
                " < 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"
               ]
              

              最后,为了了解这些值的不同之处,我们可能希望直接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)))));
              

              这将输出类似于以下内容:

              [" | components[0].components[2].components[2].components[2].FFT.min", 0, 3]
              [" | components[0].components[2].components[2].components[2].FFT.max", 100, 50]
              

              MIT 许可证 ;)

              【讨论】:

                【解决方案16】:

                这是一个带有 Lodash 深度差异检查器的简单 Typescript,它将生成一个新对象,其中包含旧对象和新对象之间的差异。

                例如,如果我们有:

                const oldData = {a: 1, b: 2};
                const newData = {a: 1, b: 3};
                

                生成的对象将是:

                const result: {b: 3};
                

                它还兼容多级深度对象,对于数组可能需要一些调整。

                import * as _ from "lodash";
                
                export const objectDeepDiff = (data: object | any, oldData: object | any) => {
                  const record: any = {};
                  Object.keys(data).forEach((key: string) => {
                    // Checks that isn't an object and isn't equal
                    if (!(typeof data[key] === "object" && _.isEqual(data[key], oldData[key]))) {
                      record[key] = data[key];
                    }
                    // If is an object, and the object isn't equal
                    if ((typeof data[key] === "object" && !_.isEqual(data[key], oldData[key]))) {
                      record[key] = objectDeepDiff(data[key], oldData[key]);
                    }
                  });
                  return record;
                };
                

                【讨论】:

                • 这个问题没有标记 Typescript,但它仍然是一个不错的答案
                【解决方案17】:

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

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

                所以如果你有以下:

                 const firstName = {name: "Alireza"};
                 const otherName = {name: "Alireza"};
                

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

                它将返回 true

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

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

                将返回 false

                【讨论】:

                  【解决方案18】:

                  为了以Sridhar Gudimela's answer 为基础,这里以使用 TypeScript 的方式进行了更新:

                  ///  U T I L S
                  
                  interface LooseObjectInterface {
                    [key: string]: any;
                  };
                  
                  type inputOptions = LooseObjectInterface | any[];
                  
                  
                  
                  ///  E X P O R T
                  
                  export const objectCompare = (objectA: inputOptions, objectB: inputOptions): LooseObjectInterface => {
                    let diffObj: LooseObjectInterface = {};
                  
                    switch(true) {
                      case (Array.isArray(objectA)):
                        objectA.forEach((elem: any, index: number) => {
                          if (!Array.isArray(diffObj))
                            diffObj = [];
                  
                          diffObj[index] = objectCompare(elem, (objectB || [])[index]);
                        });
                  
                        break;
                  
                      case (objectA !== null && typeof objectA === "object"):
                        Object.keys(objectA).forEach((key: any) => {
                          if (Array.isArray(objectA[key])) {
                            let arr = objectCompare(objectA[key], objectB[key]);
                  
                            if (!Array.isArray(arr))
                              arr = [];
                  
                            arr.forEach((elem: any, index: number) => {
                              if (!Array.isArray(diffObj[key]))
                                diffObj[key] = [];
                  
                              diffObj[key][index] = elem;
                            });
                          } else if (typeof objectA[key] === "object")
                            diffObj[key] = objectCompare(objectA[key], objectB[key]);
                          else if (objectA[key] !== (objectB || {})[key])
                            diffObj[key] = objectA[key];
                          else if (objectA[key] === (objectB || {})[key])
                            delete objectA[key];
                        });
                  
                        break;
                  
                      default:
                        break;
                    }
                  
                    Object.keys(diffObj).forEach((key: any) => {
                      if (typeof diffObj[key] === "object" && JSON.stringify(diffObj[key]) === "{}")
                        delete diffObj[key];
                    });
                  
                    return diffObj;
                  };
                  

                  编辑:我的原始答案使用了 Flow,因此被否决了(我假设,或者可能是因为我的答案没有使用 Lodash……但是,对类似问题的答案不会有什么坏处)。

                  【讨论】:

                    【解决方案19】:

                    我需要知道它们的嵌套属性之一是否存在差异

                    其他答案为这个问题提供了可能令人满意的解决方案,但它非常困难和普遍,看起来有一个非常流行的包可以帮助解决这个问题deep-object-diff

                    要使用这个包,你需要npm i deep-object-diff 然后:

                    const { diff } = require('deep-object-diff');
                    var a = {};
                    var b = {};
                    
                    a.prop1 = 2;
                    a.prop2 = { prop3: 2 };
                    
                    b.prop1 = 2;
                    b.prop2 = { prop3: 3 };
                    
                    if (!_.isEqual(a, b)) {
                      const abDiff = diff(a, b);
                      console.log(abDiff);
                      /*
                      {
                        prop2: {
                          prop3: 3
                        }
                      }
                      */
                    }
                    
                    // or alternatively
                    const abDiff = diff(a, b);
                    if(!_.isEmpty(abDiff)) {
                      // if a diff exists then they aren't deeply equal
                      // perform needed actions with diff...
                    }
                    

                    这里有一个更详细的案例,直接从他们的文档中删除属性:

                    const lhs = {
                      foo: {
                        bar: {
                          a: ['a', 'b'],
                          b: 2,
                          c: ['x', 'y'],
                          e: 100 // deleted
                        }
                      },
                      buzz: 'world'
                    };
                    
                    const rhs = {
                      foo: {
                        bar: {
                          a: ['a'], // index 1 ('b')  deleted
                          b: 2, // unchanged
                          c: ['x', 'y', 'z'], // 'z' added
                          d: 'Hello, world!' // added
                        }
                      },
                      buzz: 'fizz' // updated
                    };
                    
                    console.log(diff(lhs, rhs)); // =>
                    /*
                    {
                      foo: {
                        bar: {
                          a: {
                            '1': undefined
                          },
                          c: {
                            '2': 'z'
                          },
                          d: 'Hello, world!',
                          e: undefined
                        }
                      },
                      buzz: 'fizz'
                    }
                    */
                    

                    有关实现细节和其他使用信息,请参阅该 repo。

                    【讨论】:

                      【解决方案20】:

                      我们需要获取两个 json 更新之间的增量以跟踪数据库更新。也许其他人会觉得这很有帮助。

                      https://gist.github.com/jp6rt/7fcb6907e159d7851c8d59840b669e3d

                      const {
                        isObject,
                        isEqual,
                        transform,
                        has,
                        merge,
                      } = require('lodash');
                      const assert = require('assert');
                      
                      /**
                       * Perform a symmetric comparison on JSON object.
                       * @param {*} baseObj - The base object to be used for comparison against the withObj.
                       * @param {*} withObj - The withObject parameter is used as the comparison on the base object.
                       * @param {*} invert  - Because this is a symmetric comparison. Some values in the with object
                       *                      that doesn't exist on the base will be lost in translation.
                       *                      You can execute again the function again with the parameters interchanged.
                       *                      However you will lose the reference if the value is from the base or with
                       *                      object if you intended to do an assymetric comparison.
                       *                      Setting this to true will do make sure the reference is not lost.
                       * @returns           - The returned object will label the result of the comparison with the
                       *                      value from base and with object.
                       */
                      const diffSym = (baseObj, withObj, invert = false) => transform(baseObj, (result, value, key) => {
                        if (isEqual(value, withObj[key])
                          && has(withObj, key)) {
                          return;
                        }
                      
                        if (isObject(value)
                          && isObject(withObj[key])
                          && !Array.isArray(value)) {
                          result[key] = diffSym(value, withObj[key], invert);
                          return;
                        }
                      
                        if (!invert) {
                          result[key] = {
                            base: value,
                            with: withObj[key],
                          };
                          return;
                        }
                      
                        if (invert) {
                          result[key] = {
                            base: withObj[key],
                            with: value,
                          };
                        }
                      });
                      
                      /**
                       * Perform a assymmetric comparison on JSON object.
                       * @param {*} baseObj - The base object to be used for comparison against the withObj.
                       * @param {*} withObj - The withObject parameter is used as the comparison on the base object.
                       * @returns           - The returned object will label the values with
                       *                      reference to the base and with object.
                       */
                      const diffJSON = (baseObj, withObj) => {
                        // Deep clone the objects so we don't update the reference objects.
                        const baseObjClone = JSON.parse(JSON.stringify(baseObj));
                        const withObjClone = JSON.parse(JSON.stringify(withObj));
                      
                        const beforeDelta = diffSym(baseObjClone, withObjClone);
                        const afterDelta = diffSym(withObjClone, baseObjClone, true);
                      
                        return merge(afterDelta, beforeDelta);
                      };
                      
                      // By Example:
                      
                      const beforeDataObj = {
                        a: 1,
                        c: { d: 2, f: 3 },
                        g: 4,
                        h: 5,
                      };
                      const afterDataObj = {
                        a: 2,
                        b: 3,
                        c: { d: 1, e: 1 },
                        h: 5,
                      };
                      
                      const delta = diffJSON(beforeDataObj, afterDataObj);
                      
                      // Assert expected result.
                      assert(isEqual(delta, {
                        a: { base: 1, with: 2 },
                        b: { base: undefined, with: 3 },
                        c: {
                          d: { base: 2, with: 1 },
                          e: { base: undefined, with: 1 },
                          f: { base: 3, with: undefined },
                        },
                        g: { base: 4, with: undefined },
                      }));
                      

                      【讨论】:

                        【解决方案21】:

                        这是我解决问题的方法

                        const _ = require('lodash');
                        
                        var objects = [{ 'x': 1, 'y': 2, 'z':3, a:{b:1, c:2, d:{n:0}}, p:[1, 2, 3]  }, { 'x': 2, 'y': 1, z:3, a:{b:2, c:2,d:{n:1}}, p:[1,3], m:3  }];
                        
                        const diffFn=(a,b, path='')=>_.reduce(a, function(result, value, key) {
                        
                            if(_.isObjectLike(value)){
                              if(_.isEqual(value, b[key])){
                                return result;
                              }else{
                        
                        return result.concat(diffFn(value, b[key], path?(`${path}.${key}`):key))
                              }
                            }else{
                        return _.isEqual(value, b[key]) ?
                                result : result.concat(path?(`${path}.${key}`):key);
                            }
                            
                        }, []);
                        
                        const diffKeys1=diffFn(objects[0], objects[1])
                        const diffKeys2=diffFn(objects[1], objects[0])
                        const diffKeys=_.union(diffKeys1, diffKeys2)
                        const res={};
                        
                        _.forEach(diffKeys, (key)=>_.assign(res, {[key]:{ old: _.get(objects[0], key), new:_.get(objects[1], key)} }))
                        
                        res
                        /*
                        Returns
                        {
                          x: { old: 1, new: 2 },
                          y: { old: 2, new: 1 },
                          'a.b': { old: 1, new: 2 },
                          'a.d.n': { old: 0, new: 1 },
                          'p.1': { old: 2, new: 3 },
                          'p.2': { old: 3, new: undefined },
                          m: { old: undefined, new: 3 }
                        }
                        */
                        

                        【讨论】:

                        • 这样可以很容易地在前端生成消息。对我来说效果很好!
                        【解决方案22】:

                        已经发布了许多答案,但是对于那些好奇避免编写任何代码来计算具有任何类型结构的两个对象之间的差异的人来说,实际上有一个库可以做到这一点。 Lodash isEqual 仅返回 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);
                        

                        这个帖子里也有人问过类似的问题 Getting the difference between 2 JSON objects

                        【讨论】:

                          【解决方案23】:

                          我知道这并不能直接回答 OP 的问题,但我是通过搜索如何删除 lodash 来引导我的。所以希望这可以帮助与我处于相似位置的其他人。

                          归功于@JohanPersson。我建立了这个答案来实现比较深层嵌套的值并获取对差异的关键引用

                          getObjectDiff = (obj1, obj2) => {
                              const obj1Props = Object.keys(obj1);
                              const obj2Props = Object.keys(obj2);
                          
                              const keysWithDiffValue = obj1Props.reduce((keysWithDiffValueAccumulator, key) => {
                                const propExistsOnObj2 = obj2.hasOwnProperty(key);
                                const hasNestedValue = obj1[key] instanceof Object && obj2[key] instanceof Object;
                                const keyValuePairBetweenBothObjectsIsEqual = obj1[key] === obj2[key];
                          
                                if (!propExistsOnObj2) {
                                  keysWithDiffValueAccumulator.push(key);
                                } else if (hasNestedValue) {
                                  const keyIndex = keysWithDiffValueAccumulator.indexOf(key);
                                  if (keyIndex >= 0) {
                                    keysWithDiffValueAccumulator.splice(keyIndex, 1);
                                  }
                                  const nestedDiffs = getObjectDiff(obj1[key], obj2[key]);
                                  for (let diff of nestedDiffs) {
                                    keysWithDiffValueAccumulator.push(`${key}.${diff}`);
                                  }
                                } else if (keyValuePairBetweenBothObjectsIsEqual) {
                                  const equalValueKeyIndex = keysWithDiffValueAccumulator.indexOf(key);
                                  keysWithDiffValueAccumulator.splice(equalValueKeyIndex, 1);
                                }
                                return keysWithDiffValueAccumulator;
                              }, obj2Props);
                          
                              return keysWithDiffValue;
                            }
                          const obj1 = {a0: {a1: {a2: {a3: 'Im here'}}}};
                          const obj2 = {a0: {a1: {a2: {a3: 'Not here', b3: 'some'}}}};
                          console.log('final', getObjectDiff(obj1, obj2));

                          【讨论】:

                            猜你喜欢
                            • 2016-09-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2018-02-28
                            • 1970-01-01
                            • 2021-11-26
                            • 1970-01-01
                            相关资源
                            最近更新 更多