QSerializer: सरल JSON / XML क्रमांकन के लिए समाधान

हेलो, हेब्र!

मैंने सोचा कि किसी तरह यह गलत तरीके से निकलता है - जावा, सी #, गो, पायथन, आदि में। फैशनेबल JSON और XML में ऑब्जेक्ट डेटा के आरामदायक क्रमांकन के लिए पुस्तकालय हैं, लेकिन C ++ में वे या तो भूल गए, या नहीं करना चाहते थे, या वास्तव में इसकी आवश्यकता नहीं थी, या यह सब जटिल है, या शायद सभी एक साथ। इसलिए मैंने इस बात को ठीक करने का फैसला किया।

सभी विवरण, हमेशा की तरह, कटौती के तहत।

छवि

पृष्ठभूमि


एक बार फिर, मैंने अगला पालतू-प्रोजेक्ट लेने का फैसला किया, जिसका सार क्लाइंट-सर्वर एक्सचेंज था, जबकि कई का पसंदीदा सर्वर रास्पबेरीपी था। अन्य बातों के अलावा, मुझे "अंक बचाने" के मुद्दे में दिलचस्पी थी - इसलिए मैं जितना संभव हो उतना सरल हो सकता था, प्रोटोटाइप के ढांचे के भीतर, बाहर निकलने से पहले वस्तु की स्थिति को बचा सकता हूं और अगली शुरुआत में ठीक हो सकता हूं। पायथन के लिए मेरी अनुचित दुश्मनी और क्यूटी के प्रति मेरे बहुत गर्म रवैये के कारण, मैंने क्यूटी और सी ++ को चुना। JSON को पार्स करने के लिए लेखन कक्षाएं और स्पेगेटी फ़ंक्शन अभी भी एक खुशी थी, मुझे अपनी समस्या के लिए कुछ सार्वभौमिक और एक ही समय में आसान समाधान की आवश्यकता थी। "हमें यह पता लगाना होगा," मैंने खुद से कहा।

पहले, शर्तों के बारे में थोड़ा:
सीरियलाइजेशन बिट्स के अनुक्रम में डेटा संरचना का अनुवाद करने की प्रक्रिया है। क्रमबद्धता ऑपरेशन का विलोम डीरियलाइजेशन (स्ट्रक्चरिंग) ऑपरेशन है - बिट क्रम से डेटा संरचना की प्रारंभिक स्थिति की बहाली।
गो के पास एक बहुत ही उपयोगी "देशी" एन्कोडिंग / जसन पैकेज है जो मार्शल विधि का उपयोग करते हुए ऑब्जेक्ट के पूर्ण क्रमांकन और अनमरशाल का उपयोग करके रिवर्स स्ट्रक्चरिंग की अनुमति देता है (इस लाइब्रेरी के कारण, मुझे पहली बार मार्शलिंग के बारे में एक गलत विचार आया था, लेकिन डेसरे स्पेररे क्यू हिच इंट्रा ) । इस पैकेज की अवधारणाओं के बाद, मुझे जावा - जीएसओएन के लिए एक और पुस्तकालय मिला , जो एक बहुत ही सुखद उत्पाद बन गया, इसका उपयोग करना खुशी की बात थी।

मैंने इन पुस्तकालयों के बारे में जो कुछ भी पसंद किया, मैंने सोचा और इस निष्कर्ष पर पहुंचा कि यह उनके उपयोग में आसानी थी। लचीली कार्यक्षमता और सभी एक कॉल में, JSON में क्रमांकन के लिए यह toJson विधि को कॉल करने और उस पर क्रमिक ऑब्जेक्ट को पास करने के लिए पर्याप्त था। हालाँकि, C ++ स्वयं नहीं है, डिफ़ॉल्ट रूप से, एक वर्ग के क्षेत्रों के बारे में पर्याप्त जानकारी प्रदान करने के लिए उचित मेटाबोज़ की क्षमताएँ हैं, जैसा कि जावा (ClassName.class) में किया गया है।

मुझे केवल क्यूटी प्लेटफॉर्म के लिए क्यूजसन पसंद था, लेकिन फिर भी यह पूर्वोक्त पुस्तकालयों द्वारा उत्पन्न उपयोग की आसानी के बारे में मेरी समझ में नहीं आया। इसलिए परियोजना दिखाई दी, जिस पर यहां चर्चा की जाएगी।

छोटा अस्वीकरण:इस तरह के तंत्र आपके लिए डेटा व्याख्या की समस्या को हल नहीं करेंगे। उन सभी से जो आप उनसे प्राप्त कर सकते हैं, आपके लिए डेटा का अधिक सुविधाजनक रूप में रूपांतरण है।

QSerializer प्रोजेक्ट संरचना


परियोजना और उदाहरण GitHub ( रिपॉजिटरी से लिंक ) पर देखे जा सकते हैं विस्तृत इंस्टॉलेशन निर्देश भी दिए गए हैं।

आर्किटेक्चरल आत्महत्या का विरोध करते हुए, मैं एक आरक्षण दूंगा कि यह अंतिम संस्करण नहीं है। परित्यक्त पत्थरों के बावजूद काम जारी रहेगा, लेकिन इच्छाओं के लिए भत्ता बनाया गया है।
QSerializer पुस्तकालय की सामान्य संरचनात्मक निर्भरताएँ

इस परियोजना का मुख्य लक्ष्य सी ++ सुलभ और प्राथमिक में उपयोगकर्ता-फ्रेंडली डेटा प्रारूप का उपयोग करके क्रमांकन बनाना है। उत्पाद के गुणवत्ता विकास और रखरखाव की कुंजी इसकी वास्तुकला है। मैं इस बात को बाहर नहीं करता हूं कि कार्यान्वयन के अन्य तरीके इस लेख की टिप्पणियों में दिखाई दे सकते हैं, इसलिए मैंने "रचनात्मकता के लिए थोड़ी जगह" छोड़ दी। यदि आप कार्यान्वयन को बदलते हैं, तो आप या तो प्रॉपर्टीकीपर इंटरफ़ेस का एक नया कार्यान्वयन जोड़ सकते हैं या कारखाने के तरीकों को बदल सकते हैं ताकि आपको QSerializer फ़ंक्शन में कुछ भी बदलना न पड़े।

क्षेत्र की घोषणा


क्यूटी में मेटा-ऑब्जेक्ट जानकारी एकत्र करने का एक तरीका यह है कि इसका वर्णन क्यूटी के मेटा-ऑब्जेक्ट सिस्टम में ही किया जाए। शायद यह सबसे आसान तरीका है। MOC संकलन समय पर सभी आवश्यक मेटाडेटा उत्पन्न करेगाआप वर्णित ऑब्जेक्ट पर मेटाऑब्जेक्ट पद्धति को कॉल कर सकते हैं, जो QMetaObject क्लास का एक उदाहरण लौटाएगा, जिसके साथ हमें काम करना होगा।

अनुक्रमित होने के लिए फ़ील्ड घोषित करने के लिए, आपको QObject से वर्ग को इनहेरिट करना होगा और इसमें Q_OBJECT मैक्रो को शामिल करना होगा , ताकि वर्ग प्रकार को आधार के रूप में क्वालीफाई करने के बारे में MOC को स्पष्ट किया जा सके।

इसके बाद, Q_PROPERTY मैक्रो वर्ग के सदस्यों का वर्णन करता है। हम Q_PROPERTY में वर्णित संपत्ति संपत्ति कहेंगेQSerializer USER ध्वज के बिना गुण को अनदेखा करेगा जो सत्य पर सेट है।

क्यों USER ध्वज
, , QML. . , Q_PROPERTY QML QSerializer .

class User : public QObject
{
Q_OBJECT
// Define data members to be serialized
Q_PROPERTY(QString name MEMBER name USER true)
Q_PROPERTY(int age MEMBER age USER true)
Q_PROPERTY(QString email MEMBER email USER true)
Q_PROPERTY(std::vector<QString> phone MEMBER phone USER true)
Q_PROPERTY(bool vacation MEMBER vacation USER true)
public:
  // Make base constructor
  User() { }
 
  QString name;
  int age{0};
  QString email;
  bool vacation{false};
  std::vector<QString> phone; 
};

Qt मेटा-ऑब्जेक्ट सिस्टम में गैर-मानक उपयोगकर्ता प्रकारों को घोषित करने के लिए, मैं QS_REGISTER मैक्रो का उपयोग करने का सुझाव देता हूं , जिसे qserializer.h में परिभाषित किया गया है। QS_REGISTER प्रकार भिन्नता को पंजीकृत करने की प्रक्रिया को स्वचालित करता है। हालाँकि, आप qRegisterMetaType < T > () के साथ प्रकारों को पंजीकृत करने की क्लासिक विधि का उपयोग कर सकते हैं मेटा-ऑब्जेक्ट सिस्टम के लिए, क्लास प्रकार ( T ) और क्लास पॉइंटर ( T *) पूरी तरह से अलग-अलग प्रकार के होते हैं, सामान्य प्रकार की सूची में उनके अलग-अलग पहचानकर्ता होंगे।

#define QS_METATYPE(Type) qRegisterMetaType<Type>(#Type) ;
#define QS_REGISTER(Type)       \
QS_METATYPE(Type)               \
QS_METATYPE(Type*)              \
QS_METATYPE(std::vector<Type*>) \
QS_METATYPE(std::vector<Type>)  \

class User;
void main()
{
// define user-type in Qt meta-object system
QS_REGISTER(User)
...
}

QSerializer नामस्थान


जैसा कि आप UML आरेख से देख सकते हैं, QSerializer में क्रमांकन और संरचना के लिए कई कार्य शामिल हैं। नाम स्थान वैचारिक रूप से QSerializer के घोषित सार को दर्शाता है। कोड में कहीं भी एक वस्तु बनाने की आवश्यकता के बिना एम्बेडेड कार्यक्षमता QSerializer के नाम के माध्यम से पहुँचा जा सकता है।

ऊपर वर्णित उपयोगकर्ता वर्ग के ऑब्जेक्ट के आधार पर JSON के निर्माण के उदाहरण का उपयोग करते हुए, आपको केवल QSerializer :: toJson विधि को कॉल करना होगा:

User u;
u.name = "Mike";
u.age = 25;
u.email = "example@exmail.com";
u.phone.push_back("+12345678989");
u.phone.push_back("+98765432121");
u.vacation = true;
QJsonObject json = QSerializer::toJson(&u);

और यहाँ परिणामी JSON है:

{
    "name": "Mike",
    "age": 25,
    "email": "example@exmail.com",
    "phone": [
        "+12345678989",
        "+98765432121"
    ],
    "vacation": true
}

किसी वस्तु को बनाने के दो तरीके हैं:

  • यदि आपको किसी वस्तु को संशोधित करने की आवश्यकता है
    User u;
    QJsonObject userJson;
    QSerializer::fromJson(&u, userJson);
  • यदि आपको कोई नई वस्तु प्राप्त करने की आवश्यकता है
    QJsonObject userJson;
    User * u = QSerializer::fromJson<User>(userJson);

उदाहरण फ़ोल्डर में अधिक उदाहरण और आउटपुट देखे जा सकते हैं

रखवाले


घोषित संपत्तियों के सुविधाजनक लेखन और पढ़ने को व्यवस्थित करने के लिए, QSerializer ने Keepers classes का उपयोग किया है , जिनमें से प्रत्येक एक पॉइंटर को ऑब्जेक्ट (QObject वंशज) और इसके QMetaProperty में से एक को संग्रहीत करता है। QMetaProperty अपने आप में विशेष मूल्य की नहीं है, वास्तव में यह केवल MOC के लिए घोषित संपत्ति वर्ग के विवरण के साथ एक वस्तु है। पढ़ने और लिखने के लिए, आपको एक विशिष्ट वर्ग ऑब्जेक्ट की आवश्यकता होती है, जहां इस संपत्ति का वर्णन किया जाता है - यह मुख्य चीज है जिसे आपको याद रखने की आवश्यकता है।

क्रमांकन के दौरान प्रत्येक क्रमिक क्षेत्र इसी प्रकार के संरक्षक को पारित किया जाता है। एक विशिष्ट प्रकार के वर्णित डेटा के लिए विशिष्ट कार्यान्वयन के लिए क्रमबद्धता और संरचना की कार्यक्षमता को संलग्न करने के लिए रखवाले की आवश्यकता होती है। मैंने 4 प्रकारों पर प्रकाश डाला:

  • QMetaSimpleKeeper - प्राइमरी डेटा प्रकारों के साथ संपत्ति कीपर
  • QMetaArrayKeeper - आदिम डेटा के सरणियों के साथ संपत्ति कीपर
  • QMetaObjectKeeper - नेस्टेड ऑब्जेक्ट्स का रक्षक
  • QMetaObjectArrayKeeper - नेस्टेड ऑब्जेक्ट्स के सरणियों का रक्षक

आकड़ों का प्रवाह

आदिम डेटा कस्टोडियन के दिल में JSON / XML से QVariant तक जानकारी का रूपांतरण है और इसके विपरीत, क्योंकि QMetaProperty डिफ़ॉल्ट रूप से QVariant के साथ काम करता है।

QMetaProperty prop;
QObject * linkedObj;
...
std::pair<QString, QJsonValue> QMetaSimpleKeeper::toJson()
{
    QJsonValue result = QJsonValue::fromVariant(prop.read(linkedObj));
    return std::make_pair(QString(prop.name()), result);
}

void QMetaSimpleKeeper::fromJson(const QJsonValue &val)
{
    prop.write(linkedObj, QVariant(val));
}

ऑब्जेक्ट कीपर्स JSON / XML से अन्य ट्रांसफॉर्मर की एक श्रृंखला की जानकारी के स्थानांतरण और इसके विपरीत पर आधारित हैं। ऐसे संरक्षक अपनी संपत्ति के साथ एक अलग वस्तु के रूप में काम करते हैं, जिसके पास अपने स्वयं के संरक्षक भी हो सकते हैं, उनका कार्य संपत्ति वस्तु से क्रमबद्ध डेटा एकत्र करना और उपलब्ध डेटा के अनुसार संपत्ति वस्तु की संरचना करना है।

QMetaProperty prop;
QObject * linkedObj;
...
void QMetaObjectKeeper::fromJson(const QJsonValue &json)
{
    ...
    QSerializer::fromJson(linkedObj, json.toObject());
}

std::pair<QString, QJsonValue> QMetaObjectKeeper::toJson()
{
    QJsonObject result = QSerializer::toJson(linkedObj);;
    return std::make_pair(prop.name(),QJsonValue(result));
}

रखवाले प्रॉपर्टीकीपर इंटरफ़ेस को लागू करते हैं, जिससे रखवाले का आधार सार वर्ग विरासत में मिला है। यह आपको एक्सएमएल या जेएसएन प्रारूप में दस्तावेजों को पार्स करने और रचना करने की अनुमति देता है क्रमिक रूप से ऊपर से नीचे, बस वर्णित गुणों को नीचे जा रहा है और गहराई में जा रहा है जैसा कि आप एम्बेडेड संपत्ति में उतरते हैं, यदि कोई हो, वर्णित संपत्ति में, कार्यान्वयन के विवरण के बिना।

प्रॉपर्टीकीपर इंटरफ़ेस
class PropertyKeeper
{
public:
    virtual ~PropertyKeeper() = default;
    virtual std::pair<QString, QJsonValue> toJson() = 0;
    virtual void fromJson(const QJsonValue&) = 0;
    virtual std::pair<QString, QDomNode> toXml() = 0;
    virtual void fromXml(const QDomNode &) = 0;
};

अभिभावक का कारखाना


चूंकि सभी संरक्षक एक इंटरफ़ेस लागू करते हैं, इसलिए सभी कार्यान्वयन एक सुविधाजनक स्क्रीन के पीछे छिपे होते हैं, और इन कार्यान्वयनों का एक सेट KeepersFactory कारखाने द्वारा प्रदान किया जाता है। कारखाने में स्थानांतरित की गई वस्तु से, आप इसके QMetaObject के माध्यम से सभी घोषित संपत्ति की सूची प्राप्त कर सकते हैं, जिसके आधार पर कस्टोडियन का प्रकार निर्धारित किया जाता है।

KeepersFactory कारखाना कार्यान्वयन
    const std::vector<int> simple_t =
    {
        qMetaTypeId<int>(),
        qMetaTypeId<bool>(),
        qMetaTypeId<double>(),
        qMetaTypeId<QString>(),
    };

    const std::vector<int> array_of_simple_t =
    {
        qMetaTypeId<std::vector<int>>(),
        qMetaTypeId<std::vector<bool>>(),
        qMetaTypeId<std::vector<double>>(),
        qMetaTypeId<std::vector<QString>>(),
    };
...
PropertyKeeper *KeepersFactory::getMetaKeeper(QObject *obj, QMetaProperty prop)
{
    int t_id = QMetaType::type(prop.typeName());
    if(std::find(simple_t.begin(), simple_t.end(), t_id) != simple_t.end())
        return new QMetaSimpleKeeper(obj,prop);
    else if (std::find(array_of_simple_t.begin(),array_of_simple_t.end(), t_id) != array_of_simple_t.end())
    {
        if( t_id == qMetaTypeId<std::vector<int>>())
            return new QMetaArrayKeeper<int>(obj, prop);

        else if(t_id == qMetaTypeId<std::vector<QString>>())
            return new QMetaArrayKeeper<QString>(obj, prop);

        else if(t_id == qMetaTypeId<std::vector<double>>())
            return new QMetaArrayKeeper<double>(obj, prop);

        else if(t_id == qMetaTypeId<std::vector<bool>>())
            return new QMetaArrayKeeper<bool>(obj, prop);
    }
    else
    {
        QObject * castobj = qvariant_cast<QObject *>(prop.read(obj));
        if(castobj)
            return new QMetaObjectKeeper(castobj,prop);
        else if (QString(prop.typeName()).contains("std::vector<"))
        {
            QString t = QString(prop.typeName()).remove("std::vector<").remove(">");
            int idOfElement = QMetaType::type(t.toStdString().c_str());
            if(QMetaType::typeFlags(idOfElement).testFlag(QMetaType::PointerToQObject))
                return new QMetaObjectArrayKeeper(obj, prop);
        }
    }
    throw QSException(UnsupportedPropertyType);
}

std::vector<PropertyKeeper *> KeepersFactory::getMetaKeepers(QObject *obj)
{
    std::vector<PropertyKeeper*> keepers;
    for(int i = 0; i < obj->metaObject()->propertyCount(); i++)
    {
        if(obj->metaObject()->property(i).isUser(obj))
            keepers.push_back(getMetaKeeper(obj, obj->metaObject()->property(i)));
    }
    return keepers;
}
...


अभिभावक कारखाने की एक प्रमुख विशेषता किसी वस्तु के लिए अभिभावकों की एक पूरी श्रृंखला प्रदान करने की क्षमता है, और आप प्रकार पहचानकर्ताओं के साथ निरंतर संग्रह संपादित करके समर्थित आदिम प्रकारों की सूची का विस्तार कर सकते हैं। रखवाले की प्रत्येक श्रृंखला वस्तु के लिए संपत्ति के लिए एक तरह का नक्शा है। जब एक KeepFFactory वस्तु को नष्ट कर दिया जाता है, तो उसके द्वारा प्रदान किए गए रखवाले की श्रृंखला के लिए आवंटित की गई मेमोरी को मुक्त कर दिया जाता है।

सीमाएं और व्यवहार

परिस्थितिव्यवहार
किसी ऐसी वस्तु को क्रमबद्ध करने का प्रयास जिसका प्रकार QObject से विरासत में नहीं मिला हैसंकलन त्रुटि
क्रमबद्धता / स्तरीकरण का प्रयास करते समय अघोषित प्रकारQSException :: UnsupportedPropertyType अपवाद
सरल / t और array_of_simple_t संग्रह में वर्णित एक आदिम प्रकार के साथ एक वस्तु को क्रमबद्ध / संरचना करने का प्रयास।QSException::UnsupportedPropertyType. , — ,
JSON/XML
propertyes, JSON/XMLpropertyes . — propertyes
JSON propertyQSException


मेरी राय में, परियोजना सार्थक निकली, क्योंकि यह लेख लिखा गया था। अपने लिए, मैंने निष्कर्ष निकाला कि कोई सार्वभौमिक समाधान नहीं हैं, आपको हमेशा कुछ त्याग करना होगा। लचीलेपन का उपयोग करके, उपयोग, कार्यक्षमता के मामले में, आप सादगी को मारते हैं, और इसके विपरीत।

मैं आपसे QSerializer का उपयोग करने का आग्रह नहीं करता, मेरा लक्ष्य एक प्रोग्रामर के रूप में मेरा अपना विकास है। बेशक, मैं भी किसी की मदद करने के लक्ष्य का पीछा करता हूं, लेकिन पहली जगह में - बस खुशी मिल रही है। सकारात्मक रहें)

All Articles