2025, Oct 17 15:31

asyncpg में JSONB स्ट्रिंग क्यों लौटता है और इसे सही तरीके से डिकोड कैसे करें

asyncpg में PostgreSQL JSONB के स्ट्रिंग लौटने का कारण और हल जानें: set_type_codec के साथ json.dumps/json.loads पंजीकृत कर dict/list को सही तरह डिकोड करें।

asyncpg और PostgreSQL के JSONB के साथ काम करते समय, अक्सर हैरानी तब होती है जब मूल Python ऑब्जेक्ट्स की जगह सादा स्ट्रिंग्स लौटती हैं। अगर आपकी क्वेरियाँ JSONB मानों को टेक्स्ट के रूप में लौटाती हैं, तो dict और list पर निर्भर क्रियाएँ असहज हो जाती हैं और टाइप से जुड़ी मान्यताएँ तुरंत टूट जाती हैं। नीचे इस समस्या का संक्षिप्त विश्लेषण और एक छोटा, भरोसेमंद समाधान दिया है।

समस्या को पुनः उत्पन्न करना

निम्नलिखित छोटा-सा स्क्रिप्ट एक JSONB लिटरल चुनता है और परिणाम प्रिंट करता है। आउटपुट से पता चलता है कि JSONB मान स्ट्रिंग के रूप में मिल रहा है।

import asyncio
import asyncpg
async def runner():
    link = await asyncpg.connect("postgres://user:pass@localhost/database")
    rows = await link.fetch("select '[1, 2, 3]'::jsonb as col;")
    for rec in rows:
        for k, v in rec.items():
            print("'" + k + "'")
            print("'" + v + "'")
if __name__ == "__main__":
    asyncio.run(runner())

यहाँ सूची (list) की अपेक्षा थी, लेकिन आउटपुट में उद्धृत स्ट्रिंग दिखाई देती है। निरीक्षण से पुष्टि होती है कि मान का टाइप str है।

क्या हो रहा है और क्यों

asyncpg में JSONB को स्वतः मूल Python प्रकारों में बदलवाने के लिए आपको jsonb के लिए एक type codec पंजीकृत करना होता है। इस स्पष्ट codec के बिना, इस स्थिति में मान टेक्स्ट के रूप में लौटाया जाता है। यह व्यवहार कम्युनिटी Q&A में दी गई सलाह और लाइब्रेरी के ऑटोमैटिक JSON कनवर्ज़न वाले उदाहरण के अनुरूप है, जो jsonb पर set_type_codec कॉल करने की आवश्यकता दिखाते हैं।

asyncpg के साथ jsonb मानों को अपने-आप Python ऑब्जेक्ट्स में डिकोड कराने के लिए, आपको स्पष्ट रूप से एक कस्टम type codec पंजीकृत करना होगा।

समाधान: jsonb के लिए codec रजिस्टर करें

codec रजिस्टर करने से asyncpg को पता चलता है कि JSONB को serialize और deserialize कैसे करना है। PostgreSQL और Python डेटा स्ट्रक्चर्स के बीच round-trip के लिए json.dumps और json.loads पर्याप्त हैं।

import asyncio
import asyncpg
import json
async def run_fixed():
    dbh = await asyncpg.connect("postgres://user:pass@localhost/database")
    await dbh.set_type_codec(
        'jsonb',
        encoder=json.dumps,
        decoder=json.loads,
        schema='pg_catalog'
    )
    result = await dbh.fetch("select '[1, 2, 3]'::jsonb as col;")
    for rec in result:
        parsed = rec['col']
        print(parsed, type(parsed))  # आउटपुट: [1, 2, 3] <class 'list'>
    await dbh.close()
if __name__ == "__main__":
    asyncio.run(run_fixed())

set_type_codec जोड़ने के बाद JSONB मान सही Python dicts और lists के रूप में मिलते हैं, इसलिए आगे का कोड सीधे उनके साथ काम कर सकता है।

यह क्यों मायने रखता है

डेटा पाइपलाइनों का निर्माण, वैलिडेशन लॉजिक लिखना, या एप्लिकेशन कोड को डेटा देने वाली क्वेरियाँ बनाते समय सुसंगत प्रकार अत्यंत महत्वपूर्ण होते हैं। JSONB को स्ट्रिंग्स के रूप में पाना आपको ऐड-हॉक पार्सिंग करने पर मजबूर करता है, सूक्ष्म बग्स का जोखिम बढ़ाता है, और कनवर्ज़न लॉजिक को पूरे कोडबेस में बिखेर देता है। इस व्यवहार को कनेक्शन लेयर पर केंद्रीकृत रखने से आपका डेटा मॉडल पूर्वानुमेय रहता है और कोड सरल बनता है।

निष्कर्ष

यदि आप asyncpg के साथ JSONB से मूल Python ऑब्जेक्ट्स की उम्मीद करते हैं, तो set_type_codec के साथ json.dumps और json.loads का उपयोग करके स्पष्ट रूप से jsonb codec रजिस्टर करें। प्राप्त प्रकारों को शुरुआती चरण में ही जाँचें और कनवर्ज़न नीति को कनेक्शन सेटअप के पास रखें। यह छोटा-सा कदम टाइप मिसमैच से बचाता है और आपके I/O बॉउंड्री को साफ-सुथरा रखता है।

यह लेख StackOverflow के प्रश्न (द्वारा: CoderFF) और Preston Johnson के उत्तर पर आधारित है।