Parsing JSON in Flutter
Learn about getting and parsing JSON data from the internet when building a cross-platform app using Flutter. By Sardor Islomov.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Parsing JSON in Flutter
25 mins
- Getting Started
- Understanding the UI
- Using REST APIs
- Signing Up for the Cats API
- Making Network Calls
- Understanding JSON
- Parsing JSON
- By Hand
- Using Libraries
- The Cat API
- Using the Cat API
- Creating Models
- Using JSON Annotations
- JSON Conversion Methods
- Using build_runner
- Error Handling
- Using the Models
- Building the Cat Detail Page
- Where to Go From Here?
Using JSON Annotations
Now, you’ll use the json_annotation
library to parse the JSON data into objects of your model classes.
Go to the top of cats.dart, and add the following imports to the top:
import 'package:json_annotation/json_annotation.dart';
part 'cats.g.dart';
The part
statement imports a file and allows you to use its private variables. You’ll see an error on this statement for now until you later use build_runner
to generate the file cats.g.dart.
Next, you need to add the @JsonSerializable()
annotation to each class in cats.dart. For example, your Breed
class should look like this when you add the annotation:
@JsonSerializable()
class Breed {
String id;
String name;
String description;
String temperament;
Breed({
required this.id,
required this.name,
required this.description,
required this.temperament
});
}
Make sure you add the annotation before every class in cats.dart.
JSON Conversion Methods
In the next step, you’ll add some factory methods to each class. The build runner plugin will use these methods to create a Dart file to do all the hard work of parsing the JSON data for you.
In the Breed
class, add the following after the constructor:
factory Breed.fromJson(Map<String, dynamic> json) => _$BreedFromJson(json);
Map<String, dynamic> toJson() => _$BreedToJson(this);
Each class will include fromJson
and toJson
. These methods call the generated code that parses the JSON. At this point, you’ll notice some more errors in Android Studio. Don’t worry about these at the moment; you’ll clear them up later.
In BreedList
, add the following after the constructor:
factory BreedList.fromJson(final dynamic json) {
return BreedList(
breeds: (json as List<dynamic>)
.map((dynamic e) => Breed.fromJson(e as Map<String, dynamic>))
.toList());
}
This is the fromJson
method you need to parse the JSON array to a list of breeds.
Add fromJson
and toJson
after the constructor in Cat
:
factory Cat.fromJson(Map<String, dynamic> json) => _$CatFromJson(json);
Map<String, dynamic> toJson() => _$CatToJson(this);
Next, after the constructor in CatBreed
, add:
factory CatBreed.fromJson(Map<String, dynamic> json) =>
_$CatBreedFromJson(json);
Map<String, dynamic> toJson() => _$CatBreedToJson(this);
Finally, add the following after the constructor in CatList
:
factory CatList.fromJson(dynamic json) {
return CatList(
breeds: (json as List<dynamic>)
.map((dynamic e) => CatBreed.fromJson(e as Map<String, dynamic>))
.toList());
}
You’ve now added all the fromJson
and toJson
methods you need in your model classes.
Using build_runner
Your next step is to run the tool that generates the files that will parse the JSON. Open the Terminal tab at the bottom of Android Studio, and enter the following:
dart run build_runner build
When the command completes, if everything ran correctly, the errors you saw earlier in cats.dart will be gone. You’ll now see cats.g.dart in the same directory as cats.dart. If you open cats.g.dart, you’ll notice methods for converting JSON to your model classes and back.
Error Handling
Developers should handle unexpected values from JSON objects. For example, you expect a string type, but the server returns null. This isn’t a rare case where you should leave it as it is. Check the code below:
@JsonSerializable()
class CatBreed {
String id;
String url;
int width;
int height;
CatBreed({
required this.id,
required this.url,
required this.width,
required this.height
});
factory CatBreed.fromJson(Map<String, dynamic> json) =>
_$CatBreedFromJson(json);
Map<String, dynamic> toJson() => _$CatBreedToJson(this);
}
Cat image, in this case String url
, could be null. To avoid any NullPointerException
, pass an empty string when String url
is null.
You could modify CatBreed.fromJson()
to the following:
factory CatBreed.fromJson(Map<String, dynamic> json) {
// 1
try {
final id = json['id'] as String;
final url = json['url'] as String;
final width = json['width'] as int;
final height = json['height'] as int;
return CatBreed(
id: id,
url: url,
width: width,
height: height,
);
} catch(e) {
// 2
return CatBreed(
id: '',
url: '',
width: -1,
height: -1,
);
}
}
In the code above:
- Wraps the
fromJson
method with thetry
block to catch any cast exceptions. - The
catch
block returns a defaultCatBreed
object with all properties being default values.
The code above looks OK, but not elegant. The main drawback of this approach is that if try
throws an exception for one property, all properties will be created as a default. The developer doesn’t understand which property is causing the problem.
To fix that, modify CatBreed.fromJson()
to the following:
factory CatBreed.fromJson(Map<String, dynamic> json) { return CatBreed( id: tryCast<String>(json['id']) ?? '', url: tryCast<String>(json['url']) ?? '', width: tryCast<int>(json['width']) ?? 0, height: tryCast<int>(json['height']) ?? 0, ); }
Here, you create and return the CatBreed
object with default values using the null-coalescing operator (??
).
Next, add the tryCast
method at the end of cats.dart.
T? tryCast<T>(dynamic object) => object is T ? object : null;
tryCast
is a simple method that tries to cast an object into a given type, and if it’s unsuccessful, it returns null.
Now, the code looks elegant, small and easy to read. In the coming sections, you’ll connect the UI with a network response.
Using the Models
Now that you’ve created and generated your models, it’s time to put them to work.
Go back to cat_breeds.dart. In getCatData()
, you can now parse the JSON you got from the internet into your model classes.
To start, at the top of _CatBreedsPageState
, add a property for the breed list:
class _CatBreedsPageState extends State<CatBreedsPage> {
BreedList breedList = BreedList(breeds: List.empty());
...
Add the import import '../models/cats.dart';
at the top of the file to clear the errors you see.
In getCatData()
, add these lines after the print statement:
// 1
final dynamic catMap = json.decode(catJson);
// 2
setState(() {
// 3
breedList = BreedList.fromJson(catMap);
});
Here, you:
- Use
json.decode(catJson)
to turn the JSON string into a map. - Call
setState
to rebuild your widget due to changes in the data. - Use
BreedList.fromJson(catMap)
to convert the map into a list of breeds.
Be sure to import the dart:convert library(import 'dart:convert';
) for the json.decode()
statement. You’ve now converted your JSON data into a list of cat breeds!
But wait! You still need to get that list into the UI. How do you do that?
Since you have a list of cat breeds, what better way to display them than with a ListView
widget?
Go down to the body: ListView.builder
statement and replace itemCount: 0
with:
itemCount: breedList.breeds.length,
This sets itemCount
to the number of cat breeds you got from the internet.
Next, replace title
and subtitle
of ListTile
with the following:
title: Text(breedList.breeds[index].name),
subtitle: Text(breedList.breeds[index].description),
Now, build and run the app, and see how it looks. You’ll see a list of cat breed names and their descriptions:
Congratulations!