Are you tired of writing the same boilerplate code every time you need to parse JSON in your Flutter app? If you are dealing with API responses or storing structured data locally, the manual approach quickly becomes repetitive and error-prone. In that case, you might want to use Flutter JSON Serializable.
JSON Serializable is a code generation library that automatically creates serialization logic for your Dart models. Instead of hand-coding fromJson and toJson methods, you only annotate your class, and the package generates the conversion code for you. This makes your models type-safe, easier to maintain, and free from common mistakes like typos or missing fields.
Note: The code examples in this article are simplified for explanation and learning purposes. They are not production-ready and should be treated as a reference only.
Adding JSON Serializable to Your Project
To start using JSON Serializable, you need to add three packages: json_annotation for annotations, json_serializable for code generation, and build_runner to run the generator.
Run these commands in your project root:
flutter pub add json_annotation
flutter pub add --dev build_runner json_serializable
After installation, your pubspec.yaml should look like this:
dependencies:
json_annotation: ^latest
dev_dependencies:
build_runner: ^latest
json_serializable: ^latest
Note: json_annotation goes under dependencies because it is used at runtime, while build_runner and json_serializable are only required during development.
Creating a Model with JSON Serializable
Once the dependencies are ready, the next step is to create a model class that represents your data. Instead of writing manual fromJson and toJson methods, you only need to annotate the class with @JsonSerializable and link it to a generated file.
Here’s a simple User model:
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final String? name;
final String? email;
const User({
this.name,
this.email,
});
factory User.fromJson(Map<String, dynamic> json) =>
_$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
The line part ‘user.g.dart‘; tells Dart that the generated code will live in that file. The fromJson factory and toJson method connect your model to the generated helpers, which are created by running the build process in the next step.
Generating the Serialization Code
With the model class ready, you need to run the build process that generates the actual serialization logic. This is handled by the build_runner package. For a one-time build, run:
dart run build_runner build --delete-conflicting-outputs
If you are frequently updating your models, you can start a watcher that keeps generating files automatically:
dart run build_runner watch --delete-conflicting-outputs
After running the build, a new file named user.g.dart will be created with the _$UserFromJson and _$UserToJson methods that your model uses.
Using the Model in Your Code
You’re now all set to use the model to decode API responses into Dart objects and encode them back into JSON when sending data. Here’s an example with a simple API response:
import ‘dart:convert’;
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> fetchUser() async {
final response = await http.get(Uri.parse('https://api.example.com/user'));
if (response.statusCode == 200) {
final Map<String, dynamic> data = jsonDecode(response.body);
final user = User.fromJson(data);
print(user.name); // Access model properties safely
} else {
throw Exception('Failed to load user');
}
}
In this example, the raw JSON string from the API is first decoded into a Map<String, dynamic> using jsonDecode. The User.fromJson factory then converts that map into a strongly typed User object, which lets you access fields like name and email with full type safety.
It’s left to encode a model back to JSON before sending it to an API:
final user = User('Sample', 'sample@test.com');
final jsonString = jsonEncode(user.toJson());
print(jsonString);
Output: {“name”:”Sample”,”email”:”sample@test.com”}
Here, the toJson method generates a map from the Dart object, and jsonEncode turns it into a JSON string ready to be sent over the network. This ensures that your app code stays clean and free from repetitive parsing logic.
Wrapping Up
JSON serialization is a part of almost every Flutter app, and doing it by hand quickly becomes repetitive and error-prone. With JSON Serializable, you let the generator handle the conversion so your code stays clean, type-safe, and easier to maintain.
As a best practice, keep your model classes immutable using final fields and const constructors. Make sure field names align with your API responses, and handle null values gracefully using nullable types or @JsonKey(defaultValue: …). These small habits prevent runtime issues and keep your data layer reliable.
