如何做一个深层比较2个对象与lodash?

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

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


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


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

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

545744 次浏览

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

However, this solution doesn't show which property is different.

如果你需要知道哪些属性是不同的,使用reduce ():

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

下面是一个使用Lodash的简洁解决方案:

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

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

作为对亚当·博杜赫的回答的补充,这个问题考虑到了性质的差异

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

这段代码返回一个具有不同值的所有属性的对象,以及两个对象的值。对记录差异很有用。

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

如果你只需要键比较:

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

对于无意中发现这条线索的人,这里有一个更完整的解决方案。它将比较两个对象并给你所有属性的键,这些属性要么是仅在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]

基于亚当·博杜赫的回答,我写了这个函数在最深层的意义上比较两个对象,返回具有不同值的路径以及从一个或另一个对象中缺失的路径。

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

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>

我尝试了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"]

在没有使用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;
}
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);
};

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

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)

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

要递归地显示一个对象与其他对象的不同之处,可以使用_.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);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>

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

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

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构建一个完整的传统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));
};

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

 [
" < 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许可证;)

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

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

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

所以如果你有以下情况:

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

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

它将返回真正的

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

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

将返回

为了构建在Sridhar Gudimela的回答是之上,它在这里以一种使用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……)然而,有一个类似问题的答案也无妨)。

我需要知道它们的某个嵌套属性是否不同

其他答案为这个问题提供了潜在的令人满意的解决方案,但它足够困难和常见,看起来有一个非常流行的包来帮助解决这个问题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。

我们需要在两个json更新之间获取delta,以跟踪数据库更新。也许其他人会觉得这很有用。

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

这是我对这个问题的解决办法

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

已经有很多答案发布,但对于那些好奇的人来说,避免编写任何代码来计算具有任何类型结构的两个对象之间的差异,实际上有一个库可以做到这一点。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);
类似的问题也在这个帖子中被问到 获取2个JSON对象之间的差异 < / p >

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

此解决方案返回一个具有修改过的属性的对象。

_.reduce(a, (r, v, k) => { return _.merge(r, _.isEqual(v, b[k]) ? {} : { [k]: v }); }, {});