تسلسل JSON اليدوي مع فئات النماذج
تسلسل JSON اليدوي مع فئات النماذج
عندما يجلب تطبيق Flutter بيانات من واجهة برمجة تطبيقات REST، تصل الاستجابة بصيغة JSON خام — وهي في جوهرها Map<String, dynamic> في Dart. الوصول إلى القيم بنمط data['user']['email'] في كل أنحاء الكود هش وصعب إعادة الهيكلة ولا يوفر أي أمان في وقت الترجمة. فئات النماذج مع دوال fromJson وtoJson تحل هذه المشكلة بمنحك كائنات مكتوبة ومسماة تمثل موارد API.
لماذا تهم فئات النماذج المكتوبة
الوصول إلى الخرائط الخام يعاني من عدة عيوب خطيرة في كود الإنتاج:
- لا أمان في الأنواع:
data['age']قد يُرجعintأوStringأوnull— ولا يستطيع المترجم تحذيرك. - أخطاء إملائية في المفاتيح: مفتاح مهجوء خطأً مثل
data['emial']يُرجعnullبصمت عوضاً عن إطلاق خطأ وقت الترجمة. - لا إكمال تلقائي في بيئة التطوير: لا تستطيع بيئة التطوير اقتراح أسماء الحقول على
Mapعادية. - منطق تحليل منتشر: يتكرر تحليل JSON عبر الودجات والمستودعات والخدمات.
تقوم فئة النموذج بمركزة كل منطق تحليل JSON في مكان واحد، وتسمح لنظام الأنواع بالتحقق من كودك، وتجعل إعادة الهيكلة أمراً بسيطاً.
تشريح فئة نموذج Dart
تحتوي فئة النموذج المنظمة جيداً على ثلاثة أشياء: حقول نهائية، ومُنشئ factory مسمى fromJson يحلل الخريطة، ودالة toJson تُسلسل الكائن مرة أخرى إلى خريطة.
نموذج أساسي: User
class User {
final int id;
final String name;
final String email;
final String? avatarUrl; // قابل للقيمة الفارغة — قد يغيب من JSON
const User({
required this.id,
required this.name,
required this.email,
this.avatarUrl,
});
/// يحلل [User] من خريطة JSON مُرجعة من API.
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
avatarUrl: json['avatar_url'] as String?,
);
}
/// يحول هذا [User] إلى خريطة JSON (مفيد لطلبات POST/PUT).
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
if (avatarUrl != null) 'avatar_url': avatarUrl,
};
}
}
factory تشير إلى أن المُنشئ قد يُرجع نسخة مخزنة مؤقتاً أو يفوّض لفئة فرعية — لكن في تحليل النماذج هو ببساطة الاسم التقليدي لمُنشئ مسمى يبني الكائن من بيانات خارجية. يمكنك أيضاً استخدام مُنشئ عادي مسمى؛ factory هو النمط الاصطلاحي لتحليل JSON.معالجة الكائنات المتداخلة والقوائم
نادراً ما تُرجع APIs الحقيقية JSON مسطحاً. ستواجه في كثير من الأحيان كائنات متداخلة ومصفوفات. كل نوع متداخل يحصل على فئة نموذج خاصة به، وfromJson يفوّض إلى تلك الفئات.
نموذج متداخل: Post مع Author وتعليقات
class Author {
final int id;
final String username;
const Author({required this.id, required this.username});
factory Author.fromJson(Map<String, dynamic> json) => Author(
id: json['id'] as int,
username: json['username'] as String,
);
Map<String, dynamic> toJson() => {'id': id, 'username': username};
}
class Post {
final int id;
final String title;
final String body;
final Author author;
final List<String> tags;
final DateTime publishedAt;
const Post({
required this.id,
required this.title,
required this.body,
required this.author,
required this.tags,
required this.publishedAt,
});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'] as int,
title: json['title'] as String,
body: json['body'] as String,
// تفويض تحليل الكائن المتداخل إلى Author.fromJson
author: Author.fromJson(json['author'] as Map<String, dynamic>),
// تحليل مصفوفة JSON من سلاسل نصية إلى List<String>
tags: List<String>.from(json['tags'] as List),
// تحليل سلسلة تاريخ ISO 8601 إلى DateTime
publishedAt: DateTime.parse(json['published_at'] as String),
);
}
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'body': body,
'author': author.toJson(),
'tags': tags,
'published_at': publishedAt.toIso8601String(),
};
}
تحليل استجابات API عملياً
بعد جلب JSON باستخدام http أو dio، تفكّك جسم الاستجابة بـjsonDecode، ثم تمرر الخريطة الناتجة إلى مصنع fromJson في نموذجك.
jsonDecode إلى النوع الدقيق الذي تتوقعه (Map<String, dynamic> للكائنات، List<dynamic> للمصفوفات). التحويل المفقود يسبب TypeError في وقت التشغيل يصعب تشخيصه مقارنةً بخطأ واضح من نموذجك.التحليل الدفاعي: معالجة الحقول المفقودة والفارغة
لا تكون APIs دائماً منضبطة. قد تكون الحقول غائبة أو null بشكل غير متوقع أو تصل بنوع خاطئ. استخدم هذه الاستراتيجيات لكتابة مصانع fromJson قوية:
- الحقول القابلة للقيمة الفارغة: أعلن الحقل كـ
String?وحوّله بـjson['key'] as String?. - القيم الافتراضية: استخدم عامل دمج الفارغ:
(json['score'] as int?) ?? 0. - حقول القوائم المفقودة:
(json['tags'] as List?)?.map((e) => e as String).toList() ?? []. - تحويل الأنواع: إذا كانت API تُرجع أحياناً
intوأحياناًdoubleلحقل رقمي، استخدم(json['price'] as num).toDouble().
نمط copyWith
فئات النماذج في Flutter عادةً غير قابلة للتغيير. لإنشاء نسخة معدّلة من كائن (شائع في إدارة الحالة)، أضف دالة copyWith تُرجع نسخة جديدة مع تجاوز حقول مختارة.
final واستخدم copyWith لإنتاج نسخ محدّثة.ملخص
يمنح تسلسل JSON اليدوي مع fromJson وtoJson تطبيق Flutter طبقة بيانات آمنة الأنواع دون أي توليد للكود. تمركز كل منطق التحليل في فئات النماذج، وتكسب الإكمال التلقائي في بيئة التطوير والفحص بالمترجم، وتجعل كود المستودعات وواجهة المستخدم أكثر نظافة وصيانة. مع نمو مشروعك، يمكنك الانتقال إلى أدوات توليد الكود كـjson_serializable — لكن النمط اليدوي يبقى أساسياً لفهم ما تنتجه تلك الأدوات.