You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用JS或Lodash获取含数组的嵌套对象深度差异

问题描述

我有以下两个嵌套对象:

obj1

obj1 = {
  sofa: [
    {
      name: 'leather',
      typeSofa: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 1
        },
        {
          name: 'type2',
          text: 'type2',
          quantity: 1
        }
      ],
      stool: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 1
        },
        {
          name: 'type2',
          text: 'type2',
          quantity: 1
        }
      ]
    },
    {
      name: 'fabric',
      typeSofa: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 1
        }
      ],
      stool: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 1
        },
        {
          name: 'type2',
          text: 'type2',
          quantity: 1
        }
      ]
    }
  ],
  curtain: {
    dryclean: [{
      name: 'text1',
      text: 'text1',
      quantity: 1
    }]
  }
}

obj2

仅将sofa下leather的type1的quantity改为2:

obj2 = {
  sofa: [
    {
      name: 'leather',
      typeSofa: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 2
        },
        {
          name: 'type2',
          text: 'type2',
          quantity: 1
        }
      ],
      stool: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 1
        },
        {
          name: 'type2',
          text: 'type2',
          quantity: 1
        }
      ]
    },
    {
      name: 'fabric',
      typeSofa: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 1
        }
      ],
      stool: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 1
        },
        {
          name: 'type2',
          text: 'type2',
          quantity: 1
        }
      ]
    }
  ],
  curtain: {
    dryclean: [
      {
        name: 'text1',
        text: 'text1',
        quantity: 1
      }
    ]
  }
}

我使用以下基于Lodash的JS函数检测对象差异:

const findDifferences = (obj1, obj2) => {
    // Find the keys that are in both objects
    const keys = _.intersection(Object.keys(obj1), Object.keys(obj2));

    // Iterate through the keys
    return keys.reduce((diff, key) => {
      const val1 = obj1[key];
      const val2 = obj2[key];

      // If the values are arrays, compare them using _.differenceWith() and recursion
      if (_.isArray(val1) && _.isArray(val2)) {
        diff[key] = _.differenceWith(val2, val1, _.isEqual);
        if (diff[key].length === 0) delete diff[key];
      }
      // If the values are objects, recursively call findDifferences() on them
      else if (_.isObject(val1) && _.isObject(val2)) {
        const nestedDiff = findDifferences(val1, val2);
        if (!_.isEmpty(nestedDiff)) diff[key] = nestedDiff;
      }
      // Otherwise, compare the values directly using _.isEqual()
      else if (!_.isEqual(val1, val2)) {
        diff[key] = val2;
      }

      return diff;
    }, {});
  };

当前函数在修改curtain的quantity时能正常识别差异,但无法识别sofa数组内嵌套属性的变更。我期望得到如下格式的差异结果:

result = {
  sofa: [
    {
      name: 'leather',
      typeSofa: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 2
        }
      ]
    }
  ]
}

请问如何修改函数或通过其他方式实现该需求?


解决方案

原函数处理数组时,_.differenceWith(val2, val1, _.isEqual)仅能找出obj2中完全不存在于obj1的数组元素,但你的场景是数组内对象的部分属性修改,并非新增/删除元素,因此该逻辑不适用。

要实现需求,需遍历数组中通过标识键(如name)匹配的元素对,递归比较差异并仅保留变化部分。以下是修改后的函数:

const findDifferences = (obj1, obj2) => {
  // 按name匹配数组元素,递归查找差异
  const compareArrays = (arr1, arr2) => {
    const diffArr = [];
    arr2.forEach(item2 => {
      const item1 = arr1.find(item => item.name === item2.name);
      if (!item1) {
        // obj2新增的元素直接加入差异结果
        diffArr.push(item2);
      } else {
        // 递归比较两个对象的差异
        const itemDiff = findDifferences(item1, item2);
        if (!_.isEmpty(itemDiff)) {
          // 保留标识键name,合并差异属性
          diffArr.push({ ..._.pick(item2, 'name'), ...itemDiff });
        }
      }
    });
    return diffArr.length > 0 ? diffArr : undefined;
  };

  const keys = _.intersection(Object.keys(obj1), Object.keys(obj2));

  return keys.reduce((diff, key) => {
    const val1 = obj1[key];
    const val2 = obj2[key];

    if (_.isArray(val1) && _.isArray(val2)) {
      const arrDiff = compareArrays(val1, val2);
      if (arrDiff) diff[key] = arrDiff;
    } else if (_.isObject(val1) && _.isObject(val2)) {
      const nestedDiff = findDifferences(val1, val2);
      if (!_.isEmpty(nestedDiff)) diff[key] = nestedDiff;
    } else if (!_.isEqual(val1, val2)) {
      diff[key] = val2;
    }

    return diff;
  }, {});
};

关键修改点

  • 新增compareArrays函数:通过name字段匹配数组中的对应对象,而非直接比较整个元素是否相等
  • 递归比较匹配到的对象,仅保留有差异的属性,同时保留name标识键以保证结构符合预期
  • 仅当数组元素存在差异时,才将其加入结果数组

测试结果

调用findDifferences(obj1, obj2)将返回期望的结果:

{
  sofa: [
    {
      name: 'leather',
      typeSofa: [
        {
          name: 'type1',
          text: 'type1',
          quantity: 2
        }
      ]
    }
  ]
}

扩展说明

如果数组元素没有name这类统一标识键,可根据实际情况修改匹配逻辑(比如使用其他唯一字段,纯值数组则保留原逻辑)。该函数支持多层嵌套的数组和对象差异检测,同时保留最小化的差异结构。


内容的提问来源于stack exchange,提问作者Duy Cao Thiện

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 16:38:07