注册

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 数据时处理泛型的实践经验,如果对你有所帮助,欢迎一键三连,👍👍👍


如果大家有相关问题,欢迎评论留言。


作者:灰尘大哥
链接:https://juejin.cn/post/7127206962915180574
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册