你還在循環(huán)嵌套評(píng)論列表?教你一招把評(píng)論數(shù)據(jù)變成 Tree 結(jié)構(gòu)!
在開(kāi)發(fā)評(píng)論系統(tǒng)時(shí),我們經(jīng)常會(huì)拿到“扁平化”的評(píng)論數(shù)據(jù),比如:
[
{"id": 1, "parent_id": null, "content": "頂級(jí)評(píng)論"},
{"id": 2, "parent_id": 1, "content": "這是對(duì)1的回復(fù)"},
{"id": 3, "parent_id": 1, "content": "我也是對(duì)1的回復(fù)"},
{"id": 4, "parent_id": 2, "content": "回復(fù)2"},
{"id": 5, "parent_id": null, "content": "另一個(gè)頂級(jí)評(píng)論"}
]
這種結(jié)構(gòu)通常來(lái)自數(shù)據(jù)庫(kù)查詢(xún),邏輯上是“父子關(guān)系”,但前端展示評(píng)論樓層時(shí)更希望拿到:
[
{
"id": 1,
"content": "...",
"children": [
{
"id": 2,
"content": "...",
"children": [
{
"id": 4,
"content": "...",
"children": []
}
]
},
{
"id": 3,
"content": "...",
"children": []
}
]
},
{
"id": 5,
"content": "...",
"children": []
}
]
也就是所謂的“樹(shù)形結(jié)構(gòu)”。那么問(wèn)題來(lái)了:
如何將一組帶有 parent_id 的平鋪數(shù)據(jù)結(jié)構(gòu),轉(zhuǎn)換成嵌套的樹(shù)形結(jié)構(gòu)?
思路拆解
思路非常清晰,但你得理解三個(gè)關(guān)鍵步驟:
- 建立映射表:將所有數(shù)據(jù)按 id 索引,方便查找每一條記錄。
- 遍歷數(shù)據(jù):根據(jù) parent_id 判斷每一條記錄的“父節(jié)點(diǎn)”。
- 掛載子節(jié)點(diǎn):如果找到了父節(jié)點(diǎn),把自己加入它的 children 列表;如果沒(méi)有,就作為“頂級(jí)節(jié)點(diǎn)”。
完整代碼實(shí)現(xiàn)(可直接運(yùn)行)
from typing import List, Dict, Any
import json
def build_comment_tree(comments: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
將扁平化評(píng)論列表轉(zhuǎn)換為樹(shù)形結(jié)構(gòu)
:param comments: 包含 id 和 parent_id 的評(píng)論列表
:return: 嵌套的評(píng)論樹(shù)
"""
# 初始化映射表,key 為 id,value 是評(píng)論內(nèi)容 + 空的 children
comment_map = {
comment["id"]: {**comment, "children": []}
for comment in comments
}
tree = []
for comment in comment_map.values():
parent_id = comment.get("parent_id")
if parent_id is None:
# 頂級(jí)評(píng)論,放到根列表中
tree.append(comment)
else:
# 子評(píng)論,找到它的父節(jié)點(diǎn)并掛載
parent = comment_map.get(parent_id)
if parent:
parent["children"].append(comment)
else:
# parent_id 找不到對(duì)應(yīng)評(píng)論,可選處理方式
tree.append(comment) # 或記錄異常日志
return tree
# 示例評(píng)論數(shù)據(jù)
if __name__ == "__main__":
comment_list = [
{"id": 1, "parent_id": None, "content": "頂級(jí)評(píng)論"},
{"id": 2, "parent_id": 1, "content": "這是對(duì)1的回復(fù)"},
{"id": 3, "parent_id": 1, "content": "我也是對(duì)1的回復(fù)"},
{"id": 4, "parent_id": 2, "content": "回復(fù)2"},
{"id": 5, "parent_id": None, "content": "另一個(gè)頂級(jí)評(píng)論"}
]
tree_result = build_comment_tree(comment_list)
# 打印格式化的結(jié)果
print(json.dumps(tree_result, ensure_ascii=False, indent=2))
運(yùn)行結(jié)果展示(部分)
[
{
"id": 1,
"parent_id": null,
"content": "頂級(jí)評(píng)論",
"children": [
{
"id": 2,
"parent_id": 1,
"content": "這是對(duì)1的回復(fù)",
"children": [
{
"id": 4,
"parent_id": 2,
"content": "回復(fù)2",
"children": []
}
]
},
{
"id": 3,
"parent_id": 1,
"content": "我也是對(duì)1的回復(fù)",
"children": []
}
]
},
{
"id": 5,
"parent_id": null,
"content": "另一個(gè)頂級(jí)評(píng)論",
"children": []
}
]
常見(jiàn)應(yīng)用場(chǎng)景
- 評(píng)論/回復(fù)樓層嵌套顯示
- 組織架構(gòu)樹(shù)(部門(mén)-員工)
- 欄目分類(lèi)目錄(如博客、CMS)
- 后臺(tái)權(quán)限菜單(父子菜單結(jié)構(gòu))
可拓展功能建議
- 加入排序字段(比如按時(shí)間或點(diǎn)贊數(shù)排序)
- 限制最大層級(jí),避免無(wú)限遞歸(防止性能問(wèn)題)
- 構(gòu)建 ID → 子節(jié)點(diǎn) 的倒排索引,提高構(gòu)建效率(適合超大數(shù)據(jù))
小結(jié)
將扁平結(jié)構(gòu)轉(zhuǎn)為樹(shù)形結(jié)構(gòu),在評(píng)論系統(tǒng)、后臺(tái)管理系統(tǒng)中非常常見(jiàn),這段代碼簡(jiǎn)潔高效,可直接應(yīng)用于實(shí)際項(xiàng)目。
建議你將這段邏輯封裝成一個(gè)工具模塊或類(lèi),在多個(gè)項(xiàng)目中復(fù)用,省時(shí)省力!