JSON เป็น Dart
สร้าง Dart class จาก JSON พร้อม fromJson และ toJson
อินพุต JSON
เอาต์พุต Dart
การแปลง JSON เป็น Dart คืออะไร?
การแปลง JSON เป็น Dart จะรับอ็อบเจ็กต์ JSON ดิบแล้วสร้างนิยาม Dart class ที่มีฟิลด์ที่มีประเภท constructor ที่มีชื่อ factory ชื่อ fromJson และเมธอด toJson Dart ไม่มี runtime reflection ใน Flutter (dart:mirrors ถูกปิดใช้งาน) ดังนั้นคุณจึงไม่สามารถ deserialize JSON เป็นอ็อบเจ็กต์ที่มีประเภทได้โดยไม่ต้องเขียนโค้ด mapping อย่างชัดเจน ทุก REST API response เอกสาร Firebase หรือ config payload ต้องการ Dart model class ที่ตรงกันก่อนที่คุณจะเข้าถึงฟิลด์ด้วยความปลอดภัยของประเภทได้
โดยทั่วไป Dart model class สำหรับ JSON จะประกาศฟิลด์ final สำหรับแต่ละ key, constructor ที่มีพารามิเตอร์แบบมีชื่อ (ใช้คีย์เวิร์ด required สำหรับฟิลด์ที่ไม่ nullable), factory constructor ชื่อ fromJson ที่อ่านจาก Map<String, dynamic> และเมธอด toJson ที่คืนค่า Map<String, dynamic> อ็อบเจ็กต์ JSON ที่ซ้อนกันจะกลายเป็น class แยกต่างหาก อาเรย์จะกลายเป็นฟิลด์ List ที่มีประเภท ค่า JSON ที่ nullable จะใช้ไวยากรณ์ null-safety ของ Dart โดยเพิ่ม ? ต่อท้ายประเภท
การเขียน model class เหล่านี้ด้วยมือหมายความว่าต้องอ่านแต่ละ JSON key ตัดสินใจประเภท Dart สร้าง fromJson cast สำหรับแต่ละฟิลด์ (รวมถึงการ map ลิสต์ด้วย .map().toList()) สร้าง toJson map literal และทำซ้ำสำหรับทุกอ็อบเจ็กต์ที่ซ้อนกัน สำหรับอ็อบเจ็กต์ JSON ที่มี 12 ฟิลด์และ 2 อ็อบเจ็กต์ซ้อนกัน นั่นหมายถึง 3 class, 6 บรรทัด factory และนิพจน์ cast หลายสิบรายการ ตัวแปลงจะสร้างทั้งหมดนี้ภายในไม่กี่มิลลิวินาทีจากการวางข้อความครั้งเดียว
ทำไมต้องใช้ตัวแปลง JSON เป็น Dart?
การเขียน Dart model class จาก JSON ด้วยมือต้องอ่านชื่อฟิลด์ คาดเดาประเภทจากค่าตัวอย่าง เขียน fromJson cast พร้อมจัดการ null อย่างถูกต้อง และทำซ้ำสำหรับอ็อบเจ็กต์ที่ซ้อนกัน เมื่อรูปแบบ API เปลี่ยน การอัปเดตฟิลด์แต่ละครั้งจะส่งผลต่อ constructor, fromJson และ toJson ตัวแปลงจะขจัดงานซ้ำซากนี้ออกไป
กรณีการใช้งาน JSON เป็น Dart
การ Mapping ประเภท JSON เป็น Dart
ค่า JSON แต่ละชนิดจะ map กับประเภท Dart ที่เฉพาะเจาะจง ตารางด้านล่างแสดงวิธีที่ตัวแปลงแปลงแต่ละชนิด JSON คอลัมน์ Alternative แสดงประเภทที่ใช้ในสถานการณ์การ map ที่ไม่ค่อยพบหรือแบบ manual
| ชนิด JSON | ตัวอย่าง | ชนิด Dart | ทางเลือก |
|---|---|---|---|
| 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> |
แนวทาง JSON Serialization ใน Dart
Dart และ Flutter มีหลายวิธีในการจัดการ JSON serialization การเขียน fromJson/toJson แบบ manual เป็นวิธีที่ง่ายที่สุดและไม่ต้องการการสร้างโค้ด สำหรับโปรเจกต์ขนาดใหญ่ โซลูชันที่ใช้ build_runner อย่าง json_serializable และ freezed จะสร้าง boilerplate ในช่วง compile time ลดข้อผิดพลาดและรักษา model ให้สอดคล้องกับ JSON contract
| แนวทาง | คำอธิบาย | แหล่งที่มา |
|---|---|---|
| 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 |
Manual vs json_serializable vs freezed
Dart มีสามแนวทางทั่วไปสำหรับ JSON model class การเขียน fromJson/toJson แบบ manual ไม่มี dependency json_serializable ทำให้การ map โดยอัตโนมัติ freezed เพิ่มความไม่เปลี่ยนแปลง copyWith และ pattern matching บน json_serializable
ตัวอย่างโค้ด
ตัวอย่างเหล่านี้แสดงวิธีใช้ Dart class ที่สร้างขึ้นสำหรับ JSON deserialization วิธีตั้งค่า json_serializable กับ build_runner และวิธีสร้าง Dart class โดยโปรแกรมจาก JavaScript และ 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;
# }