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

时间:2015-07-28 17:17:57

标签: javascript lodash

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

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

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

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

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

24 个答案:

答案 0 :(得分:368)

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

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

http://jsfiddle.net/bdkeyn0h/

答案 1 :(得分:247)

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

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

答案 2 :(得分:41)

对于任何绊到这个主题的人来说,这是一个更完整的解决方案。它会比较两个对象,并为您提供所有属性的键,这些属性只能在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]

答案 3 :(得分:25)

基于the answer by 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;
}

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

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>

答案 4 :(得分:22)

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

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

答案 5 :(得分: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 :(得分:4)

要以递归方式显示对象与其他对象的不同,可以使用 _。reduce _。isEqual _。isPlainObject 结合使用。在这种情况下,您可以比较a与b的不同或b与b的不同之处:

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);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>

答案 7 :(得分:3)

我知道这并没有直接回答 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));

答案 8 :(得分:3)

不使用lodash /下划线,我编写了这段代码,并且对我来说很好,可以对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;
}

答案 9 :(得分:3)

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

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)

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

答案 10 :(得分:2)

我拿了一个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"]

答案 11 :(得分:2)

有人问,这是一个递归对象比较函数。还有一点。假设这种功能的主要用途是对象检查,我有话要说。当一些差异无关紧要时,完全深入比较是一个坏主意。例如,在TDD断言中进行盲目深度比较会使测试变得不必要。出于这个原因,我想介绍一个更有价值的部分差异。它是此线程先前贡献的递归模拟。它会忽略 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()差异输出。为此,我们需要一个uglier版本的 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]

麻省理工学院执照;)

答案 12 :(得分:1)

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

答案 13 :(得分:1)

这是一个带有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;
};

答案 14 :(得分:1)

简单易用的viewModel.awayLogoUrl.observe(this, Observer { it?.let { url -> //Show image into itemView using Picasso or Glide Glide.with(itemView.context).load(url).into(binding.awayTeamLogo) } }) viewModel.homeLogoUrl.observe(this, Observer { it?.let { url -> //Show image into itemView using Picasso or Glide Glide.with(itemView.context).load(url).into(binding.homeTeamLogo) } }) 方法,将可用于所有比较...

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

因此,如果您具有以下条件:

Object

如果您这样做: const firstName = {name: "Alireza"}; const otherName = {name: "Alireza"};

它将返回 true

如果_.isEqual(firstName, otherName);

如果您这样做:const fullName = {firstName: "Alireza", familyName: "Dezfoolian"};

将返回 false

答案 15 :(得分:1)

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

其他答案为该问题提供了可能令人满意的解决方案,但它似乎非常困难且普遍,似乎有一个非常受欢迎的软件包可以帮助解决此问题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'
}
*/

有关实现的详细信息和其他用法信息,请参阅该存储库。

答案 16 :(得分:1)

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

答案 17 :(得分:1)

如果您只需要进行密钥比较:

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

答案 18 :(得分:0)

要建立在Sridhar Gudimela's answer的基础上,此处将以使Flow满意的方式进行更新:

"use strict"; /* @flow */



//  E X P O R T

export const objectCompare = (objectA: any, objectB: any) => {
  let diffObj = {};

  switch(true) {
    case (Array.isArray(objectA)):
      objectA.forEach((elem, index) => {
        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, index) => {
            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;
};

答案 19 :(得分:0)

我们有这样的要求:获取两个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 },
}));

答案 20 :(得分:-1)

这是基于@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/

答案 21 :(得分:-1)

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

答案 22 :(得分:-1)

这是我解决问题的方法

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 }
}
*/

答案 23 :(得分:-3)

仅使用普通js

let a = {};
let b = {};

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

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

JSON.stringify(a) === JSON.stringify(b);
// false
b.prop2 = { prop3: 2};

JSON.stringify(a) === JSON.stringify(b);
// true

enter image description here