- Parsing complex JSON in Flutter
- Pooja Bhaumik
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: DateBro
- Proofread by: LeviDing
I have to admit that I’ve been missing gson’s Android world since using JSON in Flutter/Dart. JSON parsing really bothered me when I started using the API in Flutter. And I’m sure it confuses a lot of beginners, too.
We will use the built-in DART: Convert library in this blog post. This is the most basic resolution method and is recommended only if you are just starting to use Flutter or if you are writing a small project. However, it is important to know some of the basics of JSON parsing in Flutter. If you’re proficient in this, or if you need to write larger projects, consider code generator libraries like JSON_serializable. I’ll cover them in a future article if possible.
Fork this sample project. It contains all the code in this blog post, so you can try it out.
JSON structure #1: Simple map
Let’s start with a simple JSON structure — student.json
{
"id":"487349"."name":"Pooja Bhaumik"."score": 1000}Copy the code
Rule #1: Determine structure. Json strings will have either a Map (key-value pair) or a List of Maps.
Rule #2: Start with curly braces? This is a map. Start with square brackets, okay? That’s the List of maps.
Json is obviously a map (for example, id is the key and 487349 is the value of id).
Let’s make a PODO (Plain Old Dart Object?) for this JSON structure. File. You can find this code in the sample project’s student_model.dart file.
class Student{
String studentId;
String studentName;
int studentScores;
Student({
this.studentId,
this.studentName,
this.studentScores
});
}
Copy the code
Perfect! Is that so? Because there is no mapping between the JSON map and this PODO file. Even the entity names don’t match. I know, I know. We’re not done yet. We must map these class members to json objects. To do this, we need to create a Factory method. According to the Dart documentation, when we use the Factory keyword when implementing a constructor, the constructor does not always create a new instance of its class, which is what we need now.
factory Student.fromJson(Map<String, dynamic> parsedJson){
return Student(
studentId: parsedJson['id'],
studentName : parsedJson['name'],
studentScores : parsedJson ['score']); }Copy the code
Here, we created a factory method called Student.fromjson to simply deserialize your JSON.
I’m a chicken, can you tell me what deserialization is?
Of course. We’ll first introduce you to serialization and deserialization. Serialization simply means writing data (possibly in an object) as a string. Deserialization is the opposite. It takes the raw data and reconstructs the object model. In this article, we focus on the deserialization part. In the first part, we deserialized the JSON string from student.json.
So our factory method can also be called our converter method.
You must also pay attention to the arguments in the fromJson method. It’s a Map
which means it maps String keys to dynamic values. That’s why we need to identify its structure. This parameter would be different if the JSON structure were a list of mappings.
But why dynamic? Let’s take a look at another JSON structure to answer your question.
“Name” is a Map
, “majors” is a Map of String and List, and “subject” is a Map of String and List.
Because the key is always a string and the value can be of any type, we keep it dynamic for safety.
Check out the full code for student_model.dart here.
Access to the object
Let’s write student_services.dart, which has code to call student.fromjson and get the value from the Student object.
Clip #1: Imports
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'package:flutter_json/student_model.dart';
Copy the code
The last thing to import is the file name of your model.
Fragment #2: Loading Json Asset (optional)
Future<String> _loadAStudentAsset() async {
return await rootBundle.loadString('assets/student.json');
}
Copy the code
In this particular project, our JSON files are in the Assets folder, so we have to load the JSON like this. But if your JSON file is in the cloud, you can also make network calls. Network calls are beyond the scope of this article.
Fragment #3: Load the response
Future loadStudent() async {
String jsonString = await _loadAStudentAsset();
final jsonResponse = json.decode(jsonString);
Student student = new Student.fromJson(jsonResponse);
print(student.studentScores);
}
Copy the code
In the loadStudent() method, the first line: loads the raw JSON string from assets. Line 2: Decode our resulting JSON string. Line 3: Now we deserialize the decoded JSON response by calling the student.fromjson method, so we can now use the Student object to access our entity. Line 4: Just like we did here, we print Student scores in the Student class.
Check the Flutter console to see all the values printed. (In Android Studio, it’s under Run options)
Look! You just finished your first JSON parsing (or not). Note: Keep in mind the three fragments here that we will use for the next set of JSON parsing (just change the filename and method name), and I won’t repeat the code here. But you can find it all in the sample project.
JSON structure #2: A simple structure containing an array
Now we will conquer a JSON structure similar to the one above, but not single-valued, which might have an array of values.
{
"city": "Mumbai"."streets": [
"address1"."address2"]}Copy the code
So in this address.json, we have a city entity with a simple String value, but streets is a String array. As far as I know, Dart doesn’t have an array, but it does have a List, so streets here is a List
.
Now we need to examine rules #1 and #2. This is definitely a map because it starts with curly braces. Streets is still a List, but we’ll worry about that later.
So address_Model.dart looks something like this at first
class Address {
final String city;
final List<String> streets;
Address({
this.city,
this.streets
});
}
Copy the code
Now that it’s a map, our address.fromjson method still has a map
parameter.
factory Address.fromJson(Map<String, dynamic> parsedJson) {
return new Address(
city: parsedJson['city'],
streets: parsedJson['streets']); }Copy the code
Now construct address_services.dart by adding the three code snippets mentioned above. You must remember to put in the correct filename and method name. The sample project has built _address_services.dart_ for you.
If you run it now, you will find a small error.
type 'List<dynamic>' is not a subtype of type 'List<String>'
Copy the code
Let me tell you, these mistakes have cropped up in almost every step of my Dart development. You’ll encounter them, too. So let me explain what I mean by that. We are requesting List
but we get a List
because our application does not yet recognize its type.
So we have to explicitly convert this to List
.
var streetsFromJson = parsedJson['streets'];
List<String> streetsList = new List<String>.from(streetsFromJson);
Copy the code
Here, we first map the variable to the streetsFromJson entity. StreetsFromJson is still a List
. We have now explicitly created a List
streetsList that contains all the elements from streetsFromJson.
Check out the updated methods here. Notice the return statement now. Now you can run it with _address_services.dart_ and it will work perfectly.
Json structure #3: Simple nested structure
Now what if we had a nested structure like from Shape. json?
{
"shape_name":"rectangle"."property": {"width": 5.0."breadth": 10.0}}Copy the code
Here, the property contains an object rather than the basic data type. So what would POOD look like?
All right, let’s take a break. In shape_model.dart, let’s start by creating a class for Property.
class Property{
double width;
double breadth;
Property({
this.width,
this.breadth
});
}
Copy the code
Now let’s create a class for Shape. I saved both classes in the same Dart file.
class Shape{
String shapeName;
Property property;
Shape({
this.shapeName,
this.property
});
}
Copy the code
Note that the second data member, property, is the object of our previous property class.
Rule #3: For nested structures, first create classes and constructors, then add factory methods from underneath.
At the bottom, I mean, first we conquer_Property_
Class, and then we’re in_Shape_
The class is one level up. Of course, this is just my opinion, not the rules of Flutter.
factory Property.fromJson(Map<String, dynamic> json){
return Property(
width: json['width'],
breadth: json['breadth']); }Copy the code
This is a simple map.
But for factory methods in Shape, that’s all we can do.
factory Shape.fromJson(Map<String, dynamic> parsedJson){
return Shape(
shapeName: parsedJson['shape_name'],
property : parsedJson['property']); }Copy the code
Property: parsedJson[‘property’] First it throws a type mismatch error —
type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Property'
Copy the code
Second, hey, we just made this elegant class for Property, and I don’t see it being used anywhere.
That’s right, we have to map our Property class here.
factory Shape.fromJson(Map<String, dynamic> parsedJson){
return Shape(
shapeName: parsedJson['shape_name'],
property: Property.fromJson(parsedJson['property'])); }Copy the code
So basically, we call the property.fromjson method from the Property class, and whatever we get, we map it to the Property entity. Simple! Check your code here.
Run it with your shape_services.dart and you’ll be happy with the results.
JSON structure #4: Nested structure with Lists
Let’s examine our product.json
{
"id": 1,"name":"ProductName"."images":[
{
"id": 11."imageName":"xCh-rhy"
},
{
"id": 31."imageName":"fjs-eun"}}]Copy the code
Okay, now we’re getting deeper and deeper. Wow, I see a list of objects in there.
Yeah, so this structure has a list of objects, but it’s still a map itself. (See Rule #1 and Rule #2). Now referring to rule #3, let’s construct our product_model.dart.
Now let’s create the Product and Image classes. Note: _Product_ will have a data member, which is a List of _Image_
class Product {
final int id;
final String name;
final List<Image> images;
Product({this.id, this.name, this.images});
}
class Image {
final int imageId;
final String imageName;
Image({this.imageId, this.imageName});
}
Copy the code
The factory method for Image will be very simple and basic.
factory Image.fromJson(Map<String, dynamic> parsedJson){
return Image(
imageId:parsedJson['id'],
imageName:parsedJson['imageName']); }Copy the code
Here is the factory method for Product
factory Product.fromJson(Map<String, dynamic> parsedJson){
return Product(
id: parsedJson['id'],
name: parsedJson['name'],
images: parsedJson['images']); }Copy the code
This is obviously going to throw a Runtime error
type 'List<dynamic>' is not a subtype of type 'List<Image>'
Copy the code
If we do that,
images: Image.fromJson(parsedJson['images'])
Copy the code
This is also absolutely wrong, and it immediately raises an error because you can’t assign an Image object to a List.
So we have to create a List and assign it to images
var list = parsedJson['images'] as List;
print(list.runtimeType); //returns List<dynamic>
List<Image> imagesList = list.map((i) => Image.fromJson(i)).toList();
Copy the code
List is a list in this case. Now we iterate through the list by calling image.fromjson and mapping each object in the list to the Image. Then we put each map object into a new list with toList(), And store it in List imagesList. You can view the full code here.
JSON structure #5: Map list
Now let’s take a look at photo.json
[{"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"}]Copy the code
Well, rule #1 and rule #2 show that this is not a map because the JSON string begins with square brackets. So this is a list of objects, right? Yes, the object here is Photo (or whatever you want to call it).
class Photo{
final String id;
final String title;
final String url;
Photo({
this.id,
this.url,
this.title
}) ;
factory Photo.fromJson(Map<String, dynamic> json){
return new Photo(
id: json['id'].toString(),
title: json['title'],
url: json['json']); }}Copy the code
But it’s a_Photo_
List, so that means you have to create an include_List<Photo>_
The class?
Yes, I suggest so.
class PhotosList {
final List<Photo> photos;
PhotosList({
this.photos,
});
}
Copy the code
Also note that the JSON string is a list of mappings. Therefore, in our factory method, we will not have a Map
argument, because it is a List. That’s why the structure has to be determined in the first place. So our new argument is List
.
factory PhotosList.fromJson(List<dynamic> parsedJson) {
List<Photo> photos = new List<Photo>();
return new PhotosList(
photos: photos,
);
}
Copy the code
This will throw an error.
Invalid value: Valid value range is empty: 0
Copy the code
Hey, because we can never use the photo.fromjson method. What if we added this line of code after the list was initialized?
photos = parsedJson.map((i)=>Photo.fromJson(i)).toList();
Copy the code
As with the previous concept, we don’t have to map it to any key in the JSON string because it’s a List and not a map. The code is here.
JSON structure #6: Complex nested structure
This is a page. The json.
I will ask you to solve this problem. It is included in the sample project. You just need to build models and service files for this. But I won’t conclude before I give you a hint (if you need any hint at all).
Use rule #1 as well as rule #2. First determine the structure. This is a map. So all json structures 1-5 are useful.
Rule #3 requires you to create classes and constructors first, and then add factory methods from underneath. One more tip, though, is to remember to add classes from deep/bottom. For example, for this JSON structure, first create classes for Image, then Data and Author, and then create the main class Page. Add the factory methods in the same order.
For the Image and Data classes, refer to Json structure #4. For the Author class, refer to Json structure #3
Tip for beginners: When experimenting with any new asset, remember to declare it in the pubspec.yaml file.
That’s what this Fluttery article is all about. This article may not be the best JSON parsing article (because I’m still learning a lot), but I hope it helps you get started.
Did I make a mistake? Mention it in the comments. I’m all ears.
If you’ve learned a thing or two, clap your hands 👏 as often as you can to show your support! This will encourage me to write more articles.
Hello World, I’m Pooja Bhaumik. A creative developer and a rational designer. You can follow me on Linkedin or GitHub or Twitter? If that’s too social for you, if you want to talk to me about your thoughts on technology, email me at [email protected].
Have a nice day!
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.