ToolDeck

JSON به Dart

تولید کلاس‌های Dart از JSON با fromJson و toJson

یک مثال امتحان کنید
نام کلاس اصلی:

ورودی JSON

خروجی Dart

به‌صورت محلی اجرا می‌شود · جای‌گذاری اسرار امن است
کلاس‌های Dart اینجا نمایش داده می‌شوند…

تبدیل JSON به Dart چیست؟

تبدیل JSON به Dart یک شیء JSON خام را دریافت کرده و تعاریف کلاس Dart با فیلدهای نوع‌دار، یک constructor با نام، یک factory به نام fromJson، و یک متد toJson تولید می‌کند. Dart در Flutter از reflection زمان اجرا پشتیبانی نمی‌کند (dart:mirrors غیرفعال است)، بنابراین بدون نوشتن کد نگاشت صریح نمی‌توانید JSON را به اشیاء نوع‌دار تبدیل کنید. هر پاسخ REST API، سند Firebase، یا پیلود پیکربندی به یک کلاس مدل Dart متناظر نیاز دارد تا بتوانید با ایمنی نوع به فیلدهایش دسترسی داشته باشید.

یک کلاس مدل Dart معمولی برای JSON، فیلدهای final برای هر کلید، یک constructor با پارامترهای named (با کلیدواژه required برای فیلدهای غیر nullable)، یک factory constructor به نام fromJson که از یک Map از String به dynamic می‌خواند، و یک متد toJson که یک Map از String به dynamic برمی‌گرداند تعریف می‌کند. اشیاء JSON تودرتو به کلاس‌های جداگانه تبدیل می‌شوند. آرایه‌ها به فیلدهای List نوع‌دار تبدیل می‌شوند. مقادیر nullable در JSON از سینتکس null-safety Dart با پسوند ? روی نوع استفاده می‌کنند.

نوشتن این کلاس‌های مدل به‌صورت دستی به معنای خواندن هر کلید JSON، تصمیم‌گیری درباره نوع Dart، ایجاد cast fromJson برای هر فیلد (شامل نگاشت لیست با .map().toList())، ساختن map literal برای toJson، و تکرار این فرایند برای هر شیء تودرتو است. برای یک شیء JSON با ۱۲ فیلد و ۲ شیء تودرتو، این یعنی ۳ کلاس، ۶ خط factory، و ده‌ها عبارت cast. یک مبدل همه این‌ها را در میلی‌ثانیه از یک پیست ساده تولید می‌کند.

چرا از مبدل JSON به Dart استفاده کنیم؟

نوشتن دستی کلاس‌های مدل Dart از JSON به معنای خواندن نام فیلدها، حدس زدن نوع از مقادیر نمونه، نوشتن cast های fromJson با مدیریت صحیح null، و تکرار فرایند برای اشیاء تودرتو است. وقتی ساختار API تغییر می‌کند، هر به‌روزرسانی فیلد، constructor، fromJson و toJson را لمس می‌کند. یک مبدل این کار تکراری را حذف می‌کند.

تولید فوری کلاس
JSON را پیست کنید و کلاس‌های کامل Dart با constructor، factory های fromJson، و متدهای toJson را در کمتر از یک ثانیه دریافت کنید. اشیاء تودرتو و لیست‌ها به‌صورت خودکار تشخیص داده و نگاشت می‌شوند.
🔒
پردازش با اولویت حریم خصوصی
تبدیل کاملاً در مرورگر شما با استفاده از JavaScript انجام می‌شود. داده JSON شما هرگز دستگاهتان را ترک نمی‌کند. کلیدهای API، توکن‌ها و پیلودهای تولید خصوصی می‌مانند.
📝
خروجی null-safe
کلاس‌های تولیدشده از null safety صدادار Dart استفاده می‌کنند. فیلدهای nullable JSON پسوند ? روی نوع دریافت می‌کنند و کلیدواژه required را در constructor ندارند. فیلدهای غیر null با required علامت‌گذاری می‌شوند.
📦
بدون نصب یا ثبت‌نام
صفحه را باز کنید و JSON را پیست کنید. نیازی به Dart SDK، وابستگی‌های pub، یا حساب کاربری نیست. روی هر دستگاهی با مرورگر کار می‌کند.

موارد استفاده JSON به Dart

توسعه اپلیکیشن Flutter
کلاس‌های مدل برای پاسخ‌های REST API مصرف‌شده توسط پکیج‌های http یا dio تولید کنید. JSON برگشتی از backend را پیست کنید و کلاس‌های Dart آماده برای jsonDecode و رندر widget دریافت کنید.
یکپارچه‌سازی Firebase / Firestore
کلاس‌های مدل نوع‌دار برای snapshot های سند Firestore بسازید. یک سند نمونه را به‌صورت JSON پیست کنید، کلاس Dart را تولید کنید، و از fromJson با snapshot.data() در لایه repository استفاده کنید.
مدل‌های مدیریت State
اشیاء state نوع‌دار برای Riverpod، Bloc یا Provider تعریف کنید. شکل state مورد انتظار را به‌صورت JSON پیست کنید و کلاس‌های Dart immutable با toJson برای ذخیره‌سازی و hydration state دریافت کنید.
تولید کد کلاینت API
مدل‌های درخواست و پاسخ برای کلاینت‌های API Retrofit یا Chopper بسازید. نمونه پیلودهای JSON را پیست کنید و کلاس‌های Dart منطبق با طرح‌های پاسخ OpenAPI دریافت کنید.
تست خودکار
fixture های typed از پاسخ‌های API واقعی بسازید. یک نمونه JSON را پیست کنید، کلاس مدل را تولید کنید، و مستقیماً در تست‌های unit و widget بدون نوشتن دستی ساختارهای fixture از آن استفاده کنید.
یادگیری الگوهای Dart
دانشجویان در حال یادگیری Flutter می‌توانند هر ساختار JSON را پیست کنند و ببینند Dart چگونه آن را نمایش می‌دهد: فیلدهای final، constructor های named، الگوهای factory fromJson، و annotation های null-safety در عمل.

جدول نگاشت نوع JSON به Dart

هر مقدار JSON به یک نوع Dart خاص نگاشت می‌شود. جدول زیر نشان می‌دهد چگونه مبدل هر نوع JSON را ترجمه می‌کند. ستون Alternative نوع‌هایی را نشان می‌دهد که در سناریوهای نگاشت دستی یا کمتر رایج استفاده می‌شوند.

نوع 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 در Dart

Dart و Flutter روش‌های متعددی برای مدیریت سریال‌سازی JSON ارائه می‌دهند. fromJson/toJson دستی ساده‌ترین رویکرد است و نیازی به تولید کد ندارد. برای پروژه‌های بزرگ‌تر، راه‌حل‌های مبتنی بر build_runner مانند json_serializable و freezed، boilerplate را در زمان کامپایل تولید می‌کنند و خطاها را کاهش می‌دهند.

رویکردتوضیحمنبع
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

دستی در مقابل json_serializable در مقابل freezed

Dart سه رویکرد رایج برای کلاس‌های مدل JSON دارد. fromJson/toJson دستی هیچ وابستگی‌ای ندارد. json_serializable کد نگاشت را خودکار می‌کند. freezed immutability، copyWith و pattern matching را روی json_serializable اضافه می‌کند.

Manual fromJson/toJson
factory های fromJson و متدهای toJson را به‌صورت دستی بنویسید. هیچ وابستگی‌ای ندارد، نیازی به مرحله build_runner نیست. کنترل کامل بر منطق casting و مدیریت خطا دارید. بهترین گزینه برای پروژه‌های کوچک با تعداد کمی مدل، یا زمانی که به deserialization سفارشی نیاز دارید که تولیدکننده‌های کد نمی‌توانند بیان کنند. این فرمت خروجی این ابزار است.
json_serializable
کلاس خود را با @JsonSerializable() annotate کرده و dart run build_runner build را اجرا کنید. تولیدکننده یک فایل .g.dart با توابع _$ClassFromJson و _$ClassToJson می‌سازد. @JsonKey را برای تغییر نام فیلد، مقادیر پیش‌فرض و مبدل‌های سفارشی مدیریت می‌کند. انتخاب استاندارد برای پروژه‌های متوسط تا بزرگ Flutter.
freezed
با @freezed annotate کرده و کلاس‌های immutable با fromJson، toJson، copyWith، برابری (== و hashCode) و toString به‌صورت خودکار تولید کنید. از union type ها برای الگوهای sealed class پشتیبانی می‌کند. به هر دو freezed و json_serializable به‌عنوان dev dependency نیاز دارد. بهترین گزینه برای مدل‌های مدیریت state و اشیاء دامنه پیچیده.

نمونه کد

این مثال‌ها نشان می‌دهند چگونه از کلاس‌های Dart تولیدشده برای deserialization JSON استفاده کنید، چگونه json_serializable را با build_runner راه‌اندازی کنید، و چگونه کلاس‌های Dart را به‌صورت برنامه‌نویسی از 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 برای deserialization JSON استفاده کند؟
Flutter به این دلیل dart:mirrors (کتابخانه reflection) را غیرفعال می‌کند که tree-shaking نیاز دارد در زمان کامپایل بداند چه کدی استفاده می‌شود. Reflection از حذف کلاس‌های استفاده‌نشده توسط کامپایلر جلوگیری می‌کند و اندازه اپلیکیشن را افزایش می‌دهد. به همین دلیل پروژه‌های Flutter به متدهای fromJson/toJson صریح یا تولید کد نیاز دارند نه reflection زمان اجرا.
آیا از json_serializable استفاده کنم یا fromJson را به‌صورت دستی بنویسم؟
برای پروژه‌هایی با کمتر از ۱۰ کلاس مدل، fromJson/toJson دستی ساده است و هیچ وابستگی build اضافه‌ای نمی‌کند. وقتی بیش از ۱۰ مدل دارید، یا مدل‌هایی که مرتباً تغییر می‌کنند، json_serializable زمان را صرفه‌جویی می‌کند و خطاهای copy-paste را کاهش می‌دهد. کد نگاشت را از annotation ها تولید می‌کند، بنابراین اضافه کردن فیلد فقط نیاز به اجرای مجدد build_runner دارد.
مبدل چگونه اشیاء JSON تودرتو را مدیریت می‌کند؟
هر شیء JSON تودرتو به یک کلاس Dart جداگانه تبدیل می‌شود. اگر یک فیلد JSON به نام «address» شامل شیئی با کلیدهای «street» و «city» باشد، مبدل یک کلاس Address با آن فیلدها ایجاد می‌کند و فیلد والد را به‌عنوان نوع Address تعریف می‌کند. factory fromJson نگاشت تودرتو را cast می‌کند و آن را به Address.fromJson می‌دهد.
وقتی یک فیلد JSON مقدار null دارد چه اتفاقی می‌افتد؟
فیلدهای null با پسوند ? به‌عنوان dynamic? نوع‌گذاری می‌شوند زیرا مبدل نمی‌تواند نوع مورد نظر را تنها از یک مقدار null تعیین کند. وقتی نوع را از قرارداد API خود می‌دانید، dynamic? را با نوع صحیح (String?، int? و غیره) جایگزین کنید. null safety Dart سپس بررسی‌های null صحیح را در زمان کامپایل اعمال خواهد کرد.
چگونه کلیدهای JSON که شناسه معتبر Dart نیستند را مدیریت کنم؟
کلیدهای JSON مانند «first-name» یا «2nd_place» نام فیلد معتبر Dart نیستند. با fromJson دستی می‌توانید هر کلید JSON را به هر نام فیلد Dart در factory constructor نگاشت کنید. با json_serializable، از @JsonKey(name: 'first-name') برای annotate کردن فیلد استفاده کنید. مبدل به‌طور خودکار نام کلیدها را به فیلدهای camelCase Dart تبدیل می‌کند.
آیا می‌توانم از freezed با خروجی این ابزار استفاده کنم؟
ابزار کلاس‌های استاندارد Dart با fromJson/toJson دستی تولید می‌کند. برای تبدیل به freezed، کلاس را به استفاده از annotation @freezed تغییر دهید، constructor و factory دست‌نوشته را حذف کنید، و mixin مربوط به freezed را اضافه کنید. سپس build_runner را اجرا کنید تا فایل‌های .freezed.dart و .g.dart تولید شوند. نام فیلدها و نوع‌های خروجی تولیدشده مستقیماً قابل انتقال هستند.
تفاوت بین یک raw map و یک کلاس Dart نوع‌دار برای JSON چیست؟
یک raw map (Map از String به dynamic) دسترسی خام به داده JSON را بدون بررسی نوع می‌دهد. باید هر مقدار را هر جا که استفاده می‌کنید cast کنید، و غلط‌نویسی در نام کلیدها در زمان اجرا بدون هشدار شکست می‌خورد. یک کلاس Dart نوع‌دار عدم تطابق نوع را در مرز fromJson تشخیص می‌دهد، تکمیل خودکار IDE برای نام فیلدها فراهم می‌کند، و refactoring را ایمن می‌کند. تفاوت عملکرد ناچیز است؛ مزیت کاملاً درباره صحت و قابلیت نگهداری است.