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,抖音足球直播
抖音足球直播
企鹅直播
企鹅直播
足球直播
爱奇艺直播
爱奇艺足球直播
足球直播
足球直播
iqiyi直播
足球直播
足球直播
QQ足球直播
QQ足球直播
足球直播
足球直播
QQ足球直播
QQ足球直播
足球直播
足球直播
快连
快连
快连
快连下载
快连
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
新浪足球直播
新浪足球直播
足球直播
足球直播
有道翻译
有道翻译
有道翻译
有道翻译
wps
wps
wps
wps
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
新浪足球直播
新浪足球直播
足球直播
足球直播



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