SKILL.md
readonlyread-only
name
flutter-implement-json-serialization
description
使用 `dart:convert` 建立包含 `fromJson` 和 `toJson` 方法的模型類別。適用於手動將 JSON 鍵對應到類別屬性的簡單資料結構。
在 Flutter 中手動序列化 JSON
目錄
核心準則
- 匯入
dart:convert:使用 Flutter 內建的dart:convert函式庫進行手動 JSON 編碼(jsonEncode)與解碼(jsonDecode)。 - 強制型別安全:務必將
jsonDecode()回傳的dynamic結果轉型為預期型別,物件通常為Map<String, dynamic>,陣列則為List<dynamic>。 - 封裝序列化邏輯:定義純模型類別,其屬性對應 JSON 結構。在模型中實作
fromJson工廠建構子和toJson方法。 - 處理背景解析:若解析大型 JSON 文件(執行時間超過 16 毫秒),請使用 Flutter 的
compute()函式將解析邏輯卸載到獨立的隔離區(isolate),以避免 UI 卡頓。 - 失敗時拋出例外:處理 HTTP 回應時,若狀態碼不成功(例如非 200 OK 或 201 Created),請拋出例外,不要回傳
null。
工作流程:實作可序列化的模型
使用此檢查清單為資料模型實作手動 JSON 序列化。
任務進度:
- [ ] 定義包含
final屬性的純模型類別。 - [ ] 實作
factory Model.fromJson(Map<String, dynamic> json)建構子。 - [ ] 實作
Map<String, dynamic> toJson()方法。 - [ ] 為兩個序列化方法撰寫單元測試。
- [ ] 執行驗證器 -> 檢查型別不符錯誤 -> 修正轉型邏輯。
- 定義模型:建立一個類別,其屬性對應 JSON 鍵。
- 實作
fromJson:從Map中提取值並轉型為適當的 Dart 型別。可使用模式匹配或明確轉型。 - 實作
toJson:回傳一個Map<String, dynamic>,將類別屬性對應回 JSON 字串鍵。 - 驗證:執行單元測試,確保型別安全、自動完成及編譯時期例外處理功能正常。
工作流程:取得並解析 JSON
從網路請求取得並解析 JSON 時,使用此條件式工作流程。
任務進度:
- [ ] 執行 HTTP 請求。
- [ ] 驗證回應狀態碼。
- [ ] 決定解析策略(同步 vs. 隔離區)。
- [ ] 解碼並將 JSON 對應到模型。
- 執行請求:使用
http套件進行網路呼叫。 - 驗證回應:
- 若
response.statusCode == 200(或 POST 的 201),則繼續解析。 - 若狀態碼表示失敗,則拋出
Exception。
- 若
- 決定解析策略:
- 若解析小型負載(例如單一物件),在主執行緒上同步解析。
- 若解析大型負載(例如數千個物件的陣列),使用
compute(parseFunction, response.body)在背景隔離區中解析。
- 解碼並對應:將解碼後的 JSON 傳遞給模型的
fromJson建構子。
範例
高保真模型實作
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
// 用於反序列化的工廠建構子
factory User.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'id': int id,
'name': String name,
'email': String email,
} =>
User(
id: id,
name: name,
email: email,
),
_ => throw const FormatException('無法載入使用者。'),
};
}
// 用於序列化的方法
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}
同步解析(小型負載)
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<User> fetchUser(http.Client client, int userId) async {
final response = await client.get(
Uri.parse('https://api.example.com/users/$userId'),
headers: {'Accept': 'application/json'},
);
if (response.statusCode == 200) {
// 解碼回傳 dynamic,轉型為 Map<String, dynamic>
final Map<String, dynamic> jsonMap = jsonDecode(response.body) as Map<String, dynamic>;
return User.fromJson(jsonMap);
} else {
throw Exception('無法載入使用者');
}
}
背景解析(大型負載)
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
// 需要頂層函式供 compute() 使用
List<User> parseUsers(String responseBody) {
final parsed = (jsonDecode(responseBody) as List<dynamic>).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
Future<List<User>> fetchUsers(http.Client client) async {
final response = await client.get(
Uri.parse('https://api.example.com/users'),
headers: {'Accept': 'application/json'},
);
if (response.statusCode == 200) {
// 將耗時的解析卸載到背景隔離區
return compute(parseUsers, response.body);
} else {
throw Exception('無法載入使用者列表');
}
}






