Parsing complex json data in flutter
This post is a continuation of parsing JSON data. If you possess the mystical knowledge of how to parse basic JSON data using `json_serializable`, then brace yourself for more enlightenment. But if this mysterious concept is unknown to you, fear not! Simply refer to the previous post, where the tale unfolds.
Now, let us embark on this whimsical journey, where JSON and its serialization magic await!
In the previous post, I explained the process of parsing a simple object. However, in real-life projects, we often encounter more complex objects. The object provided below is a common example that you may come across while working on such projects.
Sample JSON object:
//Exmple data taken from
//https://jsonplaceholder.typicode.com/users
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
}
If you examine the given object closely, you will notice multiple child objects within it. Let’s refer to the entire object as a “user” object. Within the user object, we have two child objects: “address” and “company.” Additionally, the “address” object itself has a child object named “geo.”
It’s important to handle such nested structures when parsing JSON data in order to extract the desired information accurately. By understanding the hierarchy and relationships between the objects, we can effectively access the required data.
Let’s see the give object data graphically:
In this scenario, we need to create a serializable class for each nested object. Therefore, we will require four classes in total. Let’s commence this coding adventure by crafting the outermost class, aptly named User.
@JsonSerializable()
class User{
final int id;
final String name;
final String email;
final String mobile;
final String website;
//Address and Company class we have not defined yet
final Address address;
final Company company;
User({
required this.id,
required this.name,
required this.email,
required this.address,
required this.mobile,
required this.website,
required this.company
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
With the User class established, we can now navigate the intricate paths of nested objects and triumph over their complexities. Together, armed with our keyboards and coding prowess, we shall illuminate the mysteries of serialization.
Indeed, if you’ve keenly observed, you’ll notice that the definition follows a similar structure to what we accomplished in the previous post on Parsing JSON data. However, in this instance, we introduce our custom-defined data types: Address and Company.
Address & Geo Class
@JsonSerializable()
class Address{
final String street;
final String suite;
final String city;
final String zipcode;
final Geo geo;
Address({
required this.street,
required this.suite,
required this.city,
required this.zipcode,
required this.geo
});
factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
Map<String, dynamic> toJson() => _$AddressToJson(this);
}
@JsonSerializable()
class Geo{
final String lat;
final String lng;
Geo({
required this.lat,
required this.lng
});
factory Geo.fromJson(Map<String, dynamic> json) => _$GeoFromJson(json);
Map<String, dynamic> toJson() => _$GeoToJson(this);
}
Company Class
@JsonSerializable()
class Company{
final String name;
final String catchPhrase;
final String bs;
Company({
required this.name,
required this.catchPhrase,
required this.bs
});
factory Company.fromJson(Map<String, dynamic> json) => _$CompanyFromJson(json);
Map<String, dynamic> toJson() => _$CompanyToJson(this);
}
You can define these classes in separate file else you can write all in a single file which will look like this.
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User{
final int id;
final String name;
final String email;
final Address address;
final String mobile;
final String website;
final Company company;
User({
required this.id,
required this.name,
required this.email,
required this.address,
required this.mobile,
required this.website,
required this.company
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
@JsonSerializable()
class Address{
final String street;
final String suite;
final String city;
final String zipcode;
final Geo geo;
Address({
required this.street,
required this.suite,
required this.city,
required this.zipcode,
required this.geo
});
factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
Map<String, dynamic> toJson() => _$AddressToJson(this);
}
@JsonSerializable()
class Geo{
final String lat;
final String lng;
Geo({
required this.lat,
required this.lng
});
factory Geo.fromJson(Map<String, dynamic> json) => _$GeoFromJson(json);
Map<String, dynamic> toJson() => _$GeoToJson(this);
}
@JsonSerializable()
class Company{
final String name;
final String catchPhrase;
final String bs;
Company({
required this.name,
required this.catchPhrase,
required this.bs
});
factory Company.fromJson(Map<String, dynamic> json) => _$CompanyFromJson(json);
Map<String, dynamic> toJson() => _$CompanyToJson(this);
}
When you are done with all these process you can run the command `flutter pub run build_runner build` in terminal.
Customising individual field using JsonKey annotation
You can customize your model class using JsonKey
to modify field names or provide default values for fields when the value is absent or null. Here's an example of how you can achieve that:
@JsonSerializable()
class User{
final int id;
final String name;
final String email;
@JsonKey(name: 'phone') //Change the field from mobile to phone
final String mobile;
@JsonKey(defaultValue: 'www.dipak.co.in') //Default website
final String website;
final Address address;
final Company company;
User({
required this.id,
required this.name,
required this.email,
required this.address,
required this.mobile,
required this.website,
required this.company
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
You can checkout more properties of JsonKey here:
Keeping Your Repository Clean
To prevent generated files from being committed to Git, you can add the pattern “*.g.dart” to your .gitignore file. This will exclude any files with the “.g.dart” extension from being tracked by Git.
By doing this, you ensure that only the necessary source files and not the generated files are included in your Git repository. This practice is commonly used when working with JSON serialization libraries that generate code based on your data models.
Thank you for taking interest in this article. If you enjoyed it, don’t forget to like to hit the clap (👏) button.