本文介绍如何将扁平对象数组按指定属性顺序(如 ['a', 'b', 'c', 'd'])递归分组,生成符合图表库要求的嵌套树形结构(含 name 和 children 字段),支持多层级动态聚合与去重。
在数据可视化 场景中(如 ECharts、D3 或自定义层级图),常需将扁平的原始数据转换为具有明确父子关系的嵌套结构。典型需求是:给定一组具有相同键名(如 A、B、C、D)的对象,按属性顺序逐层分组,最终形成 name + children 的标准树节点格式。本教程提供一种简洁、健壮且可扩展的实现方案。
核心思路:两阶段构造法
我们采用「先建模、后转换」的两阶段策略:
构建嵌套哈希树 :利用 JavaScript 的 ??=(空值合并赋值)操作符,按属性顺序逐层创建嵌套对象,自动处理路径分支与重复键;
递归展平为树节点 :通过 Object.entries() + 递归映射,将嵌套对象结构转化为目标格式(无子节点时省略 children 数组)。
完整实现代码 /**
* 将对象数组按指定属性顺序分组,生成嵌套树结构
* @param {Array} data - 输入的扁平对象数组
* @param {Array} keys - 分组属性名顺序,如 ['A', 'B', 'C', 'D']
* @returns {Object} 符合 name/children 规范的根节点
*/
function groupByProperties(data, keys) {
if (!Array.isArray(data) || data.length === 0 || !Array.isArray(keys) || keys.length === 0) {
return {};
}
// 阶段一:构建嵌套对象树(以属性值为键)
const root = {};
for (const obj of data) {
let node = root;
for (const key of keys) {
const value = obj[key];
// 确保每层都存在对应子对象,自动创建缺失路径
node = (node[value] ??= {});
}
}
// 阶段二:递归转换为标准树节点结构
const buildNode = (obj) => {
return Object.entries(obj).map(([name, child]) => {
// 若 child 为空对象(即叶子节点),只返回 { name }
// 否则递归构建 children
const children = Object.keys(child).length > 0
? buildNode(child)
: undefined;
return children
? { name: Number(name), children }
: { name: Number(name) };
});
};
// 返回根节点(因 keys 非空,buildNode(root) 至少返回一个元素)
return buildNode(root)[0] || {};
}
// ✅ 示例 1:同 A/B/C,D 不同 → 单分支多叶
const data1 = [
{ A: 1, B: 5, C: 7, D: 67 },
{ A: 1, B: 5, C: 7, D: 69 }
];
console.log(groupByProperties(data1, ['A', 'B', 'C', 'D']));
// 输出:
// { name: 1, children: [{ name: 5, children: [{ name: 7, children: [{ name: 67 }, { name: 69 }] }] }] }
// ✅ 示例 2:C 层出现分支 → 多子节点并列
const data2 = [
{ A: 1, B: 5, C: 4, D: 67 },
{ A: 1, B: 5, C: 7, D: 69 }
];
console.log(groupByProperties(data2, ['A', 'B', 'C', 'D']));
// 输出:
// { name: 1, children: [{ name: 5, children: [{ name: 4, children: [{ name: 67 }] }, { name: 7, children: [{ name: 69 }] }] }] } 关键特性与注意事项
自动类型转换 :示例中使用 Number(name) 统一转为数值类型(适配多数图表库)。若需保留字符串(如 'A1'),可移除 Number() 包裹;
空值安全 :使用 ??= 避免 undefined 访问错误,天然支持稀疏或缺失属性(但需确保输入对象中所有 keys 对应字段均存在);
顺序严格性 :keys 数组顺序决定分组层级,不可颠倒(如 ['B', 'A'] 将产生完全不同的树结构);
性能优化 :时间复杂度为 O(n × k) (n 为数据量,k 为 keys 长度),空间复杂度取决于唯一路径数,适用于万级以内数据;
扩展建议 :如需支持自定义节点字段(如添加 id、label)、过滤条件或排序逻辑,可在 buildNode 内部增强映射逻辑。
该方案轻量、无外部依赖,可直接集成至前端 数据预处理流程,高效支撑各类层级可视化需求。