递归合并不同层级同键JSON数组 如何用map实现全节点匹配
嵌套JSON数组属性合并实现
需求说明
需要合并两个JSON数组生成最终结构:
- 基础数组
r1为多层嵌套树结构,子节点存储在sub_items字段中 - 数组
r2存储待追加的扩展属性,通过唯一标识item_guid和r1中的节点匹配,将r2节点的属性合并到对应r1节点上 - 合并需要支持任意深度的嵌套子节点,不能仅处理第一层子项
原始r1结构
const r1=[{ "item_guid": "5c2000c1-abc8-4d6f-85de-b8b223a42a2f", "item_id": 1, "parent_item_id": null, "description": "1", "sub_items": [ { "item_guid": "bd7c2ba3-268b-49f6-98fb-34486a3e1449", "item_id": 10, "parent_item_id": 1, "description": "1.1", "sub_items": [] }, { "item_guid": "80e073e0-2aa8-422a-9f28-51747f146bd8", "item_id": 12, "parent_item_id": 1, "description": "1.2", "sub_items": [ { "item_guid": "f97af55c-c90e-46c2-b56e-e854ff36e1e3", "item_id": 78, "parent_item_id": 12, "description": "1.2.1", "sub_items": [] }, { "item_guid": "28469fa4-2c1c-4f2a-9250-7460a74cc30a", "item_id": 79, "parent_item_id": 12, "description": "1.2.2", "sub_items": [ { "item_guid": "f97af55c-c90e-46c2-b56e-e854ff36e1e9", "item_id": 80, "parent_item_id": 12, "description": "1.2.2.1", "sub_items": [] }, ] } ], }, { "item_guid": "846daeab-edd4-4cf2-8f12-8d7231c697e3", "item_id": 13, "parent_item_id": 1, "description": "1.3", "sub_items": [], }, ], }]
原始r2结构
const r2=[ { "item_guid": "bd7c2ba3-268b-49f6-98fb-34486a3e1449", "mandatory": "True", "comment": "Item cross-reference 1.1" }, { "item_guid": "f97af55c-c90e-46c2-b56e-e854ff36e1e3", "mandatory": "True", "comment": "Item cross-reference 1.2.1" }, { "item_guid": "f97af55c-c90e-46c2-b56e-e854ff36e1e9", "mandatory": "True", "comment": "Item cross-reference 1.2.2.1" }]
预期合并结果
合并后所有匹配到item_guid的节点都追加mandatory和comment属性,不匹配的节点保持原有结构不变。
现有代码问题
原有实现仅手动处理了第一层sub_items,没有递归遍历更深层级,同时直接修改了原数组对象会产生副作用,代码如下:
const mergeById = (r1, r2) => r1.map(itm => { itm.sub_items = itm.sub_items.map(sub_item => ( { ...sub_item, ...r2.find(r2_item => r2_item.item_guid === sub_item.item_guid), } ) ) return itm }) console.log(mergeById(r1,r2))
实现方案
完全可以基于map方法配合递归实现任意层级的合并,核心逻辑是封装递归处理函数,每处理一个节点时,先合并匹配的扩展属性,再递归处理当前节点的sub_items数组。
为了提升查询性能,先把r2转换成item_guid为键的映射表,避免每层循环都用find遍历r2:
const mergeItems = (baseList, extList) => { // 构建扩展属性映射表,查询时间复杂度从O(n)降到O(1) const extMap = Object.fromEntries(extList.map(item => [item.item_guid, item])); // 递归处理节点列表 const processNodeList = (list) => { return list.map(node => { // 合并当前节点的基础属性和匹配到的扩展属性 const mergedNode = { ...node, ...(extMap[node.item_guid] || {}) }; // 存在子节点时递归处理 if (Array.isArray(node.sub_items) && node.sub_items.length) { mergedNode.sub_items = processNodeList(node.sub_items); } return mergedNode; }) } return processNodeList(baseList); } // 调用获取结果 const r3 = mergeItems(r1, r2); console.log(r3);
方案说明
- 全程使用
map方法遍历,符合预期的实现方式 - 支持任意深度的
sub_items嵌套,不存在层级限制 - 所有节点都返回新对象副本,不会修改原始的r1、r2数组,无副作用
- 映射表的实现比循环
find性能更高,数据量大时优势明显
内容的提问来源于stack exchange,提问作者JarochoEngineer
相关产品推荐
相关产品推荐

