flutter-implement-json-serialization

flutter-implement-json-serialization

熱門

使用 `dart:convert` 建立包含 `fromJson` 與 `toJson` 方法的模型類別。適用於簡單資料結構中手動將 JSON 鍵值映射至類別屬性的情境。

2783星標
163分支
更新於 2026/8/5
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() 方法。
  • [ ] 為這兩種序列化方法撰寫單元測試。
  • [ ] 執行驗證器 -> 檢視型別不符錯誤 -> 修正轉型邏輯。
  1. 定義模型:建立屬性與 JSON 鍵值對應的類別。
  2. 實作 fromJson:從 Map 中擷取數值並轉型為適當的 Dart 型別。可使用模式比對(pattern matching)或顯式轉型(explicit casting)。
  3. 實作 toJson:回傳一個 Map<String, dynamic>,將類別屬性映射回對應的 JSON 字串鍵值。
  4. 驗證:執行單元測試,確保型別安全、自動完成(autocompletion)與編譯期異常處理運作正常。

工作流程:取得與解析 JSON

從網路請求檢索並解析 JSON 時,請使用此條件式工作流程。

任務進度:

  • [ ] 執行 HTTP 請求。
  • [ ] 驗證回應狀態碼。
  • [ ] 決定解析策略(同步解析 vs. Isolate 解析)。
  • [ ] 解碼 JSON 並映射至模型。
  1. 執行請求:使用 http 套件進行網路呼叫。
  2. 驗證回應
    • response.statusCode == 200(或 POST 的 201),則繼續進行解析。
    • 若狀態碼顯示失敗,請拋出 Exception
  3. 決定解析策略
    • 若解析的是小容量負載(例如單一物件),請在主執行緒(main thread)上進行同步解析。
    • 若解析的是大容量負載(例如包含數千個物件的陣列),請使用 compute(parseFunction, response.body) 在背景 Isolate 中進行解析。
  4. 解碼與映射:將解碼後的 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');
  }
}