Как сделать глубокое сравнение между 2 объектами с лодашь?
У меня есть 2 вложенных объекта, которые отличаются, и мне нужно знать, если они имеют разницу в одном из их вложенных свойств.
var a = {};
var b = {};
a.prop1 = 2;
a.prop2 = { prop3: 2 };
b.prop1 = 2;
b.prop2 = { prop3: 3 };
объект может быть намного сложнее с более вложенными свойствами. Но это хороший пример. У меня есть возможность использовать рекурсивные функции или что-то с лодашь...
15 ответов:
простое и элегантное решение-используйте
_.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
однако это решение не показывает, какое свойство отличается.
Если вам нужно знать, какие свойства отличаются, используйте уменьшить():
_.reduce(a, function(result, value, key) { return _.isEqual(value, b[key]) ? result : result.concat(key); }, []); // → [ "prop2" ]
для тех, кто наткнулся на эту ветку, вот более полное решение. Он будет сравнивать два объекта и дать вам ключ всех свойств, которые либо только в 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>
этот код возвращает объект со всеми свойствами, которые имеют разные значения, а также значения обоих объектов. Полезно для регистрации разницы.
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; }, {});
Я взял удар код Адама Бодуха, чтобы вывести глубокий 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"]
без использования 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; }
глубокое сравнение с использованием шаблона (вложенных) свойств для проверки
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)
Это будет работать в консоли. Поддержка массива может быть добавлена при необходимости
чтобы рекурсивно показать, как объект отличается от других, вы можете использовать _.уменьшить в сочетании с _.равно и _.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>
завершая ответ от Адама Бодуха, этот учитывает различия в свойствах
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));
Если вам нужно только сравнение ключей:
_.reduce(a, function(result, value, key) { return b[key] === undefined ? key : [] }, []);
это @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 }, {}) }
как и было предложено, вот рекурсивная функция сравнения объектов. И еще немного. Предполагая, что первичным использованием такой функции является осмотр объекта, я должен что-то сказать. Полное глубокое сравнение-плохая идея, когда некоторые различия не имеют значения. Например, слепое глубокое сравнение в утверждениях 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);
возвращаясь к полному решению. Построение полного традиционного 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" ]
наконец, чтобы иметь представление о том, как отличаются значения, мы можем захотеть напрямую 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 ;)