Flutter 使用 json_serializable 解析 JSON 支持泛型

Posted 一叶飘舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 使用 json_serializable 解析 JSON 支持泛型相关的知识,希望对你有一定的参考价值。

一般情况下,服务端接口都会有一套数据结构规范,比如


    "items": [],
    "success": true,
    "msg": ""

不同的接口,items 中返回的数据结构一般都是不一样的,这时使用泛型,可以简化代码

本文将以 wanandroid 提供的开放 API 为例,介绍如何通过泛型类接解析 JSON 数据,简化代码。另外,对 wanAndroid 提供开放 API 的行为表示感谢。

本文解析 JSON 使用的方案,是官方推荐的 json_serializable,至于为什么选择 json_serializable,可以参考我之前写的一篇文章:Flutter 使用 json_serializable 解析 JSON 最佳方案

下面开始进入正文

使用 json_serializable 支持泛型

json_serializable 在大概两年前发布的 v3.5.0 版本开始支持泛型,只需要在 @JsonSerializable() 注解中设置 genericArgumentFactories 为 true,同时需要对 fromJson 和 toJson 方法进行调整,即可支持泛型解析,如下所示:

@JsonSerializable(genericArgumentFactories: true)
class Response<T> 
  int status;
  T value;
  
  factory Response.fromJson(
      Map<String, dynamic> json,
      T Function(dynamic json) fromJsonT,
      ) =>
      _$ResponseFromJson<T>(json, fromJsonT);

  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$ResponseToJson<T>(this, toJsonT);

和正常实体类相比,fromJson 方法多了一个函数参数 T Function(dynamic json) fromJsonT;toJson 方法也多了一个函数参数:Object? Function(T value) toJsonT

分析数据结构

下面使用 wanAndroid 开放 API 接口数据,进行代码实践,我们先看一下服务端接口返回的数据结构

一般接口返回数据结构如下:


  "data": [
    
      "desc": "一起来做个App吧",
      "id": 10,
      "imagePath": "https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png",
      "isVisible": 1,
      "order": 1,
      "title": "一起来做个App吧",
      "type": 1,
      "url": "https://www.wanandroid.com/blog/show/2"
    
  ],
  "errorCode": 0,
  "errorMsg": ""

带分页信息的列表接口,返回数据结构如下:


  "data": 
    "curPage": 1,
    "datas": [
      
        "id": 23300,
        "link": "https://juejin.cn/post/7114142706557075487",
        "niceDate": "2022-06-28 15:30",
        "niceShareDate": "2022-06-28 15:30",
        "publishTime": 1656401449000,
        "realSuperChapterId": 493,
        "shareDate": 1656401449000,
        "shareUser": "灰尘",
        "superChapterId": 494,
        "superChapterName": "广场Tab",
        "title": "Flutter 使用 json_serializable 解析 JSON 最佳方案"
      
    ],
    "offset": 0,
    "over": false,
    "pageCount": 3,
    "size": 20,
    "total": 46
  ,
  "errorCode": 0,
  "errorMsg": ""

通过上面的接口示例,我们可以发现,返回的数据结构有以下两种情况:

在一般情况下 data 是一个数组


    "data": [],
    "errorCode": 0,
    "errorMsg": ""

在分页相关接口,data 是一个对象


    "data": ,
    "errorCode": 0,
    "errorMsg": ""

复杂方案

如果想定义一个模型类,同时处理上述两种情况,可以把整个 data 都定义为泛型,代码如下:

import 'package:json_annotation/json_annotation.dart';

part 'base_response.g.dart';

@JsonSerializable(genericArgumentFactories: true)
class BaseResponse<T> 
  T data;
  int errorCode;
  String errorMsg;

  BaseResponse(
    required this.data,
    required this.errorCode,
    required this.errorMsg,
  );

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

  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$BaseResponseToJson<T>(this, toJsonT);




@JsonSerializable(genericArgumentFactories: true)
class ListData<T> 
  int? curPage;
  List<T> datas;
  int? offset;
  bool? over;
  int? pageCount;
  int? size;
  int? total;

  ListData(
    this.curPage,
    required this.datas,
    this.offset,
    this.over,
    this.pageCount,
    this.size,
    this.total,
  );

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

  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$ListDataToJson<T>(this, toJsonT);

测试代码如下:

void main() 
  test("json", () 
    String str =
        '"data": ["category": "设计","icon": "","id": 31,"link": "https://tool.gifhome.com/compress/","name": "gif压缩","order": 4444,"visible": 1],"errorCode": 0,"errorMsg": ""';

    Map<String, dynamic> json = jsonDecode(str);

    BaseResponse<List<CategoryModel>> result =
        BaseResponse.fromJson(json, (json) 
      return (json as List<dynamic>)
          .map((e) => CategoryModel.fromJson(e as Map<String, dynamic>))
          .toList();
    );

    List<CategoryModel> list = result.data;

    CategoryModel model = list[0];

    print(model.toJson());

    expect("category:设计", "category:$model.category");
  );

  test("json list", () 
    String str =
        '"data": "curPage": 1,"datas": ["id": 23300,"link": "https://juejin.cn/post/7114142706557075487","niceDate": "2022-06-28 15:30","niceShareDate": "2022-06-28 15:30","publishTime": 1656401449000,"realSuperChapterId": 493,"shareDate": 1656401449000,"shareUser": "灰尘","superChapterId": 494,"superChapterName": "广场Tab","title": "Flutter 使用 json_serializable 解析 JSON 最佳方案"],"offset": 0,"over": false,"pageCount": 3,"size": 20,"total": 46,"errorCode": 0,"errorMsg": ""';

    Map<String, dynamic> json = jsonDecode(str);

    BaseResponse<ListData<ArticleModel>> result =
        BaseResponse.fromJson(json, (json) 
      return ListData.fromJson(json, (json) => ArticleModel.fromJson(json));
    );

    ListData<ArticleModel> listData = result.data;
    List<ArticleModel> datas = listData.datas;
    ArticleModel model = datas[0];

    print(model.toJson());

    expect("id:23300", "id:$model.id");
  );

虽然一个 BaseResponse 解决了两种数据结构,但使用时的代码会有些复杂,很容易出错。

一般接口:

    BaseResponse<List<CategoryModel>> result =
        BaseResponse.fromJson(json, (json) 
      return (json as List<dynamic>)
          .map((e) => CategoryModel.fromJson(e as Map<String, dynamic>))
          .toList();
    );

分页接口:

    BaseResponse<ListData<ArticleModel>> result =
        BaseResponse.fromJson(json, (json) 
      return ListData.fromJson(json, (json) => ArticleModel.fromJson(json));
    );

简化方案

可以对一般接口和列表分页接口进行单独处理,

处理一般接口的泛型类,命名为 BaseCommonResponse,代码如下:

import 'package:json_annotation/json_annotation.dart';

part 'base_common_response.g.dart';

@JsonSerializable(genericArgumentFactories: true)
class BaseCommonResponse<T> 
  List<T> data;
  int errorCode;
  String errorMsg;

  BaseCommonResponse(
    required this.data,
    required this.errorCode,
    required this.errorMsg,
  );

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

  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$BaseCommonResponseToJson<T>(this, toJsonT);

处理分页列表接口的泛型类,命令为 BaseListResponse

import 'package:json_annotation/json_annotation.dart';

part 'base_list_response.g.dart';

@JsonSerializable(genericArgumentFactories: true)
class BaseListResponse<T> 
  ListData<T> data;
  int errorCode;
  String errorMsg;

  BaseListResponse(
    required this.data,
    required this.errorCode,
    required this.errorMsg,
  );

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

  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$BaseListResponseToJson<T>(this, toJsonT);




@JsonSerializable(genericArgumentFactories: true)
class ListData<T> 
  int? curPage;
  List<T> datas;
  int? offset;
  bool? over;
  int? pageCount;
  int? size;
  int? total;

  ListData(
    this.curPage,
    required this.datas,
    this.offset,
    this.over,
    this.pageCount,
    this.size,
    this.total,
  );

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

  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$ListDataToJson<T>(this, toJsonT);

测试代码如下:

void main() 
  test("json", () 
    String str =
        '"data": ["category": "设计","icon": "","id": 31,"link": "https://tool.gifhome.com/compress/","name": "gif压缩","order": 4444,"visible": 1],"errorCode": 0,"errorMsg": ""';

    Map<String, dynamic> json = jsonDecode(str);

    BaseCommonResponse<CategoryModel> result = BaseCommonResponse.fromJson(
        json, (json) => CategoryModel.fromJson(json));

    List<CategoryModel> list = result.data;

    CategoryModel model = list[0];

    print(model.toJson());

    expect("category:设计", "category:$model.category");
  );

  test("json list", () 
    String str =
        '"data": "curPage": 1,"datas": ["id": 23300,"link": "https://juejin.cn/post/7114142706557075487","niceDate": "2022-06-28 15:30","niceShareDate": "2022-06-28 15:30","publishTime": 1656401449000,"realSuperChapterId": 493,"shareDate": 1656401449000,"shareUser": "灰尘","superChapterId": 494,"superChapterName": "广场Tab","title": "Flutter 使用 json_serializable 解析 JSON 最佳方案"],"offset": 0,"over": false,"pageCount": 3,"size": 20,"total": 46,"errorCode": 0,"errorMsg": ""';

    Map<String, dynamic> json = jsonDecode(str);

    BaseListResponse<ArticleModel> result =
    BaseListResponse.fromJson(json, (json) => ArticleModel.fromJson(json));

    ListData<ArticleModel> listData = result.data;
    List<ArticleModel> datas = listData.datas;
    ArticleModel model = datas[0];

    print(model.toJson());

    expect("id:23300", "id:$model.id");
  );

这时使用时的代码,就比较简单了,代码如下:

一般接口,使用 BaseCommonResponse

    BaseCommonResponse<CategoryModel> result = BaseCommonResponse.fromJson(
    json, (json) => CategoryModel.fromJson(json));

列表分页接口,使用 BaseListResponse

    BaseListResponse<ArticleModel> result = BaseListResponse.fromJson(
    json, (json) => ArticleModel.fromJson(json));

以上就是我在 Flutter 中解析 JSON 数据时处理泛型的实践经验。

以上是关于Flutter 使用 json_serializable 解析 JSON 支持泛型的主要内容,如果未能解决你的问题,请参考以下文章

Flutter - 使用 google_sign_in 库时未找到 <Flutter/Flutter.h>

Flutter——如何使用 html 链接渲染 Flutter 文本 [重复]

flutter系列之:在flutter中使用导航Navigator

Flutter - 无法在flutter web中使用动态链接

无法使用 Flutter 1.22.3 编译 Flutter 应用程序

Flutter中使用flutter_html解析html文件