How to Implement JSON Serialization with `fromJson`/`toJson` Methods in Dart: A Complete Guide
You can implement JSON serialization in Dart either by using the json_annotation package with build_runner to auto-generate fromJson and toJson boilerplate, or by manually writing factory constructors that parse Map<String, dynamic> directly.
This article demonstrates how to implement JSON serialization with fromJson and toJson methods in Dart using the flutter/skills repository as a practical reference. The codebase provides two distinct patterns: a code generation approach for robust type safety and a manual implementation for simpler models.
Code Generation with json_annotation and build_runner
The primary approach in the flutter/skills repository uses code generation to eliminate boilerplate. In tool/dart_skills_lint/lib/src/models/ignore_entry.dart and skills_ignores.dart, the team uses the json_annotation package to define serialization contracts declaratively.
Step 1: Add Dependencies
Configure your pubspec.yaml with both runtime and dev dependencies:
dependencies:
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.0
json_serializable: ^6.7.0
As seen in tool/generator/pubspec.yaml and tool/dart_skills_lint/pubspec.yaml, these packages enable the build system to scan annotations and generate serialization logic automatically.
Step 2: Annotate Your Model
Import the annotation package and mark your class with @JsonSerializable():
import 'package:json_annotation/json_annotation.dart';
part 'person.g.dart';
@JsonSerializable()
class Person {
final String name;
final int age;
@JsonKey(name: 'email_address')
final String? email;
Person({required this.name, required this.age, this.email});
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
Map<String, dynamic> toJson() => _$PersonToJson(this);
}
The part 'person.g.dart' directive links to the generated file that build_runner will create.
Step 3: Run the Generator
Execute the build command to generate the *.g.dart files:
dart run build_runner build
This creates ignore_entry.g.dart and skills_ignores.g.dart in the repository, containing the _$ClassFromJson and _$ClassToJson implementations that handle type-safe conversion, nullable fields, and custom key mappings.
Manual Implementation for Simple Models
For cases where code generation is unnecessary, the repository demonstrates hand-written serialization in tool/generator/lib/src/models/skill_params.dart.
The SkillParams class implements a factory constructor that extracts values directly from the JSON map:
class SkillParams {
final String name;
final String description;
final List<String> resources;
final String? instructions;
final String? examplePrompt;
SkillParams({
required this.name,
required this.description,
required this.resources,
this.instructions,
this.examplePrompt,
});
factory SkillParams.fromJson(Map<String, dynamic> json) => SkillParams(
name: json['name'] as String,
description: json['description'] as String,
resources: (json['resources'] as List).cast<String>(),
instructions: json['instructions'] as String?,
examplePrompt: json['examplePrompt'] as String?,
);
}
This approach requires no build steps but demands manual updates whenever the model structure changes.
Working with Generated Code
The generated helpers in files like ignore_entry.g.dart provide robust type handling. When you call IgnoreEntry.fromJson(json), the generated _$IgnoreEntryFromJson function instantiates the class by casting each map value to the correct Dart type, including nested objects and collections.
To serialize an instance, toJson() delegates to _$IgnoreEntryToJson, returning a Map<String, dynamic> suitable for jsonEncode() or file storage.
Testing Your Implementation
The flutter/skills repository includes unit tests in tool/dart_skills_lint/test/ignore_models_test.dart that verify both serialization directions. Test your models by round-tripping JSON through fromJson and toJson to ensure field integrity.
Summary
- Annotation-driven serialization using
@JsonSerializable()andbuild_runneris the preferred approach for maintainable Dart codebases, automatically generating_$ClassFromJsonand_$ClassToJsonhelpers in*.g.dartfiles. - Manual factory constructors work for simple models without dependencies, as demonstrated in
skill_params.dart, but require manual synchronization with JSON structures. - Always include
json_annotationin dependencies andjson_serializable/build_runnerin dev_dependencies, as configured inpubspec.yamlfiles throughout the repository. - Link generated code using
part 'filename.g.dart'directives and rundart run build_runner buildto regenerate serialization logic after model changes.
Frequently Asked Questions
What is the difference between manual and generated JSON serialization in Dart?
Manual serialization requires you to write factory constructors that cast Map<String, dynamic> values to typed fields, while generated serialization uses build_runner to create boilerplate code automatically based on @JsonSerializable() annotations. The generated approach reduces human error and handles complex edge cases like nullable fields and renamed keys automatically, as seen in the IgnoreEntry implementation.
How do I handle custom JSON key names when using json_serializable?
Use the @JsonKey(name: 'custom_name') annotation above the field that differs from your Dart property name. For example, in the Person class example, the email field maps to the JSON key email_address. The generated _$PersonFromJson function automatically reads from the custom key during deserialization and writes to it during serialization.
Can I use fromJson/toJson with nested objects and collections?
Yes. The json_serializable package handles nested models and generic collections automatically. In skills_ignores.dart, the repository demonstrates serializing maps containing lists of complex objects. Ensure nested classes are also annotated with @JsonSerializable() and have their own fromJson/toJson methods, and the generator will create code that properly serializes the entire object graph.
Why does my generated file show errors before running build_runner?
The part 'file.g.dart' directive references code that does not exist until you run the generator. Execute dart run build_runner build to create the missing *.g.dart files containing the _$ClassFromJson and _$ClassToJson implementations. If the errors persist after building, ensure your part file name matches the source file name exactly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →