我有2个不同的嵌套对象,我需要知道它们是否在其中一个嵌套属性中有不同。
var a = {};
var b = {};
a.prop1 = 2;
a.prop2 = { prop3: 2 };
b.prop1 = 2;
b.prop2 = { prop3: 3 };
对象可以更复杂,有更多嵌套的属性。但这是一个很好的例子。我可以选择使用递归函数或lodash的东西…
我有2个不同的嵌套对象,我需要知道它们是否在其中一个嵌套属性中有不同。
var a = {};
var b = {};
a.prop1 = 2;
a.prop2 = { prop3: 2 };
b.prop1 = 2;
b.prop2 = { prop3: 3 };
对象可以更复杂,有更多嵌套的属性。但这是一个很好的例子。我可以选择使用递归函数或lodash的东西…
当前回答
作为对亚当·博杜赫的回答的补充,这个问题考虑到了性质的差异
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));
其他回答
基于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>
下面是一个使用Lodash的简单解决方案:
_.differenceWith(a, b, _.isEqual);
注意,两个输入都需要是数组(可能是一个对象的数组)。
这是一个简单的带有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;
};
我们需要在两个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 },
}));
如果你需要知道哪些属性是不同的,使用reduce():
_.reduce(a, function(result, value, key) {
return _.isEqual(value, b[key]) ?
result : result.concat(key);
}, []);
// → [ "prop2" ]