Auto Generated Json Deserialization With Json Annotation In Flutter

If you’re coming from an Android background like me then you’ve probably missed those POJO classes in Flutter. I believe the developers who do app development in flutter will encounter such problems. After requesting data from the server, the server will often return a json string and if we want to use data flexibly, we need to convert the json string into an object .

Since flutter only provides json to Map. Handwritten deserialization is extremely unstable in large projects and can easily lead to parsing failure. So today I will introduce you to json_annotation, an automatic deserialization library recommended by flutter team.

What you’ll learn

  • Generate code with build_runner.
  • How to parse json object in the flutter with json_serialization.

Include Dependencies

We’re gonna need to add some libraries in the pupspec.yaml, which is the package management and build system file. Here we need to add three dependencies json_annotationbuild_runner and json_serializable in the pupspec.yamlfile.

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  json_annotation: ^1.2.0  // dependecy

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^1.0.0              //  |
                                    //  | -> dev dependencies
  json_serializable: ^1.5.1         //  |

Once you have done these run packets get which is in the toolbar of the file from IntelliJ/Android Studio you can also execute flutter packages pub get  from the terminal in the current project directory if you prefer that.

Now let’s say we need to make a login request and download some json content. The following shows the sample json of a simplified login rest call.

{
  "status" : true,
  "message" : "User successfuly logged in!",
  "user_name" : "Ahsen Saeed",
  "profile_url" : "www.codinginfinite.com",
  "user_id" : 280
}

Now we need to write a dart entity class based on above json data.

class LoginResponse{
  bool status;
  String message;
  String userName;
  String profileUrl;
  int userId;
  
  LoginResponse(this.status,this.message,this.userName,this.profileUrl,this.userId);

  factory LoginResponse.fromJson(map<String,dynamic> json) {
       return LoginResponse(
            status : json['status'],
            message : json['message'],
            userName : json['user_name'],
            profileUrl : json['profile_url'],
            userId : json['user_id']
       )
  }
}

I know, I know, I just want to show you guys, the manual deserialization before showing the auto-generated json deserialization.

Generate an auto-generated json File

The following shows the JsonSerializer model of above json.

import 'package:json_annotation/json_annotation.dart';

@JsonSerializable()
class LoginResponse {
  bool status;
  String message;
  @JsonKey(name: 'user_name')
  String userName;
  @JsonKey(name: 'profile_url')
  String profileUrl;
  @JsonKey(name: 'user_id')
  int userId;

  LoginResponse(
      this.status, this.message, this.userName, this.profileUrl, this.userId);
}

If we want to use JsonSerializer to generate code, we must add the annotation @JsonSerializable() before the signature of class that needs to generate the code and if you need to define the name case of the member, use the @JsonKey annotation.

So, the question is how should the code be generated..? If you guys have remembered that we add the build_runner dependency in our pupspec.yaml file.

So, in order to generate the Pojo class for a LoginResponse run the following command in the current project directory

flutter packages pub run build_runner build

After the command runs successfully, we should be able to find a new file under the entity file.

json_serialization_generated_file

The models.g.dart json parsing file generated by build_runner based on  JsonSerializer. Below is the generated dart file.

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

LoginResponse _$LoginResponseFromJson(Map<String, dynamic> json) {
  return LoginResponse(
      json['status'] as bool,
      json['message'] as String,
      json['user_name'] as String,
      json['profile_url'] as String,
      json['user_id'] as int);
}

Map<String, dynamic> _$LoginResponseToJson(LoginResponse instance) =>
    <String, dynamic>{
      'status': instance.status,
      'message': instance.message,
      'user_name': instance.userName,
      'profile_url': instance.profileUrl,
      'user_id': instance.userId
    };

Now we only need to associate our generated file in our entity class and provide a way to parse the json in the entity class. Let’s see how we can associate the generated file with our generated file.

import 'package:json_annotation/json_annotation.dart';
part 'package:flutter_projects/models/models.g.dart';   // associated generated dart file

@JsonSerializable()
class LoginResponse {
  bool status;
  String message;
  @JsonKey(name: 'user_name')
  String userName;
  @JsonKey(name: 'profile_url')
  String profileUrl;
  @JsonKey(name: 'user_id')
  int userId;

  LoginResponse(this.status, this.message, this.userName, this.profileUrl,
      this.userId);

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

In order for the entity class file to find the generated file, we need part and let the entity class to mix with the generated file. Finally, a factory constructor is provided, which actually calls  _$LoginResponseFromJson the method of the generated file. The  _$LoginResponseFromJson method is the one who deserializes our json. And that’s how we can simply deserialize our json into dart object.

Let’s take another example where we have a user json and within that user object, we have subjects of a user. Let’s see the json first.

{
  "status" : true,
  "message" : "User successfuly logged in!",
  "user_name" : "Ahsen Saeed",
  "profile_url" : "www.codinginfinite.com",
  "user_id" : 280,
  "subjects" : [
        {
           "subject_name" : "ComputerProgramming"
        },
        {
           "subject_name" : "Calculus"
        }
    ]
}

You see in order to parse the above json, we need to add a list of subjects in our LoginResponse model.

@JsonSerializable()
class LoginResponse {
  bool status;
  String message;
  @JsonKey(name: 'user_name')
  String userName;
  @JsonKey(name: 'profile_url')
  String profileUrl;
  @JsonKey(name: 'user_id')
  int userId;
  @JsonKey(name: 'subjects')
  List<Subject> subjects;

  LoginResponse(this.status, this.message, this.userName, this.profileUrl,
      this.userId, this.subjects);

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

@JsonSerializable()
class Subject {
  @JsonKey(name: 'subject_name')
  String subjectName;

  Subject(this.subjectName);

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

Finally, below is the newly generated file.

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

LoginResponse _$LoginResponseFromJson(Map<String, dynamic> json) {
  return LoginResponse(
      json['status'] as bool,
      json['message'] as String,
      json['user_name'] as String,
      json['profile_url'] as String,
      json['user_id'] as int,
      (json['subjects'] as List)
          ?.map((e) =>
              e == null ? null : Subject.fromJson(e as Map<String, dynamic>))
          ?.toList());
}

Map<String, dynamic> _$LoginResponseToJson(LoginResponse instance) =>
    <String, dynamic>{
      'status': instance.status,
      'message': instance.message,
      'user_name': instance.userName,
      'profile_url': instance.profileUrl,
      'user_id': instance.userId,
      'subjects': instance.subjects
    };

Subject _$SubjectFromJson(Map<String, dynamic> json) {
  return Subject(json['subject_name'] as String);
}

Map<String, dynamic> _$SubjectToJson(Subject instance) =>
    <String, dynamic>{'subject_name': instance.subjectName};

I hope this article, gives you a good understanding of how to deserialize the json with json_annotation library into plain old dart object. If you’ve enjoyed this story, share this article with flutter community. 

Thank you for being here and keep reading…

LEAVE A REPLY

Please enter your comment!
Please enter your name here