# How to Implement JSON Serialization with `fromJson`/`toJson` Methods in Dart: A Complete Guide

> Learn to implement JSON serialization in Dart using auto-generated or manual fromJson toJson methods. This comprehensive guide simplifies object mapping for your Flutter skills project.

- Repository: [Flutter/skills](https://github.com/flutter/skills)
- Tags: how-to-guide
- Published: 2026-05-09

---

**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`](https://github.com/flutter/skills/blob/main/pubspec.yaml) with both runtime and dev dependencies:

```yaml
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`](https://github.com/flutter/skills/blob/main/tool/generator/pubspec.yaml) and [`tool/dart_skills_lint/pubspec.yaml`](https://github.com/flutter/skills/blob/main/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()`:

```dart
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:

```bash
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:

```dart
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`](https://github.com/flutter/skills/blob/main/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.