QSerializer已死,万岁QSerializer

自从我在这里谈论基于Qt的库项目以来已经过去了几个月,该项目用于将数据从对象视图序列化为JSON / XML,反之亦然。



而且,无论我对构建的体系结构有多么自豪,我都必须承认-坦率地说,实现存在争议。



所有这些都导致了大规模处理,其结果将在本文中进行讨论。有关详细信息-下切!







QSerializer死亡



QSerializer有缺点,其解决方案通常变成更大的缺点,以下是其中的一些:



  • 非常昂贵(序列化,将属性保持器保留在堆上,控制保持器的生存期等)
  • 仅与基于QObject的类一起使用
  • 嵌套的“复杂”对象及其集合也必须基于QObject
  • 反序列化期间无法补充集合
  • 仅理论上无限嵌套
  • 由于禁止从QObject复制,因此无法使用重要类型的“复杂”对象
  • 在Qt元对象系统中强制注册类型的需求
  • 常见的“库”问题,例如平台之间的链接和可移植性问题


除其他外,我希望能够“在这里和现在”序列化任何对象,而这必须在QSerializer命名空间中使用方法的巨大绑定。



QSerializer万岁!



QSerializer尚未完成。有必要提出一种解决方案,在该解决方案中,用户将不再依赖QObject,而是可以以较低的成本使用值类型。



上一篇文章评论,用户微喇注意,您可以考虑使用Q_GADGET



优点Q_GADGET



  • 没有复制限制
  • 具有QMetaObject的静态实例以访问属性


依靠Q_GADGET,我不得不重新考虑基于声明的类字段创建JSON和XML的方法。“高成本”的问题主要是由于:



  • 大型存储类大小(至少40个字节)
  • 为每个属性分配新监护人实体的堆并控制其TTL


为了降低成本,我制定了以下要求:

每个可序列化对象中都存在用于对类的所有属性进行序列化/反序列化的连接方法,以及使用为此属性分配的格式来读取和写入每个属性的值的方法的存在

巨集



解决C ++的强类型化(使自动序列化变得复杂)并不容易,以前的经验已经证明了这一点。另一方面,宏可以作为解决此类问题的绝妙帮助(几乎整个Qt元对象系统都是基于宏构建的),因为使用宏可以执行方法和属性的代码生成。



是的,宏通常以其最纯粹的形式是邪恶的-几乎无法调试。我可以比较编写宏以生成代码,而不是将水晶鞋放在老板的脚跟上,但是困难并不意味着不可能!



关于宏的抒情离题

— , , «» (). .



QSerializer当前提供两种将类声明为可序列化的方法:从QSerializer类继承或使用QS_CLASS代码生成宏



首先,您需要在类的主体中定义Q_GADGET,这样可以访问staticMetaObject,它将存储由宏生成的属性。



从QSerializer继承将使您可以将多个可序列化的对象转换为一种类型并批量序列化它们。



QSerializer类包含4个资源管理器方法和一个虚拟方法来获取QMetaObject的实例,这些资源管理器允许您解析对象的属性,而一个虚拟方法可以使您获得QMetaObject的实例:



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


Q_GADGET没有Q_OBJECT提供的所有元对象绑定



在QSerializer内,staticMetaObject实例将表示QSerializer类,但不会以任何方式派生自该类,因此在创建基于QSerializer的类时,必须重写metaObject方法。您可以将QS_SERIALIZER宏添加到类主体中,它将为您覆盖metaObject方法。



另外,使用staticMetaObject而不是在每个对象中存储QMetaObject实例可以从类大小节省40个字节,总的来说,很漂亮!



如果由于某种原因不想继承,可以在序列化类的主体中定义QS_CLASS宏。,它将生成所有必需的方法,而不是从QSerializer继承。



字段声明



另外,JSON和XML有4种可序列化的数据,没有这些序列化的数据将无法完成。下表显示了数据类型和相应的宏,以描述方式:

数据类型 描述 巨集
领域 基本类型的普通字段(各种数字,字符串,标志) QS_FIELD
采集 原始数据类型的值集 QS_COLLECTION
一个东西 领域的复杂结构或其他复杂结构 QS_OBJECT
对象集合 一组相同类型的复杂数据结构 QS_COLLECTION_OBJECTS


我们将假定生成这些宏的代码称为描述,而生成宏的代码称为描述性代码。



生成描述只有一种原理-对于特定字段,生成JSON和XML属性以及定义用于写入/读取值的方法。



让我们使用原始数据类型字段的示例来分析JSON描述的生成:



/* 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)  


对于int digit字段,将生成具有QJsonValue类型的属性数字,并定义私有写入和读取方法-get_json_digit和set_json_digit,然后它们将成为使用JSON序列化/反序列化数字字段的导体。



这是怎么发生的?
name digit, ('##') digit — .



type int. , type int . QVariant int .



这是复杂结构的JSON描述的生成:



/* 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)


复杂对象是一组嵌套属性,对于外部类,它们将作为一个“大”属性工作,因为此类对象还将具有连接方法。您需要做的就是在复杂结构的读写方法中调用适当的guide方法。



类创建



因此,我们有一个相当简单的基础架构来创建可序列化的类。



因此,例如,您可以通过继承QSerializer使类可序列化:



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


或像这样,使用QS_CLASS



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


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.



局限性



单个字段



用户定义或原始类型必须提供默认的构造函数。



集合



的集合类必须作为模板,并提供明确的,在大小,并添加方法。您可以根据条件使用自己的收藏集。满足以下条件的Qt集合:QVector,QStack,QList,QQueue。



Qt版本



最低Qt版本5.5.0

最低测试版本Qt 5.9.0

最高测试版本Qt 5.15.0

注意:您可以参与测试并测试早期版本的Qt上的QSerializer



结果



在重做QSerializer时,我绝对没有为自己设置降低它的任务。但是,它的大小从9个文件减少到1个,这也降低了它的复杂性。现在,QSerializer不再是我们通常使用的形式的库,现在它只是一个头文件,足以将其包含在项目中并获得舒适的序列化/反序列化的所有功能。开发工作始于三月,发明了一个棘手的体系结构,该项目因依赖关系,拐杖而长满,从0开始多次重写。一切为了最终变成一个小文件。



问我自己:“值得付出努力吗?”,我回答:“是的。”我已经在战斗项目中尝试过了,结果令我很满意。



链接

GitHub:链接

最新版本:v1.1

上一篇文章:QSerializer:简单JSON / XML序列化的解决方案



将来的列表



  • 大幅降低成本(甚至可以更便宜地完成)
  • 紧凑
  • 处理重要类型
  • 可序列化数据的基本描述
  • 支持任何提供清晰,大小,附加方法的模板化集合。甚至自己
  • 完全可变的反序列化集合
  • 支持所有流行的原始类型
  • 支持使用QSerializer描述的任何自定义类型
  • 无需注册自定义类型



All Articles