ToolDeck

JSON เป็น Dart

สร้าง Dart class จาก JSON พร้อม fromJson และ toJson

ลองตัวอย่าง
ชื่อ root class:

อินพุต JSON

เอาต์พุต Dart

ประมวลผลในเครื่อง · ปลอดภัยในการวางข้อมูลลับ
Dart class จะแสดงที่นี่…

การแปลง 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 ตัวแปลงจะขจัดงานซ้ำซากนี้ออกไป

สร้าง class ได้ทันที
วาง JSON แล้วรับ Dart class ที่สมบูรณ์พร้อม constructor, fromJson factory และเมธอด toJson ภายในไม่ถึงหนึ่งวินาที อ็อบเจ็กต์ที่ซ้อนกันและลิสต์จะถูกตรวจจับและ map โดยอัตโนมัติ
🔒
ประมวลผลโดยไม่ส่งข้อมูลออก
การแปลงทำงานทั้งหมดในเบราว์เซอร์ของคุณโดยใช้ JavaScript ข้อมูล JSON ของคุณไม่เคยออกจากเครื่อง API key, token และ payload ในระบบจริงยังคงเป็นส่วนตัว
📝
เอาต์พุตที่ปลอดภัยจาก null
class ที่สร้างขึ้นใช้ null safety ที่สมบูรณ์ของ Dart ฟิลด์ JSON ที่ nullable จะได้รับ suffix ประเภท ? และข้าม required keyword ใน constructor ฟิลด์ที่ไม่ null จะถูกทำเครื่องหมาย required
📦
ไม่ต้องติดตั้งหรือสมัครสมาชิก
เปิดหน้าและวาง JSON ของคุณ ไม่ต้องใช้ Dart SDK ไม่ต้อง pub dependency ไม่ต้องมีบัญชี ใช้งานได้บนอุปกรณ์ใดก็ได้ที่มีเบราว์เซอร์

กรณีการใช้งาน JSON เป็น Dart

การพัฒนา Flutter App
สร้าง model class สำหรับ REST API response ที่ใช้งานผ่านแพ็กเกจ http หรือ dio วาง JSON ที่ backend ของคุณส่งคืนแล้วรับ Dart class ที่พร้อมสำหรับ jsonDecode และการเรนเดอร์ widget
การผสาน Firebase / Firestore
สร้าง model class ที่มีประเภทสำหรับ Firestore document snapshot วาง document ตัวอย่างเป็น JSON สร้าง Dart class แล้วใช้ fromJson กับ snapshot.data() ใน repository layer ของคุณ
โมเดลสำหรับ State Management
กำหนด state object ที่มีประเภทสำหรับ Riverpod, Bloc หรือ Provider วาง state shape ที่คาดหวังเป็น JSON แล้วรับ Dart class ที่ไม่เปลี่ยนแปลงพร้อม toJson สำหรับการคงสถานะและการโหลดคืน
การสร้างโค้ด API Client
สร้าง request และ response model สำหรับ Retrofit หรือ Chopper API client วาง JSON payload ตัวอย่างแล้วรับ Dart class ที่ตรงกับ OpenAPI response schema ของคุณ
การทดสอบอัตโนมัติ
สร้าง test fixture ที่มีประเภทจาก API response จริง วาง JSON ตัวอย่าง สร้าง model class แล้วใช้โดยตรงใน unit test และ widget test โดยไม่ต้องเขียน fixture struct ด้วยมือ
เรียนรู้ Dart Patterns
นักศึกษาที่เรียน Flutter สามารถวาง JSON structure ใดก็ได้และดูว่า Dart แสดงออกมาอย่างไร: ฟิลด์ final, named constructor, factory fromJson pattern และ null-safety annotation ในทางปฏิบัติ

การ Mapping ประเภท JSON เป็น Dart

ค่า JSON แต่ละชนิดจะ map กับประเภท Dart ที่เฉพาะเจาะจง ตารางด้านล่างแสดงวิธีที่ตัวแปลงแปลงแต่ละชนิด JSON คอลัมน์ Alternative แสดงประเภทที่ใช้ในสถานการณ์การ map ที่ไม่ค่อยพบหรือแบบ manual

ชนิด JSONตัวอย่างชนิด Dartทางเลือก
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>

แนวทาง JSON Serialization ใน Dart

Dart และ Flutter มีหลายวิธีในการจัดการ JSON serialization การเขียน fromJson/toJson แบบ manual เป็นวิธีที่ง่ายที่สุดและไม่ต้องการการสร้างโค้ด สำหรับโปรเจกต์ขนาดใหญ่ โซลูชันที่ใช้ build_runner อย่าง json_serializable และ freezed จะสร้าง boilerplate ในช่วง compile time ลดข้อผิดพลาดและรักษา model ให้สอดคล้องกับ JSON contract

แนวทางคำอธิบายแหล่งที่มา
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

Manual vs json_serializable vs freezed

Dart มีสามแนวทางทั่วไปสำหรับ JSON model class การเขียน fromJson/toJson แบบ manual ไม่มี dependency json_serializable ทำให้การ map โดยอัตโนมัติ freezed เพิ่มความไม่เปลี่ยนแปลง copyWith และ pattern matching บน json_serializable

Manual fromJson/toJson
เขียน fromJson factory และเมธอด toJson ด้วยมือ ไม่มี dependency ไม่มีขั้นตอน build_runner คุณควบคุม casting logic และการจัดการข้อผิดพลาดได้อย่างเต็มที่ เหมาะสำหรับโปรเจกต์ขนาดเล็กที่มี model เพียงไม่กี่ตัว หรือเมื่อต้องการ deserialization แบบกำหนดเองที่ตัวสร้างโค้ดไม่สามารถแสดงออกได้ นี่คือรูปแบบเอาต์พุตที่เครื่องมือนี้สร้าง
json_serializable
ใส่ annotation @JsonSerializable() ให้ class และรัน dart run build_runner build ตัวสร้างจะสร้างไฟล์ .g.dart พร้อมฟังก์ชัน _$ClassFromJson และ _$ClassToJson รองรับ @JsonKey สำหรับการเปลี่ยนชื่อฟิลด์ ค่าเริ่มต้น และ custom converter ตัวเลือกมาตรฐานสำหรับโปรเจกต์ Flutter ขนาดกลางถึงใหญ่
freezed
ใส่ annotation @freezed แล้วรับ class ที่ไม่เปลี่ยนแปลงพร้อม fromJson, toJson, copyWith, equality (== และ hashCode) และ toString ที่สร้างโดยอัตโนมัติ รองรับ union type สำหรับ sealed class pattern ต้องการทั้ง freezed และ json_serializable เป็น dev dependency เหมาะสำหรับ state management model และ domain object ที่ซับซ้อน

ตัวอย่างโค้ด

ตัวอย่างเหล่านี้แสดงวิธีใช้ Dart class ที่สร้างขึ้นสำหรับ JSON deserialization วิธีตั้งค่า json_serializable กับ build_runner และวิธีสร้าง Dart class โดยโปรแกรมจาก JavaScript และ 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;
# }

คำถามที่พบบ่อย

ทำไม Flutter จึงไม่สามารถใช้ dart:mirrors สำหรับ JSON deserialization?
Flutter ปิดใช้งาน dart:mirrors (ไลบรารี reflection) เพราะการ tree-shaking ต้องรู้ล่วงหน้าในช่วง compile time ว่าโค้ดใดถูกใช้งาน Reflection จะป้องกันไม่ให้ compiler ลบ class ที่ไม่ใช้ออก ทำให้ขนาดแอปเพิ่มขึ้น นี่คือเหตุผลที่โปรเจกต์ Flutter ต้องการเมธอด fromJson/toJson อย่างชัดเจน หรือการสร้างโค้ด แทนที่จะใช้ runtime reflection
ควรใช้ json_serializable หรือเขียน fromJson ด้วยมือ?
สำหรับโปรเจกต์ที่มี model class น้อยกว่า 10 ตัว การเขียน fromJson/toJson ด้วยมือเป็นเรื่องตรงไปตรงมาและไม่เพิ่ม dependency ในการ build เมื่อมี model มากกว่า 10 ตัว หรือ model ที่เปลี่ยนแปลงบ่อย json_serializable จะประหยัดเวลาและลดข้อผิดพลาดจากการคัดลอกโค้ด มันสร้างโค้ด mapping จาก annotation ดังนั้นการเพิ่มฟิลด์จะต้องรัน build_runner ใหม่เท่านั้น
ตัวแปลงจัดการอ็อบเจ็กต์ JSON ที่ซ้อนกันอย่างไร?
อ็อบเจ็กต์ JSON ที่ซ้อนกันแต่ละรายการจะกลายเป็น Dart class แยกต่างหาก หากฟิลด์ JSON ชื่อ "address" มีอ็อบเจ็กต์ที่มี key "street" และ "city" ตัวแปลงจะสร้าง Address class พร้อมฟิลด์เหล่านั้นและกำหนดประเภทฟิลด์หลักเป็น Address โดย fromJson factory จะ cast nested map และส่งต่อไปยัง Address.fromJson
จะเกิดอะไรขึ้นเมื่อฟิลด์ JSON เป็น null?
ฟิลด์ null จะถูกกำหนดประเภทเป็น dynamic พร้อม suffix ? เพราะตัวแปลงไม่สามารถระบุประเภทที่ตั้งใจจากค่า null เพียงอย่างเดียวได้ แทนที่ dynamic? ด้วยประเภทที่ถูกต้อง (String?, int? เป็นต้น) เมื่อคุณทราบจาก API contract ของคุณ null safety ของ Dart จะบังคับใช้การตรวจสอบ null ที่ถูกต้องในช่วง compile time
จะจัดการ JSON key ที่ไม่ใช่ Dart identifier ที่ถูกต้องได้อย่างไร?
JSON key อย่าง "first-name" หรือ "2nd_place" ไม่ใช่ชื่อฟิลด์ Dart ที่ถูกต้อง ด้วย fromJson แบบ manual คุณสามารถ map JSON key ใดก็ได้กับชื่อฟิลด์ Dart ใดก็ได้ใน factory constructor ด้วย json_serializable ใช้ @JsonKey(name: 'first-name') เพื่อ annotate ฟิลด์ ตัวแปลงจะแปลงชื่อ key โดยอัตโนมัติเป็นฟิลด์ Dart แบบ camelCase
สามารถใช้ freezed กับเอาต์พุตจากเครื่องมือนี้ได้ไหม?
เครื่องมือนี้สร้าง Dart class มาตรฐานพร้อม fromJson/toJson แบบ manual หากต้องการแปลงเป็น freezed ให้เปลี่ยน class ให้ใช้ annotation @freezed ลบ constructor และ factory ที่เขียนด้วยมือออก แล้วเพิ่ม freezed mixin จากนั้นรัน build_runner เพื่อสร้างไฟล์ .freezed.dart และ .g.dart ชื่อฟิลด์และประเภทจากเอาต์พุตที่สร้างขึ้นจะถ่ายโอนโดยตรง
ความแตกต่างระหว่าง raw map กับ Dart class ที่มีประเภทสำหรับ JSON คืออะไร?
raw map (Map<String, dynamic>) ให้คุณเข้าถึงข้อมูล JSON ดิบโดยไม่มีการตรวจสอบประเภท คุณต้อง cast ทุกค่าที่ call site และการพิมพ์ชื่อ key ผิดจะล้มเหลวอย่างเงียบๆ ใน runtime Dart class ที่มีประเภทจะตรวจจับความไม่ตรงกันของประเภทที่ fromJson boundary ให้ IDE autocompletion สำหรับชื่อฟิลด์ และทำให้การ refactor ปลอดภัย ความแตกต่างด้านประสิทธิภาพนั้นเล็กน้อย ประโยชน์อยู่ที่ความถูกต้องและความสามารถในการบำรุงรักษาทั้งหมด