Flutter解析嵌套JSON生成带子菜单的ListTile组件问题
看起来你已经搭好了主菜单的基础框架,核心卡壳在怎么把嵌套的subMenu数据解析并渲染成ExpansionTile的子ListTile对吧?另外你问有没有更简单的方法或可用的包,我也会一起说明~
一、先解决当前代码的核心问题:解析subMenu并生成子ListTile
你的代码里已经定义了SubMenuItem类,但还没把JSON里的subMenu数据转成这个类的实例,而且MenuItem里的subMenu字段类型是List,最好改成List<SubMenuItem>更规范。我们一步步改:
1. 完善数据模型类
先给MenuItem和SubMenuItem添加工厂方法,方便从JSON数据转换:
class MenuItem { final String icon; final String menu; final String subtitle; final List<SubMenuItem> subMenu; MenuItem(this.icon, this.menu, this.subtitle, this.subMenu); // 从JSON转成MenuItem的工厂方法 factory MenuItem.fromJson(Map<String, dynamic> json) { // 把subMenu的JSON数组转成SubMenuItem列表 List<SubMenuItem> subMenuList = []; if (json['subMenu'] != null) { subMenuList = (json['subMenu'] as List) .map((subJson) => SubMenuItem.fromJson(subJson)) .toList(); } return MenuItem( json['icon'], json['menu'], json['subtitle'], subMenuList, ); } } class SubMenuItem { final String icon; final String title; final String jsonFile; SubMenuItem(this.icon, this.title, this.jsonFile); // 从JSON转成SubMenuItem的工厂方法 factory SubMenuItem.fromJson(Map<String, dynamic> json) { return SubMenuItem( json['icon'], json['title'], json['jsonFile'], ); } }
2. 简化JSON解析方法newGetCategory
原来的循环可以简化,直接用上面的工厂方法:
Future<List<MenuItem>> newGetCategory() async { String jsonString = await loadJsonFromAssets(menuDB); final jsonResponse = json.decode(jsonString); // 直接把每个category项转成MenuItem实例 List<MenuItem> menuList = (jsonResponse['categories'] as List) .map((item) => MenuItem.fromJson(item)) .toList(); return menuList; }
这里给方法指定返回类型Future<List<MenuItem>>,能让FutureBuilder的snapshot.data类型更明确,避免类型警告。
3. 创建子菜单展示组件DisplaySubMenuList
这个组件接收List<SubMenuItem>,生成对应的ListTile:
class DisplaySubMenuList extends StatelessWidget { final List<SubMenuItem> subMenuList; const DisplaySubMenuList({super.key, required this.subMenuList}); @override Widget build(BuildContext context) { return Column( children: subMenuList.map((subItem) { return ListTile( leading: Image.asset(subItem.icon), // 本地图标示例,可根据实际情况调整 title: Text(subItem.title), onTap: () { // 处理子菜单点击事件,比如跳转或加载对应jsonFile print('点击子菜单:${subItem.title},对应文件:${subItem.jsonFile}'); }, ); }).toList(), ); } }
4. 修改DisplayMenuList组件,渲染子菜单
把ExpansionTile的children替换成我们的子菜单组件:
class DisplayMenuList extends StatelessWidget { final MenuItem aMenuItem; const DisplayMenuList({super.key, required this.aMenuItem}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: ExpansionTile( leading: Image.asset(aMenuItem.icon), // 主菜单图标,可按需调整 title: Text(aMenuItem.menu), subtitle: Text(aMenuItem.subtitle), // 替换成子菜单组件 children: [ DisplaySubMenuList(subMenuList: aMenuItem.subMenu), ], ), ); } }
5. 完善FutureBuilder的类型提示
在_ScreenCategoryState里,把late Future newGeneratedCategory;改成明确的类型:
late Future<List<MenuItem>> newGeneratedCategory;
二、更简单的解析方法:使用json_serializable包
如果你的JSON结构后续会更复杂,手动写fromJson会很繁琐,推荐使用官方生态的json_serializable包,它能自动生成JSON序列化/反序列化代码:
1. 添加依赖到pubspec.yaml
dependencies: json_annotation: ^4.8.1 dev_dependencies: build_runner: ^2.4.4 json_serializable: ^6.7.1
2. 给模型类添加注解
import 'package:json_annotation/json_annotation.dart'; part 'menu_item.g.dart'; @JsonSerializable() class MenuItem { final String icon; final String menu; final String subtitle; final List<SubMenuItem> subMenu; MenuItem(this.icon, this.menu, this.subtitle, this.subMenu); factory MenuItem.fromJson(Map<String, dynamic> json) => _$MenuItemFromJson(json); Map<String, dynamic> toJson() => _$MenuItemToJson(this); } @JsonSerializable() class SubMenuItem { final String icon; final String title; final String jsonFile; SubMenuItem(this.icon, this.title, this.jsonFile); factory SubMenuItem.fromJson(Map<String, dynamic> json) => _$SubMenuItemFromJson(json); Map<String, dynamic> toJson() => _$SubMenuItemToJson(this); }
3. 运行命令生成代码
flutter pub run build_runner build
之后就可以直接用MenuItem.fromJson解析,完全不用手动写转换逻辑,非常适合复杂嵌套JSON。
最后测试
改完之后运行代码,就能看到主菜单展开后显示对应的子菜单ListTile,点击子菜单也会触发你设置的逻辑~
内容来源于stack exchange

