在客户端开发的过程中,JSON的序列化与反序列化是一个常见的操作,通常我们的API服务端会返回JSON格式的数据。此时我们就需要解析JSON并转化为客户端中的对象。在客户端开发的初期,我们需要人工解析每一个字段,类似于很多obj.id = json[‘id’],obj.name = json[‘name’]…这样的代码。
遥想当年笔者刚接触Android开发的时候,刚开始并没有使用Gson,Jackson等自动解析工具,所以就出现了全体成员编写解析JSON代码的壮观景象!
手动解析JSON的缺点是:一、效率低下,我们浪费了大量的时间在写一些枯燥无比的代码,这样简直就是浪费生命。二、容易出错,我们谁都不能保证我们在敲代码的过程中不会敲错一个字母,于是就会出现解析异常,程序崩溃等BUG。
在Flutter发布的初期,并没有一个成熟的方式解析JSON,好在目前我们有了’json_serializable‘这个插件。通过这个插件,我们可以生成所有序列化/反序列化的方法。本文我们就来介绍如何生成解析JSON的方法。
一、添加插件依赖
在我们Flutter工程的pubspec.yaml中添加如下:
1 2 3 4 5 6 7 8 9
| # 其他的依赖也添加到这里 dependencies: analyzer: 0.38.2 json_annotation: ^3.0.0 # 这里是开发期间使用的插件 dev_dependencies: build_runner: ^1.6.7 json_serializable: ^3.2.2
|
添加完后在工程根目录下执行’flutter pub get’,或者在Android Studio中点击’Packages get’按钮,下载插件。
注意这里指定版本的依赖’analyzer: 0.38.2’,加入这个是因为在json_annotation的最新3.x版本存在一些问题,会导致生成解析JSON错误,如果后续解决了这个问题,可以把这个依赖删除。
二、定义Model
下面我们定义一个Model,这里我们以User举例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import 'package:json_annotation/json_annotation.dart'; import 'package:x_marriage/core/models/region.dart';
part 'user.g.dart';
@JsonSerializable(explicitToJson: true) class User {
int id; String name; Region address;
@JsonKey(name: 'login_time') DateTime loginTime;
User();
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this); }
|
定义完后,我们会发现AS会提示错误:

原因很简单,因为文件”user.g.dart”还有下面的方法我们还没有生成。不过不用担心,下面我们执行生成命令后,这些错误都会消失。
注意这里:
1
| @JsonSerializable(explicitToJson: true)
|
这个注解的意思是在我们User中有嵌套对象时,调用User的toJson()方法时,也会调用嵌套对象的toJson()方法。比如上面的User中的address。
还有一点我们需要注意:
1 2
| @JsonKey(name: 'login_time') DateTime loginTime;
|
当API返回的字段与我们的字段不一致时,比如客户端使用驼峰的命名方式,服务器采用下划线的命名方式,我们就可以使用‘@JsonKey’来指定解析特定字段。
三、执行命令,生成解析代码
在工程的根目录下执行:
1
| flutter pub run build_runner build
|
执行后我们会看到日志信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| [INFO] Generating build script... [INFO] Generating build script completed, took 481ms
[INFO] Initializing inputs [INFO] Reading cached asset graph... [INFO] Reading cached asset graph completed, took 140ms
[INFO] Checking for updates since last build... [INFO] Checking for updates since last build completed, took 1.1s
[INFO] Running build... [INFO] 1.1s elapsed, 0/16 actions completed. [INFO] 2.3s elapsed, 0/16 actions completed. [INFO] 3.4s elapsed, 0/16 actions completed. [INFO] 4.5s elapsed, 0/16 actions completed. [INFO] 10.4s elapsed, 1/16 actions completed. [INFO] 11.6s elapsed, 3/18 actions completed. [INFO] 12.8s elapsed, 17/29 actions completed. [INFO] 13.8s elapsed, 53/65 actions completed. [INFO] 15.0s elapsed, 65/79 actions completed. [INFO] 16.0s elapsed, 87/97 actions completed. [INFO] Running build completed, took 16.4s
[INFO] Caching finalized dependency graph... [INFO] Caching finalized dependency graph completed, took 129ms
[INFO] Succeeded after 16.5s with 24 outputs (118 actions)
|
执行完后我们会发现工程中多了刚才User中缺少的文件:

在这个文件中,之前我们报错的方法’$UserFromJson’和 ‘$UserToJson’也都生成好了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
part of 'user.dart';
User _$UserFromJson(Map<String, dynamic> json) { return User() ..id = json['id'] as int ..name = json['name'] as String ..address = json['address'] == null ? null : Region.fromJson(json['address'] as Map<String, dynamic>) ..loginTime = json['login_time'] == null ? null : DateTime.parse(json['login_time'] as String); }
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{ 'id': instance.id, 'name': instance.name, 'address': instance.address?.toJson(), 'login_time': instance.loginTime?.toIso8601String(), };
|
这样解析JSON的代码就生成完毕了!Happy Coding!
其他一些问题的探讨
模板类
简单来说,生成自动解析JSON的代码就两步:一、定义Model。二、执行生成命令。非常简单。
但是我们发现定义Model时略显啰嗦。需要定义part …,和这两个方法’$UserFromJson’和 ‘$UserToJson’,相信没人喜欢每次都写这样的代码!笔者的做法是定义一个模板文件,每次需要新建Model类时就拷贝一下,改一下文件名和类名,然后只需要定义其属性即可。

代码也放上,如果有人喜欢,可以也定义这样一个类在工程中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import 'package:json_annotation/json_annotation.dart';
part 'template_model.g.dart';
@JsonSerializable(explicitToJson: true) class TemplateModel { String value;
TemplateModel();
factory TemplateModel.fromJson(Map<String, dynamic> json) => _$TemplateModelFromJson(json);
Map<String, dynamic> toJson() => _$TemplateModelToJson(this); }
|
为什么需要生成代码?
比如其他的一些序列化/反序列化的库,比如Gson、Jackson等,我们不需要生成代码,直接可以进行解析,而在Flutter中,我们却需要用生成代码的方式,为什么呢?
因为Flutter禁用了Dart中的反射机制。上面的库中由于使用了反射机制,所以在运行时可以动态地解析Json字段并赋值。
为什么Flutter要禁用反射机制呢?因为在构建阶段Flutter可以知道哪些代码被使用了,哪些代码没有被使用,在构建时可以删除掉没用使用的代码,从而减小安装包的体积。如果使用了反射,则很难去发现那些未被使用的代码。
本文原创地址为:https://www.examplecode.cn/2019/10/06/flutter-json-serializable/
转载请注明出处。