likes
comments
collection
share

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

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

1. 引言

QuickType + json_serializable库,复制后端返回的Json字符串到QuickType生成Model类,直接复制粘贴到项目中。可以使用 泛型 减少重复Model的编写,利用 build.yaml 减少重复属性的设置,根据实际情况按需 自定义字段的序列化/反序列化

本节内容如下

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

2. 常识

2.1. Json → 序列化 VS 反序列化

😊 温故知新,先区分下 Json序列化反序列化

  • 序列化:对象或数据结构 → Json字符串 → 发起接口请求时用到,Dart对象 → Json字符串。
  • 反序列化:Json字符串 → 对象或数据结构 → 解析接口响应时用到,Json字符串 → Dart对象

2.2. Flutter对Json序/反序列化的支持

Flutter中提供了 dart:convert 库来编码和解码各种数据格式,其中就包含了Json的 解码(反序列化)编码(序列化) ,对应方法:decode()encode() 。简单调用代码示例如下:

import 'dart:convert';

void main() {
  String jsonString = '{"name": "Jay", "age": 30}';
  // 解码 (反序列化),Json字符串 → Dart对象
  Map<String, dynamic> userMap = jsonDecode(jsonString);
  print(userMap); // 输出:{name: John, age: 30}
  
  // 编码 (序列化),Dart对象 → Json字符串
  Map<String, dynamic> userMap = {'name': 'Jay', 'age': 30};
  String jsonString = jsonEncode(userMap);
  print(jsonString); // 输出:{"name":"John","age":30}
}

当然,实际开发中很少这样直接解析Json,通常会根据字段写一个Model类,然后定义两个方法来 序/反序列化,通常命名为 fromJson()toJson() 。注意!只是 约定俗成 的命名,非强制,你硬要定义成decodeFromJson()encodeToJson() 也是可以的。但还是建议这样命名,特别是 toJson()

Dart中json.encode()会 自动查找并调用对象的toJson() 方法来获取对象的Json表示,这是通过 dart:convert库中的 JsonEncoder 的约定来实现的。

来一段Json样本:

{
  "desc": "我们支持订阅啦~",
  "id": 30,
  "imagePath": "https://www.wanandroid.com/blogimgs/42da12d8-de56-4439-b40c-eab66c227a4b.png",
  "isVisible": 1,
  "order": 2,
  "title": "我们支持订阅啦~",
  "type": 0,
  "url": "https://www.wanandroid.com/blog/show/3352"
}

序列化和反序列化的代码示例如下:

import 'dart:convert';

class Banner {
  final String desc;
  final int id;
  final String imagePath;
  final int isVisible;
  final int order;
  final String title;
  final int type;
  final String url;

  Banner({
    required this.desc,
    required this.id,
    required this.imagePath,
    required this.isVisible,
    required this.order,
    required this.title,
    required this.type,
    required this.url,
  });

  // 反序列化
  factory Banner.fromJson(Map<String, dynamic> json) {
    return Banner(
      desc: json['desc'],
      id: json['id'],
      imagePath: json['imagePath'],
      isVisible: json['isVisible'],
      order: json['order'],
      title: json['title'],
      type: json['type'],
      url: json['url'],
    );
  }

  // 序列化
  Map<String, dynamic> toJson() {
    return {
      'desc': desc,
      'id': id,
      'imagePath': imagePath,
      'isVisible': isVisible,
      'order': order,
      'title': title,
      'type': type,
      'url': url,
    };
  }
}

// 调用方法
void main() {
  String jsonString = "上面的json字符串";
  // 反序列化
  Map<String, dynamic> jsonMap = json.decode(jsonString);
  Banner banner = Banner.fromJson(jsonMap);
  print(banner.title); // 输出: 我们支持订阅啦~
  
  // 序列化 (不需要手动调用toJson())
  String jsonString = json.encode(banner);
  print(jsonString);
}

2.3. 为啥Flutter中处理Json这么麻烦?

🤪 每次序列化和反序列化都自己抠字段,重复劳动 之余还容易引入 人为错误 (如字段拼写错误),肯定是得向南发 自动生成 代码的。在Java中,一般通过 Gson/Jackson库 来实现自动自动序列化,原理是 运行时反射。但在Flutter中,你却找不到这样的库。Dart本身是支持反射的!!!只是 Flutter中禁用了反射, 因为它会干扰Dart的 Tree Shaking(摇树)

摇树是Dart编译过程的一个术语, 它会 移除 应用程序编译后的 未被使用的代码,以缩减应用体积。而反射需要在运行时动态查询或调用对象的属性或方法,为此,编译器必须保留应用中所有可能会被反射机制调用的代码,即便这些代码在实际工作流程中可能永远不会被执行。这直接干扰到摇树,编译器无法确定那些代码是 "多余" 的。因此,Flutter直接禁掉了运行时反射(具体表现:不能使用 dart:mirrors库),鼓励开发者使用 编译时代码生成 的方式来代替反射。

😉 官方推荐使用 json_serializable 这个 Flutter编译时工具 来生成Json序/反序列化代码,过下它的详细用法~

3. json_serializable 库

😄 用库前,得之前这个库是用来解决什么问题的~

答:简化Json数据与Dart对象的转换,提供 Json序列化和反序列化代码的自动生成

3.1. 添加依赖

它由三个部分组成:

  • json_annotation → 定义注解。
  • json_serializable → 使用这些注解来生成代码。
  • build_runner → 执行生成代码的任务。

添加库依赖的方式二选一:

# 方式一:终端直接键入下述命令安装
flutter pub add json_annotation dev:build_runner dev:json_serializable

# 方式2:打开 build.yaml文件手动添加依赖
dependencies:
flutter:
  sdk: flutter
json_annotation: ^4.8.1

dev_dependencies:
flutter_test:
  sdk: flutter
build_runner: ^2.4.7
json_serializable: ^6.7.1

3.2. 基本使用

定义Model类添加属性,给类加上 @JsonSerializable 注解,并添加 fromJson()toJson() 方法:

import 'package:json_annotation/json_annotation.dart';

part 'banner.g.dart'; // 1.指定生成的文件,一般是当前文件.g.dart

@JsonSerializable() // 2.添加注解,告知此类是要生成Model类的
class Banner {
  @JsonKey(name: 'id') // 3.可选,添加注解,告知此属性对应的json key
  final int bid;
  final String desc;
  final String imagePath;
  final int isVisible;
  final int order;
  final String title;
  final int type;
  final String url;

  Banner({
    required this.bid,
    required this.desc,
    required this.imagePath,
    required this.isVisible,
    required this.order,
    required this.title,
    required this.type,
    required this.url,
  });

  // 4、反序列化,固定写法:_${类名}FromJson(json)
  factory Banner.fromJson(Map<String, dynamic> json) => _$BannerFromJson(json);

  // 5、序列化,固定写法:_${类名}ToJson(this)
  Map<String, dynamic> toJson() => _$BannerToJson(this);
}

编写完上述代码,编译器会报_BannerFromJson和_BannerToJson找不到,没关系,只要确定没拼写错误就行,直接执行下述命令生成对应的序列化代码:

flutter pub run build_runner build --delete-conflicting-outputs

# 后面的 --delete-conflicting-outputs 是可选的,作用是:
# 自动删除任何现存的,与即将生成的输出文件冲突的文件,然后继续构建过程。
# 这样可以清理由于老版本或不同构建配置造成的遗留文件

命令执行完,原先的报错就消失了,而且会在 同级目录 生成一个 xxx.g.dart 的文件:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

然后跟前面一样调用fromJson()就行了,这里用到了这两个 注解,详细讲讲,加⭐的属性是比较常用的~

3.2.1. @JsonSerializable

用于:指示生成器如何 为类生成序/反序列化代码,可选属性如下:

  • explicitToJson → 默认为false,涉及Model类嵌套时,赋值一个 引用类型,而不是 显式调用嵌套类的toJson() !涉及对象嵌套时,建议设置为true。
  • ignoreUnannotated → 默认为false,如果设置为true时,生成器只序列化和反序列化用 @JsonKey标记 的字段。
  • includeIfNull → 序列化时是否包含值为null的字段,默认为true,即忽略为null的字段。
  • genericArgumentFactories → 用于 泛型类的序列化和反序列化,默认为false,如果设置为true,生成的fromJson()和toJson()将 需要额外的类型参数的工厂函数,以保证泛型类的正确序列化和反序列化。
  • anyMap → 默认false,如果设置为true,生成的 fromJson() 方法将接受如何类型为Map的对象,而不仅仅是Map<String, dynamic>。
  • checked → 默认为false,如果设置为true,生成的代码会包含对每个字段的类型检查,确保在反序列化期间的类型匹配,如果类型不匹配,会抛出一个有用的错误信息。
  • constructor → 指定用于生成 fromJson() 工厂构造函数的名称,默认为空字符串,即使用无名构造函数。
  • createFieldMap → 默认为true,生成器将为 Map类型的字段 生成额外的序列化逻辑。
  • createFactory → 默认为true,当你需要自定义反序列化逻辑时,可以设置为false,生成器不会生成fromJson()。
  • createToJson → 默认为true,需要自定义序列化逻辑时,可以设置为false,生成器不会生成 toJson()。
  • disallowUnrecognizedKeys → 默认为false,设置为true时,如果输入的Json中包含Model中未定义的Key,fromJson() 将抛出一个异常。
  • fieldRename → 控制如何将类字段的名称更改为Json键名称,枚举类FieldRename,可选值有:none(默认,不更改)、kebab(短横线命名a-b)、snak(蛇形命名a_b)、pascal(帕斯卡命名AxxBxx)。
  • converters → 允许自定义转换器,这些转换器可以在序列化和反序列期间使用。
  • createPerFieldToJson → 默认为false,是否为每个字段创建一个单独的 _$[FieldName]ToJson() 函数,当你需要对某些字段进行特殊处理时,如:自定义类型需要特殊的序列化逻辑、想根据字段的值改变输出的Json结构等,即复杂的自定义序列化,再考虑是否将这个属性设置为true,毕竟会增加代码的复杂性!

3.2.2. @JsonKey

用于:定制 单个字段的序/反序列化行为,可选属性如下:

  • disallowNullValue → 如果设为true,序列化时字段为null,会抛出一个异常,通常用于确保某些字段在序列化时不为null的场景。
  • ignore → 如果设置为true,序列化和反序列化时会忽略这个字段。
  • includeIfNull → 如果设置为true,即便字段的值为null,仍然会被包含在序列化的Json中。
  • name → 用于对Json中Key指定一个不同于Dart字段名的名称。
  • defaultValue → json中缺少这个字段或值为null时,反序列化过程中使用的默认值。
  • fromJson → 允许为字段提供一个自定义的反序列化函数。
  • required → 如果设置为 true,反序列化时,如果Json中缺少这个字段会抛出一个异常。
  • toJson → 允许为字段提供一个自定义的序列化函数。
  • unknownEnumValue → Json中的值无法映射到Dart枚举类型中的任何值时,可以指定一个枚举默认值。

3.3. 进阶玩法

3.3.1. 泛型

😁 在实际开发中,接口返回的Json数据大都是有固定格式的,比如玩Android的接口就由这三部分组成:data + errorCode + errorMsg。变化的结构只有 data 部分,这次是Banner,得写个BannerResponse的类,下次是Article,得写个ArticleResponse的类... 完全可以使用 泛型 来减少这种 重复劳动~

🤔 这里提一嘴 Dart泛型Java泛型 的区别:

Java泛型 是"假泛型",通过 类型擦除 来实现,泛型类型信息 只在 编译时存在,一旦代码被编译了就会被擦除,转换为它们的 边界类型 (如果指定了边界) 或 Object类型,这样做是为了 向后兼容早期的Java版本。而Dart泛型 的类型是 具象化(reified) 的,即:在运行时保留了泛型的类型信息,因此,你可以在运行时进行类型检查,比如使用 is 关键字判断对象是否为特定的泛型类型。除此之外,还可以使用 Type对象runtimeType属性 来获取泛型的类型信息。

运行时获取泛型参数类型 的简单代码示例如下:

void main() {
  List<int> numbers1 = [1,2,3];
  print(numbers1 is List<int>); // 输出:true

  List<int?> numbers2 = [];
  print("${numbers1.runtimeType} == ${numbers2.runtimeType}${numbers1.runtimeType == numbers2.runtimeType}"); // 输出:List<int> == List<int?> → false

  // 定义变量赋值
  Type type = numbers1.runtimeType;

  // 格式化输出
  print("${numbers1.runtimeType} == ${type}${numbers1.runtimeType == type}");// List<int> == List<int> → true

  // 验证相同类型泛型参数不同是否相等
  List<String> stringList = ['a', 'b', 'c'];
  print("${stringList.runtimeType} == ${type}${stringList.runtimeType == type}"); // 输出:List<String> == List<int> → false

  // 运行时类型判定
  if(numbers1.runtimeType == List<int>) print("true");// 输出:true

  // 验证泛型嵌套是否能返回完整的泛型类型信息
  List<List<List<String>>> list = [];
  print("${list.runtimeType}"); // 输出:List<List<List<String>>>
}

除此之外,还可以通过 显式传递类型信息 来实现 运行时获取泛型的类型信息,简单代码示例:

void main() {
  var intBox = Box<int>(type: int);
  var stringBox = Box<String>(type: String);

  checkType(intBox);
  checkType(stringBox);
}

void checkType<T>(Box<T> box) {
  if (box.type == int) {
    print('Box contains int');
  } else if (box.type == String) {
    print('Box contains String');
  } else {
    print('Box contains unknown type');
  }
}

class Box<T> {
  final Type type;	// 显式传递类型信息
  Box({required this.type});
}

💁‍♂️ 扯得有点远了,收一下。data字段 返回的数据类型可能是 对象列表,需要定义两个Model类,让他们支持泛型只需两步:

  • @JsonSerializable 设置 genericArgumentFactories 为 true。
  • fromJson()toJson() 中需要传递 额外的函数参数 指明如何将T类型的数据转换为Json,以及如何将Json转换为T。

具体代码示例如下:

import 'package:json_annotation/json_annotation.dart';

part 'base_response.g.dart';

// Data是对象
@JsonSerializable(genericArgumentFactories: true)
class DataResponse<T> {
  final T? data;
  final int errorCode;
  final String errorMsg;

  DataResponse({required this.data, required this.errorCode, required this.errorMsg});

  factory DataResponse.fromJson(Map<String, dynamic> json, T Function(dynamic json) fromJsonT) =>
      _$DataResponseFromJson(json, fromJsonT);

  Map<String, dynamic> toJson(dynamic Function(T value) toJsonT) => _$DataResponseToJson(this, toJsonT);
}

// Data是列表
@JsonSerializable(genericArgumentFactories: true)
class ListResponse<T> {
  final List<T>? data;
  final int errorCode;
  final String errorMsg;

  ListResponse({required this.data, required this.errorCode, required this.errorMsg});

  factory ListResponse.fromJson(Map<String, dynamic> json, T Function(dynamic json) fromJsonT) =>
      _$ListResponseFromJson(json, fromJsonT);

  Map<String, dynamic> toJson(dynamic Function(T value) toJsonT) => _$ListResponseToJson(this, toJsonT);
}

执行 flutter pub run build_runner build 生成完.g.dart文件后,写下简单的测试代码:

// 泛型的具体类型
class User {
  final int id;
  final String name;

  User({required this.id, required this.name});

  factory User.fromJson(Map<String, dynamic> json) => User(id: json['id'], name: json['name']);

  Map<String, dynamic> toJson() => {'id': id, 'name': name};

  @override
  toString() => "User{id: $id, name: $name}";
}

// 测试代码
void main() {
  var dataResponse = DataResponse<User>.fromJson(
    {
      "data": {"id": 1, "name": "张三"},
      "errorCode": 200,
      "errorMsg": "成功",
    },
    (json) => User.fromJson(json),
  );
  print("${dataResponse.runtimeType}${dataResponse.data}");

  var listResponse = ListResponse<User>.fromJson(
    {
      "data": [
        {"id": 1, "name": "张三"},
        {"id": 2, "name": "李四"},
      ],
      "errorCode": 200,
      "errorMsg": "成功",
    },
    (json) => User.fromJson(json),
  );
  print("${listResponse.runtimeType}${listResponse.data}");
}

运行输出结果如下:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

非常简单,聪明的你可能发现了:两个泛型类的代码,除了data的类型不一样,其它代码都是一样的。能不能合并成一个呢?😏 当然可以,就是调用时的传参稍微麻烦点 (需要和泛型类型保持一致),具体代码如下:

@JsonSerializable(genericArgumentFactories: true, explicitToJson: true)
class WanResponse<T> {
  final T? data;
  final int errorCode;
  final String errorMsg;

  WanResponse({required this.data, required this.errorCode, required this.errorMsg});

  factory WanResponse.fromJson(Map<String, dynamic> json, T Function(dynamic json) fromJsonT) =>
      _$WanResponseFromJson(json, fromJsonT);

  Map<String, dynamic> toJson(dynamic Function(T value) toJsonT) => _$WanResponseToJson(this, toJsonT);
}

// 测试代码
void main() {
  var dataResponse = WanResponse<User?>.fromJson(
    {
      "data": {"id": 1, "name": "张三"},
      "errorCode": 200,
      "errorMsg": "成功",
    },
    (json) => User.fromJson(json),
  );
  print("${dataResponse.runtimeType}${dataResponse.data?.name}");

  var listResponse = WanResponse<List<User>?>.fromJson({
    "data": [
      {"id": 1, "name": "张三"},
      {"id": 2, "name": "李四"},
    ],
    "errorCode": 200,
    "errorMsg": "成功",
  }, (json) => (json as List).map((e) => User.fromJson(e)).toList()); // !!!返回类型需要与泛型一致
  print("${listResponse.runtimeType}${listResponse.data?[0].name}");
}

运行输出结果如下:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

😁 每次解析List类型的data 都要写上一串 (json) => (json as List).map((e) => User.fromJson(e)).toList()) 略显繁琐,抽取成一个 全局方法

List<T> parseList<T>(dynamic json, T Function(Map<String, dynamic>) fromJson) =>
    (json as List).map((e) => fromJson(e)).toList();


// 调用处
(json) => parseList(json, User.fromJson));

3.3.2. 用 build.yaml 减少重复属性设置

  • 实例场景:后端要求值为null的字段不要提交。
  • 如何解决:在每个Model类的 @JsonSerializable 注解中都设置属性 includeIfNull: false 生成器生成toJson()方法时忽略掉值为null的字段。
  • 又比如:涉及Model类嵌套,需要设置 explicitToJson: true,让其显式调用嵌套类的toJson(),而不是复制一个引用类型。
  • 发现问题:需要手动对所有的Model进行 重复的属性设置
  • 更好解法:使用 build.yaml 文件进行 全局配置,该文件会被 build_runner项目中使用的构建插件 (如 json_serializable、source_gen 等) 所使用。

在项目中右键新建一个 build.yaml 文件,按需配置下就可:

targets:
  $default:
    builders:
      # 全局配置json_serializable库省去重复设置属性
      json_serializable:
        options:
          explicit_to_json: true  # toJson()时将嵌套的对象也转换为Map类型而非引用
          include_if_null: false  # toJson()时忽略值为null的字段

更多可选配置可见: json_serializable build configuration

3.3.3. 自定义字段序/反序列化逻辑

有时我们需要对某些字段的序/反序列化做下 特殊处理,可以通过为 @JsonKey 注解设置 toJsonfromJson 属性来进行自定义。比如:提交(序列化)的时候是时间戳字符串解析(反序列化)的时候是Datetime 的代码示例:

@JsonSerializable()
class User {
  String name;

  @JsonKey(fromJson: _timeStampFromJson, toJson: _timeStampToJson)
  DateTime timestamp;

  User({required this.name, required this.timestamp});

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

  Map<String, dynamic> toJson() => _$UserToJson(this);

  // 自定义fromJson和toJson逻辑
  static DateTime _timeStampFromJson(String timestamp) => DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp));

  static String _timeStampToJson(DateTime date) => date.millisecondsSinceEpoch.toString();
}

// 测试代码
void main() {
  var user = User(name: 'Jay', timestamp: DateTime.now());
  print(user.toJson());
  print(User.fromJson(user.toJson()).timestamp);
}

运行输出结果如下:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

3.3.4. 自定义转换器 (JsonConverter)

除了上面这种 设置属性 的方式来 自定义fromJson和toJson逻辑 外,还可以 自定义转换器(JsonConverter) 来处理特定类型的转换。一个把Json中星期几 (如Monday、Tuesday等) 转换为枚举类型的代码示例:

// 枚举类
enum DayOfWeek { monday, tuesday, wednesday, thursday, friday, saturday, sunday }

// 自定义转换器
class DayOfWeekConverter implements JsonConverter<DayOfWeek, String> {
  const DayOfWeekConverter();

  @override
  DayOfWeek fromJson(String json) {
    switch (json.toLowerCase()) {
      case 'monday':
        return DayOfWeek.monday;
      case 'tuesday':
        return DayOfWeek.tuesday;
      case 'wednesday':
        return DayOfWeek.wednesday;
      case 'thursday':
        return DayOfWeek.thursday;
      case 'friday':
        return DayOfWeek.friday;
      case 'saturday':
        return DayOfWeek.saturday;
      case 'sunday':
        return DayOfWeek.sunday;
      default:
        throw ArgumentError('Invalid day of the week: $json');
    }
  }

  @override
  String toJson(DayOfWeek object) => object.toString().split('.').last; // DayOfWeek.monday,只取后面的monday
}

@JsonSerializable()
class MyModel {
  // 使用自定义转换器
  @DayOfWeekConverter()
  DayOfWeek dayOfWeek;

  MyModel(this.dayOfWeek);

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

  Map<String, dynamic> toJson() => _$MyModelToJson(this);
}

// 测试代码
void main() {
  MyModel model = MyModel(DayOfWeek.monday);
  print(model.toJson());
  print(MyModel.fromJson(model.toJson()).dayOfWeek);
}

运行输出结果如下:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

3.4. 常见问题

3.4.1. .g.dart 是否需要提交到Git?

答:没有绝对的标准答案,可以提交,也可以不提交,只要 开发团队统一 就行了😁。个人倾向于不提交,首先这个文件是 自动生成 的,你项目能跑起来它肯定是得生成的,它经常会发生变化 (改下字段就得重新生成),这可能导致 Git合并冲突,而这完全是可以避免的。

3.4.2. 执行生成序/反序列化代码的命令真烦~!

如题,每次新建或修改Model类,都需要执行 flutter pub run build_runner 来生成序/反序列化代码,觉得麻烦可以执行下述命令启动一个 watcher,当带有 json_serializable 注解的类发生改变时,它会自动更新对应的.g.dart文件:

flutter packages pub run build_runner watch

如果你使用的IDE是 VSCode 的话,可以在 tasks.json 文件中配置下这个命令,方便快速执行:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Flutter Build Runner Watch",
            "type": "shell",
            "command": "flutter packages pub run build_runner watch",
            "isBackground": true,
            "presentation": {
                "reveal": "always",
                "panel": "shared"
            },
            "problemMatcher": []
        }
    ]
}

然后就可以在命令行面板找到这个任务啦,你还可以为其设置一个快捷键,打开命令面板,输入 Preferences: Open Keyboard Shortcuts (JSON) 并选中,在打开的 keybindings.json 文件中新增一个绑定,如:

{
    "key": "ctrl+shift+b", // 快捷键
    "command": "workbench.action.tasks.runTask",
    "args": "Flutter Build Runner Watch"	// 任务名称
}

保存后,使用你设置的快捷键就能快速执行上述任务啦~

4. Json 生成 Model类 的一些工具

json_serializable库 解决的是 自动生成Json序/反序列化代码,Model类的 属性 还是得 手动抠Json字段,可以通过一些工具来自动生成,喜欢哪个用哪个哈~

4.1. 网页

4.1.1. QuickType (⭐推荐)

QuickType,直接复制Json到左侧,右侧会自动生成Model类的代码,复制粘贴到项目里就好了:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

默认生成的代码是 不包含json_serializable注解相关,直接生成对应的toJson() 和 fromJson() 方法。需要点击 Options-Other 勾选下左图的选项。点击 Language 可以进行 编程语言的细节设置 (如这里选中Dart):

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

4.1.2. json2dart

json2dart,也是左边复制json右侧生成代码的玩法:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

4.2. 插件 & 其它

AS的插件商店搜下 JsonToDart 下载安装:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

安装后,选定要存放Model类的目录,右键,依次选中:New → Json To Dart:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

接着粘贴接口Json,写个类名,点击Generate生成就完事了~

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

😭 不支持生成json_serializable注解,类似的插件还有 FlutterJsonBeanFactory,用法大同小异。其它工具:

😆 个人还是更倾向于 QuickTypejson2dart,简单易用,通过工具生成绝大部分代码后,还需要进行微调,比如类名修改,添加属性注释等。😏 也可以根据自己的实际情况 编写自动生成脚本/插件,比如杰哥之前就写过一个py脚本,输入接口文档的URL:

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)

自动提取出json里的字段并自动添加字段注释~

跟🤡杰哥一起学Flutter (十二、实战进阶-Json序/反序列化的最佳实践)