Embrace the Lazy Way Dive into json_serializable in Flutter

Grey Minimalist Tips Blog Banner.png

Flutter development often involves lots of JSON wrangling—fetching it from APIs, converting it into Dart objects, sending it back, and all that jazz. Sure, you could write those boilerplate fromJson and toJson methods yourself. But why bother when the json_serializable package can do the heavy lifting for you? Let’s dig into this magical library and how it can save you time and headaches, with some playful examples to boot.


What is json_serializable?

json_serializable is a code generation library that automates the process of converting Dart objects to JSON and back again. It takes care of all the nitty-gritty details while you focus on building your app—or sipping coffee.

With json_serializable, you’ll:


Getting Started

Here’s how to set up json_serializable in your Flutter project and start using it like a pro.

Step 1: Add Dependencies

First, include the necessary packages in your pubspec.yaml file:

And, there is the packages:

dependencies:
  json_annotation: ^4.9.0

dev_dependencies:
  build_runner: ^2.4.14
  json_serializable: ^6.9.3

Run flutter pub get to fetch the dependencies.


Step 2: Annotate Your Model

Let’s create a CoffeeShop class—because who doesn’t love coffee?

import 'package:json_annotation/json_annotation.dart';

part 'coffee_shop.g.dart';

@JsonSerializable()
class CoffeeShop {
  final String name;
  final String location;
  final double rating;
  final List<String> specialties;

  CoffeeShop({
    required this.name,
    required this.location,
    required this.rating,
    required this.specialties,
  });

  factory CoffeeShop.fromJson(Map<String, dynamic> json) => _$CoffeeShopFromJson(json);

  Map<String, dynamic> toJson() => _$CoffeeShopToJson(this);
}

A few things to note here:


Step 3: Generate the Code

Now for the fun part. Run the following command in your terminal:

flutter pub run build_runner build

This generates the coffee_shop.g.dart file, which contains the serialization code.

Here’s a peek at what’s inside:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'coffee_shop.dart';

CoffeeShop _$CoffeeShopFromJson(Map<String, dynamic> json) {
  return CoffeeShop(
    name: json['name'] as String,
    location: json['location'] as String,
    rating: (json['rating'] as num).toDouble(),
    specialties: (json['specialties'] as List<dynamic>).map((e) => e as String).toList(),
  );
}

Map<String, dynamic> _$CoffeeShopToJson(CoffeeShop instance) => <String, dynamic>{
      'name': instance.name,
      'location': instance.location,
      'rating': instance.rating,
      'specialties': instance.specialties,
    };

Boom. All that boilerplate is taken care of for you.


Going the Extra Mile

Handling Null Values

What if your API sometimes returns null? Use the @JsonKey annotation to set default values:

@JsonSerializable()
class CoffeeShop {
  @JsonKey(defaultValue: 'Unnamed Coffee Shop')
  final String name;
  
  @JsonKey(defaultValue: 'Unknown Location')
  final String location;
  
  @JsonKey(defaultValue: 0.0)
  final double rating;

  @JsonKey(defaultValue: [])
  final List<String> specialties;

  CoffeeShop({
    required this.name,
    required this.location,
    required this.rating,
    required this.specialties,
  });

  factory CoffeeShop.fromJson(Map<String, dynamic> json) => _$CoffeeShopFromJson(json);

  Map<String, dynamic> toJson() => _$CoffeeShopToJson(this);
}

Now your app won’t break if some fields are missing.


Custom Serialization Logic

Need to convert a timestamp to a DateTime? No problem. Here’s how:

@JsonSerializable()
class CoffeeShop {
  final String name;
  final String location;

  @JsonKey(fromJson: _fromTimestamp, toJson: _toTimestamp)
  final DateTime established;

  CoffeeShop({
    required this.name,
    required this.location,
    required this.established,
  });

  factory CoffeeShop.fromJson(Map<String, dynamic> json) => _$CoffeeShopFromJson(json);

  Map<String, dynamic> toJson() => _$CoffeeShopToJson(this);

  static DateTime _fromTimestamp(int timestamp) => DateTime.fromMillisecondsSinceEpoch(timestamp);

  static int _toTimestamp(DateTime date) => date.millisecondsSinceEpoch;
}

Now your dates are seamlessly converted.


Handling Different Variable Names in Code and Database

Sometimes, the variable name in your Dart code might be different from the one in your database. You can use @JsonKey(name: 'database_field_name') to map them correctly:

@JsonSerializable()
class CoffeeShop {
  @JsonKey(name: 'shop_name')
  final String name;
  
  @JsonKey(name: 'shop_location')
  final String location;
  
  @JsonKey(name: 'avg_rating')
  final double rating;

  CoffeeShop({
    required this.name,
    required this.location,
    required this.rating,
  });

  factory CoffeeShop.fromJson(Map<String, dynamic> json) => _$CoffeeShopFromJson(json);

  Map<String, dynamic> toJson() => _$CoffeeShopToJson(this);
}

Wrapping Up

json_serializable is a must-have for Flutter developers looking to streamline their JSON parsing. It eliminates boilerplate, handles edge cases, and keeps your codebase tidy.
Why not give it a spin? You’ll have more time for the important things—like finding the best coffee shop in town. Happy coding!