If you were to interact with an API, what format do you think the response would be in? It would probably — or almost definitely — be in JSON.
JSON is the standard for transferring typed data from one point to another point. If you query an API, or even want to save settings in your app, you’re going to have to use JSON sooner or later.
In this article, we’ll take a look at how to parse a JSON string in Flutter. We’ll review:
When dealing with JSON, there are two main actions that can occur:
Actually, this is something we can do right now, in the very same browser that you’re using to read this article. If you hit F12 on your keyboard and head over to the developer console, you can type in the following:
JSON.stringify({serialization: 'fun'})
The result of this is: '{"serialization":"fun"}'
.
Okay, so what just happened? We created an in-memory JavaScript object, and then immediately converted that object to JSON via JSON.stringify
. We can see it’s a string, because it is surrounded by quotes.
We can run this process in reverse by running the following:
JSON.parse('{"serialization":"fun"}')
The result of this is as follows:
Now, we’re converting the string representation of this object back to a bona-fide JavaScript object. As JavaScript is an untyped language, we don’t need to specify the type to which we would like to serialize or deserialize our JSON string.
In a typed language, like C#, serializing and deserializing from objects to strings and vice versa is similar. However, one difference is that when we deserialize, we’re required to specify what type we would like to deserialize to by using the following:
JsonConvert.DeserializeObject<T>
In the command above, T
is the destination type to which we are trying to deserialize.
Perhaps you’d assume that something as simple and ubiquitous as serializing data in Flutter would be as easy as accomplishing the same thing in C# or JavaScript. Unfortunately, that’s not the case, but there’s good reason for this challenge.
The key difference between serializing and deserializing JSON strings in Flutter and other languages is that Flutter doesn’t support a runtime feature known as “reflection.” With reflection, other languages are able to inspect objects for information that helps with object serialization.
However, Flutter’s lack of reflection is actually a good thing. Without reflection, Flutter code avoids the associated performance caveats and results in a smaller installable binary for the end user.
We can still easily parse JSON strings in Flutter, but we need to do more than just specify a type. Fortunately, we can generate all of the code that we need to accomplish this, so we don’t have to write it by hand.
There are two situations you can be in when you want to serialize data within Flutter.
In one situation, you are in control of the data classes yourself, and you would like to convert your data to or from JSON to send to an API or to store on a device.
In the other situation, you’re not in control of the data classes yourself, and are receiving JSON from an API. You would like to convert this JSON to strongly-typed data you can operate on within your Flutter app.
We’ll consider both situations in this article. First, we’ll look at how to serialize a class within an app when we are in control of our data. Then, we’ll review deserializing JSON data in Flutter from a remote source.
Let’s imagine that we have a music app within which users can set songs as their favorite. For that to work, we would need a class called Favorite
. Within the class, we’d typically have properties such as the song’s title and artist.
The end result would be something like this:
class Favorite { final String title; final String artist; Favorite(this.title, this.artist); }
So how do we serialize and deserialize a class like this in Flutter? You could either do it yourself or automatically generate the JSON handling code. Let’s take a look at both options.
toJson
and fromJson
methodsIf you only have a few classes that you want to serialize or deserialize, it’s an option to simply do it yourself. This means defining your own toJson
and fromJson
methods that are responsible for converting data from a Map<String, dynamic>
back to the original data model.
If we did this, our class would look like this:
class Favorite { final String title; final String artist; Favorite(this.title, this.artist); /// Convert from JSON response stream to Favorite object Favorite.fromJson(Map<String, dynamic> json) : title = json['title'], artist = json['artist']; /// Convert an in-memory representation of a Favorite object to a Map<String, dynamic> Map<String, dynamic> toJson() => { 'title': title, 'artist': artist, }; }
We can then use jsonEncode
or jsonDecode
with our fromJson
and toJson
methods, respectively. But there are some downsides to defining your own methods for converting JSON data in Flutter.
For example, if we ever updated the class members, we would have to manually update the properties. It’s also possible for us to refer to the wrong member, or just make a typo and try to map a non-existent property accidentally.
This method also adds a lot of overhead for working with JSON within our Flutter apps. Using existing packages to generate JSON serialization and deserialization code can be more convenient.
The official Flutter documentation says that you can write your own JSON mapping code by hand for smaller applications. In reality, you’re always better off generating the JSON serialization and deserialization code.
The packages that automatically generate JSON handling code are well tested and have been around for as long as Flutter has, so you know they work.
One of these packages is json_serializable
. Using some easy attributes within our Flutter code, we can have our code generated for JSON serialization in no time.
First, add json_serializable
to your pubspec.yaml
file as a dependency
and also as a dev_dependency
. We also need to add a reference to build_runner
, as json_serializable
uses build_runner
to create the functionality for us.
Next, annotate our class with @JsonSerializable
. This instructs the json_serializable
package to generate the code we require to serialize this class to and from JSON in Flutter.
We also want to add the part
directive to the top of this file, and specify favorite.g.dart
. When we initially write this, we’ll get red squiggles underneath, as the file has not been created yet. But we need to define this in our file in order for the json_serializable
package to work.
part 'favorite.g.dart' @JsonSerializable class Favorite { final String title; final String artist; Favorite(this.title, this.artist); }
With these changes, now we can run the following command in our terminal:
flutter pub run build_runner build
And after some console output, we’ll be able to see our favorites.g.dart
file appear within our project. The contents of that file are as follows:
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'favorite.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** Favorite _$FavoriteFromJson(Map<String, dynamic> json) => Favorite( json['title'] as String, json['artist'] as String, ); Map<String, dynamic> _$FavoriteToJson(Favorite instance) => <String, dynamic>{ 'title': instance.title, 'artist': instance.artist, };
Unfortunately, json_serializable
doesn’t expose these toJson
or fromJson
functions directly from within the generated code. That means it’s up to us to include them in our original file. All in all, our final favorite.dart
file should look like this:
import 'package:json_annotation/json_annotation.dart'; part 'favorite.g.dart'; @JsonSerializable() class Favorite { final String title; final String artist; Favorite(this.title, this.artist); factory Favorite.fromJson(Map<String, dynamic> json) => _$FavoriteFromJson(json); Map<String, dynamic> toJson() => _$FavoriteToJson(this); }
When we want to serialize or encode the Favorite
object, we use the following command:
final favoriteJson = jsonEncode(Favorite('one', 'two'));
If we wanted to convert this text-based JSON back to an original object, we can do the following:
final favorite = Favorite.fromJson(jsonDecode(favoriteJson));
That’s how we manage our JSON when we are in control of the objects within our own app. But what do we do when we aren’t in control of the data classes? If, for example, we are querying a remote API for which we don’t have the data structure?
If we have a remote API, we can use an online tool to convert from the JSON supplied to classes we can use within our app, complete with toJson
and fromJSON
methods. One such tool that we’ll be using to demonstrate this is a JSON to Dart converter available on Github.
For this sample, we’ll use the Bored API, which can randomly give us a task to do if we feel bored. The Bored API responds in JSON, and is a good fit for this purpose. Since it’s random, the task you receive will be different from the example we’re using below.
In my case, the response I received from the API was the following:
{"activity":"Go for a walk","type":"relaxation","participants":1,"price":0,"link":"","key":"4286250","accessibility":0.1}
To serialize and parse this JSON data within our Flutter app, simply paste it into the JSON to Dart converter, as shown below:
Now we can just copy and paste the BoredAPI
class into our app as we see fit. In this case, our generated code for the Bored API would look like this:
class BoredAPI { String? activity; String? type; int? participants; int? price; String? link; String? key; double? accessibility; BoredAPI( {this.activity, this.type, this.participants, this.price, this.link, this.key, this.accessibility}); BoredAPI.fromJson(Map<String, dynamic> json) { activity = json['activity']; type = json['type']; participants = json['participants']; price = json['price']; link = json['link']; key = json['key']; accessibility = json['accessibility']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['activity'] = this.activity; data['type'] = this.type; data['participants'] = this.participants; data['price'] = this.price; data['link'] = this.link; data['key'] = this.key; data['accessibility'] = this.accessibility; return data; } }
As we can see, we have all our class members. We have the fromJson
and toJson
functions declared as well. Also, our generated code supports null-safety, so we can use it in more recent versions of Flutter.
With those in hand, we can now safely interact with this API and encode and decode JSON data in Flutter as we need to.
Parsing JSON strings in Flutter is a little bit more difficult than it is in other languages. As we’ve seen, this is due to the lack of reflection within Flutter at runtime.
However, in my opinion, Flutter more than makes up for these minor annoyances by letting us generate the applicable classes to serialize our JSON as needed.
We’ve also seen how we can create functionality for converting to and from JSON, even when we don’t own the data that is coming into our app, by generating JSON classes for responses received from APIs.
If you’re interacting with an API and serializing to and from JSON is getting you down, you could also consider using something like OpenAPI or Swagger to generate all your API code for you. That way, you’ll be able to deal with just the API functionality instead of manually converting your objects to and from JSON.
Enjoy the serialization journey!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.