أطلق العنان لتسلسل JSON المتقدم. تعلم كيفية التعامل مع أنواع البيانات المعقدة والكائنات المخصصة وتنسيقات البيانات العالمية باستخدام مشفرات مخصصة، مما يضمن تبادلاً قوياً للبيانات عبر أنظمة متنوعة.
مُشفرات JSON المخصصة: إتقان تسلسل الكائنات المعقدة للتطبيقات العالمية
في عالم تطوير البرمجيات الحديث المترابط، يقف JSON (JavaScript Object Notation) كاللغة المشتركة لتبادل البيانات. من واجهات برمجة تطبيقات الويب وتطبيقات الهاتف المحمول إلى الخدمات المصغرة وأجهزة إنترنت الأشياء، أصبح تنسيق JSON الخفيف وسهل القراءة لا غنى عنه. ومع ذلك، مع نمو التطبيقات في التعقيد وتكاملها مع أنظمة عالمية متنوعة، يواجه المطورون غالبًا تحديًا كبيرًا: كيفية تسلسل أنواع البيانات المعقدة أو المخصصة أو غير القياسية بشكل موثوق إلى JSON، وعلى العكس من ذلك، إلغاء تسلسلها مرة أخرى إلى كائنات ذات معنى.
بينما تعمل آليات تسلسل JSON الافتراضية بشكل لا تشوبه شائبة لأنواع البيانات الأساسية (السلاسل النصية، الأرقام، القيم المنطقية، القوائم، والقواميس)، إلا أنها غالبًا ما تكون قاصرة عند التعامل مع هياكل أكثر تعقيدًا مثل مثيلات الفئات المخصصة، كائنات `datetime`، أرقام `Decimal` التي تتطلب دقة عالية، `UUID`s، أو حتى التعدادات المخصصة. هذا هو المكان الذي تصبح فيه مُشفرات JSON المخصصة ليست مجرد مفيدة، بل ضرورية للغاية.
يتعمق هذا الدليل الشامل في عالم مُشفرات JSON المخصصة، ويزودك بالمعرفة والأدوات اللازمة للتغلب على تحديات التسلسل هذه. سنستكشف "لماذا" تكمن ضرورتها، و"كيف" يتم تنفيذها، والتقنيات المتقدمة، وأفضل الممارسات للتطبيقات العالمية، وحالات الاستخدام في العالم الحقيقي. بنهاية المطاف، ستكون مجهزًا لتسلسل أي كائن معقد تقريبًا إلى تنسيق JSON موحد، مما يضمن قابلية التشغيل البيني السلس للبيانات عبر نظامك البيئي العالمي.
فهم أساسيات تسلسل JSON
قبل الخوض في المُشفرات المخصصة، دعنا نراجع بإيجاز أساسيات تسلسل JSON.
ما هو التسلسل (Serialization)؟
التسلسل هو عملية تحويل كائن أو بنية بيانات إلى تنسيق يمكن تخزينه ونقله وإعادة بنائه بسهولة لاحقًا. إلغاء التسلسل هو العملية العكسية: تحويل هذا التنسيق المخزن أو المنقول مرة أخرى إلى الكائن أو بنية البيانات الأصلية. بالنسبة لتطبيقات الويب، يعني هذا غالبًا تحويل كائنات لغة البرمجة الموجودة في الذاكرة إلى تنسيق يعتمد على السلسلة النصية مثل JSON أو XML للنقل عبر الشبكة.
سلوك تسلسل JSON الافتراضي
توفر معظم لغات البرمجة مكتبات JSON مدمجة تتعامل مع تسلسل الأنواع الأولية والمجموعات القياسية بسهولة. على سبيل المثال، يمكن تحويل قاموس (أو خريطة تجزئة/كائن في لغات أخرى) يحتوي على سلاسل نصية، أعداد صحيحة، أعداد عشرية، قيم منطقية، وقوائم أو قواميس متداخلة إلى JSON مباشرةً. لننظر إلى مثال بسيط بلغة بايثون:
import json
data = {
"name": "Alice",
"age": 30,
"is_student": False,
"courses": ["Math", "Science"],
"address": {"city": "New York", "zip": "10001"}
}
json_output = json.dumps(data, indent=4)
print(json_output)
سينتج عن هذا JSON صالح تمامًا:
{
"name": "Alice",
"age": 30,
"is_student": false,
"courses": [
"Math",
"Science"
],
"address": {
"city": "New York",
"zip": "10001"
}
}
القيود المفروضة على أنواع البيانات المخصصة وغير القياسية
تختفي بساطة التسلسل الافتراضي بسرعة عندما تقوم بإدخال أنواع بيانات أكثر تطوراً وهي أساسية للبرمجة الحديثة الموجهة للكائنات. لغات مثل بايثون، جافا، سي شارب، جو، وسويفت كلها تحتوي على أنظمة أنواع غنية تمتد أبعد بكثير من الأنواع البدائية الأصلية في JSON. وتشمل هذه:
- مثيلات الفئات المخصصة: كائنات الفئات التي قمت بتعريفها (على سبيل المثال،
User
،Product
،Order
). - كائنات
datetime
: تمثل التواريخ والأوقات، وغالبًا ما تتضمن معلومات المنطقة الزمنية. - أرقام
Decimal
أو عالية الدقة: حاسمة للحسابات المالية حيث تكون الأخطاء الناتجة عن الأعداد العشرية غير مقبولة. UUID
(معرفات فريدة عالميًا): تستخدم بشكل شائع للمعرفات الفريدة في الأنظمة الموزعة.- كائنات
Set
: مجموعات غير مرتبة من العناصر الفريدة. - التعدادات (Enums): ثوابت مُسمّاة تمثل مجموعة ثابتة من القيم.
- الكائنات الجغرافية المكانية: مثل النقاط، الخطوط، أو المضلعات.
- أنواع معقدة خاصة بقاعدة البيانات: كائنات مُدارة بواسطة ORM أو أنواع حقول مخصصة.
ستؤدي محاولة تسلسل هذه الأنواع مباشرة باستخدام مُشفرات JSON الافتراضية دائمًا تقريبًا إلى `TypeError` أو استثناء تسلسل مشابه. وذلك لأن المُشفر الافتراضي لا يعرف كيفية تحويل هذه البنيات اللغوية البرمجية المحددة إلى أحد أنواع البيانات الأصلية في JSON (سلسلة نصية، رقم، قيمة منطقية، null، كائن، مصفوفة).
المشكلة: عندما يفشل JSON الافتراضي
دعنا نوضح هذه القيود بأمثلة ملموسة، باستخدام وحدة `json` في بايثون بشكل أساسي، ولكن المشكلة الأساسية عالمية عبر اللغات.
دراسة حالة 1: الفئات/الكائنات المخصصة
تخيل أنك تبني منصة للتجارة الإلكترونية تتعامل مع المنتجات عالميًا. تقوم بتعريف فئة `Product`:
import datetime
import decimal
import uuid
class ProductStatus:
AVAILABLE = "AVAILABLE"
OUT_OF_STOCK = "OUT_OF_STOCK"
DISCONTINUED = "DISCONTINUED"
class Product:
def __init__(self, product_id, name, price, stock, created_at, last_updated, status):
self.product_id = product_id # UUID type
self.name = name
self.price = price # Decimal type
self.stock = stock
self.created_at = created_at # datetime type
self.last_updated = last_updated # datetime type
self.status = status # Custom Enum/Status class
# Create a product instance
product_instance = Product(
product_id=uuid.uuid4(),
name="Global Widget Pro",
price=decimal.Decimal('99.99'),
stock=150,
created_at=datetime.datetime.now(datetime.timezone.utc),
last_updated=datetime.datetime.now(datetime.timezone.utc),
status=ProductStatus.AVAILABLE
)
# Attempt to serialize directly
# import json
# try:
# json_output = json.dumps(product_instance, indent=4)
# print(json_output)
# except TypeError as e:
# print(f"Serialization Error: {e}")
إذا قمت بإلغاء التعليق وتشغيل سطر `json.dumps()`، فستحصل على `TypeError` مشابه لـ: `TypeError: Object of type Product is not JSON serializable`. لا يحتوي المُشفر الافتراضي على تعليمات حول كيفية تحويل كائن `Product` إلى كائن JSON (قاموس). علاوة على ذلك، حتى لو عرف كيفية التعامل مع `Product`، فسيواجه بعد ذلك كائنات `uuid.UUID`، `decimal.Decimal`، `datetime.datetime`، و `ProductStatus`، وكلها أيضًا ليست قابلة للتسلسل إلى JSON بشكل أصلي.
دراسة حالة 2: أنواع البيانات غير القياسية
كائنات datetime
التواريخ والأوقات حاسمة في كل تطبيق تقريبًا. الممارسة الشائعة لقابلية التشغيل البيني هي تسلسلها إلى سلاسل نصية بتنسيق ISO 8601 (على سبيل المثال، "2023-10-27T10:30:00Z"). المُشفرات الافتراضية لا تعرف هذا الاصطلاح:
# import json, datetime
# try:
# json.dumps({"timestamp": datetime.datetime.now(datetime.timezone.utc)})
# except TypeError as e:
# print(f"Serialization Error for datetime: {e}")
# Output: TypeError: Object of type datetime is not JSON serializable
كائنات Decimal
بالنسبة للمعاملات المالية، الحساب الدقيق أمر بالغ الأهمية. يمكن أن تعاني أرقام الفاصلة العائمة (`float` في بايثون، `double` في جافا) من أخطاء في الدقة، وهي غير مقبولة للعملات. تحل أنواع `Decimal` هذه المشكلة، ولكنها، مرة أخرى، ليست قابلة للتسلسل إلى JSON بشكل أصلي:
# import json, decimal
# try:
# json.dumps({"amount": decimal.Decimal('123456789.0123456789')})
# except TypeError as e:
# print(f"Serialization Error for Decimal: {e}")
# Output: TypeError: Object of type Decimal is not JSON serializable
الطريقة القياسية لتسلسل `Decimal` هي عادةً كسلسلة نصية للحفاظ على الدقة الكاملة وتجنب مشاكل الأعداد العشرية في جانب العميل.
UUID
(معرفات فريدة عالميًا)
توفر معرفات UUID معرفات فريدة، وغالبًا ما تستخدم كمفاتيح أساسية أو للتتبع عبر الأنظمة الموزعة. يتم تمثيلها عادة كسلاسل نصية في JSON:
# import json, uuid
# try:
# json.dumps({"transaction_id": uuid.uuid4()})
# except TypeError as e:
# print(f"Serialization Error for UUID: {e}")
# Output: TypeError: Object of type UUID is not JSON serializable
المشكلة واضحة: آليات تسلسل JSON الافتراضية جامدة للغاية بالنسبة لهياكل البيانات الديناميكية والمعقدة التي تُصادف في تطبيقات العالم الحقيقي الموزعة عالميًا. هناك حاجة إلى حل مرن وقابل للتوسيع لتعليم مُسلسل JSON كيفية التعامل مع هذه الأنواع المخصصة – وهذا الحل هو مُشفر JSON المخصص.
تقديم مُشفرات JSON المخصصة
يوفر مُشفر JSON المخصص آلية لتوسيع سلوك التسلسل الافتراضي، مما يتيح لك تحديد كيفية تحويل الكائنات غير القياسية أو المخصصة إلى أنواع متوافقة مع JSON بالضبط. وهذا يمكّنك من تحديد استراتيجية تسلسل متسقة لجميع بياناتك المعقدة، بغض النظر عن مصدرها أو وجهتها النهائية.
المفهوم: تجاوز السلوك الافتراضي
الفكرة الأساسية وراء المُشفر المخصص هي اعتراض الكائنات التي لا يتعرف عليها مُشفر JSON الافتراضي. عندما يواجه المُشفر الافتراضي كائنًا لا يمكنه تسلسله، فإنه يحول المهمة إلى معالج مخصص. أنت تقدم هذا المعالج، وتقول له:
- "إذا كان الكائن من النوع X، فقم بتحويله إلى Y (نوع متوافق مع JSON مثل سلسلة نصية أو قاموس)."
- "وإلا، إذا لم يكن من النوع X، دع المُشفر الافتراضي يحاول التعامل معه."
في العديد من لغات البرمجة، يتم تحقيق ذلك عن طريق اشتقاق فئة مُشفر JSON القياسية وتجاوز طريقة محددة مسؤولة عن التعامل مع الأنواع غير المعروفة. في بايثون، هذه هي فئة `json.JSONEncoder` وطريقتها `default()`.
كيف يعمل (JSONEncoder.default()
في بايثون)
عند استدعاء `json.dumps()` باستخدام مُشفر مخصص، فإنه يحاول تسلسل كل كائن. إذا واجه كائنًا لا يدعم نوعه أصلاً، فإنه يستدعي طريقة `default(self, obj)` لفئة المُشفر المخصص الخاصة بك، ويمرر الكائن `obj` الإشكالي إليها. داخل `default()`، تكتب المنطق لفحص نوع `obj` وإرجاع تمثيل قابل للتسلسل إلى JSON.
إذا قامت طريقة `default()` الخاصة بك بتحويل الكائن بنجاح (على سبيل المثال، تحويل `datetime` إلى سلسلة نصية)، فسيتم تسلسل هذه القيمة المحولة. إذا كانت طريقة `default()` الخاصة بك لا تزال لا تستطيع التعامل مع نوع الكائن، فيجب أن تستدعي طريقة `default()` للفئة الأصلية (`super().default(obj)`) والتي سترفع بعد ذلك `TypeError`، مما يشير إلى أن الكائن غير قابل للتسلسل حقًا وفقًا لجميع القواعد المحددة.
تطبيق المُشفرات المخصصة: دليل عملي
دعنا نمر عبر مثال بايثون شامل، يوضح كيفية إنشاء واستخدام مُشفر JSON مخصص للتعامل مع فئة `Product` وأنواع بياناتها المعقدة المحددة مسبقًا.
الخطوة 1: تحديد الكائن (الكائنات) المعقدة الخاصة بك
سنعيد استخدام فئة `Product` الخاصة بنا مع `UUID`، `Decimal`، `datetime`، وتعداد `ProductStatus` مخصص. لهيكل أفضل، دعنا نجعل `ProductStatus` `enum.Enum` صحيحًا.
import json
import datetime
import decimal
import uuid
from enum import Enum
# Define a custom enumeration for product status
class ProductStatus(Enum):
AVAILABLE = "AVAILABLE"
OUT_OF_STOCK = "OUT_OF_STOCK"
DISCONTINUED = "DISCONTINUED"
# Optional: for cleaner string representation in JSON if needed directly
def __str__(self):
return self.value
def __repr__(self):
return self.value
# Define the complex Product class
class Product:
def __init__(self, product_id: uuid.UUID, name: str, description: str,
price: decimal.Decimal, stock: int,
created_at: datetime.datetime, last_updated: datetime.datetime,
status: ProductStatus, tags: list[str] = None):
self.product_id = product_id
self.name = name
self.description = description
self.price = price
self.stock = stock
self.created_at = created_at
self.last_updated = last_updated
self.status = status
self.tags = tags if tags is not None else []
# A helper method to convert a Product instance to a dictionary
# This is often the target format for custom class serialization
def to_dict(self):
return {
"product_id": str(self.product_id), # Convert UUID to string
"name": self.name,
"description": self.description,
"price": str(self.price), # Convert Decimal to string
"stock": self.stock,
"created_at": self.created_at.isoformat(), # Convert datetime to ISO string
"last_updated": self.last_updated.isoformat(), # Convert datetime to ISO string
"status": self.status.value, # Convert Enum to its value string
"tags": self.tags
}
# Create a product instance with a global perspective
product_instance_global = Product(
product_id=uuid.uuid4(),
name="Universal Data Hub",
description="A robust data aggregation and distribution platform.",
price=decimal.Decimal('1999.99'),
stock=50,
created_at=datetime.datetime(2023, 10, 26, 14, 30, 0, tzinfo=datetime.timezone.utc),
last_updated=datetime.datetime(2024, 1, 15, 9, 0, 0, tzinfo=datetime.timezone.utc),
status=ProductStatus.AVAILABLE,
tags=["API", "Cloud", "Integration", "Global"]
)
product_instance_local = Product(
product_id=uuid.uuid4(),
name="Local Artisan Craft",
description="Handmade item from traditional techniques.",
price=decimal.Decimal('25.50'),
stock=5,
created_at=datetime.datetime(2023, 11, 1, 10, 0, 0, tzinfo=datetime.timezone.utc),
last_updated=datetime.datetime(2023, 11, 1, 10, 0, 0, tzinfo=datetime.timezone.utc),
status=ProductStatus.OUT_OF_STOCK,
tags=["Handmade", "Local", "Art"]
)
الخطوة 2: إنشاء فئة فرعية مخصصة من JSONEncoder
الآن، دعنا نُعرّف `GlobalJSONEncoder` الذي يرث من `json.JSONEncoder` ويتجاوز طريقة `default()` الخاصة به.
class GlobalJSONEncoder(json.JSONEncoder):
def default(self, obj):
# Handle datetime objects: Convert to ISO 8601 string with timezone info
if isinstance(obj, datetime.datetime):
# Ensure datetime is timezone-aware for consistency. If naive, assume UTC or local.
# Consider global impact: naive datetimes are ambiguous.
# Best practice: always use timezone-aware datetimes, preferably UTC.
# For this example, we'll convert to UTC if naive.
if obj.tzinfo is None:
return obj.replace(tzinfo=datetime.timezone.utc).isoformat()
return obj.isoformat()
# Handle Decimal objects: Convert to string to preserve precision
elif isinstance(obj, decimal.Decimal):
return str(obj)
# Handle UUID objects: Convert to standard string representation
elif isinstance(obj, uuid.UUID):
return str(obj)
# Handle Enum objects: Convert to their value (e.g., "AVAILABLE")
elif isinstance(obj, Enum):
return obj.value
# Handle custom class instances (like our Product class)
# This assumes your custom class has a .to_dict() method
elif hasattr(obj, 'to_dict') and callable(obj.to_dict):
return obj.to_dict()
# Let the base class default method raise the TypeError for other unhandled types
return super().default(obj)
شرح منطق طريقة `default()`:
- `if isinstance(obj, datetime.datetime)`: يتحقق مما إذا كان الكائن هو مثيل `datetime`. إذا كان كذلك، فإن `obj.isoformat()` يحوله إلى سلسلة نصية ISO 8601 معترف بها عالميًا (على سبيل المثال، "2024-01-15T09:00:00+00:00"). لقد أضفنا أيضًا فحصًا للوعي بالمنطقة الزمنية، مع التأكيد على أفضل الممارسات العالمية لاستخدام التوقيت العالمي المنسق (UTC).
- `elif isinstance(obj, decimal.Decimal)`: يتحقق من كائنات `Decimal`. يتم تحويلها إلى `str(obj)` للحفاظ على الدقة الكاملة، وهو أمر بالغ الأهمية للبيانات المالية أو العلمية عبر أي منطقة.
- `elif isinstance(obj, uuid.UUID)`: يحول كائنات `UUID` إلى تمثيلها القياسي كسلسلة نصية، وهو مفهوم عالميًا.
- `elif isinstance(obj, Enum)`: يحول أي مثيل `Enum` إلى سمة `value` الخاصة به. وهذا يضمن أن التعدادات مثل `ProductStatus.AVAILABLE` تصبح السلسلة النصية "AVAILABLE" في JSON.
- `elif hasattr(obj, 'to_dict') and callable(obj.to_dict)`: هذا نمط قوي وعام للفئات المخصصة. بدلاً من الترميز الثابت `elif isinstance(obj, Product)`، نتحقق مما إذا كان الكائن يحتوي على طريقة `to_dict()`. إذا كان كذلك، فإننا نستدعيها للحصول على تمثيل قاموسي للكائن، والذي يمكن للمُشفر الافتراضي بعد ذلك التعامل معه بشكل متكرر. وهذا يجعل المُشفر أكثر قابلية لإعادة الاستخدام عبر فئات مخصصة متعددة تتبع اتفاقية `to_dict`.
- `return super().default(obj)`: إذا لم تتطابق أي من الشروط أعلاه، فهذا يعني أن `obj` لا يزال نوعًا غير معروف. نمرره إلى طريقة `default` للفئة الأصلية `JSONEncoder`. سيرفع هذا `TypeError` إذا لم يتمكن المُشفر الأساسي أيضًا من التعامل معه، وهو السلوك المتوقع للأنواع غير القابلة للتسلسل حقًا.
الخطوة 3: استخدام المُشفر المخصص
لاستخدام المُشفر المخصص الخاص بك، يمكنك تمرير مثيل منه (أو فئته) إلى معلمة `cls` في `json.dumps()`.
# Serialize the product instance using our custom encoder
json_output_global = json.dumps(product_instance_global, indent=4, cls=GlobalJSONEncoder)
print("\n--- Global Product JSON Output ---")
print(json_output_global)
json_output_local = json.dumps(product_instance_local, indent=4, cls=GlobalJSONEncoder)
print("\n--- Local Product JSON Output ---")
print(json_output_local)
# Example with a dictionary containing various complex types
complex_data = {
"event_id": uuid.uuid4(),
"event_timestamp": datetime.datetime.now(datetime.timezone.utc),
"total_amount": decimal.Decimal('1234.567'),
"status": ProductStatus.DISCONTINUED,
"product_details": product_instance_global, # Nested custom object
"settings": {"retry_count": 3, "enabled": True}
}
json_complex_data = json.dumps(complex_data, indent=4, cls=GlobalJSONEncoder)
print("\n--- Complex Data JSON Output ---")
print(json_complex_data)
الناتج المتوقع (مقتطع للإيجاز، ستختلف معرفات UUIDs/التواريخ والأوقات الفعلية):
--- Global Product JSON Output ---
{
"product_id": "b8a7f0e9-b1c2-4d3e-8f7a-6c5d4b3a2e1f",
"name": "Universal Data Hub",
"description": "A robust data aggregation and distribution platform.",
"price": "1999.99",
"stock": 50,
"created_at": "2023-10-26T14:30:00+00:00",
"last_updated": "2024-01-15T09:00:00+00:00",
"status": "AVAILABLE",
"tags": [
"API",
"Cloud",
"Integration",
"Global"
]
}
--- Local Product JSON Output ---
{
"product_id": "d1e2f3a4-5b6c-7d8e-9f0a-1b2c3d4e5f6a",
"name": "Local Artisan Craft",
"description": "Handmade item from traditional techniques.",
"price": "25.50",
"stock": 5,
"created_at": "2023-11-01T10:00:00+00:00",
"last_updated": "2023-11-01T10:00:00+00:00",
"status": "OUT_OF_STOCK",
"tags": [
"Handmade",
"Local",
"Art"
]
}
--- Complex Data JSON Output ---
{
"event_id": "c9d0e1f2-a3b4-5c6d-7e8f-9a0b1c2d3e4f",
"event_timestamp": "2024-01-27T12:34:56.789012+00:00",
"total_amount": "1234.567",
"status": "DISCONTINUED",
"product_details": {
"product_id": "b8a7f0e9-b1c2-4d3e-8f7a-6c5d4b3a2e1f",
"name": "Universal Data Hub",
"description": "A robust data aggregation and distribution platform.",
"price": "1999.99",
"stock": 50,
"created_at": "2023-10-26T14:30:00+00:00",
"last_updated": "2024-01-15T09:00:00+00:00",
"status": "AVAILABLE",
"tags": [
"API",
"Cloud",
"Integration",
"Global"
]
},
"settings": {
"retry_count": 3,
"enabled": true
}
}
كما ترون، قام المُشفر المخصص الخاص بنا بتحويل جميع الأنواع المعقدة بنجاح إلى تمثيلاتها المناسبة القابلة للتسلسل إلى JSON، بما في ذلك الكائنات المخصصة المتداخلة. هذا المستوى من التحكم بالغ الأهمية للحفاظ على سلامة البيانات وقابليتها للتشغيل البيني عبر أنظمة متنوعة.
ما وراء بايثون: مكافئات مفاهيمية في لغات أخرى
بينما ركز المثال المفصل على بايثون، فإن مفهوم توسيع تسلسل JSON منتشر عبر لغات البرمجة الشائعة:
-
جافا (Jackson Library): جاكسون هو معيار واقعي لـ JSON في جافا. يمكنك تحقيق التسلسل المخصص عن طريق:
- تطبيق
JsonSerializer<T>
وتسجيله معObjectMapper
. - استخدام تعليقات توضيحية مثل
@JsonFormat
للتواريخ/الأرقام أو@JsonSerialize(using = MyCustomSerializer.class)
مباشرة على الحقول أو الفئات.
- تطبيق
-
سي شارب (
System.Text.Json
أوNewtonsoft.Json
):System.Text.Json
(مدمج، حديث): تطبيقJsonConverter<T>
وتسجيله عبرJsonSerializerOptions
.Newtonsoft.Json
(طرف ثالث شائع): تطبيقJsonConverter
وتسجيله معJsonSerializerSettings
أو عبر سمة[JsonConverter(typeof(MyCustomConverter))]
.
-
جو (
encoding/json
):- تطبيق واجهة
json.Marshaler
للأنواع المخصصة. تسمح طريقةMarshalJSON() ([]byte, error)
بتحديد كيفية تحويل نوعك إلى بايتات JSON. - بالنسبة للحقول، استخدم علامات البنية (على سبيل المثال،
json:"fieldName,string"
لتحويل السلسلة) أو حذف الحقول (json:"-"
).
- تطبيق واجهة
-
جافاسكريبت (
JSON.stringify
):- يمكن للكائنات المخصصة تعريف طريقة
toJSON()
. إذا كانت موجودة، فسيستدعيJSON.stringify
هذه الطريقة ويسلسل قيمتها المرجعة. - تسمح وسيطة
replacer
فيJSON.stringify(value, replacer, space)
بوظيفة مخصصة لتحويل القيم أثناء التسلسل.
- يمكن للكائنات المخصصة تعريف طريقة
-
سويفت (بروتوكول
Codable
):- في العديد من الحالات، يكفي مجرد الامتثال لـ
Codable
. للتخصيصات المحددة، يمكنك تنفيذinit(from decoder: Decoder)
وencode(to encoder: Encoder)
يدويًا للتحكم في كيفية ترميز/فك تشفير الخصائص باستخدامKeyedEncodingContainer
وKeyedDecodingContainer
.
- في العديد من الحالات، يكفي مجرد الامتثال لـ
الخيط المشترك هو القدرة على التدخل في عملية التسلسل عند النقطة التي لا يتم فيها فهم نوع معين أصلاً وتقديم منطق تحويل محدد ومُعرّف جيدًا.
تقنيات المُشفر المخصص المتقدمة
تسلسل المُشفرات / المُشفرات المعيارية
مع نمو تطبيقك، قد تصبح طريقة `default()` الخاصة بك كبيرة جدًا، حيث تتعامل مع عشرات الأنواع. النهج الأنظف هو إنشاء مُشفرات معيارية، يكون كل منها مسؤولاً عن مجموعة محددة من الأنواع، ثم ربطها أو تركيبها. في بايثون، يعني هذا غالبًا إنشاء عدة فئات فرعية من `JSONEncoder` ثم دمج منطقها ديناميكيًا أو استخدام نمط المصنع (factory pattern).
بدلاً من ذلك، يمكن لطريقة `default()` الواحدة الخاصة بك أن تفوض المهام إلى وظائف مساعدة أو مُسلسلات أصغر خاصة بالنوع، مما يحافظ على نظافة الطريقة الرئيسية.
class AnotherCustomEncoder(GlobalJSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj) # Convert sets to lists
return super().default(obj) # Delegate to parent (GlobalJSONEncoder)
# Example with a set
set_data = {"unique_ids": {1, 2, 3}, "product": product_instance_global}
json_set_data = json.dumps(set_data, indent=4, cls=AnotherCustomEncoder)
print("\n--- Set Data JSON Output ---")
print(json_set_data)
يوضح هذا كيف يتحقق `AnotherCustomEncoder` أولاً من كائنات `set`، وإذا لم تكن كذلك، فإنه يفوض إلى طريقة `default` الخاصة بـ `GlobalJSONEncoder`، وبالتالي يربط المنطق بشكل فعال.
الترميز الشرطي والتسلسل السياقي
في بعض الأحيان تحتاج إلى تسلسل نفس الكائن بشكل مختلف بناءً على السياق (على سبيل المثال، كائن `User` كامل للمسؤول، ولكن فقط `id` و `name` لواجهة برمجة تطبيقات عامة). هذا أصعب باستخدام `JSONEncoder.default()` وحده، لأنه بلا حالة. يمكنك:
- تمرير كائن "سياق" إلى مُنشئ المُشفر المخصص الخاص بك (إذا سمحت لغتك بذلك).
- تطبيق طريقة `to_json_summary()` أو `to_json_detail()` على الكائن المخصص الخاص بك واستدعاء الطريقة المناسبة ضمن طريقة `default()` بناءً على علامة خارجية.
- استخدام مكتبات مثل Marshmallow أو Pydantic (بايثون) أو أطر عمل تحويل البيانات المماثلة التي توفر تسلسلًا أكثر تعقيدًا يعتمد على المخطط مع السياق.
التعامل مع المراجع الدائرية
أحد الأخطاء الشائعة في تسلسل الكائنات هو المراجع الدائرية (على سبيل المثال، `User` لديه قائمة من `Orders`، و `Order` لديه مرجع يعود إلى `User`). إذا لم يتم التعامل معها، يؤدي هذا إلى تكرار لانهائي أثناء التسلسل. تشمل الاستراتيجيات ما يلي:
- تجاهل المراجع العكسية: ببساطة لا تقم بتسلسل المرجع العكسي أو قم بتمييزه للاستبعاد.
- التسلسل بواسطة المعرف: بدلاً من تضمين الكائن الكامل، قم بتسلسل معرفه الفريد فقط في المرجع العكسي.
- التعيين المخصص باستخدام `json.JSONEncoder.default()`: الاحتفاظ بمجموعة من الكائنات التي تمت زيارتها أثناء التسلسل للكشف عن الدورات وكسرها. قد يكون هذا معقدًا للتطبيق بشكل قوي.
اعتبارات الأداء
بالنسبة لمجموعات البيانات الكبيرة جدًا أو واجهات برمجة التطبيقات عالية الإنتاجية، يمكن أن يُدخل التسلسل المخصص عبئًا إضافيًا. ضع في اعتبارك ما يلي:
- التسلسل المسبق: إذا كان الكائن ثابتًا أو نادرًا ما يتغير، فقم بتسلسله مرة واحدة وقم بتخزين سلسلة JSON مؤقتًا.
- التحويلات الفعالة: تأكد من أن تحويلات طريقة `default()` فعالة. تجنب العمليات المكلفة داخل حلقة إذا أمكن.
- تطبيقات C الأصلية: العديد من مكتبات JSON (مثل `json` في بايثون) تحتوي على تطبيقات C أساسية أسرع بكثير. التزم بالأنواع المضمنة حيثما أمكن واستخدم المُشفرات المخصصة فقط عند الضرورة.
- تنسيقات بديلة: لاحتياجات الأداء القصوى، فكر في تنسيقات التسلسل الثنائية مثل Protocol Buffers، Avro، أو MessagePack، والتي تكون أكثر إحكامًا وأسرع للتواصل بين الآلات، على الرغم من أنها أقل قابلية للقراءة البشرية.
معالجة الأخطاء والتصحيح
عندما ينشأ `TypeError` من `super().default(obj)`، فهذا يعني أن المُشفر المخصص الخاص بك لم يتمكن من التعامل مع نوع معين. يتضمن التصحيح فحص `obj` عند نقطة الفشل لتحديد نوعه ثم إضافة منطق معالجة مناسب إلى طريقة `default()` الخاصة بك.
من الممارسات الجيدة أيضًا جعل رسائل الأخطاء إعلامية. على سبيل المثال، إذا تعذر تحويل كائن مخصص (على سبيل المثال، افتقاد `to_dict()`)، فقد تثير استثناءً أكثر تحديدًا داخل المعالج المخصص الخاص بك.
نظائر إلغاء التسلسل (فك التشفير)
بينما يركز هذا المنشور على الترميز، من الأهمية بمكان الاعتراف بالجانب الآخر من العملة: إلغاء التسلسل (فك التشفير). عندما تتلقى بيانات JSON التي تم تسلسلها باستخدام مُشفر مخصص، فمن المحتمل أن تحتاج إلى مُفكك تشفير مخصص (أو object hook) لإعادة بناء كائناتك المعقدة بشكل صحيح.
في بايثون، يمكن استخدام معلمة `object_hook` في `json.JSONDecoder` أو `parse_constant`. على سبيل المثال، إذا قمت بتسلسل كائن `datetime` إلى سلسلة ISO 8601، فسيحتاج مُفكك التشفير الخاص بك إلى تحليل تلك السلسلة مرة أخرى إلى كائن `datetime`. بالنسبة لكائن `Product` الذي تم تسلسله كقاموس، ستحتاج إلى منطق لإنشاء مثيل لفئة `Product` من مفاتيح وقيم هذا القاموس، مع تحويل أنواع `UUID` و `Decimal` و `datetime` و `Enum` مرة أخرى بعناية.
غالبًا ما يكون إلغاء التسلسل أكثر تعقيدًا من التسلسل لأنك تستنتج الأنواع الأصلية من أنواع JSON البدائية العامة. الاتساق بين استراتيجيات الترميز وفك التشفير الخاصة بك أمر بالغ الأهمية لتحويلات البيانات الناجحة ذهابًا وإيابًا، خاصة في الأنظمة الموزعة عالميًا حيث تكون سلامة البيانات حاسمة.
أفضل الممارسات للتطبيقات العالمية
عند التعامل مع تبادل البيانات في سياق عالمي، تصبح مُشفرات JSON المخصصة أكثر حيوية لضمان الاتساق وقابلية التشغيل البيني والصحة عبر الأنظمة والثقافات المتنوعة.
1. التوحيد القياسي: الالتزام بالمعايير الدولية
- التواريخ والأوقات (ISO 8601): قم دائمًا بتسلسل كائنات `datetime` إلى سلاسل نصية بتنسيق ISO 8601 (على سبيل المثال، "2023-10-27T10:30:00Z" أو "2023-10-27T10:30:00+01:00"). الأهم من ذلك، فضل استخدام التوقيت العالمي المنسق (UTC) لجميع عمليات جانب الخادم وتخزين البيانات. دع جانب العميل (متصفح الويب، تطبيق الهاتف المحمول) يحول إلى المنطقة الزمنية المحلية للمستخدم للعرض. تجنب إرسال تواريخ وأوقات بسيطة (غير واعية بالمنطقة الزمنية).
- الأرقام (سلسلة نصية للدقة): بالنسبة لأرقام `Decimal` أو الأرقام عالية الدقة (خاصة القيم المالية)، قم بتسلسلها كسلاسل نصية. هذا يمنع عدم الدقة المحتملة في الفاصلة العائمة التي يمكن أن تختلف عبر لغات البرمجة المختلفة ومعماريات الأجهزة. يضمن التمثيل النصي دقة تامة عبر جميع الأنظمة.
- معرفات UUID: قم بتمثيل معرفات `UUID` في شكلها النصي القياسي (على سبيل المثال، "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"). هذا معيار مقبول على نطاق واسع.
- القيم المنطقية: استخدم دائمًا `true` و `false` (بأحرف صغيرة) وفقًا لمواصفات JSON. تجنب التمثيلات الرقمية مثل 0/1، والتي يمكن أن تكون غامضة.
2. اعتبارات التوطين
- التعامل مع العملات: عند تبادل قيم العملات، خاصة في الأنظمة متعددة العملات، قم بتخزينها وإرسالها كوحدة أساسية أصغر (على سبيل المثال، السنت للدولار الأمريكي، الين للياباني) كأعداد صحيحة، أو كسلاسل `Decimal`. قم دائمًا بتضمين رمز العملة (ISO 4217، على سبيل المثال، "USD"، "EUR") جنبًا إلى جنب مع المبلغ. لا تعتمد أبدًا على افتراضات العملة الضمنية بناءً على المنطقة.
- ترميز النص (UTF-8): تأكد من أن جميع عمليات تسلسل JSON تستخدم ترميز UTF-8. هذا هو المعيار العالمي لترميز الأحرف ويدعم جميع اللغات البشرية تقريبًا، مما يمنع تشوه النصوص عند التعامل مع الأسماء والعناوين والأوصاف الدولية.
- المناطق الزمنية: كما ذكرنا، قم بإرسال UTC. إذا كان الوقت المحلي ضروريًا للغاية، فقم بتضمين إزاحة المنطقة الزمنية الصريحة (على سبيل المثال، `+01:00`) أو معرف المنطقة الزمنية الخاص بـ IANA (على سبيل المثال، "Europe/Berlin") مع سلسلة التاريخ والوقت. لا تفترض أبدًا المنطقة الزمنية المحلية للمستلم.
3. تصميم وتوثيق واجهة برمجة تطبيقات قويين
- تعريفات المخطط الواضحة: إذا كنت تستخدم مُشفرات مخصصة، يجب أن يحدد توثيق واجهة برمجة التطبيقات (API) الخاصة بك بوضوح تنسيق JSON المتوقع لجميع الأنواع المعقدة. يمكن أن تساعد أدوات مثل OpenAPI (Swagger)، ولكن تأكد من أن عمليات التسلسل المخصصة الخاصة بك مذكورة صراحةً. هذا أمر بالغ الأهمية للعملاء في مواقع جغرافية مختلفة أو الذين يستخدمون حزم تقنية مختلفة للتكامل بشكل صحيح.
- التحكم في إصدار تنسيقات البيانات: مع تطور نماذج الكائنات الخاصة بك، قد تتطور أيضًا تمثيلاتها في JSON. قم بتطبيق إصدار واجهة برمجة التطبيقات (على سبيل المثال، `/v1/products`، `/v2/products`) لإدارة التغييرات بأناقة. تأكد من أن مُشفراتك المخصصة يمكنها التعامل مع إصدارات متعددة إذا لزم الأمر أو أنك تقوم بنشر مُشفرات متوافقة مع كل إصدار من واجهة برمجة التطبيقات.
4. قابلية التشغيل البيني والتوافق مع الإصدارات السابقة
- تنسيقات مستقلة عن اللغة: الهدف من JSON هو قابلية التشغيل البيني. يجب أن ينتج المُشفر المخصص الخاص بك JSON يمكن تحليله وفهمه بسهولة بواسطة أي عميل، بغض النظر عن لغة البرمجة الخاصة به. تجنب هياكل JSON المتخصصة للغاية أو الخاصة التي تتطلب معرفة محددة بتفاصيل تطبيق الواجهة الخلفية (backend) الخاص بك.
- المعالجة الأنيقة للبيانات المفقودة: عند إضافة حقول جديدة إلى نماذج الكائنات الخاصة بك، تأكد من أن العملاء الأقدم (الذين قد لا يرسلون هذه الحقول أثناء إلغاء التسلسل) لا يتعطلون، وأن العملاء الأحدث يمكنهم التعامل مع استلام JSON الأقدم بدون الحقول الجديدة. يجب تصميم المُشفرات/المُفككات المخصصة مع مراعاة هذا التوافق الأمامي والخلفي.
5. الأمان وكشف البيانات
- حجب البيانات الحساسة: كن حذرًا بشأن البيانات التي تقوم بتسلسلها. توفر المُشفرات المخصصة فرصة ممتازة لحجب أو إخفاء المعلومات الحساسة (مثل كلمات المرور، معلومات التعريف الشخصية (PII) لأدوار أو سياقات معينة) قبل أن تغادر خادمك على الإطلاق. لا تقم أبدًا بتسلسل البيانات الحساسة التي لا يطلبها العميل بشكل مطلق.
- عمق التسلسل: بالنسبة للكائنات المتداخلة بشكل كبير، ضع في اعتبارك تحديد عمق التسلسل لمنع كشف الكثير من البيانات أو إنشاء حمولات JSON كبيرة بشكل مفرط. يمكن أن يساعد هذا أيضًا في التخفيف من هجمات حجب الخدمة (Denial-of-Service) التي تعتمد على طلبات JSON الكبيرة والمعقدة.
حالات الاستخدام والسيناريوهات الواقعية
مُشفرات JSON المخصصة ليست مجرد تمرين أكاديمي؛ إنها أداة حيوية في العديد من التطبيقات الواقعية، خاصة تلك التي تعمل على نطاق عالمي.
1. الأنظمة المالية والبيانات عالية الدقة
السيناريو: منصة مصرفية دولية تعالج المعاملات وتولد التقارير عبر عملات وولايات قضائية متعددة.
التحدي: تمثيل مبالغ مالية دقيقة (على سبيل المثال، `12345.6789 EUR`)، حسابات معقدة لأسعار الفائدة، أو أسعار الأسهم دون إدخال أخطاء في الفاصلة العائمة. البلدان المختلفة لديها فواصل عشرية ورموز عملات مختلفة، ولكن JSON يحتاج إلى تمثيل عالمي.
حل المُشفر المخصص: تسلسل كائنات `Decimal` (أو ما يعادلها من أنواع النقطة الثابتة) كسلاسل نصية. تضمين رموز العملات ISO 4217 (مثل "USD"، "JPY"). إرسال الطوابع الزمنية بتنسيق UTC ISO 8601. يضمن هذا أن مبلغ معاملة تمت معالجتها في لندن يتم استلامها وتفسيرها بدقة بواسطة نظام في طوكيو، والإبلاغ عنها بشكل صحيح في نيويورك، مع الحفاظ على الدقة الكاملة ومنع التناقضات.
2. التطبيقات الجغرافية المكانية وخدمات الخرائط
السيناريو: شركة لوجستية عالمية تتعقب الشحنات ومركبات الأسطول وطرق التسليم باستخدام إحداثيات GPS وأشكال جغرافية معقدة.
التحدي: تسلسل كائنات `Point` أو `LineString` أو `Polygon` المخصصة (على سبيل المثال، من مواصفات GeoJSON)، أو تمثيل أنظمة الإحداثيات (`WGS84`، `UTM`).
حل المُشفر المخصص: تحويل الكائنات الجغرافية المكانية المخصصة إلى هياكل GeoJSON محددة جيدًا (وهي بحد ذاتها كائنات أو مصفوفات JSON). على سبيل المثال، يمكن تسلسل كائن `Point` مخصص إلى `{"type": "Point", "coordinates": [longitude, latitude]}`. يتيح هذا قابلية التشغيل البيني مع مكتبات الخرائط وقواعد البيانات الجغرافية في جميع أنحاء العالم، بغض النظر عن برنامج GIS الأساسي.
3. تحليل البيانات والحوسبة العلمية
السيناريو: باحثون يتعاونون دوليًا، يشاركون النماذج الإحصائية، القياسات العلمية، أو هياكل البيانات المعقدة من مكتبات التعلم الآلي.
التحدي: تسلسل الكائنات الإحصائية (على سبيل المثال، ملخص `Pandas DataFrame`، كائن توزيع إحصائي `SciPy`)، وحدات القياس المخصصة، أو المصفوفات الكبيرة التي قد لا تتناسب مع أنواع JSON البدائية مباشرة.
حل المُشفر المخصص: تحويل `DataFrame`s إلى مصفوفات JSON من الكائنات، ومصفوفات `NumPy` إلى قوائم متداخلة. بالنسبة للكائنات العلمية المخصصة، قم بتسلسل خصائصها الرئيسية (على سبيل المثال، `distribution_type`، `parameters`). تسلسل تواريخ/أوقات التجارب إلى ISO 8601، مما يضمن إمكانية تحليل البيانات التي تم جمعها في مختبر واحد بشكل متسق من قبل الزملاء عبر القارات.
4. أجهزة إنترنت الأشياء والبنية التحتية للمدينة الذكية
السيناريو: شبكة من أجهزة الاستشعار الذكية المنتشرة عالميًا، تجمع البيانات البيئية (درجة الحرارة، الرطوبة، جودة الهواء) ومعلومات حالة الجهاز.
التحدي: قد تقوم الأجهزة بالإبلاغ عن البيانات باستخدام أنواع بيانات مخصصة، أو قراءات مستشعر محددة ليست أرقامًا بسيطة، أو حالات جهاز معقدة تحتاج إلى تمثيل واضح.
حل المُشفر المخصص: يمكن لمُشفر مخصص تحويل أنواع بيانات المستشعر الخاصة إلى تنسيقات JSON موحدة. على سبيل المثال، يمكن تسلسل كائن مستشعر يمثل `{"type": "TemperatureSensor", "value": 23.5, "unit": "Celsius"}`. يتم تسلسل التعدادات لحالات الجهاز ("ONLINE"، "OFFLINE"، "ERROR") إلى سلاسل نصية. يتيح هذا لمركز البيانات المركزي استهلاك ومعالجة البيانات باستمرار من الأجهزة المصنعة من قبل بائعين مختلفين في مناطق مختلفة، باستخدام واجهة برمجة تطبيقات موحدة.
5. بنية الخدمات المصغرة (Microservices Architecture)
السيناريو: مؤسسة كبيرة ذات بنية خدمات مصغرة، حيث يتم كتابة خدمات مختلفة بلغات برمجة متنوعة (على سبيل المثال، بايثون لمعالجة البيانات، جافا لمنطق الأعمال، جو لبوابات API) وتتواصل عبر واجهات برمجة تطبيقات REST.
التحدي: ضمان التبادل السلس للبيانات لكائنات المجال المعقدة (على سبيل المثال، `Customer`، `Order`، `Payment`) بين الخدمات المطبقة في حزم تقنية مختلفة.
حل المُشفر المخصص: تحدد كل خدمة وتستخدم مُشفرات ومُفككات JSON المخصصة الخاصة بها لكائنات مجالها. من خلال الاتفاق على معيار تسلسل JSON مشترك (على سبيل المثال، جميع `datetime` بتنسيق ISO 8601، جميع `Decimal` كسلاسل نصية، جميع `UUID` كسلاسل نصية)، يمكن لكل خدمة تسلسل وفك تسلسل الكائنات بشكل مستقل دون معرفة تفاصيل تنفيذ الخدمات الأخرى. هذا يسهل الترابط المرن والتطوير المستقل، وهو أمر بالغ الأهمية لتوسيع نطاق الفرق العالمية.
6. تطوير الألعاب وتخزين بيانات المستخدم
السيناريو: لعبة متعددة اللاعبين عبر الإنترنت حيث تحتاج ملفات تعريف المستخدم وحالات اللعبة وعناصر المخزون إلى الحفظ والتحميل، ربما عبر خوادم ألعاب مختلفة في جميع أنحاء العالم.
التحدي: غالبًا ما تحتوي كائنات اللعبة على هياكل داخلية معقدة (على سبيل المثال، كائن `Player` مع `Inventory` من كائنات `Item`، لكل منها خصائص فريدة، تعدادات `Ability` مخصصة، تقدم `Quest`). سيفشل التسلسل الافتراضي.
حل المُشفر المخصص: يمكن للمشفرات المخصصة تحويل كائنات اللعبة المعقدة هذه إلى تنسيق JSON مناسب للتخزين في قاعدة بيانات أو تخزين سحابي. قد يتم تسلسل كائنات `Item` إلى قاموس من خصائصها. تصبح تعدادات `Ability` سلاسل نصية. يتيح هذا نقل بيانات اللاعب بين الخوادم (على سبيل المثال، إذا قام لاعب بترحيل المناطق)، وحفظ/تحميل البيانات بشكل موثوق، وربما تحليلها بواسطة خدمات الواجهة الخلفية لتحسين توازن اللعبة أو تجربة المستخدم.
الخاتمة
تُعد مُشفرات JSON المخصصة أداة قوية وغالبًا لا غنى عنها في مجموعة أدوات المطور الحديث. إنها تسد الفجوة بين بنيات لغات البرمجة الغنية والموجهة للكائنات وأنواع البيانات الأبسط والمفهومة عالميًا في JSON. من خلال توفير قواعد تسلسل صريحة لكائناتك المخصصة، ومثيلات `datetime`، وأرقام `Decimal`، ومعرفات `UUID`، والتعدادات، يمكنك الحصول على تحكم دقيق في كيفية تمثيل بياناتك في JSON.
بصرف النظر عن مجرد جعل التسلسل يعمل، تُعد المُشفرات المخصصة حاسمة لبناء تطبيقات قوية وقابلة للتشغيل المتبادل وواعية عالميًا. إنها تُمكّن من الالتزام بالمعايير الدولية مثل ISO 8601 للتواريخ، وتضمن الدقة العددية للأنظمة المالية عبر المناطق الجغرافية المختلفة، وتسهل تبادل البيانات السلس في بنى الخدمات المصغرة المعقدة. إنها تُمكّنك من تصميم واجهات برمجة تطبيقات سهلة الاستهلاك، بغض النظر عن لغة البرمجة للعميل أو موقعه الجغرافي، مما يعزز في النهاية سلامة البيانات وموثوقية النظام.
يسمح لك إتقان مُشفرات JSON المخصصة بمعالجة أي تحدي تسلسل بثقة، وتحويل الكائنات المعقدة الموجودة في الذاكرة إلى تنسيق بيانات عالمي يمكنه عبور الشبكات وقواعد البيانات والأنظمة المتنوعة في جميع أنحاء العالم. احتضن المُشفرات المخصصة، وأطلق العنان للإمكانات الكاملة لـ JSON لتطبيقاتك العالمية. ابدأ بدمجها في مشاريعك اليوم لضمان انتقال بياناتك بدقة وكفاءة وبشكل مفهوم عبر المشهد الرقمي.