QT 实现 C++ 数据类与 json 的转换

QT 提供了 QJsonDocument、QJsonObject、QJsonArray、QJsonValue 等类用于 JSON 的解析和转换。QJsonValue 支持的数据类型包括:bool、double、string、array、object、null。但是,对于 QRectF、QLineF、QColor 等类以及用户自定义数据类,QJsonObject 就无法转换,更无法生成可读的字符串。此时,需要我们自己来实现转换并定义转换后的 JSON 格式。

上篇文章,借助 QT 的反射机制实现数据类的序列化 实现了数据类的序列化,简化了数据类的编写,同时提供了转换为 JSON 的基础。通过元对象系统很容易找到我们通过宏 JSONFIELD 记录的需要序列化的字段,因为记录序列化的方法被导出并标记为 JSON_FLAG 。使用反射机制就可以找到所有记录序列化字段的方法,获取字段名后通过 getValue()、setValue() 即可获取或设置字段值。

复制代码
// serializable.h
#define JSONFIELD(field, alias, ...) \
using __type_##field = decltype(field) ;\
Q_PROPERTY(__type_##field field READ get##alias WRITE set##alias) \
    public: \
    Q_INVOKABLE JSON_FLAG inline QMap<QString, QString> __get##alias##Info__(){ \
        QMap<QString, QString> info; \
        info["name"] = #field; \
        info["alias"] = #alias; \
        info["args"] = QString(#__VA_ARGS__); \
        return info; \
    } \
    inline __type_##field get##alias() const { return field; } \
    inline void set##alias(const __type_##field &value) { \
            field = value; \
    }

定义通用的 JSON 接口

JSON 接口主要定义 4 个功能接口:1. 将数据类转换为 QJsonObject 对象;2. 将数据类转换为字符串;3. 将字符串解析为指定的数据类;4. 将 QJsonObject 转换为指定的数据类;

系统允许多个接口实现类,但是全局只允许有一个实例,用于整个工程的 JSON 转换。所以声明了一个全局的 EasyJson 对象 EASYJSON。

复制代码
#include "serializable.h"

#include <QJsonObject>

class EasyJson{
public:
    EasyJson(){}
    ~EasyJson(){}
    virtual QJsonObject toJson(const Serializable &obj) = 0;
    virtual QString toJsonString(const Serializable &obj) = 0;
    virtual QVariant parseObject(QJsonObject json, QMetaType typeName) = 0;
    virtual QVariant parseObject(QString json, QMetaType typeName) = 0;
};

extern EasyJson *EASYJSON;

EasyJson 的实现类

实现类直接继承 EasyJson 类,完成接口代码即可。QT 中数据类转换为 JSON 的难点在于 QRectF、QSizeF 等类的转换,以及 Serializable 作为数据类字段时的转换。为了便于 QT 内部封装类的解析,需要将解析方法单独封装为一个工具类,这样便于后期添加和修改。工具类的实现见 variantutil.h 文件。

复制代码
// easyjsonimpl.h
#include "easyjson.h"

class EasyJsonImpl: public EasyJson
{
public:
    EasyJsonImpl();

    // EasyJson interface
private:
    QJsonObject toJson(const Serializable &obj) override;
    QString toJsonString(const Serializable &obj) override;
    QVariant parseObject(QJsonObject json, QMetaType typeName) override;
    QVariant parseObject(QString json, QMetaType typeName) override;
};

为了便于切换不同的 JSON 实现类,EASYJSON 对象的创建与否需要通过指定的宏来判断一下。如 EasyJsonImpl 源码中规定只有定义了 EASY_JSON_DEFAULT 才会实例化 EasyJsonImpl。

这样在 .pro 文件中添加 DEFINES += EASY_JSON_DEFAULT 即可启用该实现类。如果有不同的实现类,定义不同的宏即可。

复制代码
// easyjsonimpl.cpp
#include "easyjsonimpl.h"
#include "variantutil.h"

#include <QObject>
#include <QMetaObject>
#include <QMetaProperty>
#include <QColor>
#include <QJsonArray>
#include <QLineF>
#include <QPointF>
#include <QRectF>
#include <QSizeF>
#include <QJsonDocument>

#ifdef EASY_JSON_DEFAULT
EasyJson *EASYJSON = new EasyJsonImpl();
#endif

EasyJsonImpl::EasyJsonImpl() {}

QJsonObject EasyJsonImpl::toJson(const Serializable &obj)
{
    QJsonObject json;
    Serializable *objPtr = const_cast<Serializable*>(&obj);

    const QMetaObject *metaInfo = obj.getMetaInfo();//obj.metaObject();
    do{
        int count = metaInfo->methodCount();
        for(int i=0; i< count; i++){
            if (QString(metaInfo->method(i).tag()).compare("JSON_FLAG") == 0){
                QMap<QString, QString> jsonInfo;
                jsonInfo = objPtr->invokeMethod<QMap<QString, QString>>(metaInfo, i);
                QString alias = jsonInfo["alias"];
                QVariant value = objPtr->getValue(jsonInfo["name"]);
                QMetaType type = value.metaType();
                // 对 Serializable 子类递归转换
                if (type.id() > QMetaType::User) {
                    auto valueMeta = type.metaObject();
                    auto classInfo = valueMeta->classInfo(valueMeta->indexOfClassInfo("base"));
                    if (QString("Serializable").compare(classInfo.value()) == 0) {
                        json.insert(alias, toJson(*reinterpret_cast<const Serializable*>(value.constData())));
                        continue;
                    }
                }
                // 转为json对象
                json.insert(alias, VariantUtil::toJsonValue(value));
            }
        }

        metaInfo = metaInfo->superClass();
    }while(metaInfo != nullptr);
    return json;
}

QString EasyJsonImpl::toJsonString(const Serializable &obj)
{
    QJsonObject json = toJson(obj);
    QJsonDocument doc(json);
    return QString(doc.toJson(QJsonDocument::Compact));
}

QVariant EasyJsonImpl::parseObject(QJsonObject json, QMetaType typeName)
{
    const QMetaObject *metaInfo = typeName.metaObject();
    QVariant result(typeName);
    Serializable *obj = reinterpret_cast<Serializable*>(result.data());
    do{
        int count = metaInfo->methodCount();
        for(int i=0; i< count; i++){
            if (QString(metaInfo->method(i).tag()).compare("JSON_FLAG") == 0){
                QMap<QString, QString> jsonInfo = obj->invokeMethod<QMap<QString, QString>>(metaInfo, i);

                QMetaProperty fieldType = metaInfo->property(metaInfo->indexOfProperty(jsonInfo["name"].toLocal8Bit()));
                QByteArray fieldName = jsonInfo["name"].toLocal8Bit();
                if (!json.contains(jsonInfo["alias"])){
                    continue;
                }
                QJsonValueRef jsonValue = json[jsonInfo["alias"]];
                // 对 Serializable 子类递归解析
                if (fieldType.metaType().id() > QMetaType::User) {
                    auto valueMeta = fieldType.metaType().metaObject();
                    auto classInfo = valueMeta->classInfo(valueMeta->indexOfClassInfo("base"));
                    if (QString("Serializable").compare(classInfo.value()) == 0) {
                        obj->setValue(fieldName,
                                         parseObject(jsonValue.toObject(), fieldType.metaType()));
                        continue;
                    }
                }
                // 设置字段值
                obj->setValue(fieldName,
                              VariantUtil::fromJsonValue(jsonValue, fieldType.metaType()));
            }
        }
        metaInfo = metaInfo->superClass();
    }while(metaInfo != nullptr);
    return result;
}

QVariant EasyJsonImpl::parseObject(QString json, QMetaType typeName)
{
    if (json.isEmpty()) {
        return QVariant(typeName);
    }
    QJsonDocument doc = QJsonDocument::fromJson(json.toLocal8Bit());
    return parseObject(doc.object(), typeName);
}

variantutil 部分源码如下,详细代码请到项目 https://github.com/lsyeei/dashboard 的源码目录 /common/ 中查看 variantutil.h 文件。

复制代码
inline QJsonValue VariantUtil::toJsonValue(const QVariant &var)
{
    auto type = var.metaType();
    switch (type.id()) {
    case QMetaType::QPoint:
        return QJsonArray{var.toPoint().x(), var.toPoint().y()};
        break;
    case QMetaType::QPointF:
        return QJsonArray{var.toPointF().x(), var.toPointF().y()};
        break;
	...
	default:
        if (type.flags().testFlag(QMetaType::IsEnumeration)) {
            return var.toInt();
        } else {
            return QJsonValue::fromVariant(var);
        }
        break;
    }
}

inline QVariant VariantUtil::fromJsonValue(const QJsonValue &val, QMetaType type)
{
    switch (type.id()) {
    case QMetaType::QPoint:
        return [=]{
            QJsonArray array(val.toArray());
            QPoint pt(array[0].toInt(), array[1].toInt());
            return QVariant(pt);}();
        break;
    case QMetaType::QPointF:
        return [=]{
            QJsonArray array(val.toArray());
            QPointF pt(array[0].toDouble(), array[1].toDouble());
            return QVariant(pt);}();
        break;
	...
	default:
        return val.toVariant();
        break;
    }
}

使用 EASYJSON

首先 .pro 文件中添加 DEFINES += EASY_JSON_DEFAULT 启用该实现类。需要序列化的数据类继承 Serializable 类,然后调用对应的方法即可EASYJSON->toJsonString(pen)

项目 Compelling Data Designer 用于数据的可视化设计,软件采用可扩展架构,支持扩展图形插件、数据接口。项目仍在开发中,目前已设计完成基本图形、多属性配置、动画等功能。项目中还提供了 JSON 序列化数据类的实现方式。


相关推荐
点云SLAM12 分钟前
C++中的算术转换、其他隐式类型转换和显示转换详解
c++·static_cast·dynamic_cast·c++中的类型转换·算术类型转换·其他隐式类型转换·显示类型转换
Zfox_39 分钟前
Git 进阶之路:高效协作之分支管理
大数据·linux·运维·c++·git·elasticsearch
wenchm1 小时前
细说STM32单片机FreeRTOS任务管理API函数vTaskList()的使用方法
c语言·c++·stm32·单片机·嵌入式硬件
wuqingshun3141591 小时前
蓝桥杯 10.拉马车
数据结构·c++·算法·职场和发展·蓝桥杯·深度优先
不是仙人的闲人2 小时前
算法之动态规划
数据结构·c++·算法·动态规划
rigidwill6662 小时前
LeetCode hot 100—分割等和子集
数据结构·c++·算法·leetcode
眠りたいです2 小时前
Linux-网络基础
linux·运维·服务器·网络·c++·进程间通信
姝孟2 小时前
学习笔记(C++篇)--- Day 3
c++·笔记·学习
LoveXming3 小时前
Qt工具栏中添加按钮QAction
开发语言·qt
天堂的恶魔9463 小时前
C++项目 —— 基于多设计模式下的同步&异步日志系统(3)(日志器类)
c++·算法·设计模式