移动应用程序通常需要与 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)能够提供精准的代码提示,帮助开发者快速查找和使用对象的属性或方法,而不必手动记住所有键名。