While developing a Flutter application (or any application using Dart language), you may face a situation where you get the data of an object in the form of JSON string and you need to convert it to a Dart object. This tutorial shows you a simple way how to convert a JSON-formatted text to a Dart object, from simple to complex structure.
For example, we are going to deserialize JSON text to an instance of a class. Here is a class named Item
along with the constructor.
class Item {
int id;
String name;
double price;
int stock;
bool active;
Item({this.id, this.name, this.price, this.stock, this.active});
}
Simple JSON
On the first case, the JSON is very simple. It has neither array nor nested structure. The value of each key is either integer, double, string or boolean.
{
"id":1,
"name":"Item One",
"price":100.0,
"stock":10,
"active":true
}
The JSON text needs to be encoded (parsed) to JSON object first. The easiest way is using `json.decode` method with the text as the argument. That method is available after importing `dart:convert`. After that, we need to create a function for converting the JSON object to Item
instance. As we expect to return a new instance of Item
, it's better to implement it as a constructor named fromJson
.
import 'dart:convert';
class Item {
int id;
String name;
double price;
int stock;
bool active;
Item({this.id, this.name, this.price, this.stock, this.active});
factory Item.fromJson(Map<String, dynamic> json) => _itemFromJson(json);
}
Item _itemFromJson(Map<String, dynamic> json) {
return Item(
id: json['id'] as int,
name: json['name'] as String,
price: json['price'] as double,
stock: json['stock'] as int,
active: json['active'] as bool,
);
}
Inside _itemFromJson
, we need to map each property to use which key of parsed JSON object. It means the key on the JSON text is not necessarily to be the same as the class property
Below is the usage example of the factory constructor fromJson
.
void main() {
String text = '{"id": 1, "name": "Item 1", "price": 100.0, "stock": 10, "active": true}';
Item item = Item.fromJson(json.decode(text));
print(item.name);
}
JSON with Array
Now the Item
class has a new property locations
whose type is List<String>
.
{
"id":1,
"name":"Item One",
"price":100.0,
"stock":10,
"active":true,
"locations":[
"a",
"b",
"c"
]
}
The _itemFromJson
needs to be modified. The List
class has named factory constructor from
which accepts Iterable
elements. It means we can pass the parsed JSON of locations
which can be obtained using json['locations']
. But don't forget to check if the JSON object doesn't contain locations
. Here's the modification you need inside _itemFromJson
Item _itemFromJson(Map<String, dynamic> json) {
var locationsJson = json['locations'];
List<String> locations = locationsJson != null ? new List.from(locationsJson) : null;
return Item(
// Other arguments
locations: locations, // Add locations as property and constructor parameter
);
}
JSON with Nested Object Structure
There is a new property of Item
named dimensions
which is an object.
{
"id":1,
"name":"Item One",
"price":100.0,
"stock":10,
"active":true,
"locations":[ ],
"dimensions":{
"width":100.5,
"depth":201.1,
"height":50.1
}
}
The idea is quite simple. You need to create a class for the nested structure. Then, implement the fromJson
class. Here's the example.
class Dimensions {
double width;
double depth;
double height;
Dimensions({this.width, this.depth, this.height});
factory Dimensions.fromJson(Map<String, dynamic> json) => _dimensionsFromJson(json);
}
And below is the implementation of _dimensionsFromJson
function.
Dimensions _dimensionsFromJson(Map<String, dynamic> json) => Dimensions(
width: json['width'] as double,
depth: json['depth'] as double,
height: json['height'] as double,
);
Here is the modified _otemFromJson
function. Just use Dimensions.fromJson
after checking if the dimensions
value is not null.
Item _itemFromJson(Map<String, dynamic> json) {
var dimensionsJson = json['dimensions'];
Dimensions dimensions = dimensionsJson != null ? Dimensions.fromJson(dimensionsJson) : null;
return Item(
// Other arguments
dimensions: dimensions, // Add dimensions as property and constructor parameter
);
}
If the inner class still has nested structure, you need to create another class for it. Do it recuresively until you don't find any nested structure.
JSON with Array Containing Objects
We're adding another property of Item
. List<Purchase> purchases
is a list whose elements are object.
{
"id":1,
"name":"Item One",
"price":100.0,
"stock":10,
"active":true,
"locations":[ ],
"dimensions":{ },
"purchases":[
{
"amount":1,
"customer_name":"Cust One"
},
{
"amount":2,
"customer_name":"Cust Two"
}
]
}
The solution for that structure is by mapping each element of parsed array to a Purchase
instance.
First you need to define the Purchase
class along with constructor and fromJson
factory constructor.
class Purchase {
int amount;
String customerName;
Purchase({this.amount, this.customerName});
factory Purchase.fromJson(Map<String, dynamic> json) => _purchaseFromJson(json);
}
Purchase _purchaseFromJson(Map<String, dynamic> json) => Purchase(
amount: json['amount'] as int,
customerName: json['customer_name'] as String,
);
Then, get the parsed JSON array as List
, so you can map
its elements. For each elements, return a new instance of Purchase
using fromJson
. After that, convert the map
result to List
.
Item _itemFromJson(Map<String, dynamic> json) {
var purchasesJson = json['purchases'] as List;
List<Purchase> purchases = purchasesJson != null
? purchasesJson.map((i) => Purchase.fromJson(i)).toList()
: null;
return Item(
// Other arguments
purchases: purchases, // Add purchases as property and constructor parameter
);
}
That's all about how to deserialize JSON string to a Dart object.