值得一看
广告
彩虹云商城
广告

热门广告位

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

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,这种“自下而上”的计算最好在所有子节点都处理完毕后进行。

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

Fliki

Fliki

高效帮用户创建视频,具有文本转语音功能

Fliki96

查看详情
Fliki

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 等属性是否存在,以增强代码的健壮性。

总结

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

相关标签:

javascript java js 前端 json go 浏览器 回调函数 前端开发 ai red gate JavaScript 架构 回调函数 递归 循环 数据结构 栈 map 对象 数据分析 ui

大家都在看:

JavaScript递归数组结构转换与父节点数据聚合计算
JavaScript中datetime-local输入值的日期时间格式化教程
JavaScript中datetime-local输入值的灵活日期时间格式化
JavaScript技巧:展平嵌套数组以创建“干净”的二维数组
JavaScript数组扁平化:实现特定结构的2D数组转换
温馨提示: 本文最后更新于2025-09-16 10:39:18,某些文章具有时效性,若有错误或已失效,请在下方留言或联系在线客服
文章版权声明 1 本网站名称: 创客网
2 本站永久网址:https://new.ie310.com
1 本文采用非商业性使用-相同方式共享 4.0 国际许可协议[CC BY-NC-SA]进行授权
2 本站所有内容仅供参考,分享出来是为了可以给大家提供新的思路。
3 互联网转载资源会有一些其他联系方式,请大家不要盲目相信,被骗本站概不负责!
4 本网站只做项目揭秘,无法一对一教学指导,每篇文章内都含项目全套的教程讲解,请仔细阅读。
5 本站分享的所有平台仅供展示,本站不对平台真实性负责,站长建议大家自己根据项目关键词自己选择平台。
6 因为文章发布时间和您阅读文章时间存在时间差,所以有些项目红利期可能已经过了,能不能赚钱需要自己判断。
7 本网站仅做资源分享,不做任何收益保障,创业公司上收费几百上千的项目我免费分享出来的,希望大家可以认真学习。
8 本站所有资料均来自互联网公开分享,并不代表本站立场,如不慎侵犯到您的版权利益,请联系79283999@qq.com删除。

本站资料仅供学习交流使用请勿商业运营,严禁从事违法,侵权等任何非法活动,否则后果自负!
THE END
喜欢就支持一下吧
点赞7赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容