JavaScript递归处理嵌套数组:结构转换与父级数据汇总

JavaScript递归处理嵌套数组:结构转换与父级数据汇总

本文详细介绍了如何将一个具有复杂嵌套结构的JavaScript数组,转换为统一的递归树形结构,其中包含key、name和children属性。文章核心内容在于,不仅实现数据结构的映射,更重要的是,演示了如何通过递归转换与后续处理相结合的方式,准确计算并汇总顶层父节点(如分组)的total和available属性,即使子节点也存在多层嵌套,也能实现数据的有效聚合。

理解数据结构与转换目标

在处理前端后端数据时,我们经常会遇到结构复杂、嵌套层级深的数组。本教程的目标是将以下这种包含group、categories和subcategories的原始数组结构:

const arr = [   {     group: { id: "group1", groupname: "groupname1" },     categories: [       {         id: "cat1",         categoryName: "category1",         total: 5,         available: 2,         subCategories: []       },       {         id: "cat2",         categoryName: "category2",         total: 15,         available: 12,         subCategories: [           {             id: "cat3",             categoryName: "category3",             total: 15,             available: 12,             subCategories: []           }         ]       }     ]   },   {     group: { id: "group2", groupname: "groupname2" },     categories: [       {         id: "cat4",         categoryName: "category4",         total: 25,         available: 22,         subCategories: []       },       {         id: "cat5",         categoryName: "category5",         total: 50,         available: 25,         subCategories: []       }     ]   } ];

转换为一个统一的、更易于处理的递归树形结构。新的结构将使用key、name和children作为标准属性。此外,一个关键需求是,顶层的group节点(如group1和group2)需要汇总其所有直接子节点(categories)的total和available值。

目标输出结构如下所示:

[   {     "key": "group1",     "name": "groupname1",     "total": 20, // 由 cat1.total (5) + cat2.total (15) 汇总而来     "available": 24, // 由 cat1.available (2) + cat2.available (12) 汇总而来     "children": [       {         "key": "cat1",         "name": "category1",         "total": 5,         "available": 2,         "children": []       },       {         "key": "cat2",         "name": "category2",         "total": 15,         "available": 12,         "children": [           {             "key": "cat3",             "name": "category3",             "total": 15,             "available": 12,             "children": []           }         ]       }     ]   },   // ... 其他分组 ]

可以看到,group1的total是其直接子节点cat1和cat2的total之和(5 + 15 = 20),available也是如此(2 + 12 = 24)。而cat2本身的total和available保持不变,未从其subCategories汇总。

初步结构转换实现

首先,我们通过一个递归函数来完成数据结构的基本映射。这个函数会遍历原始数组,并根据对象的类型(group或category)将其转换为目标格式。

立即学习Java免费学习笔记(深入)”;

我们将创建一个名为formatter的主函数,它会调用一个内部的递归辅助函数recursiveTree。recursiveTree函数负责处理单个节点及其子节点。

const formatter = (data) => {   const recursiveTree = (item) => {     // 判断当前项是否为顶层分组 (group)     if (item.group) {       const {         group: { id, groupname },         categories       } = item;       return {         key: id,         name: groupname,         // 初始时 total 和 available 设为 0,后续再计算         total: 0,          available: 0,         // 递归处理子分类         children: categories?.map(recursiveTree) || []       };     }      // 如果不是分组,则为分类 (category) 或子分类 (subCategory)     else {       const { id, categoryName, total, available, subCategories } = item;       return {         key: id,         name: categoryName,         total: total || 0, // 类别和子类别的 total/available 直接取原始值         available: available || 0,         // 递归处理子分类         children: subCategories?.map(recursiveTree) || []       };     }   };    // 第一次映射,生成基本结构   const result = data.map(recursiveTree);    // 此时,顶层 group 节点的 total 和 available 还是 0,需要后续处理   return result;  };

在这个初步转换中,我们成功地将group和category对象映射到了key、name和children结构。对于category和subCategory,它们的total和available属性直接取自原始数据。然而,对于顶层group节点,我们暂时将total和available初始化为0,因为它们的值需要依赖于其子节点的汇总结果。

顶层数据汇总:后处理策略

由于父节点的total和available依赖于其子节点的最终值,如果我们在同一个递归过程中尝试计算,可能会遇到子节点值尚未确定的问题(除非采用后序遍历,即先处理子节点再处理父节点)。为了简化逻辑并满足只汇总顶层group节点的需求,我们可以采用一个“后处理”策略:在完成初步结构转换后,对顶层结果进行第二次遍历,计算并更新group节点的total和available。

我们将修改formatter函数,在data.map(recursiveTree)之后添加一个循环来执行此操作。

const formatter = (data) => {   const recursiveTree = (item) => {     if (item.group) {       const {         group: { id, groupname },         categories       } = item;       return {         key: id,         name: groupname,         total: 0, // 初始设为0,待后续汇总         available: 0, // 初始设为0,待后续汇总         children: categories?.map(recursiveTree) || []       };     } else {       const { id, categoryName, total, available, subCategories } = item;       return {         key: id,         name: categoryName,         total: total || 0,         available: available || 0,         children: subCategories?.map(recursiveTree) || []       };     }   };    const result = data.map(recursiveTree);    // 后处理:计算顶层 group 节点的 total 和 available   for (const item of result) {       // 确保当前项有子节点       if (item.children && item.children.length > 0) {           // 使用 reduce 方法汇总所有子节点的 total           item.total = item.children.reduce((sum, child) => sum + child.total, 0);           // 使用 reduce 方法汇总所有子节点的 available           item.available = item.children.reduce((sum, child) => sum + child.available, 0);       }   }   return result; };

通过这个后处理循环,我们遍历了result数组中的每个顶层group项。对于每个group项,如果它有子节点(即categories),我们使用reduce方法将所有子节点的total和available累加起来,并更新到父节点上。这样就完成了顶层group数据的汇总。

JavaScript递归处理嵌套数组:结构转换与父级数据汇总

Designer

Microsoft推出的图形设计应用程序

JavaScript递归处理嵌套数组:结构转换与父级数据汇总63

查看详情 JavaScript递归处理嵌套数组:结构转换与父级数据汇总

完整示例代码

以下是整合了结构转换和顶层数据汇总逻辑的完整JavaScript代码示例:

const arr = [   {     group: { id: "group1", groupname: "groupname1" },     categories: [       {         id: "cat1",         categoryName: "category1",         total: 5,         available: 2,         subCategories: []       },       {         id: "cat2",         categoryName: "category2",         total: 15,         available: 12,         subCategories: [           {             id: "cat3",             categoryName: "category3",             total: 15,             available: 12,             subCategories: []           }         ]       }     ]   },   {     group: { id: "group2", groupname: "groupname2" },     categories: [       {         id: "cat4",         categoryName: "category4",         total: 25,         available: 22,         subCategories: []       },       {         id: "cat5",         categoryName: "category5",         total: 50,         available: 25,         subCategories: []       }     ]   } ];  const formatter = (data) => {   const recursiveTree = (item) => {     if (item.group) {       const {         group: { id, groupname },         categories       } = item;       return {         key: id,         name: groupname,         total: 0, // 初始设为0,待后续汇总         available: 0, // 初始设为0,待后续汇总         children: categories?.map(recursiveTree) || []       };     } else {       const { id, categoryName, total, available, subCategories } = item;       return {         key: id,         name: categoryName,         total: total || 0,         available: available || 0,         children: subCategories?.map(recursiveTree) || []       };     }   };    const result = data.map(recursiveTree);    // 后处理:计算顶层 group 节点的 total 和 available   for (const item of result) {       if (item.children && item.children.length > 0) {           item.total = item.children.reduce((sum, child) => sum + child.total, 0);           item.available = item.children.reduce((sum, child) => sum + child.available, 0);       }   }   return result; };  const formattedData = formatter(arr); console.log(JSON.stringify(formattedData, null, 2));

运行这段代码,将得到与目标输出完全一致的结果,其中group1和group2的total和available属性已正确汇总。

注意事项与扩展

  1. 特定需求下的解决方案: 当前的解决方案高效地满足了将复杂嵌套数组转换为标准树形结构,并仅对顶层分组节点进行total和available汇总的需求。

  2. 更深层级的汇总需求: 如果业务需求是需要对所有层级的父节点(例如,cat2也需要汇总其subCategories的total和available)进行汇总,那么简单的后处理就不够了。这种情况下,recursiveTree函数本身就需要进行调整,采用一种“后序遍历”或“自底向上”的递归策略。这意味着在处理一个父节点时,首先递归处理其所有子节点,并获取子节点(可能已汇总过)的total和available,然后再用这些值来计算当前父节点的total和available。 例如,可以这样修改recursiveTree:

    const recursiveTreeWithFullAggregation = (item) => {   let node;   if (item.group) {     node = {       key: item.group.id,       name: item.group.groupname,       total: 0,       available: 0,       children: item.categories?.map(recursiveTreeWithFullAggregation) || []     };   } else { // category 或 subCategory     node = {       key: item.id,       name: item.categoryName,       total: item.total || 0,       available: item.available || 0,       children: item.subCategories?.map(recursiveTreeWithFullAggregation) || []     };   }    // 如果节点有子节点,则汇总子节点的 total 和 available   if (node.children && node.children.length > 0) {     // 如果是 group 节点,或者需要聚合的 category 节点     // 这里的逻辑需要根据具体需求判断哪些节点需要聚合     // 对于本例,只有顶层 group 需要聚合,所以这里可以只在 top-level 外部处理     // 但如果所有层级都需要,则在此处进行     node.total = node.children.reduce((sum, child) => sum + child.total, 0);     node.available = node.children.reduce((sum, child) => sum + child.available, 0);   }   return node; }; // 这种情况下,formatter 函数就不需要额外的 for 循环了 // return data.map(recursiveTreeWithFullAggregation);

    请注意,上述recursiveTreeWithFullAggregation示例仅为概念性说明,实际实现需要根据具体聚合规则进行调整,以避免不必要的重复聚合或错误聚合。

  3. 健壮性: 在实际应用中,应考虑对输入数据进行更严格的校验,例如检查categories或subCategories是否存在,以及total和available是否为有效数字,以防止运行时错误。

  4. 性能: 对于非常庞大的数据集,递归操作可能会导致栈溢出。在这种情况下,可以考虑使用迭代方式(例如基于栈的深度优先搜索)来代替纯递归,或者优化数据结构以减少递归深度。但对于大多数常见场景,递归方案简洁且易于理解。

通过本文的讲解,您应该能够理解

javascript java js 前端 json node go 后端 ai red JavaScript 递归 循环 数据结构 map 对象

上一篇
下一篇