You've successfully subscribed to developer.school
Great! Next, complete checkout for full access to developer.school
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Billing info update failed.

Using json_serializable to Serialise Dart/Flutter Models

Paul Halliday
Paul Halliday

In this article we're going to be looking at how we can use the json_serializable package to parse from/convert to json model instances within Flutter. We'll also look at how to use JsonConverter<X, Y> to write custom conversions for non-primitive types.

Project Setup

As always, we'll be starting with a blank Flutter project. Run the following in your terminal:

$ flutter create ds_json

$ cd ds_json

$ code . (or your favourite editor)

You'll then need to add the following packages to pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter

  json_annotation: ^3.1.1

dev_dependencies:
  flutter_test:
    sdk: flutter

  build_runner: ^1.10.9
  json_serializable: ^3.5.1
pubspec.yaml

You can now run your application on the emulator or device.

Contact List Application

Imagine an application that allows us to store a list of contacts. As it turns out, we're interested in knowing the following information about our contact:

  • Name
  • Date of Birth
  • Favourite Colour

This can be expressed as a Contact model like so:

@immutable
class Contact {
  final String name;
  final DateTime dateOfBirth;
  final Color favouriteColour;

  const Contact({
    @required this.name,
    @required this.dateOfBirth,
    @required this.favouriteColour,
  });
  
  /// Below here is used for equality comparison
  ///
  /// See the freezed article for a good way to automate this
  /// https://developer.school/how-to-use-freezed-with-flutter/

  @override
  bool operator ==(Object other) =>
      other is Contact &&
      other.name == name &&
      other.dateOfBirth == dateOfBirth &&
      other.favouriteColour == favouriteColour;

  @override
  int get hashCode => hashValues(name, dateOfBirth, favouriteColour);
}
contacts/domain/models/contact_model.dart

Ideally, we'd like to be able to call contact.toJson() on a Contact instance and a Contact.fromJson(json) factory. You'll find the need to do this when you're saving or retrieving data in JSON format locally or across a network.

Without json_serializable

Without the use of this library we might write toJson/fromJson serialisation code like this:

factory Contact.fromJson(Map<String, dynamic> json) => Contact(
      name: json['name'] as String,
      dateOfBirth: DateTime.parse(json['dateOfBirth'] as String),
      favouriteColour: Color(json['favouriteColour'] as int),
    );

Map<String, dynamic> toJson() => {
      'name': name,
      'dateOfBirth': dateOfBirth.toIso8601String(),
      'favouriteColour': favouriteColour.value,
    };

The major issue with this is that the process is painfully manual. Not only do we have to update our serialisation method(s) every time we add new properties to this class, but we have to do this for every class.

It doesn't scale well.  This is where json_serializable comes in handy.

Using json_serializable

Update your Contact model to contain the following methods:

factory Contact.fromJson(Map<String, dynamic> json) =>
    _$ContactFromJson(json);
    
Map<String, dynamic> toJson() => _$ContactToJson(this);

This funky looking code is generated from json_serializable when we run the build_runner command. However, it won't generate our model yet until we add the JsonSerializable annotation and define the part that contains the generated code.

import 'package:flutter/widgets.dart';
import 'package:json_annotation/json_annotation.dart';

// 1
part 'contact_model.g.dart';

@immutable
@JsonSerializable()
class Contact {
  final String name;
  final DateTime dateOfBirth;
  final Color favouriteColour;

  const Contact({
    @required this.name,
    @required this.dateOfBirth,
    @required this.favouriteColour,
  });
	
  // 2
  factory Contact.fromJson(Map<String, dynamic> json) =>
      _$ContactFromJson(json);
  Map<String, dynamic> toJson() => _$ContactToJson(this);
}

build_runner

We can now run the generator by typing the following inside of our terminal:

$ flutter pub run build_runner watch --delete-conflicting-outputs
Make sure you run this inside of our project directory.

This starts the build_runner which acts as a task runner for the json_serializable package. As we've added the annotation and part, it'll attempt to generate the serialisation for our Contact.

Uh oh. You'll notice that we have an error.

[SEVERE] json_serializable:json_serializable on lib/src/contacts/domain/models/contact_model.dart:

Could not generate `fromJson` code for `favouriteColour`.

In order to make this work we'll need to make a custom JsonConverter that is able to perform the .toJson and .fromJson for the Color field. This is because json_serializable only contains relevant converters for primitive types and isn't able to automatically infer this.

Using JsonConverter

Let's make a a JsonConverter for our Color class. The questions we need to ask ourselves at this point are:

  • What's the best primitive data type to use when serialising this model?

    In the Flutter Color type, we can convert this to an int by using the value getter.

    You could also store this as a String if you wanted. The "best" here is based on a per-project/requirement basis and int works best for us.
  • How can we create a new model from the serialised data?

    We can create a new Color from an int.

This can be implemented like so:

import 'package:flutter/widgets.dart';
import 'package:json_annotation/json_annotation.dart';

class ColorSerialiser implements JsonConverter<Color, int> {
  const ColorSerialiser();

  @override
  Color fromJson(int json) => Color(json);

  @override
  int toJson(Color color) => color.value;
}

Finally, we can add the @ColorSerialiser() annotation to our favouriteColour and run build_runner again if needed.

@JsonSerializable()
class Contact {
  // Redacted for example

  @ColorSerialiser()
  final Color favouriteColour;
}

Serialising/Deserialising a Contact

Instead of building our a user interface for our Contact serialisation example, we'll write a quick set of unit tests that confirm it works as expected.

Create a file in the tests directory named contact_test.dart:

import 'package:ds_json/src/contacts/domain/models/contact_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group("Serialisation", () {
    test('Contact is serialised to json', () async {
      final actual = Contact(
        name: "Paul",
        dateOfBirth: DateTime(2020, 1, 1),
        favouriteColour: Color(0xff9b59b6),
      ).toJson();

      final matcher = {
        "name": "Paul",
        "dateOfBirth": "2020-01-01T00:00:00.000",
        "favouriteColour": 4288371126
      };

      expect(actual, matcher);
    });

    test('Contact is serialised from json', () async {
      final Map<String, dynamic> json = {
        "name": "Paul",
        "dateOfBirth": "2020-01-01T00:00:00.000",
        "favouriteColour": 4288371126
      };

      final actual = Contact.fromJson(json);

      final matcher = Contact(
        name: "Paul",
        dateOfBirth: DateTime(2020, 1, 1),
        favouriteColour: Color(0xff9b59b6),
      );

      expect(actual, matcher);
    });
  });
}

We can then run this test by typing the following inside of our terminal:

$ flutter test

Running "flutter pub get" in ds_json...                            564ms
00:02 +2: All tests passed!

This proves that our ColorSerialiser worked as intended. It converted the favouriteColor to an int on toJson serialisation and created a Color object upon fromJson deserialisation.

Summary

In this article we looked at using json_serializable to easily write the necessary JSON serialisation code for us. This saved us a significant amount of time in comparison to writing this manually.

You can find the code for this article here:

PaulHalliday/ds_json
This is part of the json_serializable article on https://developer.school - PaulHalliday/ds_json

Flutter

Paul Halliday

👋 Want to see more content? Head over to the YouTube channel: https://youtube.com/c/paulhalliday!