分类
联系方式
  1. 新浪微博
  2. E-mail

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。

网络资源