首页 > web前端 > js教程 > 正文

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

DDD
发布: 2025-09-16 11:37:00
原创
851人浏览过

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)之后添加一个循环来执行此操作。

即构数智人
即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36
查看详情 即构数智人
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属性已正确汇总。

注意事项与扩展

  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递归处理嵌套数组:结构转换与父级数据汇总的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号