0

0

JavaScript中递归数组的数据转换与父节点值聚合

DDD

DDD

发布时间:2025-09-16 11:18:19

|

782人浏览过

|

来源于php中文网

原创

javascript中递归数组的数据转换与父节点值聚合

本文详细阐述了如何将复杂的嵌套数组结构转换为统一的递归树形数据格式,并解决父节点数值(如总数和可用数)从其子节点动态聚合的问题。通过分步的JavaScript实现,首先构建基础的递归树,然后采用高效的后处理策略,精确计算并更新顶层父节点的聚合值,确保数据结构的完整性和准确性,适用于多层级数据的展示与处理。

1. 理解原始数据与目标结构

在处理前端数据展示或后端数据处理时,我们经常会遇到需要将某种特定结构的嵌套数据转换为更通用、更易于操作的树形结构。本教程将以一个具体的例子来演示这一过程。

原始数据结构示例:

我们有一个包含 group 和 categories 的数组,其中 categories 可以包含 subCategories,形成多层嵌套。每个分类项都包含 id、categoryName、total 和 available 等属性。

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: []
      }
    ]
  }
];

目标数据结构示例:

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

我们希望将上述数据转换为一个统一的递归结构,每个节点都包含 key、name、total、available 和 children 属性。其中,total 和 available 属性对于顶层的 group 节点,需要从其所有子节点(包括深层子节点)的相应值中聚合计算得出。

[
  {
    "key": "group1",
    "name": "groupname1",
    "total": 35, // 从其子节点聚合 (cat1.total + cat2.total)
    "available": 14, // 从其子节点聚合 (cat1.available + cat2.available)
    "children": [
      {
        "key": "cat1",
        "name": "category1",
        "total": 5,
        "available": 2,
        "children": []
      },
      {
        "key": "cat2",
        "name": "category2",
        "total": 30, // 包含cat3的total
        "available": 24, // 包含cat3的available
        "children": [
          {
            "key": "cat3",
            "name": "category3",
            "total": 15,
            "available": 12,
            "children": []
          }
        ]
      }
    ]
  },
  // ... 其他组
]

注意: 目标结构中 total 和 available 的计算方式需要特别注意。cat2 的 total 应该是它自身的值加上 cat3 的 total。而 group1 的 total 则是 cat1 的 total 加上 cat2 (已包含 cat3)的 total。这意味着聚合需要从最底层向上进行。

2. 构建基础递归树结构

首先,我们需要一个函数来遍历原始数组,并将其转换为目标结构中 key、name 和 children 的基本形式。对于 total 和 available,叶子节点(没有 subCategories 的 category)可以直接使用其自身的值,而对于中间节点和顶层节点,我们先将其初始化为0或根据自身属性赋值,后续再进行聚合。

const transformToRecursiveTree = (data) => {
  const recursiveTree = (item) => {
    // 处理组(group)节点
    if (item.group) {
      const {
        group: { id, groupname },
        categories
      } = item;
      // 初始时,group的total和available设为0,待后续聚合
      return {
        key: id,
        name: groupname,
        total: 0, // 初始设为0,待聚合
        available: 0, // 初始设为0,待聚合
        children: categories?.map(recursiveTree) || []
      };
    }
    // 处理分类(category)或子分类(subCategory)节点
    const { id, categoryName, total, available, subCategories } = item;
    // 对于分类节点,其total和available需要包含其子分类的聚合值
    // 这里先返回自身值,聚合逻辑在后续处理
    const children = subCategories?.map(recursiveTree) || [];

    // 在返回当前分类节点前,先聚合其子分类的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);
};

代码解析:

Paraflow
Paraflow

AI产品设计智能体

下载
  • transformToRecursiveTree 是主函数,它会遍历顶层数组的每个 group 项。
  • recursiveTree 是一个内部递归函数,用于处理单个节点。
  • 当 item 具有 group 属性时,它被视为一个组节点。我们提取 id 和 groupname 作为 key 和 name,并递归处理其 categories 作为 children。此时,total 和 available 暂时设置为 0,因为它们需要从子节点聚合。
  • 当 item 是一个 category 或 subCategory 时,我们提取 id、categoryName、total、available。
  • 关键在于 category 节点的 total 和 available 计算。我们首先递归处理其 subCategories 得到 children,然后使用 reduce 方法将这些 children 的 total 和 available 累加到当前 category 的 total 和 available 上。这样,total 和 available 的聚合是从最底层向上逐级完成的。

3. 聚合顶层父节点(Group)的数值

上述 transformToRecursiveTree 函数已经能够正确计算所有 category 和 subCategory 节点的 total 和 available,使其包含所有子孙节点的聚合值。现在,我们只需要对顶层的 group 节点进行后处理,计算它们的 total 和 available。

由于 group 节点的 children(即 categories)已经通过 recursiveTree 函数处理过,它们的 total 和 available 属性已经是聚合后的正确值。因此,我们只需遍历 transformToRecursiveTree 的结果,对每个顶层 group 节点,将其 children 的 total 和 available 进行累加即可。

将上述 transformToRecursiveTree 函数修改为最终版本:

const consolidateRecursiveArray = (data) => {
  // 阶段1: 构建基础递归树并聚合子分类的total/available
  const recursiveTreeBuilder = (item) => {
    // 处理组(group)节点
    if (item.group) {
      const {
        group: { id, groupname },
        categories
      } = item;
      return {
        key: id,
        name: groupname,
        total: 0, // 初始设为0,将在阶段2聚合
        available: 0, // 初始设为0,将在阶段2聚合
        children: categories?.map(recursiveTreeBuilder) || []
      };
    }
    // 处理分类(category)或子分类(subCategory)节点
    const { id, categoryName, total, available, subCategories } = item;
    const children = subCategories?.map(recursiveTreeBuilder) || [];

    // 在返回当前分类节点前,聚合其子分类的total和available
    const currentTotal = total || 0;
    const currentAvailable = available || 0;

    const aggregatedTotal = children.reduce((sum, child) => sum + child.total, currentTotal);
    const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, currentAvailable);

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

  const result = data.map(recursiveTreeBuilder);

  // 阶段2: 后处理,聚合顶层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 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 consolidateRecursiveArray = (data) => {
  // 阶段1: 构建基础递归树并聚合子分类的total/available
  const recursiveTreeBuilder = (item) => {
    // 处理组(group)节点
    if (item.group) {
      const {
        group: { id, groupname },
        categories
      } = item;
      return {
        key: id,
        name: groupname,
        total: 0, // 初始设为0,将在阶段2聚合
        available: 0, // 初始设为0,将在阶段2聚合
        children: categories?.map(recursiveTreeBuilder) || []
      };
    }
    // 处理分类(category)或子分类(subCategory)节点
    const { id, categoryName, total, available, subCategories } = item;
    const children = subCategories?.map(recursiveTreeBuilder) || [];

    // 在返回当前分类节点前,聚合其子分类的total和available
    const currentTotal = total || 0;
    const currentAvailable = available || 0;

    const aggregatedTotal = children.reduce((sum, child) => sum + child.total, currentTotal);
    const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, currentAvailable);

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

  const result = data.map(recursiveTreeBuilder);

  // 阶段2: 后处理,聚合顶层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 consolidatedData = consolidateRecursiveArray(arr);
console.log(JSON.stringify(consolidatedData, null, 2));

4. 注意事项与总结

注意事项:

  1. 递归深度: 这种方法能够处理任意深度的嵌套层级,因为 recursiveTreeBuilder 函数会一直向下递归,直到遇到没有 subCategories 的叶子节点。
  2. 数据完整性: 确保原始数据中的 total 和 available 属性存在且为数值类型。代码中使用了 total || 0 来处理可能缺失或为 null/undefined 的情况,将其视为 0。
  3. 性能考量: 整个过程分为两个主要阶段:
    • 第一阶段 (map + 递归):构建树形结构并自底向上聚合 category 级别的 total/available。
    • 第二阶段 (for...of 循环):聚合顶层 group 节点的 total/available。 对于大多数应用场景,这种两阶段处理方式的性能是可接受的。如果数据集非常庞大,且对性能有极致要求,可以考虑在递归函数中通过返回聚合值来尝试单次遍历完成,但这通常会增加递归逻辑的复杂性。
  4. 灵活性: 如果未来需要聚合其他数值型属性,只需在 reduce 逻辑中添加相应的累加操作即可。

总结:

本教程提供了一种清晰且高效的方法来处理复杂的递归数组数据转换任务。通过将问题分解为两个逻辑阶段——首先构建基础的递归树并处理中间节点的聚合,然后对顶层父节点进行后处理以完成最终的聚合——我们成功地实现了将原始嵌套数据转换为具有正确聚合值的统一树形结构。这种分步处理策略提高了代码的可读性和可维护性,是处理类似数据转换问题的有力工具

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

235

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

438

2024.03.01

treenode的用法
treenode的用法

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

538

2023.12.01

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

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

17

2025.12.22

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

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

25

2026.01.06

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

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

75

2025.09.05

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

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

36

2025.11.16

golang map原理
golang map原理

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

60

2025.11.17

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

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

精品课程

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

共58课时 | 4.2万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

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

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