JSON به Dart
تولید کلاسهای Dart از JSON با fromJson و toJson
ورودی JSON
خروجی 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
جدول نگاشت نوع JSON به Dart
هر مقدار JSON به یک نوع Dart خاص نگاشت میشود. جدول زیر نشان میدهد چگونه مبدل هر نوع JSON را ترجمه میکند. ستون Alternative نوعهایی را نشان میدهد که در سناریوهای نگاشت دستی یا کمتر رایج استفاده میشوند.
| نوع 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 در Dart
Dart و Flutter روشهای متعددی برای مدیریت سریالسازی JSON ارائه میدهند. fromJson/toJson دستی سادهترین رویکرد است و نیازی به تولید کد ندارد. برای پروژههای بزرگتر، راهحلهای مبتنی بر build_runner مانند json_serializable و freezed، boilerplate را در زمان کامپایل تولید میکنند و خطاها را کاهش میدهند.
| رویکرد | توضیح | منبع |
|---|---|---|
| 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 |
دستی در مقابل json_serializable در مقابل freezed
Dart سه رویکرد رایج برای کلاسهای مدل JSON دارد. fromJson/toJson دستی هیچ وابستگیای ندارد. json_serializable کد نگاشت را خودکار میکند. freezed immutability، copyWith و pattern matching را روی json_serializable اضافه میکند.
نمونه کد
این مثالها نشان میدهند چگونه از کلاسهای Dart تولیدشده برای deserialization JSON استفاده کنید، چگونه json_serializable را با build_runner راهاندازی کنید، و چگونه کلاسهای Dart را بهصورت برنامهنویسی از 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;
# }