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代码示例:

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属性已正确汇总。

注意事项与扩展

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

更深层级的汇总需求: 如果业务需求是需要对所有层级的父节点(例如,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示例仅为概念性说明,实际实现需要根据具体聚合规则进行调整,以避免不必要的重复聚合或错误聚合。

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

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

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

以上就是JavaScript递归处理嵌套数组:结构转换与父级数据汇总的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1520342.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
实现基于滚动进度的文本渐变高亮效果教程
上一篇 2025年12月20日 13:14:33
CSS ::after 伪元素与数据属性:实现动态错误消息显示的最佳实践
下一篇 2025年12月20日 13:14:48

相关推荐

发表回复

登录后才能评论
关注微信