[Flutter] 08-Flutter中的Json轉(zhuǎn)Model

背景: 在開(kāi)發(fā)中,服務(wù)端通常返回Json數(shù)據(jù)容为,我們需要將Json數(shù)據(jù)轉(zhuǎn)模型對(duì)象來(lái)使用壁却。一般情況下,我們會(huì)使用一些第三方庫(kù)來(lái)動(dòng)態(tài)轉(zhuǎn)化Model阳懂,但是Flutter中沒(méi)有像Java的Gson/Jackson這類(lèi)Json序列化類(lèi)庫(kù)梅尤,因?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的反射功能钝腺,而正因如此也就無(wú)法實(shí)現(xiàn)動(dòng)態(tài)轉(zhuǎn)化Model的功能抛姑。

在此基礎(chǔ)上,接下來(lái)我們看下Flutter中還有哪幾種Json轉(zhuǎn)模型的方式:

一. 手動(dòng)轉(zhuǎn)化

在上篇[Flutter] 07-Flutter中反序列化Json已經(jīng)通過(guò)6個(gè)示例分析過(guò)了, 這里不再討論艳狐。

二. json_serializable

json_serializable是dart官方推薦和提供的JSON轉(zhuǎn)Model的方式:

  • 一個(gè)自動(dòng)化源代碼生成器來(lái)為你生成 JSON 序列化數(shù)據(jù)模板定硝;
  • 由于序列化數(shù)據(jù)代碼不再需要手動(dòng)編寫(xiě)或者維護(hù),你可以將序列化 JSON 數(shù)據(jù)在運(yùn)行時(shí)的異常風(fēng)險(xiǎn)降到最低僵驰;

第1步:添加相關(guān)的依賴(lài)

依賴(lài)分為項(xiàng)目依賴(lài)(dependencies)喷斋,開(kāi)發(fā)依賴(lài)(dev_dependencies),在pubspec.yaml中添加如下依賴(lài):

dependencies:
  json_annotation:^3.0.1

dev_dependencies:
  json_serializable:^3.2.5
  build_runner:^1.8.0
  • 注意:添加后需要執(zhí)行flutter pub get確保我們的項(xiàng)目中有這些依賴(lài)蒜茴。
  • 注意:yaml配置文件對(duì)于縮進(jìn)要求十分嚴(yán)格星爪,下面的build_runnerjson_serializable應(yīng)該是與flutter_test平級(jí)的,千萬(wàn)不要寫(xiě)在flutter_test縮進(jìn)后粉私,這樣它會(huì)認(rèn)為這兩個(gè)是flutter_test的子集目錄顽腾!

由于很多朋友在這一步遇到了問(wèn)題,這里貼出源碼:

第2步:以json_serializable 的方式創(chuàng)建模型類(lèi)

  • 根據(jù)下面簡(jiǎn)單Json數(shù)據(jù)創(chuàng)建模型類(lèi):
final jsonInfo = {
  "nickname": "coderTao",
  "age": 20,
  "courses": ["政治", "高數(shù)", "英語(yǔ)"],
  "register_date": "2018-2-22",
  "computer": {
    "brand": "MackBook",
    "price": 9999
  }
};
  • User類(lèi)的代碼:
// 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è)類(lèi)是需要生成Model類(lèi)的
@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方法不是必須的, 只是用測(cè)試數(shù)據(jù)
  @override
  String toString() {
    return'User{name: $name, age: ${age}, registerDate: $registerDate, courses: $courses, computer: $computer}';
  }
}
  • Computer類(lèi)的代碼:
// 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è)類(lèi)需要進(jìn)行轉(zhuǎn)換生成Model類(lèi)
@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方法不是必須的, 只是用測(cè)試數(shù)據(jù)
  @override
  String toString() {
    return'Computer{brand: $brand, price: $price}';
  }
}

最后總結(jié)一下以json_serializable 的方式創(chuàng)建模型類(lèi)必須5步:

  • 1.import 導(dǎo)入json_annotation.dart窖杀。
import 'package:json_annotation/json_annotation.dart';
  • 2.json_serializable根據(jù)當(dāng)前類(lèi)漓摩,以part 類(lèi)名.g.dart格式生成的文件。
    以u(píng)ser.dart為例如下:
part 'user.g.dart';
  • 3.在class上標(biāo)注 @JsonSerializable() 告訴json_serializable哪一個(gè)類(lèi)需要進(jìn)行轉(zhuǎn)換生成Model類(lèi)入客。
  • 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í)體類(lèi)對(duì)象膀钠。我們就可以使用這個(gè)方法掏湾,將存有json數(shù)據(jù)的map轉(zhuǎn)化為我們需要的實(shí)體類(lèi)對(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方法不是必須的耸采,只用來(lái)打印輸出進(jìn)行測(cè)試。

第3步:生成對(duì)應(yīng)的.g.dart文件指令

該操作有兩種指令:一次性生成指令和 持續(xù)性生成指令工育。

一次性生成指令

在項(xiàng)目終端運(yùn)行下面的指令:

flutter pub run build_runner build
  • 該指令是一次性生成JSON序列化的代碼虾宇。 該指令通過(guò)我們的源文件,找出需要生成Model類(lèi)的源文件(包含@JsonSerializable標(biāo)注的)來(lái)生成對(duì)應(yīng)的.g.dart文件如绸。建議將所有Model類(lèi)放在一個(gè)單獨(dú)的目錄下嘱朽,然后在該目錄下執(zhí)行命令。

持續(xù)性生成指令

如果感覺(jué)每次更改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步:測(cè)試并打印

final jsonInfo = {
  "nickname": "coderTao",
  "age": 20,
  "courses": ["政治", "高數(shù)", "英語(yǔ)"],
  "register_date": "2018-2-22",
  "computer": {
    "brand": "MackBook",
    "price": 9999
  }
};

final user = User.fromJson(jsonInfo);
print(user);

三. 網(wǎng)頁(yè)轉(zhuǎn)換

app.quicktype.io 是一個(gè)將JSON轉(zhuǎn)換成模型類(lèi)的工具網(wǎng)站瓦侮,目前來(lái)看支持大部分常用語(yǔ)言艰赞,并且靈活的可選項(xiàng)也非常多:

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

四. 編輯器插件

目前Android Studio(或IntelliJ)有幾個(gè)插件吁断,可以將json文件轉(zhuǎn)成Model類(lèi),但插件質(zhì)量參差不齊坞生,甚至還有一些有抄襲嫌疑,故筆者在此不做優(yōu)先推薦掷伙,讀者有興趣可以自行了解是己。

Json轉(zhuǎn)Model幾種方式總結(jié):

  • 手動(dòng)序列化JSON:比較麻煩,效率低任柜,但新手還是多做嘗試和了解比較好卒废。
  • json_serializable:效率高沛厨,watch很好用。
  • 工具網(wǎng)站:效率高摔认,更多功能可選逆皮。

總體推薦使用后兩種,可以大大提升開(kāi)發(fā)效率参袱,不用埋頭去搞一些重復(fù)的序列化工作电谣。

由于筆者水平有限,文中如果有錯(cuò)誤的地方抹蚀,或者有更好的方法剿牺,還望大神指出。
附上本文的所有 demo 下載鏈接环壤,【GitHub】晒来。
如果你看完后覺(jué)得對(duì)你有所幫助,還望在 GitHub 上點(diǎn)個(gè) star郑现。贈(zèng)人玫瑰湃崩,手有余香。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末接箫,一起剝皮案震驚了整個(gè)濱河市攒读,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌列牺,老刑警劉巖整陌,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瞎领,居然都是意外死亡泌辫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)九默,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)震放,“玉大人,你說(shuō)我怎么就攤上這事驼修〉钏欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵乙各,是天一觀的道長(zhǎng)墨礁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)耳峦,這世上最難降的妖魔是什么恩静? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上驶乾,老公的妹妹穿的比我還像新娘邑飒。我一直安慰自己,他們只是感情好级乐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布疙咸。 她就那樣靜靜地躺著,像睡著了一般风科。 火紅的嫁衣襯著肌膚如雪撒轮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天丐重,我揣著相機(jī)與錄音腔召,去河邊找鬼。 笑死扮惦,一個(gè)胖子當(dāng)著我的面吹牛臀蛛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播崖蜜,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼浊仆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了豫领?” 一聲冷哼從身側(cè)響起抡柿,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎等恐,沒(méi)想到半個(gè)月后洲劣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡课蔬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年囱稽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片二跋。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡战惊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扎即,到底是詐尸還是另有隱情吞获,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布谚鄙,位于F島的核電站各拷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏闷营。R本人自食惡果不足惜烤黍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚊荣,春花似錦、人聲如沸莫杈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)筝闹。三九已至媳叨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間关顷,已是汗流浹背糊秆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留议双,地道東北人痘番。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像平痰,于是被迫代替她去往敵國(guó)和親汞舱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355