
本文详解如何将扁平化的 []map[string]interface{} 数据(如数据库查询结果)转换为具有父子关系的嵌套树结构,通过构建 Node 类型与映射索引实现高效、可序列化的树形 JSON 输出。
本文详解如何将扁平化的 `[]map[string]interface{}` 数据(如数据库查询结果)转换为具有父子关系的嵌套树结构,通过构建 `node` 类型与映射索引实现高效、可序列化的树形 json 输出。
在 Go 语言中,将线性数据(如接口切片)构建成嵌套树结构是常见需求,尤其在处理组织架构、菜单、分类等具有父子关系的数据时。与 PHP 中通过引用(&$ref)动态挂载子节点不同,Go 没有原生引用赋值语法,需借助*指针 + 映射索引(map[int]Node)** 实现逻辑上的“父节点持有子节点指针”。
核心思路分为三步:
-
定义结构体:使用强类型
Node替代map[string]interface{},明确字段(ID,ParentID,Child *Node),提升可读性与 JSON 序列化兼容性; -
预加载索引:遍历原始数据,用
map[int]*Node建立 ID 到节点指针的快速查找表; -
构建父子链:再次遍历,对每个节点,若其
ParentID存在,则将当前节点地址赋给父节点的Child字段。
以下为完整可运行示例:
package main
import (
"encoding/json"
"fmt"
)
var indata = `[
{"ID": 1, "Name": "Root", "ParentID": 0, "Path": "Root"},
{"ID": 2, "Name": "Ball", "ParentID": 1, "Path": "Root/Ball"},
{"ID": 3, "Name": "Foot", "ParentID": 2, "Depth": 2, "Path": "Root/Ball/Foot"}
]`
type Node struct {
ID int `json:"ID"`
Name string `json:"Name"`
ParentID int `json:"ParentID"`
Depth int `json:"Depth,omitempty"`
Path string `json:"Path"`
Child *Node `json:"Child,omitempty"`
}
// ConvertToTree 将 interface{}(预期为 []map[string]interface{})转换为树形结构根节点指针
func ConvertToTree(data interface{}) (*Node, error) {
// 步骤1:反序列化为 []Node
var nodes []Node
b, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("marshal input: %w", err)
}
if err := json.Unmarshal(b, &nodes); err != nil {
return nil, fmt.Errorf("unmarshal to []Node: %w", err)
}
// 步骤2:构建 ID → *Node 映射
m := make(map[int]*Node)
for i := range nodes {
m[nodes[i].ID] = &nodes[i]
}
// 步骤3:建立 Child 链接(跳过根节点自身 ParentID=0 的情况)
for _, node := range nodes {
if node.ParentID != 0 && m[node.ParentID] != nil {
m[node.ParentID].Child = &nodes[i] // 注意:此处需修正为正确索引
}
}
// ✅ 修正版:使用稳定索引避免闭包陷阱
for i := range nodes {
if nodes[i].ParentID != 0 && m[nodes[i].ParentID] != nil {
m[nodes[i].ParentID].Child = &nodes[i]
}
}
// 返回根节点(假设 Root 的 ID=1;实际中应根据 ParentID==0 动态查找)
for _, n := range nodes {
if n.ParentID == 0 {
return &n, nil
}
}
return nil, fmt.Errorf("no root node found (ParentID == 0)")
}
func main() {
// 示例:传入原始 interface{} 数据(如 HTTP 请求 body 或 DB 查询结果)
var raw interface{}
if err := json.Unmarshal([]byte(indata), &raw); err != nil {
panic(err)
}
root, err := ConvertToTree(raw)
if err != nil {
panic(err)
}
out, _ := json.MarshalIndent(root, "", " ")
fmt.Println(string(out))
}? 关键注意事项:
-
类型安全优于
interface{}:直接解析为[]Node比反复断言map[string]interface{}更健壮、高效; -
避免循环引用:确保
ParentID指向有效节点,否则m[pid]为nil,需判空; -
多根场景支持:若存在多个
ParentID == 0的节点,应返回[]*Node并分别构建子树; - 深度限制与环检测:生产环境建议加入递归深度检查或已访问 ID 集合,防止无限嵌套;
-
JSON 输出控制:使用
omitempty标签可使空Child字段不输出,保持 JSON 简洁。
该方案兼顾性能(O(n) 时间复杂度)、可维护性(清晰的结构体定义)与标准库兼容性,是 Go 中构建树形数据的推荐实践。

















