ข้ามไปยังเนื้อหา

JSON และ Typed Models

ระบบ structural type ของ TypeScript ช่วยให้คุณนิยาม interface และใช้ผลลัพธ์จาก JSON.parse เป็น type นั้นได้ทันที — compiler เชื่อ shape ที่คุณประกาศ Dart ใช้ nominal typing และไม่มี runtime structural casting คุณต้องเขียน fromJson factory constructor ที่อ่านแต่ละ field จาก decoded map โดยตรง โค้ดมากกว่า แต่ explicit มากกว่าด้วย: ถ้า field ขาดหายหรือ type ไม่ตรง คุณจะรู้ทันที ไม่ใช่ตอน call site

React
// TypeScript — interface + JSON.parse
interface User {
id: number;
name: string;
email: string;
}
// TypeScript trusts the shape (no runtime check)
const user = JSON.parse(raw) as User;
console.log(user.name);
// With Zod for runtime validation
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
const safeUser = UserSchema.parse(JSON.parse(raw));
Flutter
// Dart — class with fromJson / toJson
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
// Explicit field mapping — no structural magic
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
};
}
// Usage
final map = jsonDecode(raw) as Map<String, dynamic>;
final user = User.fromJson(map);
print(user.name);
import 'dart:convert';
import 'package:flutter/material.dart';

class User {
  final int id;
  final String name;
  final String email;

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

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

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'JSON Models',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const UserModelScreen(),
    );
  }
}

class UserModelScreen extends StatelessWidget {
  const UserModelScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // Hardcoded JSON string — no network needed
    const rawJson = '{"id": 42, "name": "Ada Lovelace", "email": "[email protected]"}';
    final map = jsonDecode(rawJson) as Map<String, dynamic>;
    final user = User.fromJson(map);

    return Scaffold(
      appBar: AppBar(
        title: const Text('JSON Model Demo'),
        backgroundColor: Colors.teal,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Card(
          margin: const EdgeInsets.all(24),
          elevation: 4,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    CircleAvatar(
                      backgroundColor: Colors.teal,
                      child: Text(
                        user.name[0],
                        style: const TextStyle(color: Colors.white),
                      ),
                    ),
                    const SizedBox(width: 12),
                    Text(
                      user.name,
                      style: const TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
                const Divider(height: 28),
                _Field(label: 'ID', value: '${user.id}'),
                const SizedBox(height: 8),
                _Field(label: 'Name', value: user.name),
                const SizedBox(height: 8),
                _Field(label: 'Email', value: user.email),
                const Divider(height: 28),
                Text(
                  'toJson(): ${user.toJson()}',
                  style: const TextStyle(
                    fontSize: 12,
                    color: Colors.grey,
                    fontFamily: 'monospace',
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _Field extends StatelessWidget {
  final String label;
  final String value;
  const _Field({required this.label, required this.value});

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(
          width: 60,
          child: Text(
            '$label:',
            style: const TextStyle(
              fontWeight: FontWeight.w600,
              color: Colors.teal,
            ),
          ),
        ),
        Expanded(child: Text(value)),
      ],
    );
  }
}
ทำไม Dart ถึงต้องมี fromJson factory constructor ส่วน TypeScript ไม่ต้องมี?
ค่าที่ส่งออกมาจาก User.fromJson() คืออะไร?
เครื่องมือ codegen ใดช่วยสร้าง fromJson/toJson อัตโนมัติใน Dart?