Flutter 使用的 Dart 語言沒有反射,無法像 Java 一樣通過反射直接將 Json 數(shù)據(jù)映射為對應(yīng)的對象實體類對象。官方解決方案是將 Json 數(shù)據(jù)轉(zhuǎn)換為字典,然后從字典中進行取數(shù)使用。但直接從字典中取數(shù)很不方便,寫代碼時沒有自動提示很不友好,而且可能在寫的時候?qū)戝e字段名。
1. FlutterJsonBeanFactory
另一種方式:將 Json 轉(zhuǎn)換為字典后再映射到對象實體字段里,這樣使用時就可以直接使用對應(yīng)實體類對象。
FlutterJsonBean就是以這種方式解析,并自動生成映射代碼。
使用AndroidStudio開發(fā),在Setting->Tools插件市場中搜索FlutterJsonBeanFactory,即可安轉(zhuǎn)。
并且可以在->FlutterJsonBeanFactory里邊自定義實體類的后綴,默認是entity。
2. 創(chuàng)建實體類
創(chuàng)建一個存放Model的目錄,在其上點擊右鍵,再選擇看到的JsonToDartBeanAction:

Class Name是實體名字,會默認加上entity
JSON TextJson文本
null-able勾選后所有屬性都是可空的?,不勾選都會加上late,延遲初始化
事先復(fù)制要解析的json到粘貼板,并粘貼至下面的 “Json Text” 中:

執(zhí)行Make后生成代碼目錄如下:
-
models項目自建,存放實體 -
generated/json是插件生成目錄 -
xx_entity.g.daet是實體類生成的輔助類方法 -
base是存放基礎(chǔ)公共代碼

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方法,分別調(diào)用了xx_entity.g.dart中的$xxEntityFromJson方法和$xxEntityToJson方法。toSring方法會把對象轉(zhuǎn)json字符串顯示。
注:若是修改了實體類
xx_entity.dart,鼠標(biāo)懸停在gernerated目錄上,執(zhí)行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;
}
實體類對應(yīng)的輔助方法文件,g.dart是文件的后綴,存放在generated/json目錄下。
主要包含$xxEntityFromJson和$xxEntityToJson方法,$+實體類名為前綴。
$xxFromJson 將 Json 數(shù)據(jù)的對應(yīng)字段取出來然后賦值給實體類的對應(yīng)字段。Json 數(shù)據(jù)轉(zhuǎn)換為實體字段使用了 jsonConvert.convert 其定義在 json_convert_content.dart 中。
$xxToJson將實體數(shù)據(jù)轉(zhuǎn)換為 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類, 用于統(tǒng)一進行 Json 與實體類的轉(zhuǎn)換。
convert
T? convert<T>(dynamic value) {
if (value == null) {
return null;
}
return asT<T>(value);
}
將json數(shù)據(jù)轉(zhuǎn)換為實體對象。首先判斷了傳入的數(shù)據(jù)是否為null,為null則直接返回null, 不為空則調(diào)用asT方法。在生成的.g.dart的$xxEntityFromJson方法中非 List 類型字段基本都是調(diào)用 convert 方法進行轉(zhuǎn)換。
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數(shù)據(jù)轉(zhuǎn)換為實體對象List。首先也是判斷了傳入的數(shù)據(jù)是否為null ,為null則直接返回null , 不為空則遍歷value使用map調(diào)用asT方法進行轉(zhuǎn)換,最終還是調(diào)用的asT方法。在轉(zhuǎn)換上加了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的區(qū)別是參數(shù)不一樣,convertList參數(shù)傳入的是List<dynamic>而convertListNotNull傳入的直接是dynamic。其次最大的區(qū)別是調(diào)用asT方法時convertListNotNull在 asT 后面加了一個 !,表示不為空。
當(dāng)在實體類里定義字段為List類型時,會根據(jù)是否為List中元素的非空類型而選擇生成 convertList或 convertListNotNull來進行轉(zhuǎn)換,非空采用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;
}
}
首先判斷傳入的數(shù)據(jù)類型是否為要轉(zhuǎn)換的數(shù)據(jù)類型,如果是的話就直接返回傳入?yún)?shù),即如果要將傳入數(shù)據(jù)轉(zhuǎn)換為User,但是傳入?yún)?shù)本身就是User類型,那就直接返回。
然后通過 T.String()獲取泛型類型的名稱,再與String、int、double、DateTime、bool這些基礎(chǔ)數(shù)據(jù)類型進行比較,如果是這些類型則調(diào)用這些類型的轉(zhuǎn)換方法進行轉(zhuǎn)換。
最后,如果不是基礎(chǔ)類型則去_convertFuncMap尋找其它的實體類型,并調(diào)用其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數(shù)據(jù)是否為null,為null則直接返回null。然后判斷Json數(shù)據(jù)是否為List,是 List則調(diào)用_getListChildType否則通過全局變量jsonConvert調(diào)用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直接創(chuàng)建對應(yīng)實體類的空 List 判斷是否為泛型類型,如果類型相同,則通過map調(diào)用對應(yīng)實體類的 fromJson方法進行轉(zhuǎn)換。
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. 使用流程
單實體解析
直接調(diào)用實體類對應(yīng)的fromJson方法即可將 Json 數(shù)據(jù)解析為實體對象。
UserEntity? user;
String userData = """
{
"id":"1",
"name":"qi",
"age":22
}
""";
user = UserEntity.fromJson(jsonDecode(userData));
調(diào)用生成的JsonConvert去解析,使用convert、asT、fromJsonAsT都能得到結(jié)果
user = jsonConvert.convert<UserEntity>(jsonDecode(userData));
user = jsonConvert.asT<UserEntity>(jsonDecode(userData));
user = JsonConvert.fromJsonAsT<UserEntity>(jsonDecode(userData));
List解析
解析 Json List 數(shù)據(jù)則需要調(diào)用 JsonConvert 的對應(yīng)方法進行解析,除了使用上面的 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 的區(qū)別在于前者的泛型為 List Item元素的泛型類型,后者則直接為對應(yīng) List 的類型。如上面 convertList、convertListNotNull的泛型直接為UserEntity , 而 convert 、asT、fromJsonAsT 的泛型為List<UserEntity> 。
JSONField的使用
- 自定義字段名
處理Json數(shù)據(jù)字段和實體屬性字段不一致的問題,如后臺返回Json命名不規(guī)范這種情況??梢杂肑SONField
自定義字段映射。如后臺返回 AGE就可以如下使用,映射成age。加完之后照樣需要執(zhí)行Alt+J
@JSONField(name: "AGE")
String? age;
- 忽略字段
JSONField 還有兩個字段 serialize 、deserialize 用于序列化和反序列化時忽略某個字段。
5. 解析流程優(yōu)化
服務(wù)端返回的數(shù)據(jù)一般是經(jīng)過了一層包裝的,如下:
{
"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);
}
}
如果像上面??這樣,每個接口數(shù)據(jù)實體都要有一個ResponseEntity,使用起來不便于統(tǒng)一封裝。
所以我們可以把ApiResponseData換成 dynamic,文件底部的ApiResponseData信息也全部刪除,再執(zhí)行Alt+J,這樣就會自動清理掉整理json_convert_content.dart和api_response_entity.g.dart中的ApiResponseData痕跡。再把dynamic替換成T,并且去除頂部的@JsonSerializable(),避免下次執(zhí)行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目錄,因為那個目錄會自動刪除無用的文件??梢院?code>api_reponse_entity.dart單獨存放在一個文件夾當(dāng)中。
優(yōu)化后使用
第一次發(fā)現(xiàn),reponse的data是null。因為新的插件在 asT方法沒有去調(diào)用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
}
]
}
""";
//基礎(chǔ)數(shù)據(jù)類型
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();
}
參考鏈接:
http://www.itdecent.cn/p/435ca449da41
https://juejin.cn/post/7043721908801503269
https://github.com/DachengWang/flutter_app_core