You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何将JSON数据转换为父子层级结构的树形视图?

从JSON动态生成Flutter树形视图方案

需求说明

我正在开发一款Flutter应用,需要从JSON数据生成树形视图,预期效果为多层级可展开/折叠的树形列表:根节点包含Root 1和Root 2,展开Root 1可看到Node 1.1和Node 1.2,展开Node 1.1又可看到Node 1.1.1和Node 1.1.2;展开Root 2可看到Node 2.1和Node 2.2。

数据结构

表格结构

ParentIDChildIDTitle
10Root 1
20Root 2
31Node 1.1
43Node 1.1.1
53Node 1.1.2
61Node 1.2
72Node 2.1
82Node 2.2

对应JSON数据

[
  {
    "ParentID": 1,
    "ChildID": 0,
    "Title": "Root 1"
  },
  {
    "ParentID": 2,
    "ChildID": 0,
    "Title": "Root 2"
  },
  {
    "ParentID": 3,
    "ChildID": 1,
    "Title": "Node 1.1"
  },
  {
    "ParentID": 4,
    "ChildID": 3,
    "Title": "Node 1.1.1"
  },
  {
    "ParentID": 5,
    "ChildID": 3,
    "Title": "Node 1.1.2"
  },
  {
    "ParentID": 6,
    "ChildID": 1,
    "Title": "Node 1.2"
  },
  {
    "ParentID": 7,
    "ChildID": 2,
    "Title": "Node 2.1"
  },
  {
    "ParentID": 8,
    "ChildID": 2,
    "Title": "Node 2.2"
  }
]

当前我通过硬编码MyNode列表生成树形视图:

static const List<MyNode> roots = <MyNode>[
  MyNode(
    title: 'Root 1',
    children: <MyNode>[
      MyNode(
        title: 'Node 1.1',
        children: <MyNode>[
          MyNode(title: 'Node 1.1.1'),
          MyNode(title: 'Node 1.1.2'),
        ],
      ),
      MyNode(title: 'Node 1.2'),
    ],
  ),
  MyNode(
    title: 'Root 2',
    children: <MyNode>[
      MyNode(title: 'Node 2.1'),
      MyNode(title: 'Node 2.2')
    ],
  ),
];

以下是从JSON动态生成树形结构的完整方案:


1. 定义数据模型

首先定义对应JSON的实体类,以及树形结构节点类:

import 'dart:convert';

// 对应JSON的原始数据模型
class NodeData {
  final int parentID;
  final int childID;
  final String title;

  NodeData({
    required this.parentID,
    required this.childID,
    required this.title,
  });

  // 从JSON解析为NodeData对象
  factory NodeData.fromJson(Map<String, dynamic> json) {
    return NodeData(
      parentID: json['ParentID'],
      childID: json['ChildID'],
      title: json['Title'],
    );
  }
}

// 树形结构节点类(调整为可变子节点列表)
class MyNode {
  final String title;
  final List<MyNode> children;

  MyNode({
    required this.title,
    List<MyNode>? children,
  }) : children = children ?? []; // 默认空列表,支持动态添加子节点
}

2. 实现平级数据转树形结构逻辑

通过Map快速查找父节点,建立层级关系:

List<MyNode> convertToTree(List<NodeData> nodeDataList) {
  final Map<int, MyNode> nodeMap = {};
  final List<MyNode> rootNodes = [];

  // 第一步:创建所有节点并存入Map(key为节点的ParentID,作为唯一标识)
  for (final data in nodeDataList) {
    nodeMap[data.parentID] = MyNode(title: data.title);
  }

  // 第二步:遍历建立父子关系
  for (final data in nodeDataList) {
    final currentNode = nodeMap[data.parentID]!;
    if (data.childID == 0) {
      // ChildID为0的是根节点,加入根列表
      rootNodes.add(currentNode);
    } else {
      // 找到父节点(父节点的ParentID等于当前节点的ChildID)
      final parentNode = nodeMap[data.childID];
      if (parentNode != null) {
        parentNode.children.add(currentNode);
      }
    }
  }

  return rootNodes;
}

// 解析JSON字符串为NodeData列表
List<NodeData> parseJsonString(String jsonStr) {
  final List<dynamic> jsonList = jsonDecode(jsonStr);
  return jsonList.map((json) => NodeData.fromJson(json)).toList();
}

3. 实现树形视图Widget

通过递归渲染ExpansionTile实现可展开/折叠的树形列表:

import 'package:flutter/material.dart';

class TreeView extends StatelessWidget {
  final List<MyNode> nodes;

  const TreeView({super.key, required this.nodes});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      shrinkWrap: true,
      itemCount: nodes.length,
      itemBuilder: (context, index) => _buildTreeNode(nodes[index]),
    );
  }

  Widget _buildTreeNode(MyNode node) {
    if (node.children.isEmpty) {
      // 叶子节点直接显示ListTile
      return ListTile(title: Text(node.title));
    } else {
      // 有子节点的节点用ExpansionTile实现展开/折叠
      return ExpansionTile(
        title: Text(node.title),
        children: node.children.map(_buildTreeNode).toList(),
      );
    }
  }
}

4. 实际使用示例

在页面中加载数据并渲染树形视图:

class TreePage extends StatelessWidget {
  // 模拟从数据库获取的JSON字符串
  final String jsonData = '''
[
  {"ParentID":1,"ChildID":0,"Title":"Root 1"},
  {"ParentID":2,"ChildID":0,"Title":"Root 2"},
  {"ParentID":3,"ChildID":1,"Title":"Node 1.1"},
  {"ParentID":4,"ChildID":3,"Title":"Node 1.1.1"},
  {"ParentID":5,"ChildID":3,"Title":"Node 1.1.2"},
  {"ParentID":6,"ChildID":1,"Title":"Node 1.2"},
  {"ParentID":7,"ChildID":2,"Title":"Node 2.1"},
  {"ParentID":8,"ChildID":2,"Title":"Node 2.2"}
]
''';

  TreePage({super.key});

  @override
  Widget build(BuildContext context) {
    // 解析并转换为树形结构
    final nodeDataList = parseJsonString(jsonData);
    final treeRoots = convertToTree(nodeDataList);

    return Scaffold(
      appBar: AppBar(title: const Text('树形视图')),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: TreeView(nodes: treeRoots),
      ),
    );
  }
}

注意事项

  • 确保JSON数据中每个节点的ChildID都能匹配到对应的父节点(即存在ParentID等于该ChildID的节点),避免出现无法归属的孤儿节点。
  • 如果数据量较大,可考虑优化递归渲染性能,比如使用懒加载或缓存节点状态。

内容的提问来源于stack exchange,提问作者Sulfy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:04:52