likes
comments
collection
share

Flutter json_serializable 解析 JSON 使用详解

作者站长头像
站长
· 阅读数 10

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

背景

目前我们项目中,解析 JSON 已经全部使用官方推荐的 json_serializable 方案,为了帮助团队熟悉 json_serializable,之前已经写过两篇相关文章:

Flutter 使用 json_serializable 解析 JSON 支持泛型

Flutter 使用 json_serializable 解析 JSON 时支持枚举 Enum 类型

并且我们还开发了一款 Android Studio/IDEA 插件 FlutterJsonToDart,帮助我们开发过程中生成 Model 类:

Flutter 使用 json_serializable 解析 JSON 最佳方案

以上文章基本能够解决我们大部分需求,不过在遇到一些特殊需求时,还是要查看官方文档。在查看官方文档时,如果不是自己动手实践一下,有时还是很难理解各个类或者字段的意思。

这篇文章主要是我参照 json_serializable 官方文档,并结合一些测试,做的一些记录,方便后续查阅,发布出来,也希望能够帮助到大家。

json_serializable 库提供的有 5 个注解类 JsonSerializable JsonKey JsonValue JsonLiteral JsonEnum,下面分别介绍一下这几个类和他们的属性

JsonSerializable

nullable

已经废弃了,即便赋值了,也会被忽略

anyMap

Flutter json_serializable 解析 JSON 使用详解

默认值为 false,如果设置为 true,生成的 _$HelloFromJson() 方法中的 json 是 Map 类型,如果不设置或者为 false ,是 Map<String,dynamic> 类型

这个参数一般情况下我们使用不到,如果没有特殊情况,不要使用,官方文档说他会增加代码大小

checked

Flutter json_serializable 解析 JSON 使用详解

默认值为 false,如果设置为 true,会对参数进行类型检测,比如 model 定义的类型是 int,服务端返回 String,解析时,发现类型不一致,就会报 CheckedFromJsonException 异常

这个参数感觉也不常用,因为有时会存在相同字段名,服务端在不同状态返回不同类型的情况,这时一般需要我们使用 JsonKey.fromJson 进行自定义解析

constructor

Flutter json_serializable 解析 JSON 使用详解

Flutter json_serializable 解析 JSON 使用详解

默认值为 "",如果设置了值,_$HelloFromJson 会使用该值对应的命名构造方法

使用场景:

我们可以通过配置 constructor: '_'

把构造方法修改为私有构造方法,防止外部调用

当 createFactory 为 false 时,由于不会生成 _$HelloFromJson 方法了,那么 constructor 配置会变得没有意义

createFactory

默认为 true,如果为 false ,就不会生成 _$HelloFromJson 方法。

createToJson 参数是用来控制是否生成 _$HelloToJson(this) 方法的。

没有想到这个参数有什么使用场景

createFieldMap

Flutter json_serializable 解析 JSON 使用详解 默认为 false,如果为 true, 会把所有的属性值生成一个 map,是为了方便其它代码生成工具使用的,比如 fieldRename.

createPerFieldToJson

默认值为 false, 如果设置为 true,会生成一个抽象类,并为每个属性生成一个静态方法。

暂时不清楚有什么用途

createToJson

默认为 true,如果设置为 false,就不生成 _$ExampleToJson(this) 方法了。

在我们项目中,我们发现 toJson 方法只有几种情况会使用到,大部分 model 类没有必要生成 toJson 方法,我们采用的做法是在全局配置文件 build.yaml 中把这个默认值修改为 false。

如果某个 model 类需要生成 toJson 方法,那么再单独配置 @JsonSerializable(createToJson: true)

disallowUnrecognizedKeys

默认为 false,是否禁止无法识别的 key,如果为 true,遇到了新的 key,会报 UnrecognizedKeysException 异常,

不建议配置这个属性

explicitToJson

Flutter json_serializable 解析 JSON 使用详解

默认为 false,这个属性是指生成 toJson 方法时,是否调用嵌套的子对象的 toJson 方法

explicitToJson 为 false,生成的数据为:

{name: 张三, type: 0, status: 0, child: Instance of \'Child\'}

explicitToJson 为 true,生成的数据为:

{name: 张三, type: 0, status: 0, child: {name: 你好}}

toJson 方法使用场景,1、向原生传值;2、数据本地持久化,涉及到这些场景时,需要设置 explicitToJson 为 true

fieldRename

默认 FieldRename.none,可以对 model 中的字段进行重命名

有以下几个值:

  • none:服务端返回的是啥,就用啥

  • kebab:把中划线 json key,重命名为驼峰,比如 kebab-case 会命名为 kebabCase

  • snake:把下划线 json key,重命名为驼峰,比如 snake_case 会命名为 snakeCase

  • pascal:把首字母大写的 json key 重命名为驼峰,比如 PascalCase 会重命名为 pascalCase

  • screamingSnake:把大写的带下划线的 json key 重命名为驼峰,比如 SCREAMING_SNAKE_CASE 会被重命名为 screamingSnakeCase

JsonKey.name 也可以修改字段名称,优先级 JsonKey.name 大于 fieldRename

如果后端接口比较规范,可以在 build.yaml 文件中进行全局配置,这样就不需要每个类,或者每个字段进行单独配置了

genericArgumentFactories

默认为 false,如果为 true, 开启泛型支持,具体使用参考这篇文章

Flutter 使用 json_serializable 解析 JSON 支持泛型

ignoreUnannotated

默认为 false,如果为 true,只有使用 JsonKey 注释的字段,才会生成代码,

和 JsonKey.includeToJson 和 JsonKey.includeFromJson 一样,

感觉没有使用场景

includeIfNull

Flutter json_serializable 解析 JSON 使用详解

默认为 true,生成 toJson 时是否过滤掉值为 null 的字段

优先级小于 JsonKey.includeIfNull

Hello hello = Hello(name: '张三', type: null, status: null);

如果 includeIfNull 为 true,toJson 生成的数据为

{name: 张三, type: null, status: null}

如果 includeIfNull 为 false,toJson 生成的数据为

{name: 张三}

converters

默认 []

设置一个 JsonConverter 集合,JsonConverter 是用来自定义解析逻辑的,它是一个抽象类,有 fromJson 和 toJson 两个方法,和 JsonKey.fromJson 和 JsonKey.toJson 一样,都是为了实现自定义解析逻辑的,它的好处是可以简化代码,方便复用

JsonKey

nullable

已经废弃了,即便赋值了,也会被忽略

defaultValue

默认值,如果 json 中没有 model 定义字段对应的 key,或者对应 key的 值为 null,这时会使用默认值,这个属性比较常用

在我们项目中,基本全部的字段设置了默认值,

  • Int 类型一般默认设置为 0
  • String 类型一般默认值设置为 ''
  • List 类型一般默认设置为 []
  • Map 类型一般默认值设置为 {}
  • 对象类型,默认值目前还没有找到好的方法,暂时采用的方法是:在 ExampleModel 中建一个叫做 defaultValue 的命名构造方法,然后设置默认值时使用 ExampleModel.defaultValue,示例如下:
@JsonKey(defaultValue:ExampleModel.defaultValue) final ExampleModel example;

其它类型,默认值示例:

@JsonSerializable()
class Hello {
  @JsonKey(defaultValue: '')
  final String name;
  @JsonKey(defaultValue: 0)
  final int type;
  @JsonKey(defaultValue: 0)
  final int status;

  Hello({
    required this.name,
    required this.type,
    required this.status,
  });

  factory Hello.fromJson(Map<String, dynamic> json) => _$HelloFromJson(json);

  Map<String, dynamic> toJson() => _$HelloToJson(this);
}
test('hello json 1', () {
  String json = """
  {
    "name": null,
    "type": null
  }
  """;
  Hello hello = Hello.fromJson(jsonDecode(json));
  expect(hello.name, '');
  expect(hello.type, 0);
  expect(hello.status, 0);
});

可以看到,json 中 name 和 type 的值都为 null,通过 JsonSerializable 解析后,name 使用了设置的默认值 '',type 使用了设置的默认值 0,json 中没有 status 这个 key,model 也使用了设置的默认值 0

disallowNullValue

默认 false, 如果为 true,禁止 json 中 key 的值为 null ,当一个 key 的值为 null 时,解析时会报 DisallowedNullValueException 异常,

这个属性不常用

fromJson

fromJson 是一个函数,用来自己实现某个字段的解析逻辑

比如:有时一个 key 在不同情况下,有不同类型的值,比如

[
  {"id":1},
  {"id":"2"}
]

比如这个 id,可能是 int,也可能是 String,这时就可以通过 fromJson 函数来进行自定义解析

ignore

默认 false,如果为 true, 解析时会忽略设置了这个属性的字段,一般应用于本地添加的字段。

比如:一个标签接口,返回了 id 和 name,我们要实现标签选中效果,就在 model 中添加了一个 selected 字段,这个字段不用参与序列化,就可以使用 ignore: true 进行忽略

includeIfNull

和 JsonSerializable.includeIfNull 一样,但是优先级大于它。 disallowNullValue 和 includeIfNull 是互斥的,如果同时设置为 true,代码生成时会报错,这个属性不常用

name

model 中字段对应 json 中的 key 的名字,不设置时,字段是什么名字,就到 json 中取对应名字 key 的值。

readValue

这是一个函数,感觉和 fromJson 类似,看 issue ,是为了解决多个 JSON 字段映射到一个model 字段的

required

默认 false,如果为 true, 会检测 json 中是否存在对应的 key(即便该 key 的值是 nulll 也是合法的),没有的话,会报 MissingRequiredKeysException 异常。

toJson

toJson 是一个函数,用来自己实现某个字段的生成 json 的逻辑,和 fromJson 类似

unknownEnumValue

枚举类型的字段专用,当出现了一个未知的枚举值时,该如何处理

可以查看这篇文章:Flutter 使用 json_serializable 解析 JSON 时支持枚举 Enum 类型

JsonEnum 和 JsonValue

JsonEnum 和 JsonValue 都是枚举专用注解,具体使用请参考这篇文章:

Flutter 使用 json_serializable 解析 JSON 时支持枚举 Enum 类型

JsonLiteral

可以把 一个 json 文件生成一个 Map 常量

@JsonLiteral('data.json')
Map get glossaryData => _$glossaryDataJsonLiteral;

测试这个属性的时候,发现 json 文件的路径只能是相对源码的相对路径

这个注解,目前不知道有什么使用场景,我能想到的使用场景可能是单元测试?

总结

这篇文章主要是参考官方文档,并结合我自己做的一些测试,主要对 JsonSerializable 和 JsonKey 中的各个属性,按照我个人的理解,做的一个解释,可能会有理解错误的地方,如果大家发现了,欢迎指出,让我们一起进步。