Code Generation in Flutter with Freezed & JsonSerializable

Code Generation in Flutter with Freezed & JsonSerializable

Code Generation in Flutter with Freezed & JsonSerializable

In real-world applications, especially those involving APIs, we constantly deal with data models.

For example, in a Hotel Booking App:

  • We have a User model (id, name, email).
  • A Hotel model (id, name, address, rating).
  • A Booking model (user, hotel, check-in date, check-out date, status).

Every time data comes from an API, you need to deserialize JSON into Dart objects, and when sending requests, you need to serialize Dart objects back to JSON.

Doing this manually means:

  • Writing fromJson() and toJson() methods.
  • Writing copyWith() for immutability.
  • Overriding == and hashCode for equality.
  • Maintaining this across multiple models.

That’s a lot of repetitive work, and one small mistake (like forgetting a field in serialization) can cause bugs in production. So we need a way to automate this boilerplate while ensuring immutability, type-safety, and clean code.

Freezed:

Freezed is a Dart package that focuses on immutability and value equality.

In Dart, classes are mutable by default, which means you can change their properties after creating them. While this is flexible, it often leads to bugs in state management, because a widget might rebuild unexpectedly or comparisons fail. Freezed solves this by:  

Generating immutable data classes (fields cannot be changed after object creation). Providing a copyWith() method so you can still create modified copies of an object without mutating the original.  Generating equality and hashCode automatically, so two objects with the same values are considered equal (useful in lists, sets, and state management). Supporting sealed classes/unions, which allow you to define multiple states for objects (ideal for state management like Bloc or Riverpod). Freezed ensures your models are clean, immutable, and predictable

Advantages:
  1. Immutability by default – All classes are immutable, which prevents accidental changes to objects and makes your app state more predictable.
  2. Auto-generated copyWith – Easily create modified versions of an object without mutating the original (very useful in state management).
  3. Value Equality – Objects are compared based on their values, not memory references, which avoids common equality bugs.
Disadvantages:
  1. Learning Curve – Developers new to code generation or annotations may find it overwhelming at first.
  2. Extra Files – Generates .freezed.dart and .g.dart files, which can clutter the project structure.
  3. Build Runner Dependency – Requires running flutter pub run build_runner build whenever models change, which adds a step to the workflow.
Code Generation in Flutter with Freezed & JsonSerializable
Uses of Freezed:
  • Defining immutable models.
  • Automatically generating copyWith.
  • Enforcing value equality instead of reference equality.
  • Using union/sealed classes to represent multiple states (e.g., Loading, Success, Error).
  • Making state management simpler and less error-prone.

JsonSerializable:

Json Serializable is a Dart package that focuses on data serialization. Serialization means converting Dart objects into JSON (for APIs, storage, etc.), and deserialization means converting JSON into Dart objects.

Traditionally, developers had to write:

  • A fromJson() factory constructor to parse JSON.
  • A toJson() method to map Dart objects back to JSON.

This becomes repetitive and error-prone. JsonSerializable solves this by using annotations and code generation. You just declare your class and annotate it, and it automatically generates the fromJson and toJson methods for you. Unlike Freezed (which focuses on immutability and equality),  JsonSerializable focuses only on JSON conversion, but the two work perfectly together. JsonSerializable ensures your models can seamlessly communicate with APIs without writing repetitive serialization logic.

Uses of JsonSerializable:
  • Parsing JSON responses from REST APIs.
  • Preparing request payloads in JSON.
  • Automatically keeping serialization logic in sync with class fields.
  • Reducing boilerplate in data-heavy applications.
Advantages:
  1. Automatic JSON Parsing – Eliminates repetitive and error-prone toJson and fromJson code.
  2. Strongly Typed – Helps prevent runtime errors by ensuring only valid JSON keys map to Dart fields.
  3. Consistent Serialization – Ensures all models follow the same pattern, avoiding inconsistencies.
Disadvantages:
  1. Still Needs Build Runner – Requires running code generation after every model update.
  2. No Immutability – JsonSerializable by itself doesn’t enforce immutability (Freezed fills this gap).
  3. Manual Updates on API Changes – If the backend API changes, developers still need to manually update the fields in the model.
Screenshot 2025 10 07 194422 - Freezed

Freezed + JsonSerializable: Combined Benefits:

Using Freezed with JsonSerializable provides immutability, value equality, copyWith methods, and automatic JSON serialization in one. This is especially useful for API-driven apps like e-commerce, booking, or finance, making models clean, reliable, and scalable. It reduces boilerplate and errors, letting developers focus on business logic. The trade-offs include extra dependencies, running code generation for model changes, a slight learning curve, and additional generated files in smaller projects. Despite this, the combination greatly improves maintainability, productivity, and code safety in medium to large Flutter applications.

Screenshot 2025 10 07 194751 - Freezed

Conclusion:

  • Freezed = immutability + equality + sealed classes → solves the problem of messy, mutable, and boilerplate-heavy models.
  • JsonSerializable = JSON serialization → solves the problem of repetitive fromJson and toJson methods.
  • Together = clean, scalable, bug-free models → ideal for real-world applications that deal with APIs and state management.

By adopting Freezed + JsonSerializable, you spend less time writing boilerplate and more time focusing on business logic. The small trade-off of the learning curve and build_runner is negligible compared to the long-term benefits.

-Anuj Kumar
Full Stack Engineer