Parsing Complex JSON in Flutter. Parse Different Types of Simple and - by Pooja Bhaumik - Flutter Community - Medium
Parsing Complex JSON in Flutter. Parse Different Types of Simple and - by Pooja Bhaumik - Flutter Community - Medium
I have to admit, I was missing the gson world of Android after working with JSON in
Flutter/Dart. When I started working with APIs in Flutter, JSON parsing really had me
struggle a lot. And I’m certain, it confuses a lot of you beginners.
We will be using the built in dart:convert library for this blog. This is the most basic
parsing method and it is only recommended if you are starting with Flutter or you’re
building a small project. Nevertheless, knowing the basics of JSON parsing in Flutter is
pretty important. When you’re good at this, or if you need to work with a larger project,
consider code generator libraries like json_serializable, etc. If possible, I will discover
them in the future articles.
Fork this sample project. It has all the code for this blog that you can experiment with.
{
"id":"487349",
"name":"Pooja Bhaumik",
"score" : 1000
}
Rule #1 : Identify the structure. Json strings will either have a Map (key-value
pairs) or a List of Maps.
student.json is clearly a map. ( E.g like, id is a key, and 487349 is the value for id )
Let’s make a PODO (Plain Old Dart Object?) file for this json structure. You can find this
code in student_model.dart in the sample project.
class Student{
String studentId;
String studentName;
int studentScores;
Student({
this.studentId,
this.studentName,
this.studentScores
});
}
Perfect!
Was it? Because there was no mapping between the json maps and this PODO file. Even the
entity names don’t match.
I know, I know. We are not done yet. We have to do the work of mapping these class
members to the json object. For that, we need to create a factory method. According to
Dart documentation, we use the factory keyword when implementing a constructor
that doesn’t always create a new instance of its class and that’s what we need right now.
Sure. Let’s tell you about Serialization and Deserialization first. Serialization simply
means writing the data(which might be in an object) as a string, and Deserialization is
the opposite of that. It takes the raw data and reconstructs the object model. In this
article, we mostly will be dealing with the deserialization part. In this first part, we are
deserializing the json string from student.json
Also must notice the parameter in the fromJson method. It’s a Map<String, dynamic> It
means it maps a String key with a dynamic value. That’s exactly why we need to
identify the structure. If this json structure were a List of maps, then this parameter
would have been different.
Since the key is always a string and the value can be of any type, we keep it as dynamic
Snippet #1 : imports
Check your Flutter console to see all your print values. (In Android Studio, its under Run
tab)
And voila! You just did your first JSON parsing (or not).
Note: Remember the 3 snippets here, we will be using it for the next set of json parsing (only
changing the filenames and method names), and I won’t be repeating the code again here.
But you can find everything in the sample project anyway.
{
"city": "Mumbai",
"streets": [
"address1",
"address2"
]
}
So in this address.json, we have city entity that has a simple String value, but streets
is an array of String .
As far as i know, Dart doesn’t have an array data type, but instead has a List<datatype>
so here streets will be a List<String> .
Now we have to check Rule#1 and Rule#2 . This is definitely a map since this starts
with a curly brace. streets is still a List though, but we will worry about that later.
class Address {
final String city;
final List<String> streets;
Address({
this.city,
this.streets
});
}
Now since this is a map, our Address.fromJson method will still have a Map<String,
dynamic> parameter.
I tell you, these errors have come in almost every step of my development with Dart. And
you will have them too. So let me explain what this means. We are requesting a
List<String> but we are getting a List<dynamic> because our application cannot
identify the type yet.
Here, first we are mapping our variable streetsFromJson to the streets entity.
streetsFromJson is still a List<dynamic> . Now we explicitly create a new List<String>
Check the updated method here. Notice the return statement now.
Now you can run this with address_services.dart and this will work perfectly.
{
"shape_name":"rectangle",
"property":{
"width":5.0,
"breadth":10.0
}
}
class Property{
double width;
double breadth;
Property({
this.width,
this.breadth
});
}
Now let’s construct the class for Shape . I am keeping both classes in the same Dart file.
class Shape{
String shapeName;
Property property;
Shape({
this.shapeName,
this.property
});
}
Notice how the second data member property is basically an object of our previous class
Property .
Rule #3: For nested structures, make the classes and constructors first, and then
add the factory methods from bottom level.
By bottom level, we mean, first we conquer Property class, and then we go one level above to
the Shape class. This is just my suggestion, not a Flutter rule.
But for our factory method at Shape class, we cant just do this.
property : parsedJson['property'] First, this will throw the type mismatch error —
And second, hey we just made this nice little class for Property, I don’t see it’s usage
anywhere.
So basically, we are calling the Property.fromJson method from our Property class and
whatever we get in return, we map it to the property entity. Simple! Check out the code
here.
Run this with your shape_services.dart and you are good to go.
JSON structure #4 : Nested structures with Lists
Let’s check our product.json
{
"id":1,
"name":"ProductName",
"images":[
{
"id":11,
"imageName":"xCh-rhy"
},
{
"id":31,
"imageName":"fjs-eun"
}
]
}
Okay, now we are getting deeper. I see a list of objects somewhere inside. Woah.
Yes, so this structure has a List of objects, but itself is still a map. (Refer Rule #1, and
Rule #2) . Now referring to Rule #3, let’s construct our product_model.dart .
class Product {
final int id;
final String name;
final List<Image> images;
class Image {
final int imageId;
final String imageName;
Image({this.imageId, this.imageName});
}
The factory method for Image will be quite simple and basic.
factory Image.fromJson(Map<String, dynamic> parsedJson){
return Image(
imageId:parsedJson['id'],
imageName:parsedJson['imageName']
);
}
return Product(
id: parsedJson['id'],
name: parsedJson['name'],
images: parsedJson['images']
);
}
And if we do this,
images: Image.fromJson(parsedJson['images'])
This is also definitely wrong, and it will throw you an error right away because you
cannot assign an Image object to a List<Image>
to Image by calling Image.fromJson and then we put each map object into a new list with
toList() and store it in List<Image> imagesList . Find the full code here.
[
{
"albumId": 1,
"id": 1,
"title": "accusamus beatae ad facilis cum similique qui sunt",
"url": "http://placehold.it/600/92c952",
"thumbnailUrl": "http://placehold.it/150/92c952"
},
{
"albumId": 1,
"id": 2,
"title": "reprehenderit est deserunt velit ipsam",
"url": "http://placehold.it/600/771796",
"thumbnailUrl": "http://placehold.it/150/771796"
},
{
"albumId": 1,
"id": 3,
"title": "officia porro iure quia iusto qui ipsa ut modi",
"url": "http://placehold.it/600/24f355",
"thumbnailUrl": "http://placehold.it/150/24f355"
}
]
Uh, oh. Rule #1 and Rule #2 tells me this can’t be a map because the json string starts
with a square bracket. So this is a List of objects? Yes. The object being here is Photo (or
whatever you’d like to call it).
class Photo{
final String id;
final String title;
final String url;
Photo({
this.id,
this.url,
this.title
}) ;
But its a list of Photo , so does this mean you have to build a class that contains a
List<Photo> ?
class PhotosList {
final List<Photo> photos;
PhotosList({
this.photos,
});
}
Also notice, this json string is a List of maps. So, in our factory method, we won’t have a
Map<String, dynamic> parameter, because it’s a List. And that is exactly why it’s
important to identify the structure first. So our new parameter would be a
List<dynamic> .
photos = parsedJson.map((i)=>Photo.fromJson(i)).toList();
Same concept as earlier, we just don’t have to map this to any key from the json string,
because it’s a List, not a map. Code here.
I will request you to solve this. It is already included in the sample project. You just have
to build the model and services file for this. But I won’t conclude before giving you hints
and tips (if case, you need any).
Rule#1 and Rule#2 as usual applies. Identify the structure first. Here it is a map. So all
the json structures from 1–5 will help.
Rule #3 asks you to make the classes and constructors first, and then add the factory
methods from bottom level. Just another tip. Also add the classes from the deep/bottom
level. For e.g, for this json structure, make the class for Image first, then Data and
Author and then the main class Page . And add the factory methods also in the same
sequence.
Beginner’s tip: While experimenting with any new assets, remember to declare it in the
pubspec.yaml file.
And that’s it for this Fluttery article. This article may not be the best JSON parsing article
out there, (because I’m still learning a lot) but I hope it got you started.