JSON sang Dart
Tạo lớp Dart từ JSON với fromJson và toJson
Đầu vào JSON
Đầu ra Dart
Chuyển đổi JSON sang Dart là gì?
Chuyển đổi JSON sang Dart lấy một đối tượng JSON thô và tạo ra các định nghĩa lớp Dart với các trường có kiểu, constructor có tên, factory fromJson, và phương thức toJson. Dart không có reflection tại runtime trong Flutter (dart:mirrors bị vô hiệu hóa), nên bạn không thể giải tuần tự hóa JSON thành các đối tượng có kiểu mà không viết code ánh xạ tường minh. Mỗi phản hồi REST API, tài liệu Firebase, hoặc payload cấu hình đều cần một lớp model Dart tương ứng trước khi bạn có thể truy cập các trường với tính an toàn kiểu.
Một lớp model Dart điển hình cho JSON khai báo các trường final cho mỗi key, constructor với tham số có tên (dùng từ khóa required cho các trường non-nullable), factory constructor có tên fromJson đọc từ Map kiểu String sang dynamic, và phương thức toJson trả về Map kiểu String sang dynamic. Các đối tượng JSON lồng nhau trở thành các lớp riêng biệt. Mảng trở thành các trường List có kiểu. Giá trị JSON nullable dùng cú pháp null-safety của Dart với hậu tố ? sau kiểu dữ liệu.
Viết các lớp model này thủ công đồng nghĩa với việc đọc từng key JSON, xác định kiểu Dart, tạo phép ép kiểu fromJson cho mỗi trường (bao gồm ánh xạ danh sách với .map().toList()), xây dựng map literal toJson, và lặp lại cho mỗi đối tượng lồng nhau. Với một đối tượng JSON có 12 trường và 2 đối tượng lồng nhau, điều đó có nghĩa là 3 lớp, 6 dòng factory và hàng chục biểu thức ép kiểu. Một bộ chuyển đổi tạo ra tất cả điều này trong vài mili giây chỉ từ một lần dán.
Tại sao nên dùng bộ chuyển đổi JSON sang Dart?
Viết lớp model Dart từ JSON thủ công đòi hỏi đọc tên trường, đoán kiểu từ giá trị mẫu, viết phép ép kiểu fromJson với xử lý null chính xác, và lặp lại quy trình cho các đối tượng lồng nhau. Khi cấu trúc API thay đổi, mỗi cập nhật trường ảnh hưởng đến constructor, fromJson và toJson. Bộ chuyển đổi loại bỏ công việc lặp đi lặp lại đó.
Các trường hợp sử dụng JSON sang Dart
Bảng ánh xạ kiểu JSON sang Dart
Mỗi giá trị JSON ánh xạ sang một kiểu Dart cụ thể. Bảng dưới đây cho thấy bộ chuyển đổi dịch từng kiểu JSON như thế nào. Cột Thay thế hiển thị các kiểu dùng trong các tình huống ánh xạ ít phổ biến hơn hoặc thủ công.
| Kiểu JSON | Ví dụ | Kiểu Dart | Thay thế |
|---|---|---|---|
| string | "hello" | String | String |
| number (integer) | 42 | int | int |
| number (float) | 3.14 | double | double |
| boolean | true | bool | bool |
| null | null | dynamic | Null / dynamic |
| object | {"k": "v"} | NestedClass | Map<String, dynamic> |
| array of strings | ["a", "b"] | List<String> | List<String> |
| array of objects | [{"id": 1}] | List<Item> | List<Item> |
| mixed array | [1, "a"] | List<dynamic> | List<dynamic> |
Các phương pháp tuần tự hóa JSON trong Dart
Dart và Flutter cung cấp nhiều cách để xử lý tuần tự hóa JSON. fromJson/toJson thủ công là phương pháp đơn giản nhất và không cần tạo code. Đối với các dự án lớn hơn, các giải pháp dựa trên build_runner như json_serializable và freezed tạo ra code boilerplate tại thời điểm biên dịch, giảm lỗi và giữ model nhất quán với hợp đồng JSON.
| Phương pháp | Mô tả | Nguồn |
|---|---|---|
| json_serializable | Code-generation-based JSON serialization. Generates .g.dart files at build time via build_runner. Type-safe and compile-time verified. | Official (Google) |
| freezed | Generates immutable data classes with copyWith, fromJson/toJson, equality, and pattern matching support. Built on top of json_serializable. | Community (rrousselGit) |
| built_value | Generates immutable value types with serialization. Enforces immutability patterns. Used in larger codebases with strict data modeling. | |
| dart_mappable | Annotation-based mapper that generates fromJson, toJson, copyWith, and equality. Simpler setup than freezed with similar features. | Community |
| Manual fromJson/toJson | Hand-written factory constructors and toJson methods. No code generation needed. Full control over the mapping logic. | Built-in Dart |
So sánh Manual, json_serializable và freezed
Dart có ba phương pháp phổ biến cho lớp model JSON. fromJson/toJson thủ công không có phụ thuộc. json_serializable tự động hóa code ánh xạ. freezed bổ sung tính bất biến, copyWith, và pattern matching trên nền json_serializable.
Ví dụ code
Các ví dụ này cho thấy cách dùng các lớp Dart được tạo ra để giải tuần tự hóa JSON, cách thiết lập json_serializable với build_runner, và cách tạo lớp Dart theo chương trình từ JavaScript và Python.
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
final bool active;
final Address address;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.active,
required this.address,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
active: json['active'] as bool,
address: Address.fromJson(json['address'] as Map<String, dynamic>),
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
'active': active,
'address': address.toJson(),
'tags': tags,
};
}
class Address {
final String street;
final String city;
final String zip;
Address({required this.street, required this.city, required this.zip});
factory Address.fromJson(Map<String, dynamic> json) => Address(
street: json['street'] as String,
city: json['city'] as String,
zip: json['zip'] as String,
);
Map<String, dynamic> toJson() => {'street': street, 'city': city, 'zip': zip};
}
void main() {
final jsonStr = '{"id":1,"name":"Alice","email":"alice@example.com","active":true,"address":{"street":"123 Main","city":"Springfield","zip":"12345"},"tags":["admin","user"]}';
final user = User.fromJson(jsonDecode(jsonStr));
print(user.name); // -> Alice
print(jsonEncode(user.toJson())); // -> round-trip back to JSON
}// pubspec.yaml dependencies:
// json_annotation: ^4.8.0
// dev_dependencies:
// build_runner: ^2.4.0
// json_serializable: ^6.7.0
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // generated by: dart run build_runner build
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
@JsonKey(name: 'is_active')
final bool isActive;
final List<String> tags;
User({
required this.id,
required this.name,
required this.email,
required this.isActive,
required this.tags,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// Run code generation:
// dart run build_runner build --delete-conflicting-outputsfunction jsonToDart(obj, name = "Root") {
const classes = [];
function infer(val, fieldName) {
if (val === null) return "dynamic";
if (typeof val === "string") return "String";
if (typeof val === "number") return Number.isInteger(val) ? "int" : "double";
if (typeof val === "boolean") return "bool";
if (Array.isArray(val)) {
const first = val.find(v => v !== null);
if (!first) return "List<dynamic>";
return `List<${infer(first, fieldName)}>`;
}
if (typeof val === "object") {
const cls = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
build(val, cls);
return cls;
}
return "dynamic";
}
function build(obj, cls) {
const fields = Object.entries(obj).map(([k, v]) =>
` final ${infer(v, k)} ${k};`
);
classes.push(`class ${cls} {\n${fields.join("\n")}\n}`);
}
build(obj, name);
return classes.join("\n\n");
}
console.log(jsonToDart({ id: 1, name: "Alice", scores: [98, 85] }, "User"));
// class User {
// final int id;
// final String name;
// final List<int> scores;
// }import json
def json_to_dart(obj: dict, class_name: str = "Root") -> str:
classes = []
def infer(val, name):
if val is None:
return "dynamic"
if isinstance(val, bool):
return "bool"
if isinstance(val, int):
return "int"
if isinstance(val, float):
return "double"
if isinstance(val, str):
return "String"
if isinstance(val, list):
if not val:
return "List<dynamic>"
return f"List<{infer(val[0], name)}>"
if isinstance(val, dict):
cls = name[0].upper() + name[1:]
build(val, cls)
return cls
return "dynamic"
def build(obj, cls):
fields = [f" final {infer(v, k)} {k};" for k, v in obj.items()]
classes.append(f"class {cls} {{\n" + "\n".join(fields) + "\n}")
build(obj, class_name)
return "\n\n".join(classes)
data = json.loads('{"id": 1, "name": "Alice", "active": true}')
print(json_to_dart(data, "User"))
# class User {
# final int id;
# final String name;
# final bool active;
# }