0

0

JavaScript递归数组结构转换与父节点数据聚合计算

霞舞

霞舞

发布时间:2025-09-16 10:11:27

|

883人浏览过

|

来源于php中文网

原创

JavaScript递归数组结构转换与父节点数据聚合计算

本文详细阐述如何将具有多层嵌套的JavaScript数组转换为统一的递归树形结构,并着重解决在父节点上聚合其所有子节点数值型数据(如总数和可用量)的挑战。通过分步实现,首先进行结构映射,随后利用后处理机制对父节点数据进行汇总,确保在任意深度层级下都能准确完成数据整合。

1. 问题背景与目标

前端开发中,我们经常需要处理具有复杂嵌套关系的数据结构,例如树形菜单、组织架构或多级分类列表。原始数据可能以扁平化或特定嵌套格式提供,但为了在ui组件(如ant design的tree组件)中展示或进行数据分析,我们需要将其转换为统一的递归树形结构。

本教程的目标是将以下这种包含 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, total, available 和 children 属性。特别地,顶层节点(如 group1, group2)的 total 和 available 属性需要聚合其所有子节点的相应值:

[
  {
    "key": "group1",
    "name": "groupname1",
    "total": 35, // 5 + 15 + 15 (cat1.total + cat2.total + cat3.total)
    "available": 26, // 2 + 12 + 12 (cat1.available + cat2.available + cat3.available)
    "children": [
      {
        "key": "cat1",
        "name": "category1",
        "total": 5,
        "available": 2,
        "children": []
      },
      {
        "key": "cat2",
        "name": "category2",
        "total": 30, // 15 + 15 (cat2.total + cat3.total)
        "available": 24, // 12 + 12 (cat2.available + cat3.available)
        "children": [
          {
            "key": "cat3",
            "name": "category3",
            "total": 15,
            "available": 12,
            "children": []
          }
        ]
      }
    ]
  },
  // ... 其他组
]

(注:根据原始问题,group1 的 total 和 available 应该聚合其直接子节点及其孙子节点的所有值。例如,cat2 的 total 应为 15 + 15 = 30,group1 的 total 应为 5 + 30 = 35。原始问题中的期望输出 total: 20, available: 24 可能是一个笔误,或者只考虑了直接子节点。本教程将实现聚合所有子孙节点的功能。)

2. 第一步:构建递归树形结构

首先,我们需要一个递归函数来遍历原始数组,并将其中的 group、category 和 subCategory 对象转换为目标格式的节点。这个函数将负责映射 id 到 key,groupname/categoryName 到 name,并处理 categories/subCategories 到 children 的转换。

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

const formatter = (data) => {
  // 递归处理单个项目,将其转换为目标结构
  const recursiveTree = (item) => {
    if (item.group) { // 处理顶层group项目
      const {
        group: { id, groupname }, // 提取group的id和名称
        categories
      } = item;
      return {
        key: id,
        name: groupname,
        total: 0,      // 初始设置为0,待后续聚合
        available: 0,  // 初始设置为0,待后续聚合
        children: categories?.map(recursiveTree) || [] // 递归处理子分类
      };
    } else { // 处理category或subCategory项目
      const { id, categoryName, total, available, subCategories } = item;
      // 对于category/subCategory,其自身的total和available是已知的
      const children = subCategories?.map(recursiveTree) || [];

      // 如果category/subCategory有子节点,其total和available也需要聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);

      return {
        key: id,
        name: categoryName,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    }
  };

  // 遍历原始数据数组,应用递归转换
  return data.map(recursiveTree);
};

代码解析:

  • formatter 函数接收原始数组 data。
  • recursiveTree 是一个内部递归函数,它根据传入 item 的结构来判断是 group 还是 category/subCategory。
  • 对于 group 类型:
    • 它提取 group.id 和 group.groupname 分别作为 key 和 name。
    • total 和 available 暂时初始化为 0,因为它们最终需要从其所有子孙节点聚合而来。
    • children 属性通过递归调用 categories.map(recursiveTree) 来生成。
  • 对于 category 或 subCategory 类型:
    • 它提取 id、categoryName、total、available。
    • children 属性通过递归调用 subCategories.map(recursiveTree) 来生成。
    • 关键点: 对于 category 或 subCategory 自身,如果它也有子节点,那么它的 total 和 available 也应该聚合自身的值和其所有子孙节点的值。这里在返回之前进行了 reduce 操作,将子节点的 total 和 available 累加到当前节点的初始值上。

3. 第二步:实现顶层父节点数据的聚合计算

上述 recursiveTree 函数已经能够处理 category 和 subCategory 自身的聚合,但对于最顶层的 group 节点,由于其 total 和 available 是从其 categories 聚合而来,而 categories 的聚合又依赖于其 subCategories,这种“自下而上”的计算最好在所有子节点都处理完毕后进行。

‎ Gemini Storybook
‎ Gemini Storybook

Google Gemini推出的AI绘本生成工具

下载

一种高效的方法是在 formatter 函数的最后,对已经完成结构转换的顶层节点进行一次后处理。

const formatterWithAggregation = (data) => {
  // 递归处理单个项目,将其转换为目标结构并计算自身及子孙节点的聚合值
  const recursiveTree = (item) => {
    if (item.group) { // 处理顶层group项目
      const {
        group: { id, groupname },
        categories
      } = item;
      // 递归处理所有子分类
      const children = categories?.map(recursiveTree) || [];

      // 对于group节点,其total和available是所有子孙节点的聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, 0);

      return {
        key: id,
        name: groupname,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    } else { // 处理category或subCategory项目
      const { id, categoryName, total, available, subCategories } = item;
      const children = subCategories?.map(recursiveTree) || [];

      // 对于category/subCategory,其自身的total和available是自身值与子孙值的聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);

      return {
        key: id,
        name: categoryName,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    }
  };

  // 直接在map过程中完成所有层级的聚合计算
  return data.map(recursiveTree);
};

优化说明:

在第一次的代码实现中,group 节点的 total 和 available 被初始化为 0,而 category 节点的 total 和 available 则在递归返回时进行了聚合。实际上,我们可以将聚合逻辑统一到 recursiveTree 函数内部,在每次递归返回时,都确保当前节点的 total 和 available 已经包含了其所有子孙节点的聚合值。这样,当 group 节点处理其 children (即 categories) 并得到它们已经聚合好的 total 和 available 后,就可以直接进行求和。

这种“自底向上”的递归聚合方法更为简洁和高效,因为它避免了额外的后处理循环。

4. 完整代码示例

将上述逻辑整合,得到最终的解决方案:

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) { // 处理顶层group项目
      const {
        group: { id, groupname },
        categories
      } = item;
      // 递归处理所有子分类
      const children = categories?.map(recursiveTree) || [];

      // 对于group节点,其total和available是所有子孙节点的聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, 0);

      return {
        key: id,
        name: groupname,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    } else { // 处理category或subCategory项目
      const { id, categoryName, total, available, subCategories } = item;
      const children = subCategories?.map(recursiveTree) || [];

      // 对于category/subCategory,其自身的total和available是自身值与子孙值的聚合
      // 注意:这里total和available的初始值是当前item自身的total/available
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);

      return {
        key: id,
        name: categoryName,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    }
  };

  // 直接在map过程中完成所有层级的聚合计算
  return data.map(recursiveTree);
};

const result = formatter(arr);
console.log(JSON.stringify(result, null, 2));

5. 注意事项与扩展

  1. N层深度支持: 这种递归方法天然支持任意深度的嵌套,无需额外修改。
  2. 默认值处理: 在聚合计算中,total || 0 和 available || 0 的写法确保了即使原始数据中缺少 total 或 available 字段,也能将其视为 0 进行计算,避免了 NaN 的出现。
  3. 性能考量: 对于非常庞大和深层的数据结构,递归可能会导致栈溢出。在JavaScript中,通常浏览器对递归深度有限制(约几千层)。如果遇到此类问题,可以考虑使用迭代方式(如队列或栈)模拟递归,或者进行尾递归优化(如果环境支持)。但对于大多数常见应用场景,此递归方案足够高效。
  4. 聚合逻辑扩展: 如果需要聚合的不仅仅是 total 和 available,或者需要不同的聚合方式(如平均值、最大值),只需在 reduce 回调函数中调整相应的逻辑即可。
  5. 错误处理: 在实际应用中,可能需要增加对输入数据格式的校验,例如检查 group、categories、subCategories 等属性是否存在,以增强代码的健壮性。

总结

本教程通过一个两阶段(但最终优化为一步到位)的递归处理方法,成功地将复杂的嵌套数组结构转换为统一的树形结构,并实现了父节点对其所有子孙节点数值型数据的聚合计算。核心在于利用递归的“自底向上”特性,在每次递归调用返回时,确保当前节点的聚合值已经计算完毕,从而使得上层节点可以直接使用这些聚合值进行进一步的汇总。这种模式在处理各种树形数据结构转换和数据汇总的场景中具有广泛的应用价值。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

550

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

30

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

45

2026.01.06

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

448

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

606

2023.08.10

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

77

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

41

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

67

2025.11.17

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

69

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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