組織架構、選單權限、分類目錄等業務場景常需將扁平化資料表(含 id、parentId、name)轉換為樹狀 JSON。本文提供基於 fastjson 的通用樹構建元件,包含資料模型 Tree<T>、建構器 BuildTree 與測試範例,可直接整合至專案。
典型資料表欄位:
code / id:節點唯一標識pcode / parentId:父節點標識(根節點為空或 "0")name / text:顯示文字測試資料(21 筆,三層架構):
List<Test> tests = new ArrayList<>();
tests.add(new Test("0", "", "關於本人")); // 根
tests.add(new Test("1", "0", "技術學習")); // 1層
tests.add(new Test("2", "0", "興趣"));
tests.add(new Test("3", "1", "JAVA")); // 2層
tests.add(new Test("4", "1", "oracle"));
tests.add(new Test("5", "1", "spring"));
tests.add(new Test("6", "1", "springmvc"));
tests.add(new Test("7", "1", "fastdfs"));
tests.add(new Test("8", "1", "linux"));
tests.add(new Test("9", "2", "騎行"));
tests.add(new Test("10", "2", "吃喝玩樂"));
tests.add(new Test("11", "2", "學習"));
tests.add(new Test("12", "3", "String")); // 3層
tests.add(new Test("13", "4", "sql"));
tests.add(new Test("14", "5", "ioc"));
tests.add(new Test("15", "5", "aop"));
tests.add(new Test("16", "1", "等等"));
tests.add(new Test("17", "2", "等等"));
tests.add(new Test("18", "3", "等等"));
tests.add(new Test("19", "4", "等等"));
tests.add(new Test("20", "5", "等等"));
Tree<T>支援序列化為 JSON、攜帶任意業務屬性、狀態控制(展開/折疊、勾選):
public class Tree<T> {
private String id;
private String text;
private String state = "open"; // open / closed
private boolean checked = false;
private List<Map<String, Object>> attributes; // 擴充屬性
private List<Tree<T>> children = new ArrayList<>();
private String parentId;
private boolean isParent = false; // 是否為父節點
private boolean isChildren = false; // 是否有子節點
// Getter/Setter 略...
@Override
public String toString() {
return JSON.toJSONString(this); // fastjson 直接序列化
}
}
BuildTree.build()採用 雙層迴圈匹配 parentId,時間複雜度 O(n²),資料量 < 5000 時效能可接受;大資料量建議改用 Map<id, node> 優化至 O(n)。
public class BuildTree {
public static <T> Tree<T> build(List<Tree<T>> nodes) {
if (nodes == null) return null;
List<Tree<T>> topNodes = new ArrayList<>();
// 1. 找出所有頂層節點(parentId 為空)
for (Tree<T> child : nodes) {
String pid = child.getParentId();
if (pid == null || pid.isEmpty()) {
topNodes.add(child);
continue;
}
// 2. 在節點列表中尋找父節點
for (Tree<T> parent : nodes) {
if (parent.getId() != null && parent.getId().equals(pid)) {
parent.getChildren().add(child);
child.setParent(true);
parent.setChildren(true);
break;
}
}
}
// 3. 建立虛擬根節點(若有多個頂層節點)
Tree<T> root = new Tree<>();
if (topNodes.size() == 1) {
root = topNodes.get(0);
} else {
root.setId("-1");
root.setText("頂級節點");
root.setChecked(true);
root.setChildren(topNodes);
root.setChildren(true);
}
return root;
}
}
public class BuildTreeTester {
public static void main(String[] args) {
List<Tree<Test>> trees = new ArrayList<>();
for (Test t : tests) {
Tree<Test> node = new Tree<>();
node.setId(t.getId());
node.setParentId(t.getPid());
node.setText(t.getText());
trees.add(node);
}
Tree<Test> root = BuildTree.build(trees);
System.out.println(root); // 輸出 JSON
}
}
{
"id": "-1",
"text": "頂級節點",
"state": "open",
"checked": true,
"children": [
{
"id": "0",
"text": "關於本人",
"parentId": "",
"parent": false,
"children": [
{
"id": "1",
"text": "技術學習",
"parentId": "0",
"parent": true,
"children": [
{ "id": "3", "text": "JAVA", "children": [
{ "id": "12", "text": "String" },
{ "id": "18", "text": "等等" }
]},
{ "id": "4", "text": "oracle", "children": [
{ "id": "13", "text": "sql" },
{ "id": "19", "text": "等等" }
]},
{ "id": "5", "text": "spring", "children": [
{ "id": "14", "text": "ioc" },
{ "id": "15", "text": "aop" },
{ "id": "20", "text": "等等" }
]},
{ "id": "6", "text": "springmvc" },
{ "id": "7", "text": "fastdfs" },
{ "id": "8", "text": "linux" },
{ "id": "16", "text": "等等" }
]
},
{
"id": "2",
"text": "興趣",
"parentId": "0",
"parent": true,
"children": [
{ "id": "9", "text": "騎行" },
{ "id": "10", "text": "吃喝玩樂" },
{ "id": "11", "text": "學習" },
{ "id": "17", "text": "等等" }
]
}
]
}
]
}
// O(n) 版本:先建 Map<id, Tree>,再單層迴圈掛載
public static <T> Tree<T> buildOptimized(List<Tree<T>> nodes) {
Map<String, Tree<T>> map = new HashMap<>();
nodes.forEach(n -> map.put(n.getId(), n));
Tree<T> root = new Tree<>();
for (Tree<T> node : nodes) {
String pid = node.getParentId();
if (pid == null || pid.isEmpty() || !map.containsKey(pid)) {
root.getChildren().add(node); // 視為頂層
} else {
map.get(pid).getChildren().add(node);
map.get(pid).setChildren(true);
node.setParent(true);
}
}
return root.getChildren().size() == 1 ? root.getChildren().get(0) : root;
}
輸出 JSON 符合 jQuery EasyUI Tree、zTree、Bootstrap Treeview 等主流前端樹元件格式,僅需調整欄位名稱(如 text→name、children→nodes)即可直接綁定。