
本文介绍如何对具有层级结构的 JSON 数组(先按 _id 分组、再按 itemId 合并属性)进行高效聚合,解决字段分散在不同对象中导致的数据冗余与覆盖问题。
本文介绍如何对具有层级结构的 json 数组(先按 `_id` 分组、再按 `itemid` 合并属性)进行高效聚合,解决字段分散在不同对象中导致的数据冗余与覆盖问题。
在实际业务场景中(如库存管理、订单聚合),常遇到同一 _id 对应多个数据片段,每个片段包含部分 itemId 的字段(如 stockIn/stockOut 或 noFreeQtyCount/freeQtyCount),需将相同 itemId 的所有属性合并到一个对象中,同时保留不同 itemId 的独立条目。
核心思路是:两层哈希索引 + 属性合并
- 第一层以
_id为键,构建父级分组容器; - 第二层以
itemId为键,在每个_id下建立子项映射表; - 使用
Object.assign()将同itemId的多个对象属性逐层浅合并(后出现的字段会覆盖同名前值,符合预期逻辑); - 最终通过
Object.values()提取归一化后的items数组。
以下是完整可运行的实现代码:
function groupByItemId(data) {
const mergedData = {};
data.forEach(entry => {
const _id = entry._id;
const items = entry.items || [];
if (!mergedData[_id]) {
mergedData[_id] = {};
}
items.forEach(item => {
const itemId = item.itemId;
if (!mergedData[_id][itemId]) {
mergedData[_id][itemId] = {};
}
Object.assign(mergedData[_id][itemId], item);
});
});
return Object.keys(mergedData).map(_id => ({
_id,
items: Object.values(mergedData[_id])
}));
}
// 示例输入
const inputData = [
{
"_id": "013",
"items": [
{ "itemId": "4", "stockIn": 50, "stockOut": 0 },
{ "itemId": "9", "stockIn": 100, "stockOut": 0 }
]
},
{
"_id": "013",
"items": [
{ "itemId": "3", "noFreeQtyCount": 20, "freeQtyCount": 0 },
{ "itemId": "9", "noFreeQtyCount": 30, "freeQtyCount": 0 }
]
}
];
console.log(groupByItemId(inputData));
// 输出:
// [
// {
// "_id": "013",
// "items": [
// { "itemId": "4", "stockIn": 50, "stockOut": 0 },
// { "itemId": "9", "stockIn": 100, "stockOut": 0, "noFreeQtyCount": 30, "freeQtyCount": 0 },
// { "itemId": "3", "noFreeQtyCount": 20, "freeQtyCount": 0 }
// ]
// }
// ]
✅ 关键优势说明:
- 支持任意数量同
_id的输入对象; - 自动识别并合并相同
itemId的所有字段(无须预定义字段名); - 时间复杂度为 O(n×m),其中 n 是外层数组长度,m 是平均每组
items长度,性能稳定; - 不修改原始数据,纯函数式设计,便于测试与复用。
⚠️ 注意事项:
-
Object.assign()执行的是浅合并,若字段值为对象或数组,需改用深拷贝方案(如structuredClone或 Lodash 的merge); - 若存在冲突字段且需自定义合并策略(如数值累加而非覆盖),应在
Object.assign前手动处理(例如判断stockIn是否已存在,做求和); - 确保
itemId字段始终存在且类型一致(建议添加校验逻辑防止undefined键异常)。
该方法简洁、健壮、易于扩展,适用于前端聚合或 Node.js 数据预处理等典型场景。










