如何对比含对象数组的复杂JS对象并找出差异(不硬编码属性)
复杂对象差异检测:支持数组内对象任意顺序的实现
需求
实现函数对比两个包含对象数组的复杂JavaScript对象,找出updatedObj相对baseObj的差异,要求不能硬编码属性,且能正确处理数组内对象顺序变化的情况。
示例数据
let baseObj = { place: {city: 'Bangalore', pin: 123456}, office: [ { name: 'Tom', age: 22, salutation: { title: 'Mr'}}, { name: 'John', age: 31, salutation: { title: 'Mr'}} ] } let updatedObj = { place: {city: 'Bangalore', pin: 99999}, office: [ { name: 'Tom', age: 22, salutation: { title: 'Mr'}}, { name: 'Peter', age: 16, salutation: { title: 'Mr'}}, { name: 'John', age: 31, salutation: { title: 'Mr'}} ] }
预期结果
const expectedResult = { place: {city: 'Bangalore', pin: 99999}, office: [ { name: 'Peter', age: 16, salutation: { title: 'Mr'}} ] }
现有代码问题
当前函数仅按数组索引对比元素,当数组内对象顺序变化时会误判,且存在拼写错误(lenght应为length)和逻辑缺陷。原代码如下:
function findDiff(obj1, obj2) { var diffObj = Array.isArray(obj2) ? [] : {} Object.getOwnPropertyNames(obj2).forEach(function(prop) { if(prop !=='lenght' ){ if (typeof obj2[prop] === 'object') { diffObj[prop] = obj1[prop]== undefined? obj2[prop]: findDiff(obj1[prop], obj2[prop]) if (Array.isArray(diffObj[prop]) && Object.getOwnPropertyNames(diffObj[prop]).length === 1 || Object.getOwnPropertyNames(diffObj[prop]).length === 0) { delete diffObj[prop] } }} else if(prop !=='lenght') { if(obj1[prop] !== obj2[prop]){ diffObj[prop] = obj2[prop] } } }); return diffObj }
解决方案
核心思路:
- 实现深比较函数,判断两个对象/值是否完全相等,用于检测数组元素是否存在于另一个数组中。
- 数组处理逻辑:遍历
updated数组,筛选出base数组中不存在的元素(新增),以及存在但内部属性有修改的元素。 - 对象处理逻辑:递归对比每个属性,可选择仅保留差异属性或合并原属性与差异(贴合示例预期)。
完整实现代码
// 深比较函数:判断两个值是否完全相等 function deepEqual(a, b) { // 基本类型直接比较 if (a === b) return true; // 处理null/undefined情况 if (a == null || b == null) return false; // 类型不同直接返回false if (typeof a !== typeof b) return false; // 处理数组 if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (!deepEqual(a[i], b[i])) return false; } return true; } // 处理对象 if (typeof a === 'object' && typeof b === 'object') { const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; for (const key of keysA) { if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false; } return true; } return false; } // 差异检测主函数 function findDiff(baseObj, updatedObj) { // 处理基本类型 if (typeof updatedObj !== 'object' || updatedObj == null) { return baseObj !== updatedObj ? updatedObj : undefined; } // 初始化差异结果 const diff = Array.isArray(updatedObj) ? [] : {}; // 处理数组 if (Array.isArray(updatedObj)) { // 遍历updated数组的每个元素 updatedObj.forEach(updatedItem => { // 检查base数组中是否存在完全相等的元素 const existsInBase = Array.isArray(baseObj) && baseObj.some(baseItem => deepEqual(baseItem, updatedItem)); if (!existsInBase) { // 元素不存在于base中,直接加入差异数组 diff.push(updatedItem); } else { // 元素存在,但检查是否有内部属性修改 const baseItem = baseObj.find(item => deepEqual(item, updatedItem)); const itemDiff = findDiff(baseItem, updatedItem); // 如果有差异,加入修改后的元素 if (itemDiff !== undefined && Object.keys(itemDiff).length > 0) { diff.push({...baseItem, ...itemDiff}); } } }); // 如果差异数组为空,返回undefined(后续会被清理) return diff.length > 0 ? diff : undefined; } // 处理对象:合并原属性与差异,贴合示例预期保留未修改属性 Object.keys(updatedObj).forEach(key => { const baseVal = baseObj[key]; const updatedVal = updatedObj[key]; const valDiff = findDiff(baseVal, updatedVal); // 如果有差异,合并原属性和差异(保留未修改的属性) if (valDiff !== undefined) { if (typeof updatedVal === 'object' && updatedVal != null && !Array.isArray(updatedVal)) { diff[key] = {...baseVal, ...valDiff}; } else { diff[key] = valDiff; } } }); // 如果对象差异为空,返回undefined return Object.keys(diff).length > 0 ? diff : undefined; } // 测试示例 const result = findDiff(baseObj, updatedObj); console.log(result); // 输出与预期结果一致
代码说明
deepEqual函数:递归对比两个值的所有层级,支持数组和嵌套对象的精准比较,解决了原代码按索引对比数组的缺陷。- 数组处理:
- 遍历
updated数组,用deepEqual判断元素是否存在于base数组中,不受顺序影响。 - 不存在的元素直接标记为新增,加入差异数组。
- 存在的元素进一步检查内部属性差异,若有修改则合并原属性与差异后加入结果。
- 遍历
- 对象处理:递归对比每个属性,通过合并原属性与差异的方式,保留嵌套对象中未修改的属性,完全贴合示例预期。
- 空结果清理:如果某个属性/数组没有差异,返回
undefined,最终结果不会包含空对象或空数组,保证输出简洁。
内容的提问来源于stack exchange,提问作者akh
相关产品推荐
相关产品推荐

