flutter-implement-json-serialization

flutter-implement-json-serialization

熱門

使用 `dart:convert` 建立包含 `fromJson` 和 `toJson` 方法的模型類別。適用於手動將 JSON 鍵對應到類別屬性的簡單資料結構。

2525星標
152分支
更新於 2026/6/18
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() 方法。
  • [ ] 為兩個序列化方法撰寫單元測試。
  • [ ] 執行驗證器 -> 檢查型別不符錯誤 -> 修正轉型邏輯。
  1. 定義模型:建立一個類別,其屬性對應 JSON 鍵。
  2. 實作 fromJson:從 Map 中提取值並轉型為適當的 Dart 型別。可使用模式匹配或明確轉型。
  3. 實作 toJson:回傳一個 Map<String, dynamic>,將類別屬性對應回 JSON 字串鍵。
  4. 驗證:執行單元測試,確保型別安全、自動完成及編譯時期例外處理功能正常。

工作流程:取得並解析 JSON

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

任務進度:

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