JSON وبايثون يتكلمان نفس اللغة
صيغة JSON هي الصيغة الافتراضية للـ APIs وملفات الإعدادات وتبادل البيانات على الويب. ولحسن حظ مبرمجي بايثون، كائن JSON يقابل مباشرةً dict في بايثون، ومصفوفة JSON تقابل list، ونص JSON يقابل str. هذا التطابق الوثيق هو ما يجعل قراءة ملف JSON بايثون وكتابته عملية من سطرين فقط.
مكتبة json في بايثون، وهي جزء من المكتبة القياسية، تتكفّل بالاتجاهين معًا.
تحويل نص JSON إلى قاموس بايثون
الدالة json.loads(text) — اختصارًا لـ "load string" — تستقبل سلسلة نصية بصيغة JSON وتُرجع كائن بايثون المقابل لها:
النتيجة هي dict عادي في بايثون. تتعامل مع مفاتيحه مثل أي قاموس — لا يوجد غلاف خاص بـ JSON ولا دالة .parse() تستدعيها على النتيجة.
وإذا لم يكن النص الممرَّر JSON صالحًا، فستطلق json.loads استثناء json.JSONDecodeError. رسالة الخطأ تُحدِّد رقم السطر والعمود لموضع المشكلة، وهذا غالبًا ما يكفي لاكتشاف فاصلة ناقصة أو علامة اقتباس لم يتم تهريبها.
تحويل البيانات إلى نص JSON باستخدام json.dumps
تؤدي json.dumps(data) — أي "dump string" — الدور المعاكس تمامًا: فهي تأخذ كائن بايثون وتُعيده على هيئة نص JSON:
قيم True وFalse وNone في بايثون تتحول تلقائياً إلى true وfalse وnull في JSON. الأرقام والسلاسل النصية تمر كما هي دون تغيير، أما القوائم فتصبح مصفوفات، والقواميس تصبح كائنات.
التنسيق المقروء لـ JSON في بايثون (Pretty Print)
الخرج الافتراضي لدالة json.dumps يكون مضغوطاً — وهذا مناسب للنقل عبر الشبكة، لكنه متعب للقراءة البشرية. مرّر indent=2 للحصول على صيغة أوضح وأسهل في القراءة:
ثمة خيارات أخرى في dumps تستحق المعرفة:
sort_keys=True— يرتّب مفاتيح الكائن أبجديًا. مفيد عندما تحتاج إلى مخرجات ثابتة ومتوقعة (ملفات الإعدادات، بيانات الاختبار، مقارنات الـ diff).ensure_ascii=False— يكتب الأحرف غير الـ ASCII (مثل é أو ü أو 中 أو الحروف العربية) كما هي بدلًا من تحويلها إلى تسلسلات\u. عادةً ما يكون هذا هو الخيار الأنسب لملفات UTF-8.separators=(",", ":")— يعطيك أصغر حجم ممكن للمخرجات. وإذا جمعته معensure_ascii=False، فستحصل على أكثر صيغة JSON بصيغة UTF-8 إحكامًا يمكن إنتاجها.
قراءة ملف JSON في بايثون
الدالة json.load(file) — بدون حرف s — تقرأ مباشرةً من كائن ملف. والنمط الشائع هو استخدامها مع with open:
import json
with open("config.json") as f:
config = json.load(f)
print(config["version"])
ما في داعي تقرأ الملف كله كسلسلة نصية أولاً؛ الدالة json.load تقرأ مباشرة من كائن الملف.
كتابة ملف JSON في بايثون
الدالة json.dump(data, file) هي المقابلة لعملية الكتابة في الملف:
import json
data = {"created": "2026-01-01", "items": [1, 2, 3]}
with open("state.json", "w") as f:
json.dump(data, f, indent=2)
خيار indent يشتغل هنا كمان. افتح الملف الناتج وسترى مستند JSON منسقًا بشكل جميل.
قراءة استجابة JSON من واجهة برمجية (API)
معظم مكتبات HTTP تُرجع لك البيانات كـ bytes أو نص، وأنت تستدعي json.loads لتحويلها إلى بيانات قابلة للاستخدام:
import json
import urllib.request
with urllib.request.urlopen("https://api.example.com/users/1") as response:
text = response.read().decode("utf-8")
user = json.loads(text)
print(user["name"])
مكتبة requests (نتناولها في درس مستقل) تختصر عليك هذه الخطوة، إذ توفّر التابع .json() على كائن الاستجابة والذي يستدعي json.loads نيابةً عنك.
ماذا يستطيع JSON تمثيله وماذا لا يستطيع
نظام الأنواع في JSON أضيق من نظام الأنواع في بايثون. إليك جدول المقابلة في الاتجاهين:
| بايثون | JSON |
|---|---|
dict | object |
list, tuple | array |
str | string |
int, float | number |
True | true |
False | false |
None | null |
لاحظ أن الـ tuple يعود بعد الترميز وفكّه على هيئة قائمة (list)، أي أن خاصيّة كونه tuple تضيع في الطريق. أما المجموعات (set) والأصناف المخصّصة وكائنات datetime فلا يمكن تحويلها إلى JSON افتراضيًا، وستصطدم بالخطأ TypeError: Object of type X is not JSON serializable.
التعامل مع datetime والكائنات المخصّصة
هناك طريقتان شائعتان لحلّ هذه المشكلة.
الطريقة الأولى: حوّل البيانات إلى بنية متوافقة مع JSON أولًا. نفّذ التحويل يدويًا في الكود، ثم مرّر قاموسًا عاديًا إلى دالة التحويل:
مرر دالة default=. ستقوم json.dumps باستدعائها مع كل قيمة لا تعرف كيف تحوّلها:
عند قراءة البيانات مرة أخرى، عليك أن تُحوِّل سلاسل ISO إلى datetime بنفسك — لأن JSON لا يحتفظ بالنوع الأصلي للبيانات.
تحويل القاموس إلى JSON والعودة منه
اختبار سريع للتأكد من أن القواميس تمر عبر JSON دون أي خلل:
القيم متساوية، لكنها كائنات مختلفة. وهذه في الحقيقة حيلة مفيدة جدًا — استخدام json.dumps ثم json.loads يُعتبر طريقة سريعة وبسيطة لعمل نسخة عميقة (deep copy) من أي بنية متوافقة مع JSON.
مثال عملي من الواقع
سكربت صغير يقرأ ملف إعدادات JSON، يُعدِّل أحد الحقول، ثم يحفظه مرة أخرى:
import json
from pathlib import Path
config_path = Path("settings.json")
# Load (with a default if the file doesn't exist).
if config_path.exists():
config = json.loads(config_path.read_text())
else:
config = {"theme": "dark", "last_opened": None}
# Update.
config["last_opened"] = "2026-01-15"
# Save, pretty-printed.
config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False))
هكذا تكتمل دورة "قراءة، تعديل، كتابة" لملفات JSON في أقل من اثني عشر سطرًا.
بعض العادات الجيدة
- استخدم دائمًا
with open(...)عند التعامل مع الملفات. فملفات JSON في النهاية مجرد نصوص، وتنطبق عليها نفس قواعد التعامل مع الملفات. - فضّل
indent=2للملفات التي يقرأها البشر كملفات الإعدادات والبيانات التجريبية والبيانات المُصدَّرة. أما في البيانات المرسلة عبر الشبكة فالأفضل تجاهله لأن الحجم المضغوط أهم. - اضبط
ensure_ascii=Falseللإخراج بترميز UTF-8 حتى تظل الأسماء العربية أو الحروف اللاتينية المُشكَّلة مقروءة كما هي. - تحقّق من صحة البيانات عبر
try/except json.JSONDecodeErrorعند تحليل بيانات قادمة من مصدر خارجي لا تتحكم فيه.
ماذا بعد؟
تتعامل JSON مع البيانات على شكل مفتاح وقيمة. الأداة التالية في نفس الفصل هي CSV، وهي الصيغة الجدولية التي يدعمها بايثون داخليًا، والتي ستجدها خلف معظم ملفات جداول البيانات المُصدَّرة التي ستصادفها.
الأسئلة الشائعة
كيف أحلّل نص JSON في بايثون؟
استخدم json.loads(text) إذا كان لديك نص JSON، أو json.load(file) إذا كنت تقرأ من ملف. الاثنان يُرجعان قاموس بايثون (dict) أو قائمة حسب محتوى JSON. مثال: data = json.loads('{"name": "Rosa"}') ثم data['name'] ستكون قيمتها 'Rosa'.
كيف أحوّل قاموس بايثون إلى JSON؟
الدالة json.dumps(my_dict) تُرجع لك نص JSON جاهز. أما json.dump(my_dict, file) فتكتب مباشرة إلى ملف. ولو أردت إخراجاً منسّقاً وسهل القراءة، استخدم المعامل indent=2 هكذا: json.dumps(data, indent=2).
ما الفرق بين json.loads و json.load؟
loads (بحرف الـ s) تستقبل نصاً (string)، بينما load (بدون s) تستقبل كائن ملف (file object). نفس القاعدة تنطبق على dumps مقابل dump. تذكّر أن حرف الـ s يرمز إلى string — وهذه أسهل طريقة للتمييز بينهما.
كيف أتعامل مع التواريخ والكائنات المخصّصة في JSON؟
JSON لا يدعم نوع تاريخ أصلاً، لذلك الحل المعتاد هو تمرير التاريخ كنص بصيغة ISO-8601 ثم تحليله يدوياً عند القراءة. أما الكائنات المخصّصة فأمامك خياران: إمّا تمرير دالة عبر default= إلى json.dumps تُرجع تمثيلاً متوافقاً مع JSON، أو تحويل الكائن إلى dict يدوياً قبل التسلسل.