Flutter JSON Serializable
介绍
Flutter 关闭了 Dart 的反射特性,好处是构建产物更小,缺点是导致失去动态性。
JSON 序列化/反序列化是常用功能,最佳实践一般是将 JSON 与实体类相互映射。由于 Flutter 没了反射,没法进行动态映射。
社区给出的办法是将映射前置,通过引入代码生成器,在编译器生成好转化代码。json_serializable 是 Google 官方团队给出的最佳实践。
该库由两部分构成:
- json_annotation:注解
- json_serializable:代码生成器
本文主要分析 json_serializable 代码生成的一些核心过程,而非介绍 json_serializable 的入门使用,也不介绍 Flutter Build Runner 的使用方法。
执行代码生成的指令
flutter packages pub run build_runner watch
使用方法
首先对需要序列化的类添加 @JsonSerializable() 注解:
import 'package:json_annotation/json_annotation.dart';
part 'example.g.dart';
@JsonSerializable(nullable: false)
class Person {
final String firstName;
final String lastName;
final DateTime dateOfBirth;
Person({this.firstName, this.lastName, this.dateOfBirth});
factory Person.fromJson(Map<String, dynamic> json)
=> _$PersonFromJson(json);
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
其中:
- _$PersonFromJson 和 _$PersonToJson 需要手动按照惯用写法去写
- 上面的 part 也是要自己手写的
进行代码生成后,会产生 example.g.dart,其内容是自动生成的,具体为:
part of 'example.dart';
Person _$PersonFromJson(Map<String, dynamic> json) {
return Person(
firstName: json['firstName'] as String,
lastName: json['lastName'] as String,
dateOfBirth: DateTime.parse(json['dateOfBirth'] as String),
);
}
Map<String, dynamic> _$PersonToJson(Person instance)
=> <String, dynamic>{
'firstName': instance.firstName,
'lastName': instance.lastName,
'dateOfBirth': instance.dateOfBirth.toIso8601String(),
};
生成器体系
Generator
该类来自于 source_gen 库,作用,根据 Dart library source 生成 Dart 代码。
核心是 generate 方法,签名:
FutureOr<String?> generate(
LibraryReader library,
BuildStep buildStep) => null;
GeneratorForAnnotation
继承自 Generator。注释翻译:
对每个源码文件的顶层元素,调用 generateForAnnotatedElement。
所有被注解的元素处理完毕后,结果会被组装到一个单独的输出,其中重复的元素会被合并。
举例来说,下面的代码,将会匹配所有顶层元素注解为 @Deprecated 的代码文件:
class DeprecatedGenerator extends GeneratorForAnnotation<Deprecated> {
@override
Future<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep) async {
// Return a string representing the code to emit.
}
}
对于非顶层元素的注解,比如类成员或者 extension 扩展,在注解搜索时是不会被搜索的。如要需要搜索。首先需要类的顶层元素先被注解标注。然后在具体生成器中,再去搜索类内部的注解。
通过注释,对该类的功能了解透彻了,对该类内部的实现原理还未了解,以后再看。
派生 Generator
json_serializable 中的具体生成器,都是基于 GeneratorForAnnotation 派生的,具体有这些:
- JsonEnumGenerator:对应泛型 JsonEnum
- JsonLiteralGenerator:对应泛型 JsonLiteral
- JsonSerializable:对应泛型 JsonSerializable,常用
一共就 3 种,第三种是最常用的。
可以看到该库采用的设计模式,这种基于泛型匹配进行代码生成的模式,json_serializable 应该是开放可扩展的,也就是说,我们可以扩展出自己的生成器出来。
JsonEnumGenerator
顾名思义处理枚举的,先看 JsonEnum,枚举都定义在 json_annotation 中。
该类有两个属性,即两个配置项:
- alwaysCreate:如果给 true,会生成对应的 _$[enum name]EnumMap 结构。默认是 false 的。如果枚举需要在 JsonSerializable 里使用,那必须要设为 true。
- fieldRename:成员的命名风格,可以自动改成 kebab、snake、pascal
JsonEnum 看完了,接着看 JsonEnumGenerator 的生成方法。
原来还有一个派生类,JsonEnumGenerator,继承自 GeneratorForAnnotation<JsonEnum>,具体生成代码在 JsonEnumGenerator 里。
最终的生成代码位于 enumValueMapFromType。
JsonLiteralGenerator
JsonLiteral 是什么?看注释:
用于生成一个私有属性,它包含 JSON 文件中的内容。
这个注解可以作用于任何成员,但是通常用于顶层 getter。
举例来说,data.json 中的内容,在生成的文件中,会变成一个顶层的 final field _$glossaryDataJsonLiteral。
JsonLiteral 的功能:在编译时,将本地的 JSON 文件转换为代码,通过 getter 方法返回字典,用于运行时访问,这样也节省了运行时加载 JSON 文件的开销。示例:
@JsonLiteral('data.json')
Map get glossaryData => _$glossaryDataJsonLiteral;
JsonSerializable
最常用最复杂的就是 JsonSerializable 了。配置项:
配置项 | 类型 | 作用 |
---|---|---|
anyMap | bool | 如果为 true,Map 类型不会假定是 Map<String, dynamic>
而是会使用 JSON decode 返回的原始类型 |
checked | bool | 如果为 true,生成的 fromJson 方法中
包含对反序列化类型的额外验证 如果报错,会抛出 CheckedFromJsonException |
constructor | String | 指定一个命名构造方法
具体用法待补充 |
createFactory | bool | 如果为 true,会创建一个 _$ExampleFromJson 方法
用于搭配工厂构造方法使用 具体用法待补充 |
createToJson | bool | 如果为 true,会创建一个顶层的 toJson 方法 |
disallowUnrecognizedKeys | bool | 默认为 false,fromJson 会忽略 Map 中的不认识的 key |
explicitToJson | bool | 如果 true,生成的 toJson 方法将显式调用嵌套对象的 toJson 对象。
默认值是 false,嵌套对象用嵌套 Map 表示 如果为 ture:嵌套对象会被转成 Json String |
fieldRename | FieldRename | 属性命名规范 |
genericArgumentFactories | bool | 处理泛型相关 |
ignoreUnannotated | bool | 如果为 true,只有注解为 JsonKey 的会参与序列化 |
includeIfNull | bool | 生成器,是否在序列化结果中,输出为 null 的属性
如果为 true,所有的字段都会转到 JSON,尽管他们为 null |
具体代码生成逻辑,位于 JsonSerializableGenerator。
最终生成方法是 GeneratorHelper 的 generate。
序列化器
解析 Dart 字段,生成该字段的序列化/反序列化代码,这个逻辑在哪里?
在 json_serializable/lib/src/type_helpers 下。
针对不通的类型,每个类型有一个 Helper。