It’s difficult to imagine a mobile app that doesn’t need to communicate with a web server or store structured data. When creating network-connected apps you’ll almost certainly need to consume some good old JSON at some point.
This article looks at how to use JSON with Flutter.
The JSON syntax is based on Javascript object literal. In this article, we will use the comment object from the JSONPlaceholder API as an example.
The Comment JSON;
1
2
3
4
5
6
7
{
"postId": 1,
"id": 1,
"name": "id labore ex et quam laborum",
"email": "Eliseo@gardner.biz",
"body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium"
}
We will try to work with this JSON object in our Flutter code using:
Manual parsing
JSON serializable
Built value
The way to approach this is to write a model class for the JSON object you are trying to parse and also write the toJson and fromJson methods as follows;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Comment {
final int postId;
final int id;
final String name;
final String email;
final String body;
Comment({
this.postId,
this.id,
this.name,
this.email,
this.body
});
factory Comment.fromJson(Map < String, dynamic > json) {
return Comment(
postId: json['postId'] as int,
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
body: json['body'] as String);
}
Map < String, dynamic > toJson() {
final map = < String, dynamic > {};
map['postId'] = postId;
map['id'] = id;
map['name'] = name;
map['email'] = email;
map['body'] = body;
return map;
}
}
The fromJson named constructor will help parse the JSON object to the Dart comment object while the toJson method will help parse the Dart comment object back to JSON.
You can also use this online tool to generate the Dart class instead of going through the stress of writing it yourself.
When you use JSON serialization with code generation, an external library creates the encoding boilerplate for you. You start a file watcher that creates code from your model classes after some initial setup.
One normal dependent (json_annotation) and two dev dependencies (build_runner and json_serializable) are required to include JSON serializable in your project. In a nutshell, dev dependencies are dependencies that are only used in the development environment and are not included in the source code of your program. Add these dependencies to your pubspec.yaml as follows;
dependencies:
json_annotation: <latest_version>
dev_dependencies:
build_runner: <latest_version>
json_serializable: <latest_version>
Run flutter pub get
to make these dependencies available in your project.
Next, we create the comment class to be used with the json_serializable package.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'package:json_annotation/json_annotation.dart';
part 'comment.g.dart';
@JsonSerializable()
class Comment {
int postId;
int id;
String name;
String email;
String body;
factory Comment.fromJson(Map < String, dynamic > json) => _$CommentFromJson(json);
Map < String, dynamic > toJson() => _$CommentToJson(this);
}
It is important to annotate the class with @JsonSerializable() and include the part ‘comment.g.dart’ for the generated class. At this point, your code should contain some errors because you don’t have the generated boilerplate code yet.
There are two ways of running the code generator command.
One-time code generation
You produce JSON serialization code for your models whenever they are needed by running “flutter pub run build_runner build” in the project root terminal. This starts a one-time build that runs through all of the source files, selects the important ones, and generates the serialization code. You have to manually run the command again every time you make changes to your code.
Generating code continuously
The source code generation process is made easier with the help of a watcher. It keeps track of changes in our project files and produces the relevant files when they’re needed. Run “flutter pub run build_runner watch” in the project root to start the watcher.
Dart does not have first-class support for serialization, immutability, and object comparison. As a result, we have to manually serialize or deserialize objects. Also, Dart objects are reference types, therefore, you have to override the equal and hashcode method to get value equality. The Built Value library was intended to address these issues in Dart. It fully supports serialization, immutability, and object comparison.
Add these dependencies to your pubspec.yaml as follows;
dependencies:
built_value: <latest_version>
dev_dependencies:
build_runner: <latest_version>
built_value_generator: <latest_version>
Run flutter pub get
to make these dependencies available in your project.
Next, copy the comment JSON object and paste it here to generate the built value class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
library comment;
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
part 'comment.g.dart';
abstract class Comment implements Built < Comment, CommentBuilder > {
Comment._();
factory Comment([updates(CommentBuilder b)]) = _$Comment;
@BuiltValueField(wireName: 'postId')
int get postId;
@BuiltValueField(wireName: 'id')
int get id;
@BuiltValueField(wireName: 'name')
String get name;
@BuiltValueField(wireName: 'email')
String get email;
@BuiltValueField(wireName: 'body')
String get body;
String toJson() {
return json.encode(serializers.serializeWith(Comment.serializer, this));
}
static Comment fromJson(String jsonString) {
return serializers.deserializeWith(
Comment.serializer, json.decode(jsonString));
}
static Serializer < Comment > get serializer => _$commentSerializer;
}
Now that we have the built value class, we need to create and instantiate the serializer to be used by built_value. Create a new Dart class and call it serializers.dart. Inside the serializers.dart add the following code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
library serializers;
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import‘ comment.dart ';
part 'serializers.g.dart';
@SerializersFor(const [
Comment,
])
final Serializers serializers =
(_$serializers.toBuilder().
.addPlugin(StandardJsonPlugin()))
.build();
Next, you need to run the code generator command as discussed above. Run flutter pub run build_runner build
to get the part files.
Varied projects have different levels of complication and use cases. Code generators may be overkill for smaller proof-of-concept projects or fast prototypes. Encoding by hand for projects with multiple JSON models of varying complexity can soon become tiresome, repetitious, and prone to numerous little errors.
When your project grows larger, manual parsing becomes less effective. Hand-writing decoding logic can be time-consuming and error-prone. Manual serialization is a good place to start if you don’t have many JSON models in your project and want to test a notion quickly. For a larger project, JSON serializable and built value works effectively. There’s no need for handwritten boilerplate, and typos in JSON field access are handled at compile time.
https://flutter.dev/docs/development/data-and-backend/json
https://pub.dev/packages/built_value