背景: 在開發(fā)中,服務(wù)端通常返回Json數(shù)據(jù),我們需要將Json數(shù)據(jù)轉(zhuǎn)模型對(duì)象來使用。一般情況下,我們會(huì)使用一些第三方庫來動(dòng)態(tài)轉(zhuǎn)化Model,但是Flutter中沒有像Java的Gson/Jackson這類Json序列化類庫,因?yàn)镕lutter中禁用運(yùn)行時(shí)反射。官方解釋是運(yùn)行時(shí)反射會(huì)干擾Dart的
tree shaking,使用tree shaking可以在release版中去除未使用的代碼,這可以顯著優(yōu)化應(yīng)用程序的大小。由于反射會(huì)默認(rèn)應(yīng)用到所有代碼,因此tree shaking會(huì)很難工作,因?yàn)樵趩⒂梅瓷鋾r(shí)很難知道哪些代碼未被使用,因此冗余代碼很難剝離,所以Flutter中禁用了Dart的反射功能,而正因如此也就無法實(shí)現(xiàn)動(dòng)態(tài)轉(zhuǎn)化Model的功能。
在此基礎(chǔ)上,接下來我們看下Flutter中還有哪幾種Json轉(zhuǎn)模型的方式:
一. 手動(dòng)轉(zhuǎn)化
在上篇[Flutter] 07-Flutter中反序列化Json已經(jīng)通過6個(gè)示例分析過了, 這里不再討論。
二. json_serializable
json_serializable是dart官方推薦和提供的JSON轉(zhuǎn)Model的方式:
- 一個(gè)自動(dòng)化源代碼生成器來為你生成 JSON 序列化數(shù)據(jù)模板;
- 由于序列化數(shù)據(jù)代碼不再需要手動(dòng)編寫或者維護(hù),你可以將序列化 JSON 數(shù)據(jù)在運(yùn)行時(shí)的異常風(fēng)險(xiǎn)降到最低;
第1步:添加相關(guān)的依賴
依賴分為項(xiàng)目依賴(dependencies),開發(fā)依賴(dev_dependencies),在pubspec.yaml中添加如下依賴:
dependencies:
json_annotation:^3.0.1
dev_dependencies:
json_serializable:^3.2.5
build_runner:^1.8.0
- 注意:添加后需要執(zhí)行
flutter pub get確保我們的項(xiàng)目中有這些依賴。 - 注意:yaml配置文件對(duì)于縮進(jìn)要求十分嚴(yán)格,下面的
build_runner和json_serializable應(yīng)該是與flutter_test平級(jí)的,千萬不要寫在flutter_test縮進(jìn)后,這樣它會(huì)認(rèn)為這兩個(gè)是flutter_test的子集目錄!
由于很多朋友在這一步遇到了問題,這里貼出源碼:

第2步:以json_serializable 的方式創(chuàng)建模型類
- 根據(jù)下面簡單Json數(shù)據(jù)創(chuàng)建模型類:
final jsonInfo = {
"nickname": "coderTao",
"age": 20,
"courses": ["政治", "高數(shù)", "英語"],
"register_date": "2018-2-22",
"computer": {
"brand": "MackBook",
"price": 9999
}
};
- User類的代碼:
// 1.import 導(dǎo)入json_annotation.dart
import 'package:json_annotation/json_annotation.dart';
import 'computer_model.dart';
// 2.user.g.dart 將在我們運(yùn)行生成命令后json_serializable幫我們自動(dòng)生成.g.dart文件,在未執(zhí)行命令前該行可能會(huì)報(bào)錯(cuò)
part 'user_model.g.dart';
// 3.這個(gè)標(biāo)注是告訴生成器,這個(gè)類是需要生成Model類的
@JsonSerializable()
class User {
String name;
int age;
//顯式關(guān)聯(lián)JSON字段名與Model屬性的對(duì)應(yīng)關(guān)系,
// 如下將屬性registerDate和register_date字段關(guān)聯(lián)
@JsonKey(name: "register_date")
String registerDate;
List<String> courses;
Computer computer;
// 4.必須的構(gòu)造方法
User(this.name, this.age, this.registerDate, this.courses, this.computer);
// 5.必須有的對(duì)應(yīng)工廠構(gòu)造器
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
//這里 toString方法不是必須的, 只是用測試數(shù)據(jù)
@override
String toString() {
return'User{name: $name, age: ${age}, registerDate: $registerDate, courses: $courses, computer: $computer}';
}
}
- Computer類的代碼:
// 1.import 導(dǎo)入json_annotation.dart
import 'package:json_annotation/json_annotation.dart';
// 2.computer.g.dart 將在我們運(yùn)行生成命令后json_serializable幫我們自動(dòng)生成.g.dart文件,在未執(zhí)行命令前該行可能會(huì)報(bào)錯(cuò)
part 'computer.g.dart';
// 3.這個(gè)標(biāo)注告訴json_serializable哪一個(gè)類需要進(jìn)行轉(zhuǎn)換生成Model類
@JsonSerializable()
class Computer {
String brand;
double price;
//4.必須的構(gòu)造方法
Computer(this.brand, this.price);
//5.必須有的對(duì)應(yīng)工廠構(gòu)造器
factory Computer.fromJson(Map<String, dynamic> json) => _$ComputerFromJson(json);
Map<String, dynamic> toJson() => _$ComputerToJson(this);
//這里 toString方法不是必須的, 只是用測試數(shù)據(jù)
@override
String toString() {
return'Computer{brand: $brand, price: $price}';
}
}
最后總結(jié)一下以json_serializable 的方式創(chuàng)建模型類必須5步:
- 1.import 導(dǎo)入
json_annotation.dart。
import 'package:json_annotation/json_annotation.dart';
- 2.json_serializable根據(jù)當(dāng)前類,以
part 類名.g.dart格式生成的文件。
以u(píng)ser.dart為例如下:
part 'user.g.dart';
- 3.在class上標(biāo)注
@JsonSerializable()告訴json_serializable哪一個(gè)類需要進(jìn)行轉(zhuǎn)換生成Model類。 - 4.創(chuàng)建必須的構(gòu)造方法。
- 5.創(chuàng)建必須的對(duì)應(yīng)的工廠構(gòu)造器。
備注1:
第五步實(shí)際就是創(chuàng)建兩個(gè)方法:
- 提供一個(gè)工廠構(gòu)造方法User.fromJson,該方法實(shí)際調(diào)用生成文件的UserFromJson方法進(jìn)行反序列化。
- 提供一個(gè)toJson()序列化對(duì)象的方法,實(shí)際調(diào)用生成文件的_$UserToJson()方法,并將調(diào)用對(duì)象解析生成Map<String ,dynamic>。
備注2:
-
_$UserFromJson(json): 它接收了一個(gè)map:Map<String, dynamic>,并將這個(gè)Map里的值映射為我們所需要的實(shí)體類對(duì)象。我們就可以使用這個(gè)方法,將存有json數(shù)據(jù)的map轉(zhuǎn)化為我們需要的實(shí)體類對(duì)象。 -
_$UserToJson(this): 將調(diào)用此方法的對(duì)象直接根據(jù)字段映射成Map。
而這兩個(gè)都是私有方法,part讓兩個(gè)文件共享作用域與命名空間,所以我們需要將生成的方法暴露給外部。
備注3:
UserFromJson(json)和 ToJson()調(diào)用方法,在未執(zhí)行生成對(duì)應(yīng)的.g.dart文件指令前該行可能會(huì)報(bào)錯(cuò)。

part 'computer.g.dart';和 part 'user.g.dart'; ,在未執(zhí)行生成對(duì)應(yīng)的.g.dart文件指令前該行可能會(huì)報(bào)錯(cuò)。

備注4:
toString方法不是必須的,只用來打印輸出進(jìn)行測試。
第3步:生成對(duì)應(yīng)的.g.dart文件指令
該操作有兩種指令:一次性生成指令和 持續(xù)性生成指令。
一次性生成指令
在項(xiàng)目終端運(yùn)行下面的指令:
flutter pub run build_runner build
- 該指令是一次性生成JSON序列化的代碼。 該指令通過我們的源文件,找出需要生成Model類的源文件(包含@JsonSerializable標(biāo)注的)來生成對(duì)應(yīng)的.g.dart文件。建議將所有Model類放在一個(gè)單獨(dú)的目錄下,然后在該目錄下執(zhí)行命令。
持續(xù)性生成指令
如果感覺每次更改Model時(shí)都需要執(zhí)行一次性生成指令比較繁瑣,這時(shí)可以使用下面的持續(xù)生成指令:
flutter pub run build_runner watch
在項(xiàng)目根目錄下運(yùn)行該指令后會(huì)啟動(dòng)觀察器, 觀察器可以監(jiān)視我們項(xiàng)目中文件的變化,并在需要時(shí)自動(dòng)構(gòu)建必要的文件。只需啟動(dòng)一次觀察器,然后它就會(huì)在后臺(tái)運(yùn)行,這種方式也很安全。
第4步:測試并打印
final jsonInfo = {
"nickname": "coderTao",
"age": 20,
"courses": ["政治", "高數(shù)", "英語"],
"register_date": "2018-2-22",
"computer": {
"brand": "MackBook",
"price": 9999
}
};
final user = User.fromJson(jsonInfo);
print(user);
三. 網(wǎng)頁轉(zhuǎn)換
app.quicktype.io 是一個(gè)將JSON轉(zhuǎn)換成模型類的工具網(wǎng)站,目前來看支持大部分常用語言,并且靈活的可選項(xiàng)也非常多:

優(yōu)點(diǎn): 這種方式操作起來會(huì)比使用json_serializable操作起來更簡便一些,并且?guī)聞澗€字段會(huì)自動(dòng)轉(zhuǎn)換為駝峰命名的屬性名。
缺點(diǎn): 如果數(shù)據(jù)過于復(fù)雜的話,在生成的時(shí)候可能會(huì)少了某一個(gè)類,并且不能進(jìn)行父類抽取。

四. 編輯器插件
目前Android Studio(或IntelliJ)有幾個(gè)插件,可以將json文件轉(zhuǎn)成Model類,但插件質(zhì)量參差不齊,甚至還有一些有抄襲嫌疑,故筆者在此不做優(yōu)先推薦,讀者有興趣可以自行了解。
Json轉(zhuǎn)Model幾種方式總結(jié):
- 手動(dòng)序列化JSON:比較麻煩,效率低,但新手還是多做嘗試和了解比較好。
- json_serializable:效率高,watch很好用。
- 工具網(wǎng)站:效率高,更多功能可選。
總體推薦使用后兩種,可以大大提升開發(fā)效率,不用埋頭去搞一些重復(fù)的序列化工作。
由于筆者水平有限,文中如果有錯(cuò)誤的地方,或者有更好的方法,還望大神指出。
附上本文的所有 demo 下載鏈接,【GitHub】。
如果你看完后覺得對(duì)你有所幫助,還望在 GitHub 上點(diǎn)個(gè) star。贈(zèng)人玫瑰,手有余香。