一、FlutterJsonBeanFactory
Flutter 使用的是 Dart 语言进行开发,而 Dart 语言没有反射,所以无法像 Java 一样通过反射直接将 Json 数据映射为对应的对象实体类对象。官方解决方案是将 Json 数据转换为字典,然后从字典中进行取数使用。但直接从字典中取数很不方便,写代码时没有自动提示很不友好,而且可能在写的时候写错字段名。
我们可以将 Json 转换为字典后再映射到对象实体字段里,这样使用时就可以直接使用对应实体类对象。而FlutterJsonBean就可以帮我们自动生成映射代码。
二、插件安装
插件市场搜索FlutterJsonBeanFactory,安转后重启AS。我安装的版本是4.4.5,实例代码也是基于此版本的分析。
并且可以在Setting->Tools->FlutterJsonBeanFactory里边自定义实体类的后缀,默认是entity。
三、创建实体类
复制json到粘贴板,右键自己要存放实体的目录,可以看到JsonToDartBeanAction
Class Name
是实体名字,会默认加上entity
JSON Text
Json文本
null-able
勾选后所有属性都是可空的?
,不勾选都会加上late,延迟初始化
执行Make后生成代码目录如下:
models
项目自建,存放实体
generated/json
是插件生成目录,xx_entity.g.daet
是实体类生成的辅助类方法, base
是存放基础公共代码
1.xx_entity.dart
@JsonSerializable()
class UserEntity {
String? id;
String? name;
String? age;
UserEntity();
factory UserEntity.fromJson(Map json) =>
$UserEntityFromJson(json);
Map toJson() => $UserEntityToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
会生成fromJson
的工厂方法和toJson
方法,分别调用xx_entity.g.dart
中的$xxEntityFromJson
方法和$xxEntityToJson
方法。toSring
方法会把对象转json字符串显示。
如果是修改了实体类,鼠标悬停在gernerated目录上,执行Alt+J快捷键,就会自动生成新的映射代码,并且去除多余的。
2.xx_entity.g.dart
实体类对应的辅助方法文件,g.dart
是文件的后缀,存放在generated/json
目录下。
主要包含$xxEntityFromJson
和$xxEntityToJson
方法,$
+实体类名
为前缀。
$xxFromJson
将 Json 数据的对应字段取出来然后赋值给实体类的对应字段。Json 数据转换为实体字段使用了 jsonConvert.convert 其定义在 json_convert_content.dart 中。
$xxToJson
将实体数据转换为 Map 字典。
UserEntity $UserEntityFromJson(Map json) {
final UserEntity userEntity = UserEntity();
final String? id = jsonConvert.convert(json['id']);
if (id != null) {
userEntity.id = id;
}
final String? user = jsonConvert.convert(json['user']);
if (user != null) {
userEntity.user = user;
}
final String? age = jsonConvert.convert(json['age']);
if (age != null) {
userEntity.age = age;
}
final int? sex = jsonConvert.convert(json['sex']);
if (sex != null) {
userEntity.sex = sex;
}
return userEntity;
}
Map $UserEntityToJson(UserEntity entity) {
final Map data = {};
data['id'] = entity.id;
data['user'] = entity.user;
data['age'] = entity.age;
data['sex'] = entity.sex;
return data;
}
3.json_convert_content.dart
json_convert_content.dart
为JsonConvert
类, 用于统一进行 Json 与实体类的转换。
JsonConvert jsonConvert = JsonConvert();
typedef JsonConvertFunction = T Function(Map json);
class JsonConvert {
static final Map _convertFuncMap = {
(UserEntity).toString(): UserEntity.fromJson,
};
T? convert(dynamic value) {...}
List? convertList(List? value) {...}
List? convertListNotNull(dynamic value) {...}
T? asT(dynamic value) {...}
//list is returned by type
static M? _getListChildType(List
convert
将json数据转换为实体对象。首先判断了传入的数据是否为null
,为null
则直接返回null
, 不为空则调用asT
方法。在生成的.g.dart
的$xxEntityFromJson
方法中非 List 类型字段基本都是调用 convert 方法进行转换。
T? convert(dynamic value) {
if (value == null) {
return null;
}
return asT(value);
}
convertList
将json数据转换为实体对象List。首先也是判断了传入的数据是否为null
,为null
则直接返回null
, 不为空则遍历value
使用map
调用asT
方法进行转换,最终还是调用的asT
方法。在转换上加了try-catch
,如果报错则返回空的List
。
List? convertList(List? value) {
if (value == null) {
return null;
}
try {
return value.map((dynamic e) => asT(e)).toList();
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
return [];
}
}
converListNotNull
与 convertList
的区别是参数不一样,convertList
参数传入的是List
而convertListNotNull
传入的直接是dynamic
。其次最大的区别是调用asT
方法时convertListNotNull
在 asT 后面加了一个 !
,表示不为空。
当在实体类里定义字段为List
类型时,会根据是否为List
中元素的非空类型而选择生成 convertList
或 convertListNotNull
来进行转换,非空采用convertListNotNull
,可空采用convertList
-
List
采用? foodList1; convertList
-
List
采用? foodList2; convertListNotNull
-
late List
采用foodList3; convertList
-
late List
采用foodList4; convertListNotNull
List? convertListNotNull(dynamic value) {
if (value == null) {
return null;
}
try {
return (value as List).map((dynamic e) => asT(e)!).toList();
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
return [];
}
}
as
首先判断传入的数据类型是否为要转换的数据类型,如果是的话就直接返回传入参数,即如果要将传入数据转换为User
,但是传入参数本身就是User
类型,那就直接返回。
然后通过 T.String()
获取泛型类型的名称,再与String
、int
、double
、DateTime
、bool
这些基础数据类型进行比较,如果是这些类型则调用这些类型的转换方法进行转换。
最后,如果不是基础类型则去_convertFuncMap
寻找其它的实体类型,并调用其value,即fromJson方法,类似递归的去解析。
T? asT(dynamic value) {
if (value is T) {
return value;
}
final String type = T.toString();
try {
final String valueS = value.toString();
if (type == "String") {
return valueS as T;
} else if (type == "int") {
final int? intValue = int.tryParse(valueS);
if (intValue == null) {
return double.tryParse(valueS)?.toInt() as T?;
} else {
return intValue as T;
}
} else if (type == "double") {
return double.parse(valueS) as T;
} else if (type == "DateTime") {
return DateTime.parse(valueS) as T;
} else if (type == "bool") {
if (valueS == '0' || valueS == '1') {
return (valueS == '1') as T;
}
return (valueS == 'true') as T;
} else if (type == "Map" || type.startsWith("Map<")) {
return value as T;
} else {
if (_convertFuncMap.containsKey(type)) {
return _convertFuncMap[type]!(value) as T;
} else {
throw UnimplementedError('$type unimplemented');
}
}
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
return null;
}
}
fromJsonAsT
判断传入Json
数据是否为null
,为null
则直接返回null
。然后判断Json
数据是否为List
,是 List
则调用_getListChildType
否则通过全局变量jsonConvert
调用asT
static M? fromJsonAsT(dynamic json) {
if (json is List) {
return _getListChildType(
json.map((e) => e as Map).toList());
} else {
return jsonConvert.asT(json);
}
}
_getListChildType
直接创建对应实体类的空 List 判断是否为泛型类型,如果类型相同,则通过map
调用对应实体类的 fromJson
方法进行转换.
static M? _getListChildType(List
4.json_field.dart
JsonSerializable
类注解,二次生成代码时插件查找该注解的类进行生成。
JSONField
字段注解,用于自定义字段映射和配置是否序列化和反序列化字段。
class JsonSerializable{
const JsonSerializable();
}
class JSONField {
//Specify the parse field name
final String? name;
//Whether to participate in toJson
final bool? serialize;
//Whether to participate in fromMap
final bool? deserialize;
const JSONField({this.name, this.serialize, this.deserialize});
}
四、使用
1.单实体解析
直接调用实体类对应的``fromJson```方法即可将 Json 数据解析为实体对象。
UserEntity? user;
String userData = """
{
"id":"1",
"name":"qi",
"age":22
}
""";
user = UserEntity.fromJson(jsonDecode(userData));
调用生成的JsonConvert去解析,使用convert
、asT
、fromJsonAsT
都能得到结果
user = jsonConvert.convert(jsonDecode(userData));
user = jsonConvert.asT(jsonDecode(userData));
user = JsonConvert.fromJsonAsT(jsonDecode(userData));
2.List解析
解析 Json List 数据则需要调用 JsonConvert
的对应方法进行解析,除了使用上面的 convert
、asT
、fromJsonAsT
外,还可以使用 convertList
、convertListNotNull
List? users;
List? userNulls;
users = jsonConvert.convert>(jsonDecode(userData));
users = jsonConvert.asT>(jsonDecode(userData));
users = JsonConvert.fromJsonAsT>(jsonDecode(userData));
users = jsonConvert.convertListNotNull(jsonDecode(userData));
userNulls = jsonConvert.convertList(jsonDecode(userData));
convertList
、convertListNotNull
与 convert
、asT
、fromJsonAsT
的区别在于前者的泛型为 List Item元素的泛型类型,后者则直接为对应 List 的类型。如上面 convertList
、convertListNotNull
的泛型直接为UserEntity
, 而 convert
、asT
、fromJsonAsT
的泛型为List
。
3.JSONField的使用
自定义字段名
处理Json数据字段和实体属性字段不一致的问题,如后台返回Json命名不规范这种情况。可以用JSONField
自定义字段映射。如后台返回 AGE
就可以如下使用,映射成age
。加完之后照样需要执行Alt
+J
@JSONField(name: "AGE")
String? age;
忽略字段
JSONField 还有两个字段 serialize
、deserialize
用于序列化和反序列化时忽略某个字段。
五、优化
后台返回的数据一般是经过一层包装
{
"code": 200,
"message": "success",
"data":{
"id": "1",
"name": "qi1",
"age": 18
}
}
而重新用插件生成会生成如下代码:
@JsonSerializable()
class ApiResponseEntity {
int? code;
String? message;
ApiResponseData? data;
ApiResponseEntity();
factory ApiResponseEntity.fromJson(Map json) => $ApiResponseEntityFromJson(json);
Map toJson() => $ApiResponseEntityToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
@JsonSerializable()
class ApiResponseData {
String? id;
String? name;
int? age;
ApiResponseData();
factory ApiResponseData.fromJson(Map json) => $ApiResponseDataFromJson(json);
Map toJson() => $ApiResponseDataToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
要死这样,每一个接口的都有一个ResponseEntity
,使用起来不便于统一封装。
所以我们可以把ApiResponseData
换成 dynamic
,文件底部的ApiResponseData
信息也全部删除,再执行Alt
+J
,这样就会自动清理掉整理json_convert_content.dart
和api_response_entity.g.dart
中的ApiResponseData
痕迹。再把dynamic
替换成T
,并且去除顶部的@JsonSerializable()
,避免下次执行Alt
+J
,替换掉自己的自定义。
@JsonSerializable()
class ApiResponseEntity {
late int code;
late String message;
late T data;
ApiResponseEntity();
factory ApiResponseEntity.fromJson(Map json) =>
$ApiResponseEntityFromJson(json);
Map toJson() => $ApiResponseEntityToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
ApiResponseEntity $ApiResponseEntityFromJson(Map json) {
final ApiResponseEntity apiResponseEntity = ApiResponseEntity();
final int? code = jsonConvert.convert(json['code']);
if (code != null) {
apiResponseEntity.code = code;
}
final String? message = jsonConvert.convert(json['message']);
if (message != null) {
apiResponseEntity.message = message;
}
final T data = jsonConvert.convert(json['data']);
if (data != null) {
apiResponseEntity.data = data;
}
return apiResponseEntity;
}
Map $ApiResponseEntityToJson(ApiResponseEntity entity) {
final Map data = {};
data['code'] = entity.code;
data['message'] = entity.message;
data['data'] = entity.data;
return data;
}
并且把api_response_entity.g.dart
移除generated
目录,因为那个目录会自动删除无用的文件。可以和api_reponse_entity.dart
单独存放在一个文件夹当中。
优化后使用
第一次发现,reponse的data是null。因为新的插件在 asT
方法没有去调用fromJsonAsT
,这个需要我们自加上,否则会失败。
if (_convertFuncMap.containsKey(type)) {
return _convertFuncMap[type]!(value) as T;
} else {
return fromJsonAsT(value);
// throw UnimplementedError('$type unimplemented');
}
//单实体
String responseData1 = """
{
"code": 200,
"message": "success",
"data":{
"id": 1,
"name": "qi1",
"age": 21
}
}
""";
//List
String responseData2 = """
{
"code": 200,
"message": "success",
"data":[
{
"id": 1,
"name": "qi1",
"age": 21
},{
"id": 2,
"name": "qi2",
"age": 22
}
]
}
""";
//基础数据类型
String responseData3 = """
{
"code": 200,
"message": "success",
"data": 18
}
""";
_apiResponseDecode() {
setState(() {
response1 = ApiResponseEntity.fromJson(jsonDecode(responseData1));
response2 = ApiResponseEntity.fromJson(jsonDecode(responseData2));
response3 = ApiResponseEntity.fromJson(jsonDecode(responseData3));
});
}
_getApiResponseContent() {
return response1.toString() +
"\n" +
response2.toString() +
"\n" +
response3.toString();
}
参考链接:https://juejin.cn/post/7043721908801503269