Flutter 自定义序列化并生成
json model定义的时候,定义了一个必须型,但是服务端返回的时候,这个字段是null,这个时候,如果直接序列化,就会报错。在freezed中,如何利用自动生成功能,又能手动解决这个问题呢?
o1 | grok3 | claude 3.7 |
---|---|---|
❌ | ✅ | ❌ |
竟然是 grok3 直接找出了这个答案,o1 试了很多次都没有,即便是你给它提示它都还不太理解,claude 3.7 第一时间也没给这个答案,但是你给它提示它就理解了。神奇(不过文章都是o1来总结的😂)
一句话总结:
(explicitToJson: true, genericArgumentFactories: true)
JP
以下は、Freezed + 泛型(ジェネリック)の場面で、 fromJson
に独自ロジックを挿入しながら .g.dart
ファイルを正常に生成する方法についての 完全なまとめ です:
背景
Freezed でジェネリックを含む実体クラスを定義するときは、通常こんな書き方をします:
class MyResponse<T> with _$MyResponse<T> {
const factory MyResponse({
// ...
}) = _MyResponse<T>;
factory MyResponse.fromJson(
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) => _$MyResponseFromJson(json, fromJsonT);
}
そのあと build_runner を使うと、 my_response.g.dart
ファイル( _$MyResponseFromJson
などを含む)を自動的に生成してくれます。
しかし、ときどきビジネスロジック(検証、補完、ログの出力、エラー処理など)を反序列化の際に追加したい場合があります。もし fromJson
を直接こんな風に書き換えると:
factory MyResponse.fromJson(
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) {
// ここで JSON を先に処理して、
// 自動生成された反序列化関数に渡したい
return _$MyResponseFromJson(json, fromJsonT);
}
このとき、 Freezed や内部の json_serializable
がコードの生成に失敗する可能性が出てきます。
解決策 : 対象のクラスに @JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
を加えて、正しい関数シグネチャと引数の順序を保ちましょう。
なぜこのアノテーションで上手くいくのか?
-
@JsonSerializable(genericArgumentFactories: true)
これは内部のjson_serializable
および Freezed に:- 「汎用(ジェネリック)を反序列化するための工場関数が必要だ」
→T Function(Object?) fromJsonT
のような形のシグネチャを対応できるようにする。 - 「このクラスは追加の処理が必要な型を含む」
- 「汎用(ジェネリック)を反序列化するための工場関数が必要だ」
-
explicitToJson: true
主にtoJson
の際、ネストされたオブジェクトにも明示的にtoJson()
を呼ぶよう指示してくれます。そうしないと、単純にインスタンスを放り込むだけで不完全なシリアライズになる事があります。
この2つのパラメータを両方オンにしておくことで、 fromJson
の大括弧+独自ロジックという書き方にも対応できるようになり、大括弧と追加ロジックを理由にコードが生成できなくなる問題が解消されます。
正しい例
(explicitToJson: true, genericArgumentFactories: true)
class xxxProjectResponse<T> with _$xxxProjectResponse<T> {
const factory xxxProjectResponse({
// 定义字段...
}) = _xxxProjectResponse<T>;
factory xxxProjectResponse.fromJson(
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) {
// ここで独自ロジックを挿入
if (!json.containsKey('datas')) {
json['datas'] = {"item": []};
}
return _$xxxProjectResponseFromJson(json, fromJsonT);
}
}
そのあと、
flutter pub run build_runner build
を実行すれば xxx_project_response.g.dart
が正常に生成され、 _$xxxProjectResponseFromJson
/ _$xxxProjectResponseToJson
関数などが含まれています。
重要ポイントまとめ
fromJson
のシグネチャは自動生成の要件に合致させる@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
を付与する- 「追加ロジック」を
_$xxxProjectResponseFromJson
の中には直接書かない - もし
fromJson
を波括弧にしたくない場合は、矢印(アローシンタックス)のままにしておき、別の静的メソッドで包む - もし完全に
fromJson
/toJson
を手書きしたいなら、@Freezed(fromJson: false, toJson: false)
にして自力で書く
結論
-
Freezed + ジェネリック + 手動修正した
fromJson
という場面で:@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
を付け、- シグネチャ / 引数を既定どおりに揃える
- 大括弧の中で独自の処理を追加し、最後に
_$XXXFromJson(...)
を呼ぶ
-
そうすれば、自分のロジックを挿入しつつも、コード 生成を正常に継続できるようになります。
CN
以下是一份关于在 Freezed + 泛型 场景下,如何在 fromJson
中插入自定义逻辑、并确保 .g.dart
文件正常生成的完整总结:
背景
在使用 Freezed 来定义一个带泛型的实体类时,通常我们会这样写:
class MyResponse<T> with _$MyResponse<T> {
const factory MyResponse({
// ...
}) = _MyResponse<T>;
factory MyResponse.fromJson(
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) => _$MyResponseFromJson(json, fromJsonT);
}
然后通过 build_runner
会自动生成 my_response.g.dart
文件(其中包括 _$MyResponseFromJson
等方法)。
但有时我们需要在反序列化时插入一些业务逻辑(例如:检查、补全、打印日志、异常处理等)。如果我们直接把 fromJson
写成:
factory MyResponse.fromJson(
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) {
// 我想在这里先处理一波 JSON
// 再把它交给自动生成的反序列化函数
return _$MyResponseFromJson(json, fromJsonT);
}
就有可能导致 Freezed 或底层的 json_serializable
生成代码失败。
解决方法:给该类添加 @JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
,并且保持正确的函数签名和参数顺序。
为什么会失败?
- 当我们开启了
genericArgumentFactories: true
,生成器就必须帮“泛型”生成对应的反序列化方法签名(带T Function(Object?) fromJsonT
)。 - Freezed(基于
json_serializable
)对fromJson
的解析十分严格:- 函数签名必须符合预期(包括参数名称、类型、顺序)。
- 如果不是箭头函数而是大括号,生成器默认会“先读”我们手写的逻辑,再结合自动逻辑,看是否能完美拼装。稍有不一致,就会导致解析失败。
加上 @JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
可以让 json_serializable
知道这个类在序列化时需要对泛型和嵌套对象进行处理,也允许我们在 fromJson
里写大括号并添加手动处理,不会破坏生成器对签名的识别。
正确示例
import 'package:freezed_annotation/freezed_annotation.dart';
part 'xxx_project_response.freezed.dart';
part 'xxx_project_response.g.dart';
// 核心:在类上加这个注解
(explicitToJson: true, genericArgumentFactories: true)
class xxxProjectResponse<T> with _$xxxProjectResponse<T> {
const factory xxxProjectResponse({
// 定义字段...
}) = _xxxProjectResponse<T>;
// fromJson 大括号版本
factory xxxProjectResponse.fromJson(
Map<String, dynamic> json,
T Function(Object?) fromJsonT,
) {
// 在这里插入自定义逻辑
if (!json.containsKey('datas')) {
json['datas'] = {"item": []};
}
return _$xxxProjectResponseFromJson(json, fromJsonT);
}
}
然后执行
flutter pub run build_runner build
就能正常生成 xxx_project_response.g.dart
,并包含 _$xxxProjectResponseFromJson
、_$xxxProjectResponseToJson
等方法。
关键点总结
-
@JsonSerializable(genericArgumentFactories: true)
必须有- 告诉生成器要为泛型
T
生成(反)序列化所需的工厂函数。 - 没有这个,带泛型的
fromJson
往往会引起冲突或直接失败。
- 告诉生成器要为泛型
-
@JsonSerializable(explicitToJson: true)
一般也推荐- 保证嵌套对象也会自动调用它们的
toJson()
,而不是直接把对象插入到 JSON 中。 - 特别是当你的泛型类型或其他字段包含更多可序列化对象时,很有必要。
- 保证嵌套对象也会自动调用它们的
-
fromJson
的函数签名和_...FromJson
必须一致- 保证:
factory MyResponse.fromJson(Map<String, dynamic> json, T Function(Object?) fromJsonT)
- 保持这两个参数,且名字、类型不能乱改。
- 最后
return _$MyResponseFromJson(json, fromJsonT);
调用自动生成的函数。
- 保证:
-
如果你不想在
fromJson
里加大括号,也可以先保留箭头函数,让生成器把_$FromJson
生成好,然后你再创建一个新的静态方法来做处理:static MyResponse<T> fromJsonWithLog<T>( Map<String, dynamic> json, T Function(Object?) fromJsonT, ) { print('json: $json'); return MyResponse<T>.fromJson(json, fromJsonT); }
这样就更简单,不会破坏生成器的默认识别。
-
如果想彻底自己写
fromJson
/toJson
(不依赖自动生成),可以把@Freezed(fromJson: false, toJson: false)
关掉自动生成,然后完全手写。但这样就失去了 Freezed 对序列化的辅助,需要你自己处理所有字段、泛型转换等逻辑。
结论
- 在使用 Freezed + 泛型 + 手动修改
fromJson
的场景下:- 加上
@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
, - 签名/参数保持一致,
- 大括号里写你的自定义逻辑再调用
_$XXXFromJson(...)
。
- 加上
- 这样就能既插入自定义逻辑,又保持代码生成正常。