如何用Python/JS将非嵌套JSON转换为树形图适用的嵌套JSON结构
问题说明
现有扁平化节点列表(问题中给出的为伪JSON格式,不符合标准JSON语法,实际使用时需先将键、字符串值补充双引号转为合法结构),节点间通过name字段做关联引用,直接传入绘图逻辑会生成网状非树形结构,需要转换为单根标准树形结构。
注:问题示例中原始数据main节点的children字段里的fun3为笔误,实际应为func,和最终输出结构匹配。
转换核心逻辑
- 以节点
name作为唯一标识,先遍历全量显式定义的节点建立全局映射表 - 遍历所有节点的
children字段收集引用节点,未显式定义的引用节点自动补全为{name: 节点名, children: null}的基础结构 - 统计每个节点的入度(被其他节点引用为子节点的次数),入度为0的节点即为整棵树的根节点
- 从根节点开始递归标准化结构,将children里的名称引用替换为对应的节点对象,过程中标记已访问节点避免循环递归
代码实现
Python 版本
def convert_to_tree(raw_nodes): node_map = {} # 登记所有显式定义的节点 for node in raw_nodes: node_map[node["name"]] = node.copy() ref_set = set() # 收集所有子节点引用,补全缺失节点 for node in raw_nodes: children = node.get("children") if not children: node["children"] = None continue # 统一把子节点转为列表格式处理 child_names = [children] if isinstance(children, str) else children for child_name in child_names: ref_set.add(child_name) if child_name not in node_map: node_map[child_name] = {"name": child_name, "children": None} # 查找入度为0的根节点 root_name = None for name in node_map.keys(): if name not in ref_set: root_name = name break visited = set() def standardize(node): if node["name"] in visited: return visited.add(node["name"]) children = node.get("children") if not children: node["children"] = None return # 将名称引用替换为实际节点对象 if isinstance(children, str): child_list = [node_map[children]] else: child_list = [node_map[c] for c in children] node["children"] = child_list for child in child_list: standardize(child) root = node_map[root_name] standardize(root) return root # 测试用例(对应问题给出的原始数据) raw_data = [ {"name": "func1", "children": "func"}, {"name": "func2", "children": "func"}, {"name": "main", "children": ["func1", "func2", "func"]} ] standard_tree = convert_to_tree(raw_data)
JavaScript 版本
function convertToTree(rawNodes) { const nodeMap = {}; // 登记所有显式定义的节点 rawNodes.forEach(node => { nodeMap[node.name] = {...node}; }); const refSet = new Set(); // 收集所有子节点引用,补全缺失节点 rawNodes.forEach(node => { const children = node.children; if (!children) { node.children = null; return; } const childNames = Array.isArray(children) ? children : [children]; childNames.forEach(childName => { refSet.add(childName); if (!nodeMap[childName]) { nodeMap[childName] = {name: childName, children: null}; } }); }); // 查找入度为0的根节点 let rootName = null; for (const name of Object.keys(nodeMap)) { if (!refSet.has(name)) { rootName = name; break; } } const visited = new Set(); function standardize(node) { if (visited.has(node.name)) return; visited.add(node.name); const children = node.children; if (!children) { node.children = null; return; } // 将名称引用替换为实际节点对象 const childList = Array.isArray(children) ? children.map(c => nodeMap[c]) : [nodeMap[children]]; node.children = childList; childList.forEach(child => standardize(child)); } const root = nodeMap[rootName]; standardize(root); return root; } // 测试用例(对应问题给出的原始数据) const rawData = [ {name: "func1", children: "func"}, {name: "func2", children: "func"}, {name: "main", children: ["func1", "func2", "func"]} ]; const standardTree = convertToTree(rawData);
适配说明
- 如果场景下存在多个入度为0的节点,可以创建一个虚拟根节点,将所有无入度节点挂载到虚拟根下,保证单根结构符合树形组件要求
- 如果业务要求严格单父节点(禁止节点共享,彻底避免网状结构),处理子节点时对已访问过的节点做深拷贝后再挂载即可
- 代码默认支持任意层级嵌套,循环引用场景下不会出现死递归
内容的提问来源于stack exchange,提问作者vincand
相关产品推荐
相关产品推荐

