
本文介绍如何在 go 中高效构建未知深度的分类树,通过分层查询与逆向组装策略,将扁平数据库记录转换为带 children 字段的递归结构,并直接输出标准 json 树形数组。
本文介绍如何在 go 中高效构建未知深度的分类树,通过分层查询与逆向组装策略,将扁平数据库记录转换为带 children 字段的递归结构,并直接输出标准 json 树形数组。
在处理具有父子关系的分类数据(如商品类目、组织架构、菜单导航)时,数据库通常以扁平方式存储(ID, Name, ParentID),而前端或 API 消费方往往需要嵌套的树形结构(如 JSON 数组含 Children 字段)。由于层级深度未知,递归查询(N+1 问题)或单次全量加载后内存递归组装均非最优解。本文采用「分层拉取 + 逆向挂载」策略,在保证性能与内存可控的前提下,实现优雅的树形构建。
✅ 核心思路:BFS 分层 + 自底向上组装
-
首层查询:获取所有
ParentID = 0的根节点; -
迭代查询:用上一层所有 ID 作为
ParentID条件,查询下一层子节点,直至无新数据; -
分层存储:将每层结果存入
[][]Category(store[i]表示第i层节点); -
逆向挂载:从最深层开始,逐层将子节点追加到其父节点的
Children切片中。
该方法避免了深度优先递归带来的栈溢出风险,也规避了反复遍历全量数据的低效问题,时间复杂度为 O(n),空间复杂度为 O(n)。
? 结构定义与完整实现
type Category struct {
ID int `json:"id"`
Name string `json:"name"`
ParentID int `json:"parent"`
Children []Category `json:"children"`
}
func GetCategories(db *gorm.DB) []Category {
var categories []Category
var store [][]Category // store[0] = root layer, store[1] = level-2, ...
// Step 1: Fetch root categories (ParentID = 0)
db.Where("parent_id = ?", 0).Find(&categories)
if len(categories) == 0 {
return nil
}
store = append(store, categories)
// Step 2: BFS layer-by-layer fetch
for len(categories) > 0 {
var ids []int
for _, cat := range categories {
ids = append(ids, cat.ID)
}
categories = []Category{}
db.Where("parent_id IN (?)", ids).Find(&categories)
if len(categories) > 0 {
store = append(store, categories)
}
}
// Step 3: Attach children from bottom up
for layer := len(store) - 1; layer > 0; layer-- {
for _, child := range store[layer] {
for i := range store[layer-1] {
if store[layer-1][i].ID == child.ParentID {
store[layer-1][i].Children = append(store[layer-1][i].Children, child)
break // assume unique parent match
}
}
}
}
return store[0]
}
⚠️ 注意事项与优化建议
-
GORM 版本兼容性:
IN (?)语法在 GORM v2 中需确保使用db.Where("parent_id IN ?", ids)(v2 推荐写法),v1 可保留原写法; -
空值安全:实际项目中建议对
db.Find()添加错误检查(如if err != nil { /* handle */ }); -
性能增强:若分类量极大(>10k),可考虑预加载全部数据后内存分组(
map[int][]Category按ParentID索引),再递归构建,减少 DB 往返; - JSON 输出:配合 HTTP handler 直接编码,注意设置响应头:
func Output(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if err := json.NewEncoder(w).Encode(GetCategories(db)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
✅ 最终效果(符合预期 JSON 格式)
调用 GetCategories() 将返回如下结构的 []Category,经 json.Encoder 序列化后即为标准树形 JSON:
[
{"id":1,"name":"Car","parent":0,"children":[]},
{"id":2,"name":"Boat","parent":0,"children":[
{"id":4,"name":"Fast","parent":2,"children":[]},
{"id":5,"name":"Slow","parent":2,"children":[
{"id":6,"name":"ExtraSlow","parent":5,"children":[]}
]}
]},
{"id":3,"name":"Rocket","parent":0,"children":[]}
]
此方案兼顾可读性、可维护性与生产环境性能,是 Go 生态中处理无限层级树结构的经典实践。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











