JSON sang Dart

Tạo lớp Dart từ JSON với fromJson và toJson

Thử ví dụ
Tên lớp gốc:

Đầu vào JSON

Đầu ra Dart

Chạy cục bộ · An toàn để dán thông tin bí mật
Các lớp Dart sẽ hiển thị ở đây…

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 đó.

Tạo lớp tức thì
Dán JSON và nhận các lớp Dart hoàn chỉnh với constructor, factory fromJson, và phương thức toJson trong chưa đến một giây. Các đối tượng lồng nhau và danh sách được phát hiện và ánh xạ tự động.
🔒
Xử lý ưu tiên bảo mật
Quá trình chuyển đổi chạy hoàn toàn trong trình duyệt bằng JavaScript. Dữ liệu JSON của bạn không bao giờ rời khỏi thiết bị. API key, token và payload thực tế được giữ riêng tư.
📝
Đầu ra null-safe
Các lớp được tạo ra dùng null safety của Dart. Các trường JSON nullable có hậu tố kiểu ? và bỏ qua từ khóa required trong constructor. Các trường non-null được đánh dấu required.
📦
Không cần cài đặt hay đăng ký
Mở trang và dán JSON của bạn. Không cần Dart SDK, không cần phụ thuộc pub, không cần tài khoản. Hoạt động trên mọi thiết bị có trình duyệt.

Các trường hợp sử dụng JSON sang Dart

Phát triển ứng dụng Flutter
Tạo lớp model cho các phản hồi REST API được sử dụng bởi gói http hoặc dio. Dán JSON mà backend trả về và nhận các lớp Dart sẵn sàng cho jsonDecode và hiển thị widget.
Tích hợp Firebase / Firestore
Tạo lớp model có kiểu cho các snapshot tài liệu Firestore. Dán một tài liệu mẫu dưới dạng JSON, tạo lớp Dart, và dùng fromJson với snapshot.data() trong tầng repository.
Model quản lý trạng thái
Định nghĩa các đối tượng trạng thái có kiểu cho Riverpod, Bloc hoặc Provider. Dán hình dạng trạng thái dự kiến dưới dạng JSON và nhận các lớp Dart bất biến với toJson để lưu trữ và khôi phục trạng thái.
Tạo code API Client
Tạo model request và response cho Retrofit hoặc Chopper API client. Dán các payload JSON mẫu và nhận các lớp Dart khớp với schema response OpenAPI của bạn.
Kiểm thử tự động
Xây dựng fixture có kiểu từ các phản hồi API thực tế. Dán một mẫu JSON, tạo lớp model, và dùng trực tiếp trong các bài kiểm thử unit và widget mà không cần viết tay fixture.
Học các mẫu Dart
Sinh viên học Flutter có thể dán bất kỳ cấu trúc JSON nào và xem cách Dart biểu diễn nó: trường final, constructor có tên, mẫu factory fromJson, và chú thích null-safety trong thực tế.

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 JSONVí dụKiểu DartThay thế
string"hello"StringString
number (integer)42intint
number (float)3.14doubledouble
booleantrueboolbool
nullnulldynamicNull / dynamic
object{"k": "v"}NestedClassMap<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ápMô tảNguồn
json_serializableCode-generation-based JSON serialization. Generates .g.dart files at build time via build_runner. Type-safe and compile-time verified.Official (Google)
freezedGenerates immutable data classes with copyWith, fromJson/toJson, equality, and pattern matching support. Built on top of json_serializable.Community (rrousselGit)
built_valueGenerates immutable value types with serialization. Enforces immutability patterns. Used in larger codebases with strict data modeling.Google
dart_mappableAnnotation-based mapper that generates fromJson, toJson, copyWith, and equality. Simpler setup than freezed with similar features.Community
Manual fromJson/toJsonHand-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.

Manual fromJson/toJson
Tự viết factory fromJson và phương thức toJson. Không phụ thuộc, không có bước build_runner. Bạn có toàn quyền kiểm soát logic ép kiểu và xử lý lỗi. Phù hợp nhất cho dự án nhỏ với vài model, hoặc khi bạn cần giải tuần tự hóa tùy chỉnh mà các bộ tạo code không thể diễn đạt. Đây là định dạng đầu ra mà công cụ này tạo ra.
json_serializable
Chú thích lớp của bạn với @JsonSerializable() và chạy dart run build_runner build. Bộ tạo tạo ra file .g.dart với các hàm _$ClassFromJson và _$ClassToJson. Xử lý @JsonKey để đổi tên trường, giá trị mặc định và converter tùy chỉnh. Lựa chọn tiêu chuẩn cho các dự án Flutter trung bình đến lớn.
freezed
Chú thích với @freezed và nhận các lớp bất biến với fromJson, toJson, copyWith, equality (== và hashCode), và toString được tạo tự động. Hỗ trợ union types cho các mẫu sealed class. Yêu cầu cả freezed và json_serializable làm dev dependency. Phù hợp nhất cho model quản lý trạng thái và đối tượng domain phức tạp.

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.

Dart (manual fromJson / toJson)
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
}
Dart (json_serializable + build_runner)
// 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-outputs
JavaScript (generate Dart classes from JSON)
function 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;
// }
Python (generate Dart model from JSON)
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;
# }

Câu hỏi thường gặp

Tại sao Flutter không thể dùng dart:mirrors để giải tuần tự hóa JSON?
Flutter vô hiệu hóa dart:mirrors (thư viện reflection) vì tree-shaking yêu cầu biết tại thời điểm biên dịch code nào được sử dụng. Reflection sẽ ngăn trình biên dịch loại bỏ các lớp không dùng đến, làm tăng kích thước ứng dụng. Đây là lý do tại sao các dự án Flutter cần phương thức fromJson/toJson tường minh hoặc tạo code thay vì reflection tại runtime.
Nên dùng json_serializable hay tự viết fromJson?
Với dự án có ít hơn 10 lớp model, fromJson/toJson tự viết rất đơn giản và không thêm phụ thuộc build. Khi bạn có hơn 10 model, hoặc các model thay đổi thường xuyên, json_serializable tiết kiệm thời gian và giảm lỗi copy-paste. Nó tạo code ánh xạ từ annotation, nên việc thêm trường chỉ cần chạy lại build_runner.
Bộ chuyển đổi xử lý các đối tượng JSON lồng nhau như thế nào?
Mỗi đối tượng JSON lồng nhau trở thành một lớp Dart riêng biệt. Nếu một trường JSON có tên "address" chứa đối tượng với key "street" và "city", bộ chuyển đổi tạo lớp Address với các trường đó và gán kiểu Address cho trường cha. Factory fromJson ép kiểu map lồng nhau và truyền vào Address.fromJson.
Điều gì xảy ra khi một trường JSON là null?
Các trường null được gán kiểu dynamic với hậu tố ? vì bộ chuyển đổi không thể xác định kiểu dự kiến chỉ từ giá trị null. Thay dynamic? bằng kiểu chính xác (String?, int?, v.v.) khi bạn biết từ hợp đồng API. Null safety của Dart sau đó sẽ đảm bảo kiểm tra null chính xác tại thời điểm biên dịch.
Làm thế nào để xử lý các key JSON không phải là tên định danh Dart hợp lệ?
Các key JSON như "first-name" hay "2nd_place" không phải là tên trường Dart hợp lệ. Với fromJson thủ công, bạn có thể ánh xạ bất kỳ key JSON nào sang tên trường Dart bất kỳ trong factory constructor. Với json_serializable, dùng @JsonKey(name: 'first-name') để chú thích trường. Bộ chuyển đổi tự động chuyển đổi tên key sang trường Dart dạng camelCase.
Tôi có thể dùng freezed với đầu ra từ công cụ này không?
Công cụ tạo ra các lớp Dart chuẩn với fromJson/toJson thủ công. Để chuyển sang freezed, thay đổi lớp để dùng annotation @freezed, xóa constructor và factory tự viết, và thêm freezed mixin. Sau đó chạy build_runner để tạo các file .freezed.dart và .g.dart. Tên trường và kiểu từ đầu ra được tạo sẽ chuyển sang trực tiếp.
Sự khác biệt giữa map thô và lớp Dart có kiểu khi xử lý JSON là gì?
Map thô (Map kiểu String sang dynamic) cho bạn truy cập thô vào dữ liệu JSON mà không kiểm tra kiểu. Bạn phải ép kiểu mỗi giá trị tại điểm gọi, và lỗi đánh máy trong tên key sẽ thất bại âm thầm tại runtime. Lớp Dart có kiểu bắt lỗi không khớp kiểu tại ranh giới fromJson, cung cấp tự động hoàn thành IDE cho tên trường, và làm cho việc tái cấu trúc an toàn. Sự khác biệt hiệu suất là không đáng kể; lợi ích hoàn toàn nằm ở tính đúng đắn và khả năng bảo trì.