Code Generation

35.1 build_runner + json_serializable

// lib/models/user.dart
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';

@JsonSerializable(explicitToJson: true, fieldRename: FieldRename.snake)
class User {
  final int id;
  final String fullName;
  @JsonKey(defaultValue: false)
  final bool isAdmin;
  final DateTime createdAt;

  User({required this.id, required this.fullName, required this.isAdmin, required this.createdAt});

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
dart pub add json_annotation
dart pub add --dev build_runner json_serializable
dart run build_runner build --delete-conflicting-outputs
dart run build_runner watch

35.2 freezed (Immutable Data Classes and Unions)

Other generators: injectable (DI), retrofit/chopper (HTTP clients), drift (SQL), riverpod_generator, mockito (mocks), auto_route, built_value, dart_mappable.

Writing generators: source_gen + analyzer + the build package (builders, build.yaml).

Macros: the experimental macros feature was cancelled by the Dart team in early 2025. Focus moved to improving build_runner performance and language features (such as augmentations and data-class work). Code generation remains the standard approach.

import 'package:freezed_annotation/freezed_annotation.dart';
part 'todo.freezed.dart';
part 'todo.g.dart';

@freezed
abstract class Todo with _$Todo {
  const factory Todo({
    required String id,
    required String title,
    @Default(false) bool done,
  }) = _Todo;

  factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
}

void main() {
  final t = Todo(id: '1', title: 'Learn Dart');
  final done = t.copyWith(done: true);           // copyWith, ==, hashCode, toString generated
  print(t == Todo(id: '1', title: 'Learn Dart')); // true
}