flutter中的JSON和序列化方法及使用詳解
引言
很難想象一款移動(dòng)應(yīng)用程序不需要與web服務(wù)器通信,也不需要存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)。在開發(fā)一款網(wǎng)絡(luò)連接的應(yīng)用程序時(shí),它遲早會(huì)需要使用一些JSON。
這里簡單介紹一下JSON在flutter中的使用。
Tips:
編碼和序列化是將數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為字符串的同一件事。解碼和反序列化是將字符串轉(zhuǎn)換為數(shù)據(jù)結(jié)構(gòu)的相反過程。然而,序列化通常也指將數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為更易于閱讀的格式的整個(gè)過程。
哪種JSON序列化方法適合
這里主要簡單介紹兩種序列化方式:
- 手動(dòng)序列化
- 使用代碼自動(dòng)序列化
不同的項(xiàng)目復(fù)雜度以及用例都不同,對于一些較小的項(xiàng)目或者類似原型的的應(yīng)用,使用代碼生成可能有些大材小用,而對于有很多不同json模型的應(yīng)用程序,使用手動(dòng)序列化則除了無聊之外,有可能會(huì)產(chǎn)生不必要的問題和麻煩。
手動(dòng)進(jìn)行序列化
手動(dòng)進(jìn)行json解碼說的是使用dart:convert內(nèi)置的json解碼器,通過將原始的json數(shù)據(jù)傳遞給jsonDecode()方法,然后在返回的Map<String, dynamic>這個(gè)類型的數(shù)據(jù)中我們可以找到我們想用的數(shù)據(jù)。不需要?jiǎng)e的依賴和其他的設(shè)置過程,對于驗(yàn)證一些快速的原型或者小型的項(xiàng)目非常有效。
當(dāng)項(xiàng)目逐漸變的越來越大的時(shí)候,手動(dòng)解碼可能會(huì)表現(xiàn)的不盡人意。手動(dòng)編寫解碼邏輯可能會(huì)變得越來越難以管理,而且變得非常容易出錯(cuò),如果訪問到不存在的字段,或者編寫時(shí)有拼寫錯(cuò)誤,代碼在運(yùn)行時(shí)就會(huì)發(fā)生錯(cuò)誤。
使用代碼自動(dòng)序列化
對于中大型項(xiàng)目來說,使用代碼自動(dòng)進(jìn)行序列化可能會(huì)是一個(gè)比較不錯(cuò)的選擇,意味著我們可以使用外部的依賴庫來生成我們想要的模版。我們通過設(shè)置一些初始化的配置,然后運(yùn)行一個(gè)file watcher從我們的模型類中生成我們想要的代碼數(shù)據(jù)。
比如我們可以使用:json_serializable或者build_value諸如之類的庫。
這種方法適用于更大的項(xiàng)目。不需要手工編寫模版,并且在編譯時(shí)會(huì)捕捉到訪問JSON字段時(shí)的拼寫錯(cuò)誤。
代碼生成的缺點(diǎn)是需要一些初始設(shè)置。另外,生成的源文件可能會(huì)在項(xiàng)目導(dǎo)航器中產(chǎn)生視覺上的混亂。
Flutter 中是否有 GSON/Jackson/Moshi 之類的序列化類庫?
GSON以及Jackson都是 Java中用來序列化json的類庫。
Moshi則是Kotlin中用來序列化json的類庫。
事實(shí)上Flutter中并沒有類似的庫。
因?yàn)?,這樣的庫需要使用運(yùn)行時(shí)反射,這在Flutter中是禁用的。運(yùn)行時(shí)反射會(huì)干擾【樹抖動(dòng)】treeShaking,Dart已經(jīng)支持了很長時(shí)間。通過treeShaking樹抖動(dòng),您可以從發(fā)布版本中“抖掉”未使用的代碼,這可以優(yōu)化應(yīng)用程序的大小。
由于反射默認(rèn)情況下會(huì)隱式使用所有代碼,因此很難進(jìn)行treeShaking樹抖動(dòng)。這些工具無法知道哪些部分在運(yùn)行時(shí)未使用,因此冗余代碼很難去除。使用反射時(shí),無法輕松優(yōu)化應(yīng)用程序大小。
雖然我們不能在Flutter中使用運(yùn)行時(shí)反射,但有些庫提供了類似的API,是基于代碼生成。
使用dart:convert內(nèi)置庫手動(dòng)進(jìn)行序列化
Flutter中的基本JSON序列化非常簡單。Flutter有一個(gè)內(nèi)置的dart:convert庫,其中包含一個(gè)簡單的JSON編碼器和解碼器。
看下面的示例:
{
"name": "John Smith",
"email": "john@example.com"
}
使用dart:convert庫,我們有兩種方法進(jìn)行序列化。
調(diào)用jsonDecode()方法:
Map<String, dynamic> user = jsonDecode(jsonString);
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
但是需要注意的是,jsonDecode()方法會(huì)返回一個(gè)類型為Map<String, dynamic>的類型,這樣的話,我們就特別需要注意json中字段的各種類型。
在模型類中序列化JSON
此外,我們可以引入一個(gè)簡單的模型類(在本例中稱為User)來解決前面提到的問題。在User類中,我們可以發(fā)現(xiàn):
User.fromJson()構(gòu)造函數(shù),用于從Map構(gòu)造新的User實(shí)例。toJson()方法,將User實(shí)例轉(zhuǎn)換為Map。
使用這種方法,調(diào)用代碼時(shí)可以具有類型安全及編譯時(shí)異常提醒。如果我們輸入了錯(cuò)別字,或者將字段視為int而不是String,應(yīng)用程序?qū)⒉粫?huì)編譯,而不會(huì)在運(yùn)行時(shí)崩潰。
// user.dart
class User {
final String name;
final String email;
User(this.name, this.email);
User.fromJson(Map<String, dynamic> json)
: name = json['name'],
email = json['email'];
Map<String, dynamic> toJson() => {
'name': name,
'email': email,
};
}
解碼邏輯的責(zé)任現(xiàn)在轉(zhuǎn)移到模型本身內(nèi)部。使用這種新方法,您可以輕松地解碼User:
Map<String, dynamic> userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
和解碼(Decode)相反的是編碼(Encode),如果我們想要對User進(jìn)行編碼,我們可以使用jsonEncode()方法:
String json = jsonEncode(user);
使用這種方法,調(diào)用代碼根本不必?fù)?dān)心JSON序列化。然而,模型類仍然必須這樣做。在生產(chǎn)應(yīng)用程序中,我們需要確保序列化工作正常進(jìn)行。在實(shí)際開發(fā)過程中,User.fromJson()和User.toJson()方法可能都需要進(jìn)行單元測試以保證結(jié)果的正確性。
使用序列化庫
盡管有其他庫可用,但是這里使用了json_serializable,這是一個(gè)自動(dòng)源代碼生成器,可為我們生成json序列化模版。
要在項(xiàng)目中包含json_serializable,需要一個(gè)常規(guī)依賴項(xiàng)和兩個(gè)開發(fā)依賴項(xiàng)。簡而言之,開發(fā)依賴項(xiàng)是不包含在我們的應(yīng)用程序源代碼中的依賴項(xiàng),它們只在開發(fā)環(huán)境中使用。
我們需要在pubspec.yaml進(jìn)行如下配置:
**pubspec.yaml** dependencies: # Your other regular dependencies here json_annotation: <latest_version> dev_dependencies: # Your other dev_dependencies here build_runner: <latest_version> json_serializable: <latest_version>
然后在項(xiàng)目根文件夾中運(yùn)行flutter pub-get以安裝依賴。
然后我們以json_serializable的方式創(chuàng)建模型類:
// user.dart
import 'package:json_annotation/json_annotation.dart';
/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';
/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
@JsonSerializable()
class User {
User(this.name, this.email);
String name;
String email;
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String, dynamic> toJson() => _$UserToJson(this);
}
通過這種設(shè)置,源代碼生成器生成用于對JSON中的name和email字段進(jìn)行編碼和解碼的代碼。
如果需要的話,我們還可以定制命名策略,比如,如果API返回帶有的對象帶有snake_case屬性,并且我們希望在模型中使用lowerCamelCase,則可以使用帶有name參數(shù)的@JsonKey注釋:
/// Tell json_serializable that "registration_date_millis" should be /// mapped to this property. @JsonKey(name: 'registration_date_millis') final int registrationDateMillis;
服務(wù)器和客戶端最好都遵循相同的命名策略。
@JsonSerializable()提供了fieldRename的枚舉,用于將dart字段完全轉(zhuǎn)換為JSON鍵。
修改@JsonSerializable(fieldRename:fieldRename.sake)相當(dāng)于向每個(gè)字段添加@JsonKey(name:“<snake_case>”)。
服務(wù)器返回的數(shù)據(jù)是不確定的,所以有必要驗(yàn)證和保護(hù)客戶端上的數(shù)據(jù)。
其他常用的@JsonKey注釋包括:
/// Tell json_serializable to use "defaultValue" if the JSON doesn't /// contain this key or if the value is `null`. @JsonKey(defaultValue: false) final bool isAdult; /// When `true` tell json_serializable that JSON must contain the key, /// If the key doesn't exist, an exception is thrown. @JsonKey(required: true) final String id; /// When `true` tell json_serializable that generated code should /// ignore this field completely. @JsonKey(ignore: true) final String verificationCode;
運(yùn)行代碼生成實(shí)用程序
當(dāng)?shù)谝淮蝿?chuàng)建json_serializable類時(shí),會(huì)出現(xiàn)類似下圖所示的錯(cuò)誤。

這些錯(cuò)誤完全是正常的,只是因?yàn)闉槟P皖惿傻拇a還不存在。要解決此問題,我們需要運(yùn)行生成序列化樣板的代碼生成器。
運(yùn)行代碼生成器有兩種方法。
- 一次性代碼生成
- 持續(xù)生成代碼
一次性代碼生成
通過在項(xiàng)目根目錄中運(yùn)行
flutter pub run build_runner build --delete-conflicting-outputs
我們可以在需要時(shí)為模型生成JSON序列化代碼。這將觸發(fā)一次性構(gòu)建,該構(gòu)建將遍歷源文件,選擇相關(guān)文件,并為它們生成必要的序列化代碼。
雖然這很方便,但如果我們不必每次在模型類中進(jìn)行更改時(shí)都手動(dòng)運(yùn)行構(gòu)建,那就更好了。
持續(xù)生成代碼
觀察者模式使我們的源代碼生成過程更加方便。它監(jiān)聽項(xiàng)目文件中的更改,并在需要時(shí)自動(dòng)生成必要的文件。 通過在項(xiàng)目根目錄中運(yùn)行
flutter pub run build_runner watch --delete-conflicting-outputs
可以安全地啟動(dòng)一次觀察程序,并讓它在一直后臺運(yùn)行。
使用json_serializable模型
要以JSON_serializable的方式解碼JSON字符串,實(shí)際上不需要對我們之前的代碼進(jìn)行任何更改。
Map<String, dynamic> userMap = jsonDecode(jsonString); var user = User.fromJson(userMap);
編碼也是如此。調(diào)用API與之前相同。
String json = jsonEncode(user);
使用json_serializable,我們可以放棄User類中的任何手動(dòng)json序列化。源代碼生成器創(chuàng)建一個(gè)名為user.g.dart的文件,該文件具有所有必要的序列化邏輯。我們不再需要編寫自動(dòng)化測試來確保序列化工作,現(xiàn)在庫負(fù)責(zé)確保序列化工作正常。
ps:這里所說的解碼和編碼,對應(yīng)的是Decode和Encode。
以上就是flutter中的JSON和序列化方法及使用詳解的詳細(xì)內(nèi)容,更多關(guān)于flutter JSON序列化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android仿微信右上角點(diǎn)擊加號彈出PopupWindow
這篇文章主要為大家詳細(xì)介紹了Android仿微信右上角點(diǎn)擊加號彈出PopupWindow,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04
RecyclerView實(shí)現(xiàn)縱向和橫向滾動(dòng)
這篇文章主要為大家詳細(xì)介紹了RecyclerView實(shí)現(xiàn)縱向和橫向滾動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
WheelView實(shí)現(xiàn)上下滑動(dòng)選擇器
這篇文章主要為大家詳細(xì)介紹了WheelView實(shí)現(xiàn)上下滑動(dòng)選擇器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android編程實(shí)現(xiàn)ActionBar的home圖標(biāo)動(dòng)畫切換效果
這篇文章主要介紹了Android編程實(shí)現(xiàn)ActionBar的home圖標(biāo)動(dòng)畫切換效果,涉及Android布局、樣式、Activity及菜單相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
Android自定義Drawable之在Drawable中部指定透明區(qū)域方法示例
對于不同的屏幕密度、不同的設(shè)備方向,不同的語言和區(qū)域,都會(huì)涉及到備選 drawable 資源,下面這篇文章主要給你大家介紹了關(guān)于Android自定義Drawable之在Drawable中部指定透明區(qū)域的相關(guān)資料,需要的朋友可以參考下2018-07-07
詳解Android中Application設(shè)置全局變量以及傳值
這篇文章主要介紹了詳解Android中Application設(shè)置全局變量以及傳值的相關(guān)資料,希望通過本文大家能夠理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-09-09
解決android studio引用遠(yuǎn)程倉庫下載慢(JCenter下載慢)
這篇文章主要介紹了解決android studio引用遠(yuǎn)程倉庫下載慢(JCenter下載慢),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03

