12

Since Flutter took out dart: mirrors off of its SDK, it's no longer possible to use libraries like dartson for JSON to object serialization/deserialization. However I've read that built_value is another way of achieving a similar purpose. I couldn't find any good examples on how to implement it as it contains a significant amount of boilerplate code. Can someone give me an example? For instance, this is the JSON I'm trying to serialize to objects:

{
    "name":"John",
    "age":30,
    "cars": [
        { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },
        { "name":"BMW", "models":[ "320", "X3", "X5" ] },
        { "name":"Fiat", "models":[ "500", "Panda" ] }
    ]
 }
user3217522
  • 5,786
  • 7
  • 26
  • 34

6 Answers6

14

I was hoping for more details from the answers provided. Even though they were good suggestions, they were too general for me to understand. So after doing my own research, I'll share my implementation to the above JSON example I provided in hope that it would save someone's else's time. So here are the steps I followed:

  • In my Flutter project, first I imported the following libraries:

dependencies:

built_value: ^1.0.1
built_collection: ^1.0.0

dev_dependencies:

build_runner: ^0.3.0
built_value_generator:^1.0.1

  • I created a folder called tool. In it, I put 2 files: build.dart and watch.dart. There implementations of those files are show below

build.dart

// Copyright (c) 2015, Google Inc. Please see the AUTHORS file for details.
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import 'dart:async';

import 'package:build_runner/build_runner.dart';
import 'package:built_value_generator/built_value_generator.dart';
import 'package:source_gen/source_gen.dart';

/// Example of how to use source_gen with [BuiltValueGenerator].
///
/// Import the generators you want and pass them to [build] as shown,
/// specifying which files in which packages you want to run against.
Future main(List<String> args) async {
  await build(
      new PhaseGroup.singleAction(
          new GeneratorBuilder([new BuiltValueGenerator()]),
          new InputSet('built_value_example', const [
            'lib/model/*.dart',
            'lib/*.dart',
          ])),
      deleteFilesByDefault: true);
}

watch.dart

// Copyright (c) 2016, Google Inc. Please see the AUTHORS file for details.
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import 'dart:async';

import 'package:build_runner/build_runner.dart';
import 'package:built_value_generator/built_value_generator.dart';
import 'package:source_gen/source_gen.dart';

/// Example of how to use source_gen with [BuiltValueGenerator].
///
/// This script runs a watcher that continuously rebuilds generated source.
///
/// Import the generators you want and pass them to [watch] as shown,
/// specifying which files in which packages you want to run against.
Future main(List<String> args) async {
  watch(
      new PhaseGroup.singleAction(
          new GeneratorBuilder([new BuiltValueGenerator()]),
          new InputSet('built_value_example', const [
            'lib/model/*.dart',
            'lib/*.dart'])),
      deleteFilesByDefault: true);
}
  • I created a serializers.dart file that would serialize my json string to my custom dart object, and my model object person.dart

serializers.dart

library serializers;

import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'model/person.dart';

part 'serializers.g.dart';

Serializers serializers = (
    _$serializers.toBuilder()..addPlugin(new StandardJsonPlugin())
).build();

person.dart

library person;

import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';

part 'person.g.dart';

abstract class Person implements Built<Person, PersonBuilder> {
  String get name;
  int get age;
  BuiltList<Car> get cars;

  Person._();
  factory Person([updates(PersonBuilder b)]) = _$Person;
  static Serializer<Person> get serializer => _$personSerializer;
}

abstract class Car implements Built<Car, CarBuilder> {
  String get name;
  BuiltList<String> get models;

  Car._();
  factory Car([updates(CarBuilder b)]) = _$Car;
  static Serializer<Car> get serializer => _$carSerializer;
}
  • After creating the 4 files above, it will show some compiler errors. Don't mind them yet. This is because the build.dart file hasn't been run yet. So in this step, run build.dart. If you're using Webstorm, simply right click on build.dart and hit "Run build.dart". This will create 2 files: "person.g.dart" and "serializers.g.dart". If you notice carefully, in our build.dart file, we put 'lib/model/.dart' and 'lib/.dart'. The build knows where to look for those files by going through the paths specified and looks for files which have part "something" included. So it's important to keep that line in those files before running the build.dart file

  • Finally, now I can use the serializer in my main.dart file to serialize the json string to my custom dart object class Person. In my main.dart, I added the following code in initState()

main.dart

  Person _person;

  @override
  void initState() {
    super.initState();
    String json = "{"
        "\"name\":\"John\",\"age\":30,\"cars\": "
        "["
        "{ \"name\":\"Ford\", \"models\":[ \"Fiesta\", \"Focus\", \"Mustang\" ] },"
        "{ \"name\":\"BMW\", \"models\":[ \"320\", \"X3\", \"X5\" ] },"
        "{ \"name\":\"Fiat\", \"models\":[ \"500\", \"Panda\" ] }"
        "]}";

    setState(() {
      _person = serializers.deserializeWith(
          Person.serializer, JSON.decode(json));
    });
  }

My sample project is also available on Github Built value sample project

Community
  • 1
  • 1
user3217522
  • 5,786
  • 7
  • 26
  • 34
  • Thank you very much for sharing your solution. however i met a problem when i want to instanciate a Person class and set value to age for exemple. It is impossible because the setter is missing. Do you have any solutions to deal with that ? I want to use same class into my project and for serialization – Vincenzo Dec 20 '17 at 20:41
  • built_value uses immutable objects. This is a design choice. That way, you never have mutated objects in your code that might cause unexpected behaviour, because you have to instantiate a new object every time you want to change a value. – Daniel Eberl Feb 14 '20 at 08:30
7

json_serialization

This package by the Dart Team generates everything needed for the fromJson constructor and toJson method in a seprate file.

Dependencies

Add the following dependencies:

dependencies:
  json_annotation: ^2.0.0

dev_dependencies:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

Model class

Adapt your model class to have the following parts:

import 'package:json_annotation/json_annotation.dart';

// will be generated later
part 'person.g.dart';

@JsonSerializable()
class Person {
  Person(this.name, this.age);

  final String name;
  final int age;

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

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

Generate code

Generate the person.g.dart file from the terminal:

flutter packages pub run build_runner build

Use it

Then use it like this:

JSON → object

String rawJson = '{"name":"Mary","age":30}';
Map<String, dynamic> map = jsonDecode(rawJson);
Person person = Person.fromJson(map);

Object → JSON

Person person = Person('Mary', 30);
Map<String, dynamic> map = person.toJson();
String rawJson = jsonEncode(map);

Notes

  • In a Dart project use pub run build_runner build.
  • See this answer for more ways to serialize JSON.
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • What if I receive a list of Persons? How do I deserialize it using this package? Like say this one: [{"name":"Mary","age":30}, {"name":"Joe","age":20}] Thanks :) – hkop Jun 12 '19 at 16:17
  • 1
    @hkop, to deserialize a list of json objects, check out [this Q&A](https://stackoverflow.com/questions/51053954/how-to-deserialize-a-list-of-objects-from-json-in-flutter) – Suragch Jun 12 '19 at 18:45
4

From the Dart web site:

The dart:convert library provides a JsonCodec class, which you can use to convert simple types (map, list, int, num, string) automatically from a and to a JSON string. The two key static methods are JSON.encode(object) and JSON.decode(string).

Decoding example:

import 'dart:convert';
...    
Map<String, dynamic> parsedMap = JSON.decode(json);
print(parsedMap['name']); // John
print(parsedMap['age']); // 30

Encoding example:

Map<String, dynamic> mapData = <String, dynamic>{ 'hello': 'world!' };
String jsonData = JSON.encode(mapData); // convert map to String

If you want to have your JSON inflate into custom Dart classes instead of a tree of primitive objects, Hadrien's answer should point you in the right direction, but I just wanted to leave this here in case anyone else is trying to get basic JSON serialization/deserialization working.

Collin Jackson
  • 110,240
  • 31
  • 221
  • 152
2

You can use Jaguar Serializer, it is easy to start and work perfectly for Flutter, or Server and Web dev.

https://github.com/Jaguar-dart/jaguar_serializer

Hadrien Lejard
  • 5,644
  • 2
  • 21
  • 18
  • This lib looks interesting! I tried it, but I'm getting this error when I run serializer build: 'package:jaguar_generator_config/src/generator.dart': error: line 17: illegal implicit access to receiver 'this' _config = loadYaml(new File(configFileName).readAsStringSync()); – user3217522 Jun 19 '17 at 14:49
  • I've created an issue https://stackoverflow.com/questions/44634860/error-when-running-serializer-build-in-jaguar-serializer – user3217522 Jun 19 '17 at 15:47
1

You should prepare a configuration file for Built_value that will parse you dart source and generate the .g.dart. Once there are ready json serialisation is automatic. You can generate those files once or using a watch command.

Those file will be added at the same level than the source and the dart command

part of data;

to be seen as the same Class.

Here's the config I'm using with my Flutter project :

import 'dart:async';

import 'package:build_runner/build_runner.dart';
import 'package:built_value_generator/built_value_generator.dart';
import 'package:source_gen/source_gen.dart';


Future main(List<String> args) async {

await build(

new PhaseGroup.singleAction(

new GeneratorBuilder([

new BuiltValueGenerator(),

]),

new InputSet('flutter_project', const ['lib/data/*.dart'])),

deleteFilesByDefault: true);

}

You may find useful to read all the posts by David Morgan to understand the benefits. It need some time to turn your mind around but it's a very good pattern.

https://medium.com/dartlang/darts-built-value-for-immutable-object-models-83e2497922d4

https://medium.com/dartlang/darts-built-value-for-serialization-f5db9d0f4159

The trick is to understand how sourcegen parse and then enrich your classes by adding a lot of behaviors like Builders and Serializers.

Robert Felker
  • 890
  • 1
  • 9
  • 18
  • Hi Robert. Thanks for your reply! I'm still confused as to how this works. Sorry I'm kinda new to Flutter and Dart. Could you add more details to your answer? Or point me to some other links that give more details? I'm still not sure how this works with the example JSON I gave – user3217522 Jun 16 '17 at 15:37
0
            1) first put json code to any convert website ex: 
            [JSON to Darthttps://javiercbk.github.io ›][1] 
            it will give like this output 
            
            
                class Autogenerated {
                  int? code;
                  List<Result>? result;
                  String? status;
                
                  Autogenerated({this.code, this.result, this.status});
                
                  Autogenerated.fromJson(Map<String, dynamic> json) {
                    code = json['code'];
                    if (json['result'] != null) {
                      result = <Result>[];
                      json['result'].forEach((v) {
                        result!.add(new Result.fromJson(v));
                      });
                    }
                    status = json['status'];
                  }
                
                  Map<String, dynamic> toJson() {
                    final Map<String, dynamic> data = new Map<String, dynamic>();
                    data['code'] = this.code;
                    if (this.result != null) {
                      data['result'] = this.result!.map((v) => v.toJson()).toList();
                    }
                    data['status'] = this.status;
                    return data;
                  }
                }
                
                class Result {
                  int? id;
                  int? projectId;
                  String? projectName;
                  int? userId;
                  String? userName;
                
                  Result(
                      {this.id, this.projectId, this.projectName, this.userId, this.userName});
                
                  Result.fromJson(Map<String, dynamic> json) {
                    id = json['id'];
                    projectId = json['project_id'];
                    projectName = json['project_name'];
                    userId = json['user_id'];
                    userName = json['user_name'];
                  }
                
                  Map<String, dynamic> toJson() {
                    final Map<String, dynamic> data = new Map<String, dynamic>();
                    data['id'] = this.id;
                    data['project_id'] = this.projectId;
                    data['project_name'] = this.projectName;
                    data['user_id'] = this.userId;
                    data['user_name'] = this.userName;
                    return data;
                  }
                }
        
        --------------------------------------------------------------------------------------
and change that code like  this format for easy use 
                
                ***get_client.dart***   
                
                 import 'package:json_annotation/json_annotation.dart';
                    
                    part 'get_client.g.dart';
                    
                    @JsonSerializable(
                      explicitToJson: true,
                    )
                    class ClientModel {
                      @JsonKey(name: "address1")
                      String? address1;
                      @JsonKey(name: "city")
                      int? city;
                      @JsonKey(name: "city_name")
                      String? cityName;
                      @JsonKey(name: "country")
                      int? country;
                      @JsonKey(name: "country_name")
                      String? countryName;
                      @JsonKey(name: "email")
                      String? email;
                      @JsonKey(name: "first_name")
                      String? firstName;
                      @JsonKey(name: "gender")
                      String? gender;
                      @JsonKey(name: "id")
                      int? id;
                      @JsonKey(name: "last_name")
                      String? lastName;
                      @JsonKey(name: "mobile_no")
                      String? mobileNo;
                      @JsonKey(name: "password")
                      String? password;
                      @JsonKey(name: "pincode")
                      String? pincode;
                      @JsonKey(name: "role")
                      int? role;
                      @JsonKey(name: "role_name")
                      String? roleName;
                      @JsonKey(name: "state")
                      int? state;
                      @JsonKey(name: "state_name")
                      String? stateName;
                    
                      ClientModel(
                          {this.address1,
                          this.city,
                          this.cityName,
                          this.country,
                          this.countryName,
                          this.email,
                          this.firstName,
                          this.gender,
                          this.id,
                          this.lastName,
                          this.mobileNo,
                          this.password,
                          this.pincode,
                          this.role,
                          this.roleName,
                          this.state,
                          this.stateName});
                    
                      factory ClientModel.fromJson(Map<String, dynamic> map) =>
                          _$ClientModelFromJson(map);
                    
                      Map<String, dynamic> toJson() => _$ClientModelToJson(this);
                    }
            
            -------------------------------------------------------------------
            
            
              [1]: https://%20JSON%20to%20Darthttps://javiercbk.github.io%20%E2%80%BA
        
        after run build runner cmd in terminal 
        ***flutter pub run build_runner build*** 
        
        then it will create the following output file 
        // GENERATED CODE - DO NOT MODIFY BY HAND
        
        part of 'get_client.dart';
        
        // **************************************************************************
        // JsonSerializableGenerator
        // **************************************************************************
        
        ClientModel _$ClientModelFromJson(Map<String, dynamic> json) => ClientModel(
              address1: json['address1'] as String?,
              city: json['city'] as int?,
              cityName: json['city_name'] as String?,
              country: json['country'] as int?,
              countryName: json['country_name'] as String?,
              email: json['email'] as String?,
              firstName: json['first_name'] as String?,
              gender: json['gender'] as String?,
              id: json['id'] as int?,
              lastName: json['last_name'] as String?,
              mobileNo: json['mobile_no'] as String?,
              password: json['password'] as String?,
              pincode: json['pincode'] as String?,
              role: json['role'] as int?,
              roleName: json['role_name'] as String?,
              state: json['state'] as int?,
              stateName: json['state_name'] as String?,
            );
        
        Map<String, dynamic> _$ClientModelToJson(ClientModel instance) =>
            <String, dynamic>{
              'address1': instance.address1,
              'city': instance.city,
              'city_name': instance.cityName,
              'country': instance.country,
              'country_name': instance.countryName,
              'email': instance.email,
              'first_name': instance.firstName,
              'gender': instance.gender,
              'id': instance.id,
              'last_name': instance.lastName,
              'mobile_no': instance.mobileNo,
              'password': instance.password,
              'pincode': instance.pincode,
              'role': instance.role,
              'role_name': instance.roleName,
              'state': instance.state,
              'state_name': instance.stateName,
            };
    
    
    
    after we can use like this in repository file 
    
    static Future<dynamic> getAllClients(int id) async {
        String url = "${HttpUrls.clientList}/${0}";
        Response response = await dio.get(url);
        if (response.statusCode == 200) {
          List<ClientModel> clientLists = (response.data['result'] as List)
              .map((eachItem) => ClientModel.fromJson(eachItem))
              .toList();
          print(clientLists.toString());
          if (clientLists.isNotEmpty) {
            return clientLists;
          }
        } else {
          BaseResponse baseResponse =
              BaseResponse.fromJson(response.data as Map<String, dynamic>);
          return baseResponse;
        }
      }
btm me
  • 368
  • 3
  • 11