Flutter 自定义序列化并生成

json model定义的时候,定义了一个必须型,但是服务端返回的时候,这个字段是null,这个时候,如果直接序列化,就会报错。在freezed中,如何利用自动生成功能,又能手动解决这个问题呢?

o1 grok3 claude 3.7

竟然是 grok3 直接找出了这个答案,o1 试了很多次都没有,即便是你给它提示它都还不太理解,claude 3.7 第一时间也没给这个答案,但是你给它提示它就理解了。神奇(不过文章都是o1来总结的😂)

一句话总结

(explicitToJson: true, genericArgumentFactories: true)

JP

以下は、FreezedFreezed + 泛型はんけいジェネリックgeneric)の場面ばめんで、 fromJson独自どくじロジックを挿入そうにゅうしながら .g.dart ファイルファイル正常せいじょう生成せいせいする方法ほうほうについての 完全かんぜんまとめまとめ です:


背景はいけい

FreezedFreezedジェネリックgenericふく実体じったいクラスclass定義ていぎするときは、通常つうじょうこんなかたしますします


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 などをふくむ)を自動的じどうてき生成せいせいしてくれます。

しかし、ときどきビジネスbusinessロジック(検証けんしょう補完ほかんログログ出力しゅつりょくエラーエラー処理しょりなど)を反序列化はんじょれつかさい追加ついかしたい場合ばあいありますあります。もし fromJson直接ちょくせつこんなふうえると:

factory MyResponse.fromJson(
  Map<String, dynamic> json,
  T Function(Object?) fromJsonT,
) {
  // ここで JSON を先に処理して、
  // 自動生成された反序列化関数に渡したい
  return _$MyResponseFromJson(json, fromJsonT);
}

このこのとき、 FreezedFreezed内部ないぶjson_serializableコードコード生成せいせい失敗しっぱいする可能性かのうせいてきます。
解決かいけつさく対象たいしょうクラスclass@JsonSerializable(explicitToJson: true, genericArgumentFactories: true)くわえて、正しいただ関数かんすうシグネチャsignature引数ひきすう順序じゅんじょたもちましょう。


なぜなぜこのアノテーションannotation上手うまくいくのか?

  • @JsonSerializable(genericArgumentFactories: true)
    これは内部ないぶjson_serializable および FreezedFreezed に:

    1. 汎用はんようジェネリックgeneric)を反序列化はんじょれつかするためため工場こうじょう関数かんすう必要ひつようだ」
      T Function(Object?) fromJsonTようようかたちシグネチャsignature対応たいおうできるようにするする
    2. 「このクラスclass追加ついか処理しょり必要ひつようかたふくむ」
  • explicitToJson: true
    おもtoJsonさい、ネストされたオブジェクトobjectにも明示的めいしてきtoJson()ぶよう指示しじしてくれます。そうしないと、単純たんじゅんインスタンスinstanceほうむだけで不完全ふかんぜんシリアライズserializationになることがあります。

この2つのパラメータparameters両方りょうほうオンにしておくことで、 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 関数かんすうなどがふくまれています。


重要じゅうようポイントpointまとめ

  1. fromJsonシグネチャsignature自動生成じどうせいせい要件ようけん合致がっちさせる
  2. @JsonSerializable(explicitToJson: true, genericArgumentFactories: true)付与ふよする
  3. 追加ついかロジック」を _$xxxProjectResponseFromJsonなかには直接ちょくせつかない
  4. もし fromJson波括弧なみかっこにしたくない場合ばあいは、矢印やじるしアローarrowシンタックスsyntax)のままにしておき、べつ静的せいてきメソッドmethodつつ
  5. もし完全かんぜんfromJson / toJson手書てがきしたいなら、 @Freezed(fromJson: false, toJson: false) して自力じりき

結論けつろん

  • FreezedFreezed + ジェネリックgeneric + 手動しゅどう修正しゅうせいした fromJson という場面ばめんで:

    • @JsonSerializable(explicitToJson: true, genericArgumentFactories: true)け、
    • シグネチャsignature / 引数ひきすう既定きていどおりにそろえる
    • 大括弧だいかっこなか独自どくじ処理しょり追加ついかし、最後さいご_$XXXFromJson(...)
  • そうすれば、自分じぶんロジックlogic挿入そうにゅうしつつも、コードコード 生成せいせい正常せいじょう継続けいぞくできるようになります。

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 的解析十分严格:
    1. 函数签名必须符合预期(包括参数名称、类型、顺序)。
    2. 如果不是箭头函数而是大括号,生成器默认会“先读”我们手写的逻辑,再结合自动逻辑,看是否能完美拼装。稍有不一致,就会导致解析失败。

加上 @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 等方法。


关键点总结

  1. @JsonSerializable(genericArgumentFactories: true) 必须有

    • 告诉生成器要为泛型 T 生成(反)序列化所需的工厂函数。
    • 没有这个,带泛型的 fromJson 往往会引起冲突或直接失败。
  2. @JsonSerializable(explicitToJson: true) 一般也推荐

    • 保证嵌套对象也会自动调用它们的 toJson(),而不是直接把对象插入到 JSON 中。
    • 特别是当你的泛型类型或其他字段包含更多可序列化对象时,很有必要。
  3. fromJson 的函数签名和 _...FromJson 必须一致

    • 保证:factory MyResponse.fromJson(Map<String, dynamic> json, T Function(Object?) fromJsonT)
    • 保持这两个参数,且名字、类型不能乱改。
    • 最后 return _$MyResponseFromJson(json, fromJsonT); 调用自动生成的函数。
  4. 如果你不想在 fromJson 里加大括号,也可以先保留箭头函数,让生成器把 _$FromJson 生成好,然后你再创建一个新的静态方法来做处理:

    static MyResponse<T> fromJsonWithLog<T>(
      Map<String, dynamic> json,
      T Function(Object?) fromJsonT,
    ) {
      print('json: $json');
      return MyResponse<T>.fromJson(json, fromJsonT);
    }

    这样就更简单,不会破坏生成器的默认识别。

  5. 如果想彻底自己写 fromJson / toJson(不依赖自动生成),可以把 @Freezed(fromJson: false, toJson: false) 关掉自动生成,然后完全手写。但这样就失去了 Freezed 对序列化的辅助,需要你自己处理所有字段、泛型转换等逻辑。

结论

  • 在使用 Freezed + 泛型 + 手动修改 fromJson 的场景下
    • 加上 @JsonSerializable(explicitToJson: true, genericArgumentFactories: true)
    • 签名/参数保持一致,
    • 大括号里写你的自定义逻辑再调用 _$XXXFromJson(...)
  • 这样就能既插入自定义逻辑,又保持代码生成正常。