Flutter自动解析JSON数据(序列化/反序列化)

在客户端开发的过程中,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
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'user.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

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可以知道哪些代码被使用了,哪些代码没有被使用,在构建时可以删除掉没用使用的代码,从而减小安装包的体积。如果使用了反射,则很难去发现那些未被使用的代码。

本文原创地址为:http://examplecode.cn/2019/10/06/flutter-json-serializable/
转载请注明出处。