浅出:使用JavaScript构建和操作JSON树
在JavaScript开发中,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,无处不在,它结构清晰,易于人阅读和编写,也易于机器解析和生成,而“JSON树”通常指由嵌套的对象和数组构成的层级数据结构,如何用JavaScript动态地构建、查询和修改这种JSON树,是每个前端和Node.js开发者的必备技能,本文将浅出地介绍如何实现这一切。
什么是JSON树?
JSON树本质上是一个嵌套的数据结构,它由两种主要类型组成:
- 对象 (Object):用花括号 表示,由键值对组成,非常适合表示树中的节点,其中键是属性名,值可以是叶子节点(如字符串、数字)或子节点(另一个对象或数组)。
- 数组 (Array):用方括号
[]表示,由有序的值列表组成,非常适合表示树中的子节点列表或兄弟节点集合。
一个典型的JSON树结构如下:
{
"name": "公司",
"type": "root",
"children": [
{
"name": "技术部",
"type": "department",
"children": [
{ "name": "张三", "type": "employee", "role": "前端工程师" },
{ "name": "李四", "type": "employee", "role": "后端工程师" }
]
},
{
"name": "市场部",
"type": "department",
"children": [
{ "name": "王五", "type": "employee", "role": "市场经理" }
]
}
]
}
在这个例子中,整个公司是根节点,children 数组包含了各个部门,而每个部门节点又包含一个children数组,里面存放着员工节点。
如何构建JSON树?
构建JSON树主要有两种方式:静态定义和动态构建。
静态定义
对于结构固定、数据已知的情况,最简单的方式就是直接在代码中写出JSON结构。
// 静态定义一个JSON树
const companyTree = {
name: "公司",
type: "root",
children: [
{
name: "技术部",
type: "department",
children: [
{ name: "张三", type: "employee", role: "前端工程师" },
{ name: "李四", type: "employee", role: "后端工程师" }
]
},
{
name: "市场部",
type: "department",
children: [
{ name: "王五", type: "employee", role: "市场经理" }
]
}
]
};
console.log(companyTree);
动态构建
在实际应用中,JSON树的数据通常来自API、数据库或用户输入,需要我们通过JavaScript代码动态地创建和组装。
核心思想: 从叶子节点开始,逐层向上构建父节点,最终形成整棵树。
假设我们有以下扁平化的员工数据:
const employees = [
{ id: 1, name: '张三', role: '前端工程师', departmentId: 101 },
{ id: 2, name: '李四', role: '后端工程师', departmentId: 101 },
{ id: 3, name: '王五', role: '市场经理', departmentId: 102 },
{ id: 4, name: '赵六', role: '销售专员', departmentId: 102 }
];
const departments = [
{ id: 101, name: '技术部' },
{ id: 102, name: '市场部' }
];
我们可以编写一个函数来将其转换为树形结构:
function buildTree(employees, departments) {
// 1. 创建一个Map来存储部门节点,方便快速查找
const departmentMap = new Map();
departments.forEach(dept => {
// 初始化每个部门的children数组
departmentMap.set(dept.id, { ...dept, children: [] });
});
// 2. 创建一个Map来存储所有员工节点
const employeeMap = new Map();
employees.forEach(emp => {
employeeMap.set(emp.id, { ...emp, type: 'employee' });
});
// 3. 将员工节点挂载到对应的部门下
employees.forEach(emp => {
const deptNode = departmentMap.get(emp.departmentId);
if (deptNode) {
deptNode.children.push(employeeMap.get(emp.id));
}
});
// 4. 获取所有构建好的部门节点
const departmentNodes = Array.from(departmentMap.values());
// 5. 构建最终的根节点
const tree = {
name: "公司",
type: "root",
children: departmentNodes
};
return tree;
}
const companyTreeDynamic = buildTree(employees, departments);
console.log(JSON.stringify(companyTreeDynamic, null, 2));
如何遍历和查询JSON树?
构建好树之后,最常见的操作就是遍历它来查找、修改或处理数据,遍历树结构最经典的方法是深度优先搜索和广度优先搜索。
深度优先搜索
DFS会沿着一条分支走到最深,然后回溯,非常适合查找特定路径或进行递归操作。
递归实现(最直观):
// 查找指定名称的节点
function findNodeByNameDFS(node, name) {
// 1. 检查当前节点
if (node.name === name) {
return node;
}
// 2. 如果当前节点有children,则递归遍历
if (node.children && node.children.length > 0) {
for (const child of node.children) {
const found = findNodeByNameDFS(child, name);
// 3. 如果在子树中找到了,立即返回结果
if (found) {
return found;
}
}
}
// 4. 如果遍历完所有子节点都没找到,返回null
return null;
}
const techDept = findNodeByNameDFS(companyTreeDynamic, '技术部');
console.log("查找结果:", techDept);
广度优先搜索
BFS会逐层遍历树,先访问所有兄弟节点,再访问它们的子节点,非常适合查找最近的节点或按层级处理数据。
使用队列实现:
// 查找指定名称的节点
function findNodeByNameBFS(root, name) {
if (!root) return null;
// 使用数组作为队列,将根节点入队
const queue = [root];
while (queue.length > 0) {
// 从队列头部取出一个节点
const currentNode = queue.shift();
// 检查当前节点
if (currentNode.name === name) {
return currentNode;
}
// 如果当前节点有children,将它们全部入队
if (currentNode.children && currentNode.children.length > 0) {
queue.push(...currentNode.children);
}
}
// 队列为空,说明没找到
return null;
}
const marketDept = findNodeByNameBFS(companyTreeDynamic, '市场部');
console.log("查找结果:", marketDept);
如何修改JSON树?
修改JSON树的关键在于定位到目标节点,然后直接对其属性进行赋值操作,由于JavaScript对象是引用类型,我们修改的是内存中的同一个对象。
示例1:修改员工角色
// 假设我们已经通过DFS或BFS找到了张三这个节点
const zhangSan = findNodeByNameDFS(companyTreeDynamic, '张三');
if (zhangSan) {
zhangSan.role = '高级前端工程师';
console.log("张三的新角色:", zhangSan.role);
}
示例2:添加新员工
// 找到技术部节点
const techDeptNode = findNodeByNameDFS(companyTreeDynamic, '技术部');
if (techDeptNode && techDeptNode.children) {
// 向其children数组中push一个新员工对象
techDeptNode.children.push({
name: '孙七',
type: 'employee',
role: '测试工程师'
});
}
console.log("添加新员工后的技术部:", techDeptNode.children);
示例3:删除节点
// 找到市场部节点
const marketDeptNode = findNodeByNameDFS(companyTreeDynamic, '市场部');
if (marketDeptNode && marketDeptNode.children) {
// 找到要删除的员工(王五)
const wangwuIndex = marketDeptNode.children.findIndex(emp => emp.name === '王五');
if (wangwuIndex > -1) {
// 使用splice方法从数组中移除
marketDeptNode.children.splice(wangwuIndex, 1);
}


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