QSerializer:简单JSON / XML序列化的解决方案

哈Ha!

我以某种方式证明结果不公平-使用Java,C#,Go,Python等。有一些库可以将对象数据舒适地序列化为现在流行的JSON和XML,但是在C ++中,它们要么忘记了,要么不想要,或者根本不需要它,或者它们都很复杂,或者可能全部在一起。因此,我决定修复此问题。

所有细节照常切割。

图片

背景


我再次决定承担下一个宠物项目,其实质是客户端-服务器交换,而许多服务器中最喜欢的服务器是RaspberryPi。除其他事项外,我对创建“保存点”的问题很感兴趣-因此,我可以在原型框架内尽可能简单地保存对象状态,然后退出并在下次启动时恢复。由于我对Python不合理的敌对态度以及对Qt的热情态度,我选择了Qt&C ++。编写用于解析JSON的类和意大利面条函数仍然是一种乐趣,我需要一些通用且同时易于解决问题的解决方案。 “我们必须弄清楚,”我告诉自己。

首先,关于条款:
序列化是将数据结构转换为位序列的过程。序列化操作的反过程是反序列化(结构化)操作-从位序列恢复数据结构的初始状态。
Go有一个非常有用的“本机” 编码/ json,它允许使用Marshal方法对对象进行完整的序列化,并使用Unmarshal进行反向结构化(由于这个库,我对编组的概念一无所知,但Desine sperare quihic intras) 。遵循该软件包的概念,我找到了另一个Java的库-GSON,结果证明这是一个非常令人愉悦的产品,使用它是一种乐趣。

我考虑了我对这些库的喜欢,并得出结论,这就是它们的易用性。灵活的功能和一次调用,对于JSON序列化来说,调用toJson方法并将可序列化的对象传递给它就足够了。但是,就其本身而言,C ++默认情况下不具备适当的元对象功能,以提供有关类的字段的足够信息,例如在Java(ClassName.class)中那样。

我只喜欢QT平台上的QJson,但是我对上述库产生的易用性的理解仍然不太合适。因此该项目出现了,将在这里讨论。

小免责声明:这种机制将无法为您解决数据解释的问题。您从它们身上所能获得的就是将数据转换为更方便的形式。

QSerializer项目结构


可以在GitHub上查看项目和示例(链接到存储库)。此处也提供了详细的安装说明。

预计会发生建筑自杀,我将保留这不是最终版本。尽管遗弃了石头,但工作仍将继续进行,但已实现了愿望。
QSerializer库的一般结构依赖性

该项目的主要目标是使使用C ++中用户喜欢的数据格式进行序列化变得可访问和基本。产品质量开发和维护的关键是其体系结构。我并不排除本文的评论中可能会出现其他实现方式,因此我留了一点“创造力”。如果更改实现,则可以添加PropertyKeeper接口的新实现,也可以更改工厂方法,从而不必在QSerializer函数中进行任何更改。

现场申报


在Qt中收集元对象信息的一种方法是在Qt本身的元对象系统中对其进行描述。也许这是最简单的方法。MOC将在编译时生成所有必要的元数据。您可以在描述的对象上调用metaObject方法,该方法将返回我们必须使用的QMetaObject类的实例。

要声明要序列化的字段,您需要从QObject继承该类,并在其中包含Q_OBJECT,以便向MOC明确说明将类类型限定为QObject的基类。Q_PROPERTY

描述了该类的成员。我们将调用Q_PROPERTY中描述的property属性。如果没有将USER标志设置为true,QSerializer将忽略属性。

为什么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的名称访问嵌入式功能,而无需在代码中的任何位置创建对象。

使用基于上述User类的对象构建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);

example文件夹中可以看到更多示例和输出

守护者


为了组织方便的写入和读取已声明的属性,QSerializer使用Keepers,每个都存储一个指向对象(QObject后代)的指针及其QMetaProperty之一。QMetaProperty本身没有特别的价值,实际上,它只是为MOC声明的具有描述属性类的对象。要进行读写,您需要一个描述该属性的类的特定对象-这是要记住的主要内容。

序列化过程中的每个可序列化字段都传递给相应类型的保管人。需要Keeper封装序列化和结构化功能,以实现针对特定类型描述数据的特定实现。我重点介绍了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));
}

Keeper实现PropertyKeeper接口,从该接口继承Keeper的基本抽象类。这使您可以从上到下依次解析和编写XML或JSON格式的文档,只需向下浏览所描述的存储属性,并在进入所描述的属性中的嵌入式对象(如果有的话)时就更深入,而无需深入了解实现的细节。

PropertyKeeper界面
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;
}
...


保护工厂的一项关键功能是能够为对象提供完整的一系列保护,并且您可以通过使用类型标识符编辑常量集合来扩展受支持的原始类型的列表。每个系列的守护者都是一种针对对象属性的映射。销毁KeepersFactory对象时,将释放为该对象提供的一系列Keeper分配的内存。

局限性与行为

情况行为
尝试序列化其类型未从QObject继承的对象编译错误
尝试序列化/结构化时未声明的类型QSException :: UnsupportedPropertyType异常
尝试序列化/构造具有与simple_t和array_of_simple_t集合中所述不同的原始类型的对象。QSException::UnsupportedPropertyType. , — ,
JSON/XML
propertyes, JSON/XMLpropertyes . — propertyes
JSON propertyQSException


我认为,该项目值得,因为本文是写的。对于我自己,我得出结论,没有通用解决方案,您总是必须牺牲一些东西。通过在使用,功能方面开发灵活的方法,您将丧失简单性,反之亦然。

我不敦促您使用QSerializer,我的目标是我自己作为程序员的开发。当然,我也追求帮助他人的目标,但首先是获得乐趣。要乐观)

All Articles