Flutter 使用的 Dart 语言没有反射,无法像 Java 一样通过反射直接将 Json 数据映射为对应的对象实体类对象。官方解决方案是将 Json 数据转换为字典,然后从字典中进行取数使用。但直接从字典中取数很不方便,写代码时没有自动提示很不友好,而且可能在写的时候写错字段名。
1. FlutterJsonBeanFactory
另一种方式:将 Json 转换为字典后再映射到对象实体字段里,这样使用时就可以直接使用对应实体类对象。
FlutterJsonBean就是以这种方式解析,并自动生成映射代码。
使用AndroidStudio开发,在Setting->Tools插件市场中搜索FlutterJsonBeanFactory,即可安转。
并且可以在->FlutterJsonBeanFactory里边自定义实体类的后缀,默认是entity。
2. 创建实体类
创建一个存放Model的目录,在其上点击右键,再选择看到的JsonToDartBeanAction:
JsonToDartBeanActionClass Name
是实体名字,会默认加上entity
JSON Text
Json文本
null-able
勾选后所有属性都是可空的?
,不勾选都会加上late,延迟初始化
事先复制要解析的json到粘贴板,并粘贴至下面的 “Json Text” 中:
创建实体类执行Make后生成代码目录如下:
-
models
项目自建,存放实体 -
generated/json
是插件生成目录 -
xx_entity.g.daet
是实体类生成的辅助类方法 -
base
是存放基础公共代码
2.1 xx_entity.dart
@JsonSerializable()
class UserEntity {
String? id;
String? name;
String? age;
UserEntity();
factory UserEntity.fromJson(Map<String, dynamic> json) =>
$UserEntityFromJson(json);
Map<String, dynamic> toJson() => $UserEntityToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
其中已生成了fromJson
的工厂方法和toJson
方法,分别调用了xx_entity.g.dart
中的$xxEntityFromJson
方法和$xxEntityToJson
方法。toSring
方法会把对象转json字符串显示。
注:若是修改了实体类
xx_entity.dart
,鼠标悬停在gernerated
目录上,执行Alt+J快捷键,就会自动生成新的映射代码,并且去除多余的。
2.2 xx_entity.g.dart
UserEntity $UserEntityFromJson(Map<String, dynamic> json) {
final UserEntity userEntity = UserEntity();
final String? id = jsonConvert.convert<String>(json['id']);
if (id != null) {
userEntity.id = id;
}
final String? user = jsonConvert.convert<String>(json['user']);
if (user != null) {
userEntity.user = user;
}
final String? age = jsonConvert.convert<String>(json['age']);
if (age != null) {
userEntity.age = age;
}
final int? sex = jsonConvert.convert<int>(json['sex']);
if (sex != null) {
userEntity.sex = sex;
}
return userEntity;
}
Map<String, dynamic> $UserEntityToJson(UserEntity entity) {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = entity.id;
data['user'] = entity.user;
data['age'] = entity.age;
data['sex'] = entity.sex;
return data;
}
实体类对应的辅助方法文件,g.dart
是文件的后缀,存放在generated/json
目录下。
主要包含$xxEntityFromJson
和$xxEntityToJson
方法,$
+实体类名
为前缀。
$xxFromJson
将 Json 数据的对应字段取出来然后赋值给实体类的对应字段。Json 数据转换为实体字段使用了 jsonConvert.convert 其定义在 json_convert_content.dart 中。
$xxToJson
将实体数据转换为 Map 字典。
2.3 json_convert_content.dart
JsonConvert jsonConvert = JsonConvert();
typedef JsonConvertFunction<T> = T Function(Map<String, dynamic> json);
class JsonConvert {
static final Map<String, JsonConvertFunction> _convertFuncMap = {
(UserEntity).toString(): UserEntity.fromJson,
};
T? convert<T>(dynamic value) {...}
List<T?>? convertList<T>(List<dynamic>? value) {...}
List<T>? convertListNotNull<T>(dynamic value) {...}
T? asT<T extends Object?>(dynamic value) {...}
//list is returned by type
static M? _getListChildType<M>(List<Map<String, dynamic>> data) {...}
static M? fromJsonAsT<M>(dynamic json) {...}
}
json_convert_content.dart
为JsonConvert
类, 用于统一进行 Json 与实体类的转换。
convert
T? convert<T>(dynamic value) {
if (value == null) {
return null;
}
return asT<T>(value);
}
将json数据转换为实体对象。首先判断了传入的数据是否为null
,为null
则直接返回null
, 不为空则调用asT
方法。在生成的.g.dart
的$xxEntityFromJson
方法中非 List 类型字段基本都是调用 convert 方法进行转换。
convertList
List<T?>? convertList<T>(List<dynamic>? value) {
if (value == null) {
return null;
}
try {
return value.map((dynamic e) => asT<T>(e)).toList();
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
return <T>[];
}
}
将json数据转换为实体对象List。首先也是判断了传入的数据是否为null
,为null
则直接返回null
, 不为空则遍历value
使用map
调用asT
方法进行转换,最终还是调用的asT
方法。在转换上加了try-catch
,如果报错则返回空的List
。
converListNotNull
List<T>? convertListNotNull<T>(dynamic value) {
if (value == null) {
return null;
}
try {
return (value as List<dynamic>).map((dynamic e) => asT<T>(e)!).toList();
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
return <T>[];
}
}
与 convertList
的区别是参数不一样,convertList
参数传入的是List<dynamic>
而convertListNotNull
传入的直接是dynamic
。其次最大的区别是调用asT
方法时convertListNotNull
在 asT 后面加了一个 !
,表示不为空。
当在实体类里定义字段为List
类型时,会根据是否为List
中元素的非空类型而选择生成 convertList
或 convertListNotNull
来进行转换,非空采用convertListNotNull
,可空采用convertList
-
List<String?>? foodList1;
采用convertList
-
List<String>? foodList2;
采用convertListNotNull
-
late List<String?> foodList3;
采用convertList
-
late List<String> foodList4;
采用convertListNotNull
as<T>
T? asT<T extends Object?>(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;
}
}
首先判断传入的数据类型是否为要转换的数据类型,如果是的话就直接返回传入参数,即如果要将传入数据转换为User
,但是传入参数本身就是User
类型,那就直接返回。
然后通过 T.String()
获取泛型类型的名称,再与String
、int
、double
、DateTime
、bool
这些基础数据类型进行比较,如果是这些类型则调用这些类型的转换方法进行转换。
最后,如果不是基础类型则去_convertFuncMap
寻找其它的实体类型,并调用其value,即fromJson方法,类似递归的去解析。
fromJsonAsT
static M? fromJsonAsT<M>(dynamic json) {
if (json is List) {
return _getListChildType<M>(
json.map((e) => e as Map<String, dynamic>).toList());
} else {
return jsonConvert.asT<M>(json);
}
}
判断传入Json
数据是否为null
,为null
则直接返回null
。然后判断Json
数据是否为List
,是 List
则调用_getListChildType
否则通过全局变量jsonConvert
调用asT
。
_getListChildType
static M? _getListChildType<M>(List<Map<String, dynamic>> data) {
if (<UserEntity>[] is M) {
return data
.map<UserEntity>((Map<String, dynamic> e) => UserEntity.fromJson(e))
.toList() as M;
}
debugPrint("${M.toString()} not found");
return null;
}
<UserEntity>[] is M
直接创建对应实体类的空 List 判断是否为泛型类型,如果类型相同,则通过map
调用对应实体类的 fromJson
方法进行转换。
3. json_field.dart
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});
}
JsonSerializable
类注解,二次生成代码时插件查找该注解的类进行生成。
JSONField
字段注解,用于自定义字段映射和配置是否序列化和反序列化字段。
4. 使用流程
单实体解析
直接调用实体类对应的fromJson
方法即可将 Json 数据解析为实体对象。
UserEntity? user;
String userData = """
{
"id":"1",
"name":"qi",
"age":22
}
""";
user = UserEntity.fromJson(jsonDecode(userData));
调用生成的JsonConvert去解析,使用convert
、asT
、fromJsonAsT
都能得到结果
user = jsonConvert.convert<UserEntity>(jsonDecode(userData));
user = jsonConvert.asT<UserEntity>(jsonDecode(userData));
user = JsonConvert.fromJsonAsT<UserEntity>(jsonDecode(userData));
List解析
解析 Json List 数据则需要调用 JsonConvert
的对应方法进行解析,除了使用上面的 convert
、asT
、fromJsonAsT
外,还可以使用 convertList
、convertListNotNull
List<UserEntity>? users;
List<UserEntity?>? userNulls;
users = jsonConvert.convert<List<UserEntity>>(jsonDecode(userData));
users = jsonConvert.asT<List<UserEntity>>(jsonDecode(userData));
users = JsonConvert.fromJsonAsT<List<UserEntity>>(jsonDecode(userData));
users = jsonConvert.convertListNotNull<UserEntity>(jsonDecode(userData));
userNulls = jsonConvert.convertList<UserEntity>(jsonDecode(userData));
convertList
、convertListNotNull
与 convert
、asT
、fromJsonAsT
的区别在于前者的泛型为 List Item元素的泛型类型,后者则直接为对应 List 的类型。如上面 convertList
、convertListNotNull
的泛型直接为UserEntity
, 而 convert
、asT
、fromJsonAsT
的泛型为List<UserEntity>
。
JSONField的使用
- 自定义字段名
处理Json数据字段和实体属性字段不一致的问题,如后台返回Json命名不规范这种情况。可以用JSONField
自定义字段映射。如后台返回 AGE
就可以如下使用,映射成age
。加完之后照样需要执行Alt
+J
@JSONField(name: "AGE")
String? age;
- 忽略字段
JSONField 还有两个字段 serialize
、deserialize
用于序列化和反序列化时忽略某个字段。
5. 解析流程优化
服务端返回的数据一般是经过了一层包装的,如下:
{
"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<String, dynamic> json) => $ApiResponseEntityFromJson(json);
Map<String, dynamic> toJson() => $ApiResponseEntityToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
@JsonSerializable()
class ApiResponseData {
String? id;
String? name;
int? age;
ApiResponseData();
factory ApiResponseData.fromJson(Map<String, dynamic> json) => $ApiResponseDataFromJson(json);
Map<String, dynamic> 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<T> {
late int code;
late String message;
late T data;
ApiResponseEntity();
factory ApiResponseEntity.fromJson(Map<String, dynamic> json) =>
$ApiResponseEntityFromJson<T>(json);
Map<String, dynamic> toJson() => $ApiResponseEntityToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
ApiResponseEntity<T> $ApiResponseEntityFromJson<T>(Map<String, dynamic> json) {
final ApiResponseEntity<T> apiResponseEntity = ApiResponseEntity<T>();
final int? code = jsonConvert.convert<int>(json['code']);
if (code != null) {
apiResponseEntity.code = code;
}
final String? message = jsonConvert.convert<String>(json['message']);
if (message != null) {
apiResponseEntity.message = message;
}
final T data = jsonConvert.convert<dynamic>(json['data']);
if (data != null) {
apiResponseEntity.data = data;
}
return apiResponseEntity;
}
Map<String, dynamic> $ApiResponseEntityToJson(ApiResponseEntity entity) {
final Map<String, dynamic> data = <String, dynamic>{};
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<T>(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://www.jianshu.com/p/435ca449da41
https://juejin.cn/post/7043721908801503269
https://github.com/DachengWang/flutter_app_core
网友评论