在閱讀了前一天的文章30天Flutter手滑系列 - 狀態管理 (State Management),似乎還有點懵懵懂懂。
沒關係,後面我會以實做專案的方式一步一步搭建專案,順便引入狀態管理的機制。今天就先來看看比較簡單的主題,認識JSON能為你做些什麼。
JSON是一種常見的資料格式,以其淺顯易懂的編排方式來進行資料的交換。最早起源於JavaScript,現已被大多數程式語言支援。
進一步的說明可以參考JSON。
序列化又另外分成序列化跟反序列化兩種。
簡單來說,序列化的目的是為了把資料結構轉換成可被取用的格式,例如字串。相反地,反序列化是為了把字串轉換成其原本的資料結構。
進一步的說明可以參考序列化
適合小專案使用。
手動序列化是使用dart:convert
中內建的JSON解碼器。它將原始的JSON字串傳遞給JSON.decode()
,然後回傳在Map<String, dynamic>
中搜尋到的值。並不需要額外的設置,可直接使用。
透過官方範例來看一下dart:convert
手動序列化JSON的用法:
這是一個簡單的JSON格式
{
"name": "John Smith",
"email": "john@example.com"
}
通過調用jsonDecode()
方法來解碼JSON,並把JSON字串當作參數傳進去。
Map<String, dynamic> user = jsonDecode(jsonString);
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
不幸的是,jsonDecode()
只會回傳一個Map<String, dynamic>
,這意味著你必須等到執行時才會知道數值的型別。如此一來就喪失了靜態語言的特性:型別安全(type safety)、自動補齊(autocompletion)以及最重要的編譯時異常(compile-time exceptions),造成我們的程式很容易出錯。
例如,當我們欲存取name
或email
欄位,卻名稱打錯了,但是在編譯時卻不會報錯。
為了解決前面的問題,這裡可以引入一個model class
。在這範例中為User
,在這個Class內有:
User.fromJson()
建構子,為了從map structure中,建構出一個新的User實例。toJson()
,用來轉換一個User
實例成map。如此一來,這樣就具備了型別安全、自動補齊以及編譯時異常的特性。如果發生拼寫錯誤或是將欄位視為int
而不是String
,則會在這檢查出錯誤。
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,
};
}
現在,反序列化的邏輯被移到Model內部,因此可以很容易地進行反序列化。
Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
要序列化一個user,只需把該對象傳入jsonEncode
這個方法中,不需要額外呼叫toJson()
。
String json = jsonEncode(user);
因此,為了程式碼的品質以及正確,加入Model是較理想的做法,如此一來就能確保序列化與反序列化能正確地運作。
適合複雜度較高的大型專案。
隨著開發的專案功能漸多,整體複雜度變大,如果純用手動方式產出序列化資料會容易出錯,這時候官方推薦了兩套函式庫json_serializable和built_value。函式庫需要額外做一些設定,但是透過自動生成的方式可以避免一些格式錯誤。
接下來看一下官方推薦的json_serializable
怎麼實作這部分。
dependencies:
# Your other regular dependencies here
json_annotation: ^2.0.0
dev_dependencies:
# Your other dev_dependencies here
build_runner: ^1.0.0
json_serializable: ^2.0.0
然後在專案的根目錄下執行flutter pub get
指令或是在編輯器點擊Package Get
,確保這些相依的項目可以被使用。
import 'package:json_annotation/json_annotation.dart';
// 這允許User去存取這些產生的檔案中的私有成員
part 'user.g.dart';
// 這個修飾符是用來告訴生成器,這個Class是要來產生Model的
@JsonSerializable()
class User {
User(this.name, this.email);
String name;
String email;
// 這個facotry是必須要有的,為了從map創建一個新的User實例
// 把整個map傳遞`_$UserFromJson()`
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
// `toJson`是用來限制即將進行序列化到JSON
Map<String, dynamic> toJson() => _$UserToJson(this);
}
有了這些設置,程式碼生成器會開始進行從JSON中編碼和解碼name
和email
這兩個欄位。
如果有需要,也可以自定義命名策略。例如,如果使用的API回傳帶有snake_case,而你想轉換成lowerCamelCase,這時候就可以使用帶有參數的@JsonKey
。
/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
在初次建立json_serializable
,會有以下錯誤:
這是很正常的,因為model還不存在,必須透過兩種方式去產生序列化的模板。
透過在專案的根目錄下執行flutter packages pub run build_runner build
,這個方式是在我們需要為我們的model進行json序列化時候,產生一次性的建構。
另外可以透過watcher,在我們的根目錄下執行flutter package pub run build_runner watch
,監聽我們專案中的變化,並在需要時自動建構必要的文件。
序列化的方式在跟後端伺服器做資料交換時是很重要的一部分,制定好事前的準備,會減少實務上遇到不明錯誤發生的機會,有時候真的很常前後端為了找簡單的JSON錯誤,花上一些冤枉時間。
https://flutter.dev/docs/development/data-and-backend/json