$npx -y skills add flutter/agent-plugins --skill flutter-implement-json-serializationCreate model classes with fromJson and toJson methods using dart:convert. Use when manually mapping JSON keys to class properties for simple data structures.
| 1 | # Serializing JSON Manually in Flutter |
| 2 | |
| 3 | ## Contents |
| 4 | - [Core Guidelines](#core-guidelines) |
| 5 | - [Workflow: Implementing a Serializable Model](#workflow-implementing-a-serializable-model) |
| 6 | - [Workflow: Fetching and Parsing JSON](#workflow-fetching-and-parsing-json) |
| 7 | - [Examples](#examples) |
| 8 | |
| 9 | ## Core Guidelines |
| 10 | |
| 11 | - **Import `dart:convert`**: Utilize Flutter's built-in `dart:convert` library for manual JSON encoding (`jsonEncode`) and decoding (`jsonDecode`). |
| 12 | - **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. |
| 13 | - **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. |
| 14 | - **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. |
| 15 | - **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`. |
| 16 | |
| 17 | ## Workflow: Implementing a Serializable Model |
| 18 | |
| 19 | Use this checklist to implement manual JSON serialization for a data model. |
| 20 | |
| 21 | **Task Progress:** |
| 22 | - [ ] Define the plain model class with `final` properties. |
| 23 | - [ ] Implement the `factory Model.fromJson(Map<String, dynamic> json)` constructor. |
| 24 | - [ ] Implement the `Map<String, dynamic> toJson()` method. |
| 25 | - [ ] Write unit tests for both serialization methods. |
| 26 | - [ ] Run validator -> review type mismatch errors -> fix casting logic. |
| 27 | |
| 28 | 1. **Define the Model**: Create a class with properties matching the JSON keys. |
| 29 | 2. **Implement `fromJson`**: Extract values from the `Map` and cast them to the appropriate Dart types. Use pattern matching or explicit casting. |
| 30 | 3. **Implement `toJson`**: Return a `Map<String, dynamic>` mapping the class properties back to their JSON string keys. |
| 31 | 4. **Validate**: Execute unit tests to ensure type safety, autocompletion, and compile-time exception handling function correctly. |
| 32 | |
| 33 | ## Workflow: Fetching and Parsing JSON |
| 34 | |
| 35 | Use this conditional workflow when retrieving and parsing JSON from a network request. |
| 36 | |
| 37 | **Task Progress:** |
| 38 | - [ ] Execute the HTTP request. |
| 39 | - [ ] Validate the response status code. |
| 40 | - [ ] Determine parsing strategy (Synchronous vs. Isolate). |
| 41 | - [ ] Decode and map the JSON to the model. |
| 42 | |
| 43 | 1. **Execute Request**: Use the `http` package to perform the network call. |
| 44 | 2. **Validate Response**: |
| 45 | - If `response.statusCode == 200` (or 201 for POST), proceed to parsing. |
| 46 | - If the status code indicates failure, throw an `Exception`. |
| 47 | 3. **Determine Parsing Strategy**: |
| 48 | - If parsing a **small payload** (e.g., a single object), parse synchronously on the main thread. |
| 49 | - If parsing a **large payload** (e.g., an array of thousands of objects), use `compute(parseFunction, response.body)` to parse in a background isolate. |
| 50 | 4. **Decode and Map**: Pass the decoded JSON to your model's `fromJson` constructor. |
| 51 | |
| 52 | ## Examples |
| 53 | |
| 54 | ### High-Fidelity Model Implementation |
| 55 | |
| 56 | ```dart |
| 57 | import 'dart:convert'; |
| 58 | |
| 59 | class User { |
| 60 | final int id; |
| 61 | final String name; |
| 62 | final String email; |
| 63 | |
| 64 | const User({ |
| 65 | required this.id, |
| 66 | required this.name, |
| 67 | required this.email, |
| 68 | }); |
| 69 | |
| 70 | // Factory constructor for deserialization |
| 71 | factory User.fromJson(Map<String, dynamic> json) { |
| 72 | return switch (json) { |
| 73 | { |
| 74 | 'id': int id, |
| 75 | 'name': String name, |
| 76 | 'email': String email, |
| 77 | } => |
| 78 | User( |
| 79 | id: id, |
| 80 | name: name, |
| 81 | email: email, |
| 82 | ), |
| 83 | _ => throw const FormatException('Failed to load User.'), |
| 84 | }; |
| 85 | } |
| 86 | |
| 87 | // Method for serialization |
| 88 | Map<String, dynamic> toJson() { |
| 89 | return { |
| 90 | 'id': id, |
| 91 | 'name': name, |
| 92 | 'email': email, |
| 93 | }; |
| 94 | } |
| 95 | } |
| 96 | ``` |
| 97 | |
| 98 | ### Synchronous Parsing (Small Payload) |
| 99 | |
| 100 | ```dart |
| 101 | import 'dart:convert'; |
| 102 | import 'package:http/http.dart' as http; |
| 103 | |
| 104 | Future<User> fetchUser(http.Client client, int userId) async { |
| 105 | final response = await client.get( |
| 106 | Uri.parse('https://api.example.com/users/$userId'), |
| 107 | headers: {'Accept': 'application/json'}, |
| 108 | ); |
| 109 | |
| 110 | if (response.statusCode == 200) { |
| 111 | // Decode returns dynamic, cast to Map<String, dynamic> |
| 112 | final Map<String, dynamic> jsonMap = jsonDecode(response.body) as Map<String, dynamic>; |
| 113 | return User.fromJson(jsonMap); |
| 114 | } else { |
| 115 | throw Exception('Failed to load user'); |
| 116 | } |
| 117 | } |
| 118 | ``` |
| 119 | |
| 120 | ### Background Parsing (Large Payload) |
| 121 | |
| 122 | ```dart |
| 123 | import 'dart:convert'; |
| 124 | import 'package:flutter/foundation.dart'; |
| 125 | import 'package:http/http.dart' as http; |
| 126 | |
| 127 | / |