FlutterJSON
JSON和序列化 - Flutter中文网
移动应用程序通常需要与 Web 服务器通信或存储结构化数据,而 JSON 是最常用的数据交换格式之一。
而在 Flutter 开发中,我们主要会用到两种 JSON 序列化方式:
-
手动序列化
-
代码生成自动序列化
1. 哪种 JSON 序列化方法适合我?
1.1 小项目:手动序列化
特点
-
优点:
-
使用 Dart 内置的
dart:convert
库,无需额外依赖。 -
对于简单 JSON 或模型较少的小项目非常方便。
-
-
缺点:
-
直接使用
JSON.decode()
得到的是Map<String, dynamic>
,类型信息丢失。 -
编译器无法捕捉字段名称的拼写错误,容易导致运行时异常。
-
当模型增多或嵌套结构复杂时,手动编写转换代码会变得冗长且易出错。
-
示例
内联序列化JSON
直接使用 JSON.decode()
解码:
import 'dart:convert'; void main() { String jsonString = '{"name": "John Smith", "email": "john@example.com"}'; Map<String, dynamic> user = json.decode(jsonString); print('Howdy, ${user['name']}!'); print('We sent the verification link to ${user['email']}!'); }
在模型类中封装 JSON 序列化
通过创建模型类,将序列化逻辑封装到模型内部:
class User { final String name; final String email; User(this.name, this.email); // 从 JSON 构造 User 实例 User.fromJson(Map<String, dynamic> json) : name = json['name'], email = json['email']; // 将 User 实例转换为 JSON Map<String, dynamic> toJson() => { 'name': name, 'email': email, }; }
使用示例:
import 'dart:convert'; void main() { String jsonString = '{"name": "John Smith", "email": "john@example.com"}'; Map<String, dynamic> userMap = json.decode(jsonString); var user = User.fromJson(userMap); print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}!'); // 序列化时,JSON.encode() 会自动调用 toJson 方法 String encodedJson = json.encode(user); print(encodedJson); }
1.2 大中型项目:使用代码生成自动序列化
背景说明
-
当项目中涉及多个 JSON 模型时,手动编写
fromJson
和toJson
方法容易出错且维护成本高。 -
Flutter 中不存在像 Gson、Jackson、Moshi 这样的库,因为它们依赖运行时反射,而 Flutter 禁用了反射以支持 tree shaking,从而优化应用体积。
-
类似 dartson 的库也依赖运行时反射,所以在 Flutter 中不可用。
代码生成的优势
-
自动生成:通过工具自动生成序列化代码,避免手写重复代码。
-
编译时检查:拼写错误等问题可以在编译期捕获,降低运行时异常风险。
-
维护方便:当模型发生变化时,只需重新生成代码即可,无需手动修改序列化逻辑。
2. 使用 json_serializable 自动生成 JSON 序列化代码
2.1 设置项目依赖
在项目的 pubspec.yaml
中添加以下依赖项:
dependencies: # 其他依赖... json_annotation: ^2.0.0 dev_dependencies: # 其他开发依赖... build_runner: ^1.0.0 json_serializable: ^2.0.0
然后在项目根目录运行:
flutter packages get
2.2 创建模型类
使用 json_serializable
注解生成序列化代码。示例 User 模型如下:
import 'package:json_annotation/json_annotation.dart'; // 生成的代码文件,运行 build_runner 后会自动生成 part 'user.g.dart'; @JsonSerializable() class User { String name; String email; User(this.name, this.email); // 通过生成的 _$UserFromJson 反序列化 factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); // 通过生成的 _$UserToJson 序列化 Map<String, dynamic> toJson() => _$UserToJson(this); }
自定义 JSON 键名
若 API 返回的 JSON 键名与模型属性名不一致,可以使用 @JsonKey
进行映射,例如:
@JsonKey(name: 'registration_date_millis') final int registrationDateMillis;
2.3 运行代码生成器
有两种方式运行代码生成器,为模型自动生成序列化代码:
一次性生成
在项目根目录下运行:
flutter packages pub run build_runner build
持续生成(Watch 模式)
启动观察者模式,自动监视文件变化并生成代码:
flutter packages pub run build_runner watch
运行后,生成文件 user.g.dart
将包含所有序列化和反序列化的实现代码。
2.4 使用自动生成的模型
生成后的使用方法与手动方式类似:
import 'dart:convert'; void main() { String jsonString = '{"name": "John Smith", "email": "john@example.com"}'; Map<String, dynamic> userMap = json.decode(jsonString); var user = User.fromJson(userMap); print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}!'); String encodedJson = json.encode(user); print(encodedJson); }
实践
原代码
if (response.statusCode == 200) { final responseData = jsonDecode(response.body); List<dynamic> items = responseData['data']['items']; List<String> photoUidsList = items .where((item) => item['type'] == 'photo') .map((item) => item['uid'] as String) .toList();//获取uid setState(() { photoUids = photoUidsList; }); } else { throw Exception('Failed to load photos'); } }
依据本篇文章来改进
加入
// 定义 Item 模型类,通过 fromJson 方法实现 JSON 数据解析 class Item { final String type; final String uid; Item({required this.type, required this.uid}); factory Item.fromJson(Map<String, dynamic> json) { return Item( type: json['type'] as String, uid: json['uid'] as String, ); } }
原有部分替换为
class Item { final String type; final String uid; Item({required this.type, required this.uid}); //构造函数,使用命名参数,并通过 required 强制必须传入两个属性值。 factory Item.fromJson(Map<String, dynamic> json) { return Item( type: json['type'] as String, uid: json['uid'] as String, ); } }//这个工厂构造函数 fromJson接收一个 Map<String, dynamic> 格式的 JSON 数据,并将其中的 type 和 uid 字段转换为 Item 对象。通过这种方式,可以轻松将 JSON 数据转换为 Dart 模型实例 if (response.statusCode == 200) { final responseData = jsonDecode(response.body); //使用 jsonDecode(response.body) 将服务器返回的 JSON 格式字符串转换成 Dart 的数据结构(通常为 Map 或 List) List<dynamic> itemsJson = responseData['data']['items']; //取出嵌套在 data 内的 items 列表,并将其存储到变量 itemsJson 中 // 将 JSON 数据映射为 Item 实例列表 List<Item> items = itemsJson .map<Item>((json) => Item.fromJson(json)) .toList(); // 使用模型类的属性进行筛选和映射 List<String> photoUid = items .where((item) => item.type == 'photo') .map((item) => item.uid) .toList(); // 更新状态,刷新 UI setState(() { photoUids = photoUid;
总结
-
手动序列化:适合简单或模型较少的小项目,使用
dart:convert
和自定义模型类实现;缺点是容易出错且缺少编译时检查。 -
代码生成自动序列化:适合中大型项目,通过
json_serializable
自动生成序列化代码,提高类型安全和开发效率;初期需要进行一些配置和代码生成步骤。 -
Flutter 中不支持使用运行时反射的库(如 Gson/Jackson/Moshi),因此推荐使用基于代码生成的方案。
把 JSON 数据转换成静态类型明确的 Item 对象后,IDE 就能知道 item 的具体类型,从而提供属性和方法的自动补全,原理如下:
-
静态类型系统 Item 是一个明确的类,定义了 type 和 uid 两个属性。当你调用 item. 时,IDE 根据 Item 类的定义知道有哪些可用属性和方法,从而进行自动补全。
-
编译时类型检查 使用工厂构造函数 fromJson 将 Map<String, dynamic> 转换成 Item 对象后,编译器就能在编译期检查属性的正确性,减少因拼写错误或类型错误引起的运行时异常。
-
代码提示功能 静态类型信息使得 IDE(如 VS Code 或 Android Studio)能够提供精准的代码提示,帮助开发者快速查找和使用对象的属性或方法,而不必手动记住所有键名。