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() and build_runner is the preferred approach for maintainable Dart codebases, automatically generating _$ClassFromJson and _$ClassToJson helpers in *.g.dart files.
  • 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_annotation in dependencies and json_serializable/build_runner in dev_dependencies, as configured in pubspec.yaml files throughout the repository.
  • Link generated code using part 'filename.g.dart' directives and run dart run build_runner build to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →