QSerializer sudah mati, QSerializer berumur panjang

Sudah beberapa bulan sejak saya di sini berbicara tentang proyek perpustakaan berbasis Qt saya untuk membuat serial data dari tampilan objek ke JSON / XML dan sebaliknya.



Dan betapapun bangganya saya dengan arsitektur yang dibangun, saya harus mengakui - implementasinya ternyata, terus terang, kontroversial.



Semua ini menghasilkan revisi skala besar, yang hasilnya akan dibahas dalam artikel ini. Untuk detailnya - di bawah potongan!







QSerializer meninggal



QSerializer memiliki kekurangan, yang solusinya sering kali menjadi kekurangan yang lebih besar, berikut beberapa di antaranya:



  • Sangat mahal (serialisasi, menjaga penjaga properti di heap, mengontrol umur penjaga, dll.)
  • Bekerja hanya dengan kelas berbasis QObject
  • Objek "kompleks" bersarang dan koleksinya juga harus berbasis QObject
  • Ketidakmampuan untuk melengkapi koleksi selama deserialisasi
  • Hanya bersarang secara teoritis tak terbatas
  • Ketidakmampuan untuk bekerja dengan jenis objek "kompleks" yang signifikan, karena larangan menyalin dari QObject
  • Perlunya registrasi tipe wajib dalam sistem objek meta Qt
  • Masalah umum "pustaka" seperti masalah penautan dan portabilitas antar platform


Antara lain, saya ingin dapat membuat serial objek apa pun "di sini dan sekarang", ketika ini harus menggunakan metode pengikatan yang sangat besar di namespace QSerializer.



QSerializer panjang umur!



QSerializer belum selesai. Itu perlu untuk menemukan solusi di mana pengguna tidak akan bergantung pada QObject, akan mungkin untuk bekerja dengan tipe nilai dan dengan biaya lebih rendah.



Dalam komentar di artikel sebelumnya , penggunamicrolaperhatikan bahwa Anda dapat berpikir untuk menggunakan Q_GADGET .



Keuntungan Q_GADGET :



  • Tidak ada batasan penyalinan
  • Memiliki instance statis QMetaObject untuk mengakses properti


Mengandalkan Q_GADGET , saya harus mempertimbangkan kembali pendekatan cara membuat JSON dan XML berdasarkan bidang kelas yang dideklarasikan. Masalah "biaya tinggi" dimanifestasikan terutama karena:



  • Ukuran kelas penyimpanan besar (setidaknya 40 byte)
  • Mengalokasikan heap untuk entitas penjaga baru untuk setiap properti dan mengontrol TTL mereka


Untuk mengurangi biaya, saya merumuskan persyaratan berikut:

Kehadiran di setiap objek serial yang dapat diserialkan metode kawat untuk membuat serial / deserialisasi semua properti kelas dan adanya metode untuk membaca dan menulis nilai untuk setiap properti menggunakan format yang ditetapkan untuk properti ini

Makro



Menyiasati pengetikan kuat C ++ yang memperumit serialisasi otomatis tidaklah mudah, dan pengalaman sebelumnya telah menunjukkan hal ini. Makro, di sisi lain, dapat menjadi bantuan yang bagus untuk memecahkan masalah seperti itu (hampir seluruh sistem objek meta Qt dibangun di atas makro), karena dengan menggunakan makro, Anda dapat melakukan pembuatan kode pada metode dan properti.



Ya, makro seringkali jahat dalam bentuknya yang paling murni - mereka hampir tidak mungkin untuk di-debug. Saya dapat membandingkan menulis makro untuk menghasilkan kode dengan meletakkan sepatu kristal di tumit bos Anda, tetapi sulit bukan berarti tidak mungkin!



Penyimpangan lirik tentang makro

โ€” , , ยซยป (). .



QSerializer saat ini menyediakan 2 cara untuk mendeklarasikan kelas sebagai dapat diserialkan: mewarisi dari kelas QSerializer atau menggunakan makro pembuatan kode QS_CLASS .



Pertama-tama, Anda perlu mendefinisikan makro Q_GADGET di badan kelas, ini memberikan akses ke staticMetaObject, ini akan menyimpan properti yang dihasilkan oleh makro.



Mewarisi dari QSerializer akan memungkinkan Anda mentransmisikan beberapa objek yang dapat diserialkan ke satu jenis dan membuat serialisasi secara massal.



Kelas QSerializer berisi 4 metode explorer yang memungkinkan Anda mengurai properti objek dan satu metode virtual untuk mendapatkan instance QMetaObject:



QJsonValue toJson() const
void fromJson(const QJsonValue &)
QDomNode toXml() const
void fromXml(const QDomNode &)
virtual const QMetaObject * metaObject() const


Q_GADGET tidak memiliki semua meta-object binding yang disediakan oleh Q_OBJECT .



Di dalam QSerializer, instance staticMetaObject akan mewakili kelas QSerializer, tetapi tidak diturunkan darinya dengan cara apa pun, jadi saat membuat kelas berbasis QSerializer, Anda harus mengganti metode metaObject. Anda dapat menambahkan makro QS_SERIALIZER ke badan kelas dan itu akan menimpa metode metaObject untuk Anda.



Selain itu, menggunakan staticMetaObject alih-alih menyimpan instance QMetaObject di setiap objek menghemat 40 byte dari ukuran kelas, yah, secara umum, cantik!



Jika Anda tidak ingin mewarisi karena alasan tertentu, Anda dapat menentukan makro QS_CLASS di badan kelas serial, ini akan menghasilkan semua metode yang diperlukan alih-alih mewarisi dari QSerializer.



Deklarasi bidang



Secara terpisah, ada 4 jenis data yang dapat diserialkan dalam JSON dan XML, yang tanpanya serialisasi ke format ini tidak akan lengkap. Tabel menunjukkan jenis data dan makro yang sesuai sebagai cara untuk menjelaskan:

Tipe data Deskripsi Makro
bidang bidang biasa tipe primitif (berbagai angka, string, bendera) QS_FIELD
koleksi kumpulan nilai tipe data primitif QS_COLLECTION
Sebuah Objek struktur kompleks bidang atau struktur kompleks lainnya QS_OBJECT
koleksi benda satu set struktur data kompleks dengan tipe yang sama QS_COLLECTION_OBJECTS


Kami akan berasumsi bahwa kode yang menghasilkan makro ini disebut deskripsi, dan makro yang menghasilkannya disebut deskriptif.



Hanya ada satu prinsip untuk menghasilkan deskripsi - untuk bidang tertentu, buat properti JSON dan XML dan tentukan metode untuk menulis / membaca nilai.



Mari kita analisis pembuatan deskripsi JSON menggunakan contoh bidang tipe data primitif:



/* Create JSON property and methods for primitive type field*/
#define QS_JSON_FIELD(type, name)                                                           
    Q_PROPERTY(QJsonValue name READ get_json_##name WRITE set_json_##name)                  
    private:                                                                                
        QJsonValue get_json_##name() const {                                                
            QJsonValue val = QJsonValue::fromVariant(QVariant(name));                       
            return val;                                                                     
        }                                                                                   
        void set_json_##name(const QJsonValue & varname){                                   
            name = varname.toVariant().value<type>();                                       
        }   
...
int digit;
QS_JSON_FIELD(int, digit)  


Untuk bidang digit int, digit properti dengan tipe QJsonValue akan dihasilkan dan metode tulis dan baca pribadi - get_json_digit dan set_json_digit akan ditentukan, mereka kemudian akan menjadi konduktor untuk membuat serial / deserialisasi bidang digit menggunakan JSON.



Bagaimana ini bisa terjadi?
name digit, ('##') digit โ€” .



type int. , type int . QVariant int .



Dan inilah generasi deskripsi JSON untuk struktur yang kompleks:



/* Generate JSON-property and methods for some custom class */
/* Custom type must be provide methods fromJson and toJson */
#define QS_JSON_OBJECT(type, name)
    Q_PROPERTY(QJsonValue name READ get_json_##name WRITE set_json_##name)
    private:
    QJsonValue get_json_##name() const {
        QJsonObject val = name.toJson();
        return QJsonValue(val);
    }
    void set_json_##name(const QJsonValue & varname) {
        if(!varname.isObject())
        return;
        name.fromJson(varname);
    } 
...
SomeClass object;
QS_JSON_OBJECT(SomeClass, object)


Objek kompleks adalah sekumpulan properti bertingkat yang akan berfungsi sebagai satu properti "besar" untuk kelas eksternal, karena objek tersebut juga akan memiliki metode kawat. Yang perlu Anda lakukan untuk ini adalah memanggil metode panduan yang sesuai dalam metode baca dan tulis struktur yang kompleks.



Pembuatan kelas



Jadi, kami memiliki infrastruktur yang cukup sederhana untuk membuat kelas yang dapat serial.



Jadi, misalnya, Anda dapat membuat kelas dapat diserialkan dengan mewarisi dari QSerializer:



class SerializableClass : public QSerializer {
Q_GADGET
QS_SERIALIZER
QS_FIELD(int, digit)
QS_COLLECTION(QList, QString, strings)
};


Atau seperti ini, menggunakan makro QS_CLASS :



class SerializableClass {
Q_GADGET
QS_CLASS
QS_FIELD(int, digit)
QS_COLLECTION(QList, QString, strings)
};


Contoh serialisasi JSON
:



class CustomType : public QSerializer {
Q_GADGET
QS_SERIALIZER
QS_FIELD(int, someInteger)
QS_FIELD(QString, someString)
};

class SerializableClass : public QSerializer {
Q_GADGET
QS_SERIALIZER
QS_FIELD(int, digit)
QS_COLLECTION(QList, QString, strings)
QS_OBJECT(CustomType, someObject)
QS_COLLECTION_OBJECTS(QVector, CustomType, objects)
};


, :



SerializableClass serializable;
serializable.someObject.someString = "ObjectString";
serializable.someObject.someInteger = 99999;
for(int i = 0; i < 3; i++) {
    serializable.digit = i;
    serializable.strings.append(QString("list of strings with index %1").arg(i));
    serializable.objects.append(serializable.someObject);
}
QJsonObject json = serializable.toJson();


JSON:



{
    "digit": 2,
    "objects": [
        {
            "someInteger": 99999,
            "someString": "ObjectString"
        },
        {
            "someInteger": 99999,
            "someString": "ObjectString"
        },
        {
            "someInteger": 99999,
            "someString": "ObjectString"
        }
    ],
    "someObject": {
        "someInteger": 99999,
        "someString": "ObjectString"
    },
    "strings": [
        "list of strings with index 0",
        "list of strings with index 1",
        "list of strings with index 2"
    ]
}


โ€” , XML , toJson toXml.



example.



Batasan



Kolom Tunggal Jenis yang



ditentukan pengguna atau primitif harus menyediakan konstruktor default.



Koleksi



Kelas koleksi harus memiliki template dan menyediakan metode yang jelas, pada, ukuran, dan penambahan. Anda dapat menggunakan koleksi Anda sendiri, sesuai dengan ketentuan. Koleksi Qt yang memenuhi ketentuan ini: QVector, QStack, QList, QQueue.



Versi Qt



Versi Qt Minimum 5.5.0 Versi yang

diuji minimum Qt 5.9.0

Versi yang diuji maksimum Qt 5.15.0

CATATAN: Anda dapat berpartisipasi dalam pengujian dan pengujian QSerializer pada versi Qt sebelumnya



Hasil



Saat mengerjakan ulang QSerializer, saya sama sekali tidak menetapkan diri saya tugas untuk menguranginya secara signifikan. Namun, ukurannya turun dari 9 file menjadi 1, yang juga mengurangi kerumitannya. Sekarang QSerializer bukan lagi perpustakaan dalam bentuk biasa kami, sekarang hanya file header, yang cukup untuk disertakan dalam proyek dan mendapatkan semua fungsionalitas untuk serialisasi / deserialisasi yang nyaman. Pengembangan dimulai kembali pada bulan Maret, arsitektur yang rumit ditemukan dan proyek tersebut dipenuhi dengan dependensi, kruk, yang ditulis ulang dari 0 beberapa kali. Dan semua agar pada akhirnya berubah menjadi file kecil.



Bertanya pada diri sendiri: "Apakah dia sepadan dengan usaha yang dihabiskan untuk itu?", Saya akan menjawab: "Ya, dia." Saya sudah mencobanya di proyek tempur saya dan hasilnya menyenangkan saya.



Tautan

GitHub: tautan

Rilis terbaru: v1.1

Artikel sebelumnya: QSerializer: solusi untuk serialisasi JSON / XML



daftar Future sederhana



  • Pengurangan biaya yang substansial (dapat dilakukan bahkan lebih murah)
  • Kekompakan
  • Bekerja dengan tipe yang signifikan
  • Deskripsi dasar tentang data yang dapat bersambung
  • Dukungan untuk koleksi template apa pun yang menyediakan metode yang jelas, di, ukuran, dan tambahkan. Bahkan milik mereka sendiri
  • Koleksi yang sepenuhnya dapat berubah pada deserialization
  • Dukungan untuk semua tipe primitif populer
  • Dukungan untuk semua jenis kustom yang dijelaskan menggunakan QSerializer
  • Tidak perlu mendaftarkan tipe khusus



All Articles