SKILL.md
唯讀
名稱
flutter-implement-json-serialization
描述
使用 `dart:convert` 建立包含 `fromJson` 與 `toJson` 方法的模型類別。適用於簡單資料結構中手動將 JSON 鍵值映射至類別屬性的情境。
在 Flutter 中手動序列化 JSON
目錄
核心準則
- 匯入
dart:convert:利用 Flutter 內建的dart:convert函式庫來進行手動 JSON 編碼(jsonEncode)與解碼(jsonDecode)。 - 強制執行型別安全:務必將
jsonDecode()的dynamic回傳結果轉型為預期的型別,物件通常為Map<String, dynamic>,陣列則為List<dynamic>。 - 封裝序列化邏輯:定義純模型類別(plain model classes),其中包含與 JSON 結構對應的屬性。在模型中實作
fromJson建構函式(factory constructor)與toJson方法。 - 處理背景解析:若要解析大型 JSON 文件(執行時間 > 16ms),請使用 Flutter 的
compute()函式將解析邏輯交由獨立的 Isolate 處理,以避免 UI 卡頓(jank)。 - 失敗時拋出異常:處理 HTTP 回應時,若狀態碼非成功狀態(例如非 200 OK 或 201 Created),請直接拋出異常(Exception),切勿回傳
null。
工作流程:實作可序列化的模型
使用此檢查清單為資料模型實作手動 JSON 序列化。
任務進度:
- [ ] 定義帶有
final屬性的純模型類別。 - [ ] 實作
factory Model.fromJson(Map<String, dynamic> json)建構函式。 - [ ] 實作
Map<String, dynamic> toJson()方法。 - [ ] 為這兩種序列化方法撰寫單元測試。
- [ ] 執行驗證器 -> 檢視型別不符錯誤 -> 修正轉型邏輯。
- 定義模型:建立屬性與 JSON 鍵值對應的類別。
- 實作
fromJson:從Map中擷取數值並轉型為適當的 Dart 型別。可使用模式比對(pattern matching)或顯式轉型(explicit casting)。 - 實作
toJson:回傳一個Map<String, dynamic>,將類別屬性映射回對應的 JSON 字串鍵值。 - 驗證:執行單元測試,確保型別安全、自動完成(autocompletion)與編譯期異常處理運作正常。
工作流程:取得與解析 JSON
從網路請求檢索並解析 JSON 時,請使用此條件式工作流程。
任務進度:
- [ ] 執行 HTTP 請求。
- [ ] 驗證回應狀態碼。
- [ ] 決定解析策略(同步解析 vs. Isolate 解析)。
- [ ] 解碼 JSON 並映射至模型。
- 執行請求:使用
http套件進行網路呼叫。 - 驗證回應:
- 若
response.statusCode == 200(或 POST 的 201),則繼續進行解析。 - 若狀態碼顯示失敗,請拋出
Exception。
- 若
- 決定解析策略:
- 若解析的是小容量負載(例如單一物件),請在主執行緒(main thread)上進行同步解析。
- 若解析的是大容量負載(例如包含數千個物件的陣列),請使用
compute(parseFunction, response.body)在背景 Isolate 中進行解析。
- 解碼與映射:將解碼後的 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 建構函式
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('Failed to load User.'),
};
}
// 用於序列化的方法
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('Failed to load user');
}
}
背景解析(大容量負載)
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) {
// 將耗時的解析工作卸載至背景 Isolate
return compute(parseUsers, response.body);
} else {
throw Exception('Failed to load users');
}
}






