JSON (JavaScript Object Notation) 是一種輕量級的資料交換格式,使用簡單的文字來表示物件和陣列。JSON 被設計得易於人類閱讀和編寫,同時也易於機器解析和生成。常用於在伺服器和客戶端之間傳遞資料。
由於 JSON 是由 Javascript 的物件所設計,因此在 dart 中,無法直接使用,要使 JSON 轉成 dart 的物件需要再下一些功夫!在 Day-12 在 Flutter 中使用原生方法及第三方套件實現 http 連線 中,我們有提到怎麼使用 http 與伺服器連線,也有使用 dart:convert
中的 jsonDecode
將 JSON 資料轉為 Map。雖然看似方便,但如果我們希望直接將整個 JSON 格式,轉為一個 Dart 物件時,或著,Dart 物件轉為 JSON 格式時,每次都需要 encode, decode,屬實有點麻煩。此時最直覺的方式,就是將 encode 和 decode 轉為 Dart 物件中的函式。
以下程式 user.dart
class User {
final String name;
final int age;
User(this.name, this.age);
User.fromJson(Map<String, dynamic> json)
: name = json['name'] as String,
age = json['age'] as int;
Map<String, dynamic> toJson() => {
'name': name,
'age': age,
};
}
參考官方文件:https://docs.flutter.dev/data-and-backend/serialization/json
這種將 Dart 物件轉為 JSON 的方式稱為 Serialization (序列化);而將 JSON 轉為 Dart 物件則稱為 Deserialization (反序列化)。這個過程極其枯燥,可以由程式來完成!
本次範例程式碼:https://github.com/ksw2000/ironman-2024/tree/master/flutter-practice/json_serialization
本次教學會提及如何使用 json_serializable
來自動生成「序列化」的程式碼。並使用 flutter 中的單元測試進行測試
首先,我們先安裝 json_serializable
的套件 dev
會將套件加入 dev_dependencies
,此邏輯與 npm 類似,這些套件只有在開發時會使用到,比如用來生成程式碼,而在發佈時並不會使用這些相依套件。
flutter pub add json_annotation dev:build_runner dev:json_serializable
安裝完成後,我們可以將原本的 User 物件改為 User2,並存成 user2.dart
import 'package:json_annotation/json_annotation.dart';
/// 允許 `User2` 類別訪問在生成文件中的 private 成員。
/// *.g.dart,其中星號 * 表示源文件。
part 'user2.g.dart';
/// @ annotation 用於告知程式碼生成器這個類別需要生成 JSON 序列化邏輯。
@JsonSerializable()
class User2 {
final String name;
final int age;
User2(this.name, this.age);
/// 配合生成的程示碼必需使用 factory 構造函數,用於從 map 中創建新的實例。
/// 將 map 傳遞給生成的 `_$User2FromJson()` 構造函數。
/// 構造函數的名稱與原本的 class 相同,比如 User2。
factory User2.fromJson(Map<String, dynamic> json) => _$User2FromJson(json);
/// `toJson` 是一個序列化 JSON 常用的慣例名稱。
/// 實現時僅需調用生成的 private 方法 `_$User2ToJson`。
Map<String, dynamic> toJson() => _$User2ToJson(this);
}
接著我們可以使用 build_runner 生成程式碼
dart run build_runner build --delete-conflicting-outputs
以上指令只能一次性生成,如果我們希望 dart 能一邊觀察我們的 code 一邊生成,可以改用以下指令:
dart run build_runner watch --delete-conflicting-outputs
生成後會產生 user2.g.dart
,如下:
.
└── json_serialization (project name)
└── lib/
├── main.dart
├── user.dart
├── user2.dart
└── user2.g.dart (生成程式碼)
透過 part
關鍵字,user2.g.dart
的程式碼編譯時會加入到 user2.dart
中,因此 user2.dart
可以調用 user2.g.dart
中的 private 物件。
我們可以使用 flutter 中的單元測試來對 User2
進行測試。
.
└── json_serialization/
└── test/
├── user2_test.dart (自己新增)
└── widget_test.dart (預設測試檔)
首先我們可以針對反序列化做測試
test('test fromJSON', () {
const json = '{"name": "日野下花帆", "age": 17}';
final Map<String, dynamic> jsonMap = jsonDecode(json);
final user = User2.fromJson(jsonMap);
expect(user.name, '日野下花帆');
expect(user.age, 17);
});
並針對序列化做測試
test('test toJson', () {
final user = User2('藤島慈', 18);
final jsonMap = user.toJson();
expect(jsonMap['name'], '藤島慈');
expect(jsonMap['age'], 18);
});
最後,我們可以使用 flutter test
來運行測試檔
$ flutter test
00:05 +3: All tests passed!
後記:鐵人賽第15天過完一半了,太累了,希望網友們可以幫忙點個愛心