JSON và Serialization trong flutter

Trần Đình Quý

Updated on:

JSON là viết tắt của JavaScript Object Notation, là một kiểu định dạng dữ liệu tuân theo một quy luật nhất định mà hầu hết các ngôn ngữ lập trình hiện nay đều có thể đọc được. JSON là một tiêu chuẩn mở để trao đổi dữ liệu trên web.
Và dưới đây là 1 đoạn ví dụ về JSON

{
    "title" : "Tora-tech",
    "date" : "07/09/2023",
    "imageUrl" : "https://abc.xyz/photo.jpg"
}

Ta thấy được JSON này sẽ mô tả tương ứng với 1 đối tượng (có thể là mảng các đối tượng đối với các chuỗi JSON khác).
Vậy thì có cách nào để chuyển từ 1 đoạn JSON như thế này thành đối tượng trong Flutter không? Dưới đây là 2 cách để giải quyết vấn đề trên.

Quá trình trên được gọi là serialize và deserialize.

Trước tiên, ta hãy tạo ra 1 class Event như sau:

//event.dart
class Event {
  final String title;
  final String date;
  final String imageUrl;
  Event(this.title, this.date, this.imageUrl);
}

1. Manual serialization

Trên class Event của chúng ta, chúng ta sẽ cần tạo 2 hàm được gọi là fromJson và toJson có chức năng parse json thành class Event và ngược lại. 

fromJson nhận vào Map<String, dynamic> và sẽ cung cấp cho bạn một instance của class dữ liệu.
toJson là một hàm class cung cấp cho một Map bắt đầu từ các tham số dữ liệu.

Lúc đó class Event được viết lại thành:

class Event {
  final String title;
  final String date;
  final String imageUrl;
  
  Event (this.title, this.date, this.imageUrl);
  
  Event.fromJson(Map<String, dynamic> json): 
        title= json['title'],
        date = json['date'],
        imageUrl= json['imageUrl'];
        
  Map<String, dynamic> toJson() =>
    {
      'title': title,
      'date': date,
      'imageUrl': imageUrl
    };
}

Sau đó, serialize và deserialize bằng:

//JSON String thành map
Map eventMap = jsonDecode(jsonString);
//Serialize thành object
var event = Event.fromJson(eventMap);
//Chuyển thành JSON String
String json = jsonEncode(event);

Việc serialize và deserialize ở đây khá là đơn giản, nhưng khi đối mặt với các cấu trúc JSON phức tạp hơn, ví dụ như JSON lồng nhau thì việc sử dụng cách bằng tay như thế này thực sự chưa ổn.

Nên bây giờ chúng ta sẽ đến với cách thứ 2.

2. Automation serialization

Một package hữu ích cho chúng ta trong trường hợp này là json_serializable. Yên tâm vì package này do chính Google xuất bản với nhiệm vụ tự động tạo code để serialize và giải mã hóa(deserialize) class dữ liệu của chúng ta.

json_serializable

Vào pubspec.yaml, thêm các package sau:

dependencies:
  json_annotation: <latest_version>
dev_dependencies:
  build_runner: <latest_version>
  json_serializable: <latest_version>

Sau đó, trên class Event, chúng ta cần thêm annotation @JsonSerializable(), và implement hàm fromJson và toJson như sau:

import 'package:json_annotation/json_annotation.dart';

part 'event.g.dart';

@JsonSerializable()
class Event{
  Event(this.title, this.date, this.imageUrl);
  
  String title;
  String date;
  String imageUrl;
  
  factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);
  
  Map<String, dynamic> toJson() => _$EventToJson(this);
}

Thao tác này sẽ tạo code để mã hóa và giải mã tất cả các tham số trong class này. Mặc định thì tên các property sẽ được sử dụng trong JSON luôn. Nhưng nếu ta muốn thay đổi tên của nó trong JSON thì có thể dùng cách thêm annotation @JsonKey và truyền vào name mới.

@JsonKey(name: 'event_image_url')
String imageUrl;

Ta cũng có thể đặt một số giá trị mặc định, phần tử bắt buộc hoặc bỏ qua một số property:

@JsonKey(defaultValue: 'Empty Title')
String title;

@JsonKey(required: true)
String date;

@JsonKey(ignore: true)
String imageUrl;

Lúc này, ta vẫn thấy lỗi cú pháp xảy ra ở class Event. Lý do là chưa có các class cần thiết. Để khắc phục vấn đề đó thì phải chạy 1 trong 2 câu lệnh sau ở terminal trên root folder của project:

// Chỉ build 1 lần
flutter pub run build_runner build

// Build mỗi lúc code thay đổi
flutter pub run build_runner watch

Sử dụng:

Map eventMap = jsonDecode(jsonString);
var event = Event.fromJson(eventMap);
String json = jsonEncode(event);

Nếu class của ta có class lồng nhau thì class cha và class con cũng đều phải JsonSerializable, và thêm explicitToJson: true vào class cha.

@JsonSerializable(explicitToJson: true)
class Event{
  String title;
  // Class Address đã serializable rồi.
  Address address;
}

3. Kết luận

Như vậy ta đã biết cách sử dụng JsonSerializable.
Trong thực tế, người ta sẽ sử dụng cách thứ 2 để serialize và deserialize vì các ưu điểm của nó, mặc dù nó khá lằng nhằng.

Hi vọng kiến thức này sẽ giúp ích được mọi người.

Viết một bình luận