
在处理前端或后端数据时,我们经常会遇到结构复杂、嵌套层级深的数组。本教程的目标是将以下这种包含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递归处理嵌套数组:结构转换与父级数据汇总的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号