Trong quá trình phát triển ứng dụng với Dart và Flutter, việc làm việc với JSON là không thể tránh khỏi, đặc biệt khi ứng dụng cần trao đổi dữ liệu với API. Tuy nhiên, JSON chỉ là một định dạng dữ liệu thô, còn Dart lại sử dụng các đối tượng có kiểu dữ liệu cụ thể. Điều này khiến việc chuyển đổi JSON sang Dart trở thành một bước quan trọng để đảm bảo dữ liệu được xử lý một cách an toàn và hiệu quả, tránh mất nhiều thời gian xử lý lỗi hoặc viết code dư thừa.

Đọc bài viết này để tìm hiểu thêm:

  • Các phương pháp chuyển đổi JSON sang Dart
  • Các ví dụ thực tế
  • Công cụ hỗ trợ chuyển đổi JSON sang Dart
  • Các câu hỏi thường gặp

JSON là gì trong ngữ cảnh của Dart?

JSON (JavaScript Object Notation) là một định dạng dữ liệu phổ biến, được sử dụng rộng rãi để trao đổi thông tin giữa máy chủ và ứng dụng. Với cú pháp đơn giản, dễ đọc và nhẹ, JSON trở thành một lựa chọn lý tưởng trong phát triển ứng dụng, đặc biệt là khi làm việc với API.

Trong ngữ cảnh của Flutter/Dart, JSON đóng vai trò quan trọng trong việc xử lý dữ liệu từ backend. Tuy nhiên, do sự khác biệt về kiểu dữ liệu giữa JSON và Dart, việc chuyển đổi JSON sang các đối tượng Dart cần được thực hiện một cách hiệu quả để đảm bảo tính nhất quán và tối ưu hiệu suất.

Đọc thêm: Dart Flutter là gì: Cách xây dựng mobile app với Dart trong Flutter

Vì sao cần chuyển đổi từ JSON sang Dart?

Trong phát triển ứng dụng Flutter, việc chuyển đổi từ JSON sang Dart cần thiết vì:

  • Dễ dàng quản lý dữ liệu: JSON là một định dạng phổ biến, nhưng dạng Map<String, dynamic> của nó khiến việc truy xuất dữ liệu thiếu an toàn và dễ gây lỗi. Việc chuyển đổi JSON thành các model Dart giúp làm việc với dữ liệu theo cách có kiểm soát hơn.
  • Tăng cường kiểm tra kiểu dữ liệu (Type Safety): Khi chuyển đổi JSON sang class Dart, các thuộc tính sẽ có kiểu dữ liệu cụ thể (int, String, bool, List, v.v.), giúp giảm thiểu lỗi runtime.
  • Dễ dàng sử dụng và bảo trì: Khi có một model Dart, bạn có thể sử dụng chúng để:
    • Dễ dàng parse dữ liệu từ JSON mà không cần viết nhiều đoạn code xử lý.
    • Tạo object từ JSON và chuyển object thành JSON một cách thuận tiện.
    • Dễ dàng mở rộng khi API thay đổi (chỉ cần cập nhật model thay vì sửa nhiều nơi trong code).
  • Hỗ trợ tốt cho State Management: Khi làm việc với Provider, Riverpod, Bloc, GetX, dữ liệu thường cần ở dạng object thay vì JSON để dễ dàng thao tác. Chuyển đổi JSON sang Dart giúp bạn dễ dàng cập nhật và theo dõi trạng thái của ứng dụng.
  • Cải thiện hiệu suất: Việc truy xuất dữ liệu từ object nhanh hơn so với Map vì Dart có thể tối ưu hóa việc truy cập thuộc tính. Ngoài ra, còn giúp giảm thiểu lỗi null nhờ null safety của Dart.

Các phương pháp chuyển đổi JSON sang Dart 

Nếu bạn đang làm việc với JSON trong Flutter/Dart, có hai phương pháp phổ biến để chuyển đổi dữ liệu:

  1. Chuyển đổi thủ công: Phù hợp với JSON đơn giản, dễ hiểu nhưng có thể gây mất công khi xử lý JSON phức tạp.
  2. Sử dụng json_serializable: Giúp tự động hóa quá trình ánh xạ JSON, giảm thiểu lỗi và tiết kiệm thời gian nhưng yêu cầu thiết lập ban đầu.

Tùy vào nhu cầu dự án, bạn có thể lựa chọn phương pháp phù hợp nhất để làm việc với JSON một cách hiệu quả trong Flutter/Dart.

Sử dụng dart:convert với jsonDecode()

Dart cung cấp thư viện dart:convert để giúp chúng ta phân tích cú pháp JSON một cách dễ dàng. Dưới đây là một ví dụ:

import 'dart:convert';
void main() {
  String jsonString = '{"id": 1, "name": "Flutter"}';
  Map<String, dynamic> jsonData = jsonDecode(jsonString);
  print(jsonData['name']); // Output: Flutter
}

Thay vì làm việc trực tiếp với Map, ta có thể tạo một model Dart bằng tay để ánh xạ dữ liệu từ JSON:

class User {
  final int id;
  final String name;
  User({required this.id, required this.name});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
    };
  }
}

Sau khi đã tạo, sử dụng model này như sau:

void main() {
  String jsonString = '{"id": 1, "name": "Flutter"}';
  Map<String, dynamic> jsonData = jsonDecode(jsonString);
  User user = User.fromJson(jsonData);

  print(user.name); // Output: Flutter
}

Ưu điểm:

  • Dễ hiểu, không cần phụ thuộc vào package bên ngoài.
  • Kiểm soát tốt quá trình chuyển đổi dữ liệu.

Nhược điểm:

  • Dễ xảy ra lỗi nếu JSON phức tạp.
  • Yêu cầu viết nhiều mã lặp đi lặp lại, đặc biệt với các model lớn.

Chuyển đổi JSON sang Dart bằng json_serializable

json_serializable là một package hỗ trợ code generation, giúp tự động tạo phương thức ánh xạ JSON sang Dart model mà không cần viết thủ công.

Bạn có thể tìm hiểu thêm và tiến hành cài đặt package này tại đây: https://pub.dev/packages/json_serializable

Sau khi cài đặt, bạn thiết lập json_serializable trong dự án Flutter/Dart bằng cách thêm các package sau vào pubspec.yaml:

dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  build_runner: ^2.4.6
  json_serializable: ^6.7.1

Chạy lệnh:

flutter pub get

Tạo model Dart với @JsonSerializable và chạy code generation:

  • Tạo model User:
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  final int id;
  final String name;

  User({required this.id, required this.name});

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
  • Chạy lệnh để tạo file .g.dart:
flutter pub run build_runner build
  • Bây giờ bạn có thể sử dụng model này như sau:
void main() {
  String jsonString = '{"id": 1, "name": "Flutter"}';
  Map<String, dynamic> jsonData = jsonDecode(jsonString);
  User user = User.fromJson(jsonData);

  print(user.name); // Output: Flutter
}

Ưu điểm:

  • Tự động hóa việc ánh xạ JSON, giúp giảm mã lặp đi lặp lại.
  • Hạn chế lỗi sai khi xử lý JSON phức tạp.
  • Hỗ trợ dễ dàng cập nhật model khi thay đổi JSON.

Nhược điểm:

  • Cần thiết lập ban đầu và chạy build_runner.
  • Phụ thuộc vào package bên ngoài.

Sử dụng freezed để tạo model Dart

freezed là một package mạnh mẽ giúp tạo immutable data classes, hỗ trợ serialization và copyWith dễ dàng. Đây là một giải pháp hiện đại thay thế cho việc viết model thủ công.

Sử dụng freezed giúp cho việc chuyển đổi JSON thành Dart (và ngược lại) được biết đến là một nhu cầu phổ biến khi làm việc với dữ liệu từ API trở nên tự động, dễ dàng và chính xác hơn  với các điểm mạnh sau:

Đặc điểm  Lợi ích của Freezed
Chuyển đổi JSON dễ dàng hơn Tự động tạo fromJson()toJson() với json_serializable.
Bất biến (Immutable) Dữ liệu không thể thay đổi ngoài ý muốn, an toàn hơn.
Hỗ trợ JSON phức tạp Dễ dàng ánh xạ JSON lồng nhau vào model Dart.
Hỗ trợ Union Types Dễ dàng xử lý nhiều trạng thái dữ liệu trong API.
Giảm boilerplate code Tự động sinh mã, giúp code ngắn gọn, dễ bảo trì.
An toàn kiểu dữ liệu Hạn chế lỗi null, tránh lỗi runtime.
Tích hợp tốt Hoạt động mượt với Bloc, Provider, Riverpod.

Bạn có thể thiết lập và sử dụng freezed với các bước sau:

  • Thêm các package vào pubspec.yaml:
dependencies:

  freezed_annotation: ^2.4.1

dev_dependencies:

  build_runner: ^2.4.6

  freezed: ^2.4.5
  • Chạy lệnh:
flutter pub get
  • Tạo model User:
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  factory User({
    required int id,
    required String name,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

}
  • Chạy lệnh để tạo code:
flutter pub run build_runner build

Bây giờ bạn có thể sử dụng model User tương tự như với json_serializable nhưng có thêm nhiều tính năng nâng cao.

So sánh freezed với json_serializable

Tiêu chí  json_serializable freezed
Tự động tạp model Có  Có 
Tự động immutable  Không  Không 
Hỗ trợ copyWith Không  Có 
Hỗ trợ Union types  Không  Có 
Độ phức tạp  Trung bình  Cao 

Khi nào nên dùng freezed?

  • Khi bạn muốn có các model immutable.
  • Khi cần hỗ trợ copyWith, equality tốt hơn.
  • Khi muốn sử dụng Union types.

Khi nào nên dùng json_serializable?

  • Khi chỉ cần ánh xạ JSON đơn giản.
  • Khi không cần các tính năng nâng cao như Union types hay copyWith.

Các công cụ hỗ trợ chuyển đổi từ JSON sang Dart

Việc tạo các model Dart từ JSON bằng tay có thể tốn thời gian và dễ mắc lỗi. Dưới đây là một số công cụ phổ biến giúp tự động tạo class từ JSON, giúp tiết kiệm thời gian và giảm lỗi trong quá trình viết model:

JSON to Dart: Công cụ trực tuyến giúp tạo model Dart từ JSON

  • Ưu điểm: Giao diện đơn giản, dễ sử dụng cho việc chuyển đổi nhanh chóng.​
  • Nhược điểm:
    • Thiếu hỗ trợ null-safety, khiến mã nguồn không an toàn trong môi trường Dart hiện đại.​
    • Tạo getters và setters không cần thiết khi các trường được đặt là private, làm tăng độ phức tạp của mã.​
    • Không nhận diện đúng kiểu dữ liệu mảng, ví dụ như nhận diện List<int> thành Object<int>, gây ra lỗi khi biên dịch.​
    • Thiếu thông tin về nguồn gốc và hỗ trợ, khiến người dùng khó tin tưởng và sử dụng trong dự án thực tế.

Dart Data Class Generator: Công cụ hỗ trợ chuyển đổi với nhiều tùy chỉnh

  • Ưu điểm:
    • Cung cấp nhiều tùy chọn tùy chỉnh cho mã nguồn được tạo ra, giúp lập trình viên điều chỉnh theo nhu cầu cụ thể.​
    • Mã nguồn mở và có sẵn trên GitHub, giúp cộng đồng dễ dàng đóng góp và cải thiện.​
    • Hỗ trợ null-safety, phù hợp với các phiên bản Dart hiện đại.
  • Nhược điểm:
    • Chèn các đoạn JSON dưới dạng chú thích trong mã nguồn, khiến mã dài dòng và thiếu tính rõ ràng.
    • Xử lý mảng phức tạp và đôi khi tạo ra các đối tượng trùng lặp với tên khác nhau, gây nhầm lẫn và khó bảo trì.​
    • Phong cách mã nguồn không nhất quán, đòi hỏi lập trình viên phải chỉnh sửa lại để phù hợp với tiêu chuẩn dự án.

Quicktype: Công cụ giúp chuyển đổi JSON sang nhiều ngôn ngữ

  • Ưu điểm:
    • Hỗ trợ đa ngôn ngữ, bao gồm cả Dart, giúp chuyển đổi JSON sang nhiều ngôn ngữ lập trình khác nhau.​
    • Cung cấp các tùy chọn tùy chỉnh cho lớp được tạo ra, giúp lập trình viên điều chỉnh theo nhu cầu cụ thể.​
    • Có sẵn tiện ích mở rộng cho Visual Studio Code, giúp tích hợp trực tiếp vào môi trường phát triển..
  • Nhược điểm:
    • Không sử dụng từ khóa required mà thay bằng chú thích @required, không phù hợp với chuẩn hiện tại của Dart.​
    • Mã nguồn cần được chỉnh sửa thủ công để phù hợp với tiêu chuẩn hiện tại, gây mất thời gian và dễ phát sinh lỗi.
    • Không hỗ trợ null-safety và các trường không được khai báo là final, dẫn đến mã nguồn không an toàn và không tối ưu.

JSON formatter

  • Ưu điểm: Chuyển đổi cấu trúc JSON thành lớp Dart cơ bản một cách nhanh chóng.​
  • Nhược điểm: Thiếu các phương thức fromJsontoJson, làm giảm tính hữu dụng của mã nguồn được tạo ra.​

Lưu ý khi làm việc với JSON trong Dart

Khi làm việc với JSON trong Dart, bạn cần chú ý những điểm sau:

Xử lý trường hợp JSON có thể null hoặc thiếu dữ liệu

JSON từ API có thể không đầy đủ hoặc chứa giá trị null. Do đó, cần kiểm tra và xử lý hợp lý để tránh lỗi runtime.

Ví dụ:

class User {
  final String name;
  final int? age; // Có thể null

  User({required this.name, this.age});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] ?? 'Unknown',
      age: json['age'] as int?,
    );
  }
}

Sử dụng required và giá trị mặc định trong model

Dart 2.12+ hỗ trợ null safety, do đó, sử dụng required giúp đảm bảo các giá trị quan trọng không bị null.

Ví dụ:

class Product {
  final String id;
  final String name;
  final double price;

  Product({
    required this.id,
    required this.name,
    this.price = 0.0, // Giá trị mặc định
  });
}

Hiệu suất và tối ưu hóa khi parse JSON trong Flutter

Parse JSON có thể ảnh hưởng đến hiệu suất, đặc biệt khi xử lý dữ liệu lớn. Một số cách tối ưu:

  • Sử dụng const factory@immutable: Giúp tránh việc tạo nhiều instance không cần thiết.
  • Dùng compute() để xử lý JSON trên isolate khác:
import 'dart:convert';
import 'package:flutter/foundation.dart';

Future<User> parseUser(String jsonString) async {
  return compute(_parseUser, jsonString);
}

User _parseUser(String jsonString) {
  final Map<String, dynamic> json = jsonDecode(jsonString);
  return User.fromJson(json);
}
  • Dùng json_serializable thay vì parse thủ công: Giúp parse nhanh hơn và tránh lỗi khi cập nhật model.

Các câu hỏi thường gặp về JSON to Dart

Khi nào nên dùng json_serializable thay vì viết tay fromJson()toJson()?

Khi bạn làm việc với dữ liệu JSON phức tạp hoặc nhiều lớp, json_serializable giúp tự động tạo code chuyển đổi JSON, giảm lỗi sai và tiết kiệm thời gian.

Làm thế nào để xử lý giá trị null trong JSON khi parse?

Để xử lý giá trị null trong JSON khi parse, dùng toán tử ?? để đặt giá trị mặc định:

factory User.fromJson(Map<String, dynamic> json) {
  return User(
    name: json['name'] ?? 'Unknown',
    age: json['age'] ?? 0,
  );
}

Làm thế nào để xử lý kiểu dữ liệu không khớp trong JSON?

Dùng try-catch hoặc ép kiểu an toàn (as) để tránh lỗi runtime:

factory User.fromJson(Map<String, dynamic> json) {
  return User(
    name: json['name'] as String? ?? 'Unknown',
    age: json['age'] is int ? json['age'] : int.tryParse(json['age'].toString()) ?? 0,
  );
}

Tổng kết

Chuyển đổi JSON sang Dart là bước quan trọng trong việc xây dựng ứng dụng Flutter. Việc sử dụng các công cụ hỗ trợ như JSON to Dart, Dart Data Class Generator, và các package như json_serializable giúp tiết kiệm thời gian và giảm lỗi.