JSON数据如何高效转换为树形结构
在前后端开发中,JSON(JavaScript Object Notation)因其轻量级、易读性强的特点,成为数据交换的主流格式,但实际业务中,后端返回的JSON数据往往是扁平的(如列表、数组),而前端渲染(如菜单、组织架构、文件目录等)常需要树形结构,如何将扁平JSON数据高效转换为树形结构,是开发者必须的技能,本文将系统介绍转换的核心逻辑、常见方法及代码实现,并附上优化技巧。
树形结构的核心概念:什么是“树”?
在开始转换前,需明确树形结构的特征:
- 节点(Node):树的基本单元,包含数据标识(如
id)和层级关系。 - 父子关系:每个节点可能有零个或多个子节点(
children),通过唯一标识(如parentId)关联父节点。 - 根节点(Root Node):没有父节点的节点,是树的起点。
一个典型的树形JSON结构如下:
{
"id": 1,
"name": "根节点",
"children": [
{
"id": 2,
"name": "子节点1",
"children": [
{"id": 4, "name": "孙节点1", "children": []}
]
},
{"id": 3, "name": "子节点2", "children": []}
]
}
转换的核心逻辑:如何建立父子关系?
将扁平JSON转为树形,本质是通过节点标识(如id和parentId)建立父子关联,核心步骤可概括为:
明确节点标识规则
需提前确定数据中表示节点唯一标识的字段(如id)和父节点标识的字段(如parentId)。
- 节点自身ID:
id - 父节点ID:
parentId(根节点的parentId通常为null、0或特定值)
构建节点映射表
遍历扁平数据,用Map或对象以id为键存储节点,方便快速查找。
const nodeMap = new Map();
flatData.forEach(item => {
nodeMap.set(item.id, { ...item, children: [] }); // 初始化children数组
});
建立父子关联
再次遍历数据,通过parentId找到父节点,将当前节点添加到父节点的children中。
flatData.forEach(item => {
const parentId = item.parentId;
if (parentId && nodeMap.has(parentId)) {
const parentNode = nodeMap.get(parentId);
parentNode.children.push(nodeMap.get(item.id));
}
});
提取根节点
根节点的parentId为特殊值(如null),从nodeMap中筛选出所有根节点,构成最终树形结构。
常见场景与代码实现
场景1:标准扁平数据(带id和parentId)
假设有如下扁平数据(部门列表),需转换为部门树:
[
{ "id": 1, "name": "总公司", "parentId": null },
{ "id": 2, "name": "技术部", "parentId": 1 },
{ "id": 3, "name": "市场部", "parentId": 1 },
{ "id": 4, "name": "前端组", "parentId": 2 },
{ "id": 5, "name": "后端组", "parentId": 2 }
]
实现代码(JavaScript)
function flatToTree(flatData, idKey = 'id', parentIdKey = 'parentId') {
// 1. 构建节点映射表
const nodeMap = new Map();
flatData.forEach(item => {
nodeMap.set(item[idKey], { ...item, children: [] });
});
// 2. 建立父子关联,并收集根节点
const tree = [];
flatData.forEach(item => {
const currentNode = nodeMap.get(item[idKey]);
const parentId = item[parentIdKey];
if (parentId === null || parentId === undefined) {
tree.push(currentNode); // 根节点
} else if (nodeMap.has(parentId)) {
const parentNode = nodeMap.get(parentId);
parentNode.children.push(currentNode);
}
});
return tree;
}
// 测试
const flatData = [
{ id: 1, name: "总公司", parentId: null },
{ id: 2, name: "技术部", parentId: 1 },
{ id: 3, name: "市场部", parentId: 1 },
{ id: 4, name: "前端组", parentId: 2 },
{ id: 5, name: "后端组", parentId: 2 }
];
console.log(JSON.stringify(flatToTree(flatData), null, 2));
输出结果
[
{
"id": 1,
"name": "总公司",
"parentId": null,
"children": [
{
"id": 2,
"name": "技术部",
"parentId": 1,
"children": [
{ "id": 4, "name": "前端组", "parentId": 2, "children": [] },
{ "id": 5, "name": "后端组", "parentId": 2, "children": [] }
]
},
{ "id": 3, "name": "市场部", "parentId": 1, "children": [] }
]
}
]
场景2:无parentId,需通过层级字段构建
部分数据可能没有parentId,而是通过level(层级)和order(排序)字段隐含父子关系,
[
{ "id": 1, "name": "根节点", "level": 1, "order": 1 },
{ "id": 2, "name": "子节点1", "level": 2, "order": 1 },
{ "id": 3, "name": "子节点2", "level": 2, "order": 2 },
{ "id": 4, "name": "孙节点1", "level": 3, "order": 1 }
]
转换逻辑:按level分组,同一层级按order排序,再遍历将当前层级的节点挂载到上一层级的最后一个节点。
实现代码
function treeByLevel(flatData, levelKey = 'level', orderKey = 'order') {
// 1. 按 level 分组,并按 order 排序
const levelMap = new Map();
flatData.forEach(item => {
const level = item[levelKey];
if (!levelMap.has(level)) {
levelMap.set(level, []);
}
levelMap.get(level).push(item);
});
// 2. 按 level 从小到大排序
const levels = Array.from(levelMap.keys()).sort((a, b) => a - b);
if (levels.length === 0) return [];
// 3. 初始化第一层(根节点)
const tree = levelMap.get(levels[0]);
let parentNodes = tree; // 当前父节点列表
// 4. 遍历后续层级,挂载到对应父节点
for (let i = 1; i < levels.length; i++) {
const currentLevel = levels[i];
const currentNodes = levelMap.get(currentLevel);
const nextParentNodes = [];
currentNodes.forEach(node => {
// 挂载到 parentNodes 的最后一个节点(假设 order 连续)
if (parentNodes.length > 0) {
const parentNode = parentNodes[parentNodes.length - 1];
if (!parentNode.children) parentNode.children = [];
parentNode.children.push(node);
nextParentNodes.push(node);
}
});
parentNodes = nextParentNodes; // 更新父节点列表为当前层级的节点
}
return tree;
}
// 测试
const levelData = [
{ id: 1, name: "根节点", level: 1, order: 1 },
{ id: 2, name: "子节点1", level: 2, order: 1 },
{ id: 3, name: "子节点2", level: 2, order: 2 },
{ id: 4, name: "孙节点1", level: 3, order: 1 }
];
console.log(JSON.stringify(treeByLevel(levelData), null, 2));
输出结果
[
{
"id": 1,
"name": "根节点",
"level": 1,


还没有评论,来说两句吧...