حوِّل JSON إلى أصناف Dart غير قابلة للتغيير باستخدام fromJson وtoJson وcopyWith، مع مساواة قيمية موثوقة.
افتح هذا الدرس في Kodokonفي هذا المستوى، لم يعد الموضوع الحقيقي هو صياغة الأصناف بل الحدّ الفاصل بين العالم الخارجي وكودك. فما دامت استجابة واجهة برمجية (API) تتنقّل على هيئة Map<String, dynamic>، فإن قراءة كل حقل مجازفة أثناء التشغيل. القاعدة الاحترافية: حوِّل JSON إلى كائنات ذات أنواع في اللحظة التي تستقبلها فيها، ثم مرِّر تلك الكائنات وحدها. اجمع هذا مع عدم القابلية للتغيير - حقول final وباني const - فتقضي على عائلة كاملة من العلل: لم يعد بإمكان أحد أن يعدّل كائنًا مشتركًا من وراء ظهرك، كما أن نسختين const متطابقتين يوحّدهما المُصرِّف.
class User {
const User({
required this.id,
required this.name,
this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
email: json['email'] as String?,
);
}
final int id;
final String name;
final String? email;
}ثلاثة خيارات تستحق التوضيح. أولًا، (json['id'] as num).toInt(): تُسلسِل كثير من واجهات البرمجة عددًا صحيحًا تارةً على شكل 3 وتارةً على شكل 3.0 حسب الخادم الخلفي؛ والتحويل المباشر إلى int ينهار عند مصادفة double. ثانيًا، يفشل as String فورًا مع TypeError واضح إذا كان الحقل مفقودًا: وهذا مقصود. فالفشل السريع عند التحليل خير من null ينتشر ثم ينفجر بعد ثلاث شاشات. وأخيرًا، يوثّق as String? في النوع أن الحقل اختياري فعلًا.
class User {
const User({required this.id, this.email});
final int id;
final String? email;
Map<String, dynamic> toJson() => {
'id': id,
'email': email,
};
User copyWith({int? id, String? email}) {
return User(
id: id ?? this.id,
email: email ?? this.email,
);
}
}يستدعي عدم القابلية للتغيير مساواةً قيمية. فبشكل افتراضي، تقارن Dart المراجع: إذ يُعدّ كائنا User متطابقان حقلًا بحقل "مختلفين". وهذه مشكلة ملموسة بمجرد أن تقارن الحالات - في الاختبارات، وفي select الخاص بـ Provider، وفي البحث داخل قائمة. أعِد تعريف == وhashCode معًا، دائمًا.
class Point {
const Point(this.x, this.y);
final int x;
final int y;
@override
bool operator ==(Object other) =>
other is Point && other.x == x && other.y == y;
@override
int get hashCode => Object.hash(x, y);
}(json['id'] as num).toInt() بدلًا من json['id'] as int؟as int أبطأ من as num أثناء التشغيل.3.0: فالتحويل المباشر إلى int سيفشل عند مصادفة double.json['id'] سلسلة نصية دائمًا.user.copyWith(email: null) مع التنفيذ التقليدي لـ copyWith؟email إلى null كما هو مطلوب.email الحالية: إذ يرتد null ?? this.email إلى القيمة الموجودة.== وhashCode في نموذج غير قابل للتغيير؟const.final.