main
md 153 lines 5.52 KB
Rendered Raw
1 ---
2 name: flutter-implement-json-serialization
3 description: Create model classes with `fromJson` and `toJson` methods using `dart:convert`. Use when manually mapping JSON keys to class properties for simple data structures.
4 metadata:
5 model: models/gemini-3.1-pro-preview
6 last_modified: Tue, 21 Apr 2026 21:44:50 GMT
7 ---
8 # Serializing JSON Manually in Flutter
9
10 ## Contents
11 - [Core Guidelines](#core-guidelines)
12 - [Workflow: Implementing a Serializable Model](#workflow-implementing-a-serializable-model)
13 - [Workflow: Fetching and Parsing JSON](#workflow-fetching-and-parsing-json)
14 - [Examples](#examples)
15
16 ## Core Guidelines
17
18 - **Import `dart:convert`**: Utilize Flutter's built-in `dart:convert` library for manual JSON encoding (`jsonEncode`) and decoding (`jsonDecode`).
19 - **Enforce Type Safety**: Always cast the `dynamic` result of `jsonDecode()` to the expected type, typically `Map<String, dynamic>` for objects or `List<dynamic>` for arrays.
20 - **Encapsulate Serialization Logic**: Define plain model classes containing properties corresponding to the JSON structure. Implement a `fromJson` factory constructor and a `toJson` method within the model.
21 - **Handle Background Parsing**: If parsing large JSON documents (execution time > 16ms), offload the parsing logic to a separate isolate using Flutter's `compute()` function to prevent UI jank.
22 - **Throw Exceptions on Failure**: When handling HTTP responses, throw an exception if the status code is not successful (e.g., not 200 OK or 201 Created). Do not return `null`.
23
24 ## Workflow: Implementing a Serializable Model
25
26 Use this checklist to implement manual JSON serialization for a data model.
27
28 **Task Progress:**
29 - [ ] Define the plain model class with `final` properties.
30 - [ ] Implement the `factory Model.fromJson(Map<String, dynamic> json)` constructor.
31 - [ ] Implement the `Map<String, dynamic> toJson()` method.
32 - [ ] Write unit tests for both serialization methods.
33 - [ ] Run validator -> review type mismatch errors -> fix casting logic.
34
35 1. **Define the Model**: Create a class with properties matching the JSON keys.
36 2. **Implement `fromJson`**: Extract values from the `Map` and cast them to the appropriate Dart types. Use pattern matching or explicit casting.
37 3. **Implement `toJson`**: Return a `Map<String, dynamic>` mapping the class properties back to their JSON string keys.
38 4. **Validate**: Execute unit tests to ensure type safety, autocompletion, and compile-time exception handling function correctly.
39
40 ## Workflow: Fetching and Parsing JSON
41
42 Use this conditional workflow when retrieving and parsing JSON from a network request.
43
44 **Task Progress:**
45 - [ ] Execute the HTTP request.
46 - [ ] Validate the response status code.
47 - [ ] Determine parsing strategy (Synchronous vs. Isolate).
48 - [ ] Decode and map the JSON to the model.
49
50 1. **Execute Request**: Use the `http` package to perform the network call.
51 2. **Validate Response**:
52 - If `response.statusCode == 200` (or 201 for POST), proceed to parsing.
53 - If the status code indicates failure, throw an `Exception`.
54 3. **Determine Parsing Strategy**:
55 - If parsing a **small payload** (e.g., a single object), parse synchronously on the main thread.
56 - If parsing a **large payload** (e.g., an array of thousands of objects), use `compute(parseFunction, response.body)` to parse in a background isolate.
57 4. **Decode and Map**: Pass the decoded JSON to your model's `fromJson` constructor.
58
59 ## Examples
60
61 ### High-Fidelity Model Implementation
62
63 ```dart
64 import 'dart:convert';
65
66 class User {
67 final int id;
68 final String name;
69 final String email;
70
71 const User({
72 required this.id,
73 required this.name,
74 required this.email,
75 });
76
77 // Factory constructor for deserialization
78 factory User.fromJson(Map<String, dynamic> json) {
79 return switch (json) {
80 {
81 'id': int id,
82 'name': String name,
83 'email': String email,
84 } =>
85 User(
86 id: id,
87 name: name,
88 email: email,
89 ),
90 _ => throw const FormatException('Failed to load User.'),
91 };
92 }
93
94 // Method for serialization
95 Map<String, dynamic> toJson() {
96 return {
97 'id': id,
98 'name': name,
99 'email': email,
100 };
101 }
102 }
103 ```
104
105 ### Synchronous Parsing (Small Payload)
106
107 ```dart
108 import 'dart:convert';
109 import 'package:http/http.dart' as http;
110
111 Future<User> fetchUser(http.Client client, int userId) async {
112 final response = await client.get(
113 Uri.parse('https://api.example.com/users/$userId'),
114 headers: {'Accept': 'application/json'},
115 );
116
117 if (response.statusCode == 200) {
118 // Decode returns dynamic, cast to Map<String, dynamic>
119 final Map<String, dynamic> jsonMap = jsonDecode(response.body) as Map<String, dynamic>;
120 return User.fromJson(jsonMap);
121 } else {
122 throw Exception('Failed to load user');
123 }
124 }
125 ```
126
127 ### Background Parsing (Large Payload)
128
129 ```dart
130 import 'dart:convert';
131 import 'package:flutter/foundation.dart';
132 import 'package:http/http.dart' as http;
133
134 // Top-level function required for compute()
135 List<User> parseUsers(String responseBody) {
136 final parsed = (jsonDecode(responseBody) as List<dynamic>).cast<Map<String, dynamic>>();
137 return parsed.map<User>((json) => User.fromJson(json)).toList();
138 }
139
140 Future<List<User>> fetchUsers(http.Client client) async {
141 final response = await client.get(
142 Uri.parse('https://api.example.com/users'),
143 headers: {'Accept': 'application/json'},
144 );
145
146 if (response.statusCode == 200) {
147 // Offload expensive parsing to a background isolate
148 return compute(parseUsers, response.body);
149 } else {
150 throw Exception('Failed to load users');
151 }
152 }
153 ```