У меня есть два вложенных объекта, которые разные, и мне нужно знать, имеют ли они разницу в одном из своих вложенных свойств.
var a = {};
var b = {};
a.prop1 = 2;
a.prop2 = { prop3: 2 };
b.prop1 = 2;
b.prop2 = { prop3: 3 };
Объект может быть намного сложнее с более вложенными свойствами. Но это хороший пример. У меня есть возможность использовать рекурсивные функции или что-то с lodash...
Ответ 1
Простое и элегантное решение - использовать _.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/
Ответ 2
Если вам нужно знать, какие свойства отличаются, используйте reduce():
_.reduce(a, function(result, value, key) {
return _.isEqual(value, b[key]) ?
result : result.concat(key);
}, []);
// → [ "prop2" ]
Ответ 3
Для тех, кто наткнулся на эту тему, здесь более полное решение. Он сравнит два объекта и предоставит вам ключ всех свойств, которые либо только в объекте1, только в объекте2, либо как в объекте1, так и в объекте2, но имеют разные значения:
/*
* 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 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]
.
Ответ 4
На основе ответа Адама Бодуча я написал эту функцию, которая сравнивает два объекта в самом глубоком смысле, возвращая пути, которые имеют разные значения, а также пути, отсутствующие у одного или другого объекта.
Код не был написан с учетом эффективности, и улучшения в этом отношении приветствуются, но вот основная форма:
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>
Ответ 5
Здесь краткое решение:
_.differenceWith(a, b, _.isEqual);
Ответ 6
Этот код возвращает объект со всеми свойствами, которые имеют другое значение, а также значения обоих объектов. Полезно регистрировать разницу.
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;
}, {});
Ответ 7
Чтобы рекурсивно показать, как объект отличается от другого, вы можете использовать _.reduce в сочетании с _.isEqual и _.isPlainObject. В этом случае вы можете сравнить разницу a с b или b с a:
var a = {prop1: {prop1_1: 'text 1', prop1_2: 'text 2', prop1_3: [1, 2, 3]}, prop2: 2, prop3: 3};
var b = {prop1: {prop1_1: 'text 1', prop1_3: [1, 2]}, prop2: 2, prop3: 4};
var diff = function(obj1, obj2) {
return _.reduce(obj1, function(result, value, key) {
if (_.isPlainObject(value)) {
result[key] = diff(value, obj2[key]);
} else if (!_.isEqual(value, obj2[key])) {
result[key] = value;
}
return result;
}, {});
};
var res1 = diff(a, b);
var res2 = diff(b, a);
console.log(res1);
console.log(res2);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>
Ответ 8
Без использования 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;
}
Ответ 9
Глубокое сравнение с использованием шаблона (вложенных) свойств для проверки
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
Я взял ударный код Адама Бодуха, чтобы вывести глубокий diff - это полностью непроверено, но части есть:
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
Как и было задано, здесь рекурсивная функция сравнения объектов. И еще немного. Предполагая, что основное использование такой функции - проверка объекта, мне есть что сказать. Полное глубокое сравнение - плохая идея, когда некоторые различия не имеют значения. Например, слепое глубокое сравнение в утверждениях TDD делает тесты ненужными хрупкими. По этой причине я хотел бы представить гораздо более ценный частичный diff. Это рекурсивный аналог предыдущего вклада в эту тему. Он игнорирует ключи, отсутствующие в
var bdiff = (a, b) =>
_.reduce(a, (res, val, key) =>
res.concat((_.isPlainObject(val) || _.isArray(val)) && b
? bdiff(val, b[key]).map(x => key + '.' + x)
: (!b || val != b[key] ? [key] : [])),
[]);
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);
Возвращаясь к полному решению. Построить полный традиционный diff с помощью 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"
]
Наконец, для того, чтобы получить представление о различии значений, мы можем напрямую вычислить() вывод 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;)
Ответ 12
Завершая ответ от Адама Бодуча, он учитывает различия в свойствах
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
Если вам нужно только сравнение клавиш:
_.reduce(a, function(result, value, key) {
return b[key] === undefined ? key : []
}, []);
Ответ 14
Вот простой Typescript с Lodash для проверки глубинных различий, который создаст новый объект с теми же различиями, что и между старым и новым объектами.
Например, если бы мы имели:
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;
};
Ответ 15
просто с помощью Vanilla 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
Ответ 16
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);
};
Ответ 17
это было основано на @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/