skills/flutter/flutter-implement-json-serialization/SKILL.md
Create model classes with fromJson/toJson using dart:convert and Dart 3 pattern matching. Use when manually mapping JSON to classes, parsing HTTP responses, or choosing between manual and code-generated serialization.
npx skillsauth add dhruvanbhalara/skills flutter-implement-json-serializationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
dart:convert: Use jsonEncode() and jsonDecode() for manual serialization.jsonDecode() result to Map<String, dynamic> (objects) or List<dynamic> (arrays). Never work with raw dynamic.fromJson factory constructor and toJson method within the model class.compute() if parsing takes > 16ms (large JSON payloads).FormatException on invalid JSON. Never return null from fromJson.Use switch expressions with destructuring for type-safe, concise deserialization:
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': final int id,
'name': final String name,
'email': final String email,
} =>
User(id: id, name: name, email: email),
_ => throw const FormatException('Invalid User JSON'),
};
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
};
}
Benefits over manual casting:
FormatException thrown automatically on type mismatchclass Post {
final int id;
final String title;
final User author;
const Post({required this.id, required this.title, required this.author});
factory Post.fromJson(Map<String, dynamic> json) {
return switch (json) {
{
'id': final int id,
'title': final String title,
'author': final Map<String, dynamic> authorJson,
} =>
Post(id: id, title: title, author: User.fromJson(authorJson)),
_ => throw const FormatException('Invalid Post JSON'),
};
}
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'author': author.toJson(),
};
}
For large JSON payloads (thousands of objects), offload parsing to a background isolate:
import 'dart:convert';
import 'package:flutter/foundation.dart';
// MUST be a top-level function (not a method or closure)
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'));
if (response.statusCode == 200) {
return compute(parseUsers, response.body);
} else {
throw Exception('Failed to load users: ${response.statusCode}');
}
}
Rules:
compute() for simple isolate tasks. For complex scenarios, use Isolate.run().| Criteria | Manual (dart:convert) | Code-Gen (json_serializable / freezed) |
|---|---|---|
| Model count | < 5 models | > 5 models |
| Nesting depth | Shallow (1-2 levels) | Deep / complex hierarchies |
| Dev dependency | None | build_runner, json_annotation |
| Type safety | Dart 3 pattern matching | Generated code |
| Boilerplate | Manual per model | Auto-generated |
| Build time | None | Adds build_runner step |
| Flexibility | Full control over parsing | Constrained by annotations |
Recommendation: Start with manual serialization for prototypes and small models. Migrate to json_serializable or freezed when the model count exceeds 5 or nesting becomes complex. See flutter-code-gen skill for code generation workflows.
final properties and const constructor.factory Model.fromJson(Map<String, dynamic> json) using pattern matching.Map<String, dynamic> toJson() method.fromJson(toJson(model)) == model).dart test or flutter test.compute(parseFunction, response.body).fromJson.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) {
final Map<String, dynamic> jsonMap =
jsonDecode(response.body) as Map<String, dynamic>;
return User.fromJson(jsonMap);
} else {
throw Exception('Failed to load user: ${response.statusCode}');
}
}
Future<List<User>> fetchAllUsers(http.Client client) async {
final response = await client.get(
Uri.parse('https://api.example.com/users'),
);
if (response.statusCode == 200) {
final List<dynamic> jsonList = jsonDecode(response.body) as List<dynamic>;
return jsonList
.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList();
} else {
throw Exception('Failed to load users');
}
}
import 'package:flutter_test/flutter_test.dart';
void main() {
group('$User', () {
test('fromJson creates valid User', () {
final json = {'id': 1, 'name': 'Alice', 'email': '[email protected]'};
final user = User.fromJson(json);
expect(user.id, 1);
expect(user.name, 'Alice');
expect(user.email, '[email protected]');
});
test('toJson returns valid map', () {
const user = User(id: 1, name: 'Alice', email: '[email protected]');
final json = user.toJson();
expect(json['id'], 1);
expect(json['name'], 'Alice');
});
test('round-trip serialization', () {
const original = User(id: 1, name: 'Alice', email: '[email protected]');
final json = original.toJson();
final restored = User.fromJson(json);
expect(restored.id, original.id);
expect(restored.name, original.name);
expect(restored.email, original.email);
});
test('throws FormatException on invalid JSON', () {
final invalidJson = {'id': 'not_an_int', 'name': 'Alice'};
expect(() => User.fromJson(invalidJson), throwsFormatException);
});
});
}
development
Perform REST API networking operations (GET, POST, PUT, DELETE) using the lightweight and robust standard `http` package, including platform configurations and background parsing models.
development
Configure internationalization and localization support using Flutter's built-in l10n system, App Resource Bundle (ARB) files, and ICU formatting syntax.
data-ai
Diagnose and fix Flutter layout constraint violations (RenderFlex overflow, unbounded height/width, ParentData misuse). Use when encountering layout exceptions, yellow-black overflow stripes, or red error screens.
development
Build adaptive layouts using LayoutBuilder, MediaQuery, or Expanded/Flexible widgets to ensure the UI looks elegant across all mobile, tablet, and desktop form factors.