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 序列化数据类的实现方式。
QT C++ 实现数据类与 json 的转换的更多相关文章
- 实体类和json互相转换
/// <summary> /// 将实体类转换为json数据 /// </summary> /// <returns></returns> publi ...
- 数据类型和Json格式
1. 前几天,我才知道有一种简化的数据交换格式,叫做yaml. 我翻了一遍它的文档,看懂的地方不多,但是有一句话令我茅塞顿开. 它说,从结构上看,所有的数据(data)最终都可以分解成三种类型: 第一 ...
- 数据类型和Json格式(转载)
作者: 阮一峰 日期: 2009年5月30日 1. 前几天,我才知道有一种简化的数据交换格式,叫做yaml. 我翻了一遍它的文档,看懂的地方不多,但是有一句话令我茅塞顿开. 它说,从结构上看,所有的数 ...
- 其它数据类型和Json的转化
1.ResultSet→Json public static String resultSetToJson(ResultSet rs) throws SQLException,JSONExceptio ...
- 数据类型和Json格式[转]
1. 前几天,我才知道有一种简化的数据交换格式,叫做yaml. 我翻了一遍它的文档,看懂的地方不多,但是有一句话令我茅塞顿开. 它说,从结构上看,所有的数据(data)最终都可以分解成三种类型: 第一 ...
- [转]数据类型和Json格式
作者: 阮一峰 日期: 2009年5月30日 1. 前几天,我才知道有一种简化的数据交换格式,叫做yaml. 我翻了一遍它的文档,看懂的地方不多,但是有一句话令我茅塞顿开. 它说,从结构上看,所有的数 ...
- JavaScript学习总结(六)数据类型和JSON格式
转自:http://segmentfault.com/a/1190000000668072 什么是JSON JSON:JavaScript 对象表示法(JavaScript Object Notati ...
- C#工具类:Json操作帮助类(转载)
原文转载自C#工具类:Json操作帮助类_IT技术小趣屋. Json序列化和反序列化在程序开发中时常会遇到,在C#中可以使用很多种方法实现对数据的Json序列化和反序列化,封装一个Json操作工具类来 ...
- 【转载】C#工具类:Json操作帮助类
Json序列化和反序列化在程序开发中时常会遇到,在C#中可以使用很多种方法实现对数据的Json序列化和反序列化,封装一个Json操作工具类来简化相应的操作,该工具类中包含以下功能:对象转JSON.数据 ...
- 关于ABAP和JSON互相转换
关于ABAP数据结构和JSON格式转换,需要用到标准的类/UI2/CL_JSON一下两个方法, DESERIALIZE是把JSON格式转换成ABAP数据结构,SERIALIZE是把ABAP数据结构转换 ...
随机推荐
- rabbitmq不同模式的区别
RPC模式
- VMware常用操作
VMware常用操作 VMware作为一款功能强大的虚拟化软件,为用户提供了一个灵活.高效的虚拟环境.在日常使用中,掌握VMware的常用操作对于提高工作效率.优化资源配置至关重要.以下将详细介绍VM ...
- Linux环境下安装phantomjs
一.创建文件夹,用来存放软件 cd /opt/softWare mkdir phantomJS cd phantomJS 二.下载并解压 wget https://bitbucket.org/ari ...
- JUC并发—2.Thread源码分析及案例应用
大纲 1.什么是线程以及并发编程 2.微服务注册中心案例 3.以工作线程模式开启微服务的注册和心跳线程 4.微服务注册中心的服务注册功能 5.微服务注册中心的心跳续约功能 6.微服务的存活状态监控线程 ...
- 从 14 秒到 1 秒:MySQL DDL 性能优化实战
1. 问题背景 MySQL版本:8.0.30 测试表数据量:200万 在 MySQL 中,研发人员最初执行了以下 SQL 语句,向表 t_email 中添加了一个允许为 NULL 的列 id3,并设置 ...
- 赶上AI的大潮:在VSCode中使用DeepSeek编程的极简方法
1 赶上AI的大潮:在VSCode中使用DeepSeek编程的极简方法 1.1 背景 DeepSeek在春节期间突然大行其道,欣喜国力大增的同时,对于普通IT工作者,如何才能享受这一波AI红利,让 ...
- docker - [11] 数据卷之DockerFile
通过DockerFile可以生成一个镜像 一.DockerFile的介绍 狂神:dockerfile是用来构建docker镜像的文件命令参数脚本. 狂神:dockerfile是面向开发的,我们以后要发 ...
- 使用mybatis-plus转换枚举值
1. 使用mybatis-plus转换枚举值 枚举值转换方式有很多,有以下方式: 后端写一个通用方法,只要前端传枚举类型,后端返回相应的枚举值前端去匹配 优点:能够实时保持数据一致性 缺点:如果有大量 ...
- MyCat分库分表-练习
模拟这样一个场景: 1.将数据库按业务"垂直"拆分成用户库和订单库: 2.用户库做主从,读写分离: 3.订单库做"水平"切分,并且每个分片表做主从,读写分离: ...
- SpringSecurity学习笔记-前后端分离
1. 简介 Spring Security是Spring家族中的一个安全管理框架.相比于另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富. 一般来说中大型的项目都是使用Sp ...