持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情
freezed | Dart Package (flutter-io.cn)
译时版本:2.2.0
FromJson/ToJson
虽然 Freezed 不会自己生成典型的 fromJson/toJson ,但它知道 json_serializable 是什么。
声明一个兼容 json_serializable 的类是很简单的。
考虑这样的代码片段:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'model.freezed.dart';
@freezed
class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
}
这个必需的修改是为了兼容 json_serializable 构成的下面两行:
- 一个新的
part:part 'model.g.dart'; - 目标类的新的构造方法:
factory Model.fromJson(Map<String, dynamic> json) => _$ModelFromJson(json);
结果是:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'model.freezed.dart';
part 'model.g.dart';
@freezed
class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
factory Model.fromJson(Map<String, dynamic> json) => _$ModelFromJson(json);
}
别忘了在 pubspec.yaml 中添加 json_serializable :
dev_dependencies:
json_serializable:
大功告成!
加上这些改动,Freezed 会自动告诉 json_serializable 生成所有必需的 fromJson/toJson 。
注意:
如果工厂方法使用的是 => , 那只会生成 fromJson 。
fromJSON - 带多个构造方法的类
对于有多个构造方法的类,Freezed 会检查为一个名为 runtimeType 的字符串元素对应的 JSON 响应,然后基于它的值选择对应的构造方法。
例如,有下面的构造方法:
@freezed
class MyResponse with _$MyResponse {
const factory MyResponse(String a) = MyResponseData;
const factory MyResponse.special(String a, int b) = MyResponseSpecial;
const factory MyResponse.error(String message) = MyResponseError;
factory MyResponse.fromJson(Map<String, dynamic> json) => _$MyResponseFromJson(json);
}
然后 Freezed 会使用每个 JSON 对象的 runtimeType 来选择构造方法,如下:
[
{
"runtimeType": "default",
"a": "This JSON object will use constructor MyResponse()"
},
{
"runtimeType": "special",
"a": "This JSON object will use constructor MyResponse.special()",
"b": 42
},
{
"runtimeType": "error",
"message": "This JSON object will use constructor MyResponse.error()"
}
]
也可以用 @Freezed 和 @FreezedUnionValue 修饰符自定义不同的键值:
@Freezed(unionKey: 'type', unionValueCase: FreezedUnionCase.pascal)
class MyResponse with _$MyResponse {
const factory MyResponse(String a) = MyResponseData;
@FreezedUnionValue('SpecialCase')
const factory MyResponse.special(String a, int b) = MyResponseSpecial;
const factory MyResponse.error(String message) = MyResponseError;
// ...
}
这会将前面的 json 更新为如下内容:
[
{
"type": "Default",
"a": "This JSON object will use constructor MyResponse()"
},
{
"type": "SpecialCase",
"a": "This JSON object will use constructor MyResponse.special()",
"b": 42
},
{
"type": "Error",
"message": "This JSON object will use constructor MyResponse.error()"
}
]
如果想要为所有的类自定义键和值,可以在 build.yaml 中指定。例如:
targets:
$default:
builders:
freezed:
options:
union_key: type
union_value_case: pascal
如果你不想控制 JSON 响应,可以实现一个自定义的转换器。 这个自定义转换器需要实现决定使用哪个构造方法的逻辑。
class MyResponseConverter implements JsonConverter<MyResponse, Map<String, dynamic>> {
const MyResponseConverter();
@override
MyResponse fromJson(Map<String, dynamic> json) {
// type 的数据已经设置 (例如,因为我们自己进行了序列化)
if (json['runtimeType'] != null) {
return MyResponse.fromJson(json);
}
// 需要找到一些条件来知道它是哪种类型。例如,检查 json 中某些字段的存在。
if (isTypeData) {
return MyResponseData.fromJson(json);
} else if (isTypeSpecial) {
return MyResponseSpecial.fromJson(json);
} else if (isTypeError) {
return MyResponseError.fromJson(json);
} else {
throw Exception('Could not determine the constructor for mapping from JSON');
}
}
@override
Map<String, dynamic> toJson(MyResponse data) => data.toJson();
}
然后把自定义的转换器应用到构造方法参数的装饰符。
@freezed
class MyModel with _$MyModel {
const factory MyModel(@MyResponseConverter() MyResponse myResponse) = MyModelData;
factory MyModel.fromJson(Map<String, dynamic> json) => _$MyModelFromJson(json);
}
这样做的话 json serializable 可以使用 MyResponseConverter.fromJson() 和 MyResponseConverter.toJson() 来转换 MyResponse 。
也可以对包含 List 的构造方法参数使用自定义转换器。
@freezed
class MyModel with _$MyModel {
const factory MyModel(@MyResponseConverter() List<MyResponse> myResponse) = MyModelData;
factory MyModel.fromJson(Map<String, dynamic> json) => _$MyModelFromJson(json);
}
注意:
为了序列化 Freezed 对象中嵌套的列表,应该要指定 @JsonSerializable(explicitToJson: true) 或着修改 build.yaml 文件中的 explicit_to_json (查看文档) 。
反序列化泛型类
为了反序列化/序列化泛型的 Freezed 对象,可以使 genericArgumentFactories 可用。
所有需要做的是修改 fromJson 方法的签名并在 freezed 配置中添加 genericArgumentFactories: true 。
@Freezed(genericArgumentFactories: true)
class ApiResponse<T> with _$ApiResponse<T> {
const factory ApiResponse.data(T data) = ApiResponseData;
const factory ApiResponse.error(String message) = ApiResponseError;
factory ApiResponse.fromJson(Map<String, dynamic> json, T Function(Object?) fromJsonT) => _$ApiResponseFromJson(json, fromJsonT);
}
作为代替方案,可以如下修改 build.yaml 文件为整个工程开启 genericArgumentFactories:
targets:
$default:
builders:
freezed:
options:
generic_argument_factories: true
@JsonKey 注解如何使用?
所有传给构造方法参数的修饰符也会 "复制-粘贴" 到生成的属性。
因此,可以如下编写:
@freezed
class Example with _$Example {
factory Example(@JsonKey(name: 'my_property') String myProperty) = _Example;
factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}
@JsonSerializable 注解如何使用?
可以在构造方法上面放置 @JsonSerializable 注解。
例如:
@freezed
class Example with _$Example {
@JsonSerializable(explicitToJson: true)
factory Example(@JsonKey(name: 'my_property') SomeOtherClass myProperty) = _Example;
factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}
如果想要为所有的类定义一些自定义的 json_serializable 标志(例如,explicit_to_json 或 any_map),可以参考 这里 的描述修改 build.yaml 文件。
也参考一下 修饰符 部分。