0.前言
JSON(JavaScript Object Notation)是一种轻量级的结构化数据格式,相对于XML语法更简洁。它具有6种基本数据类型:bool(true或false字符串表示)、double(对应JS中number)、string、array(值的列表)、object(键值对集合)、null 。
虽然cJSON和JsonCpp也是常用的C/C++ JSON解析器,但是在Qt框架中一般用Qt提供的解析类就行了。本文主要介绍QJson的使用,以及使用QTreeView展示和编辑JSON文档(代码链接在最后)。
目录
《偶然》 ----徐志摩
我是天空里的一片云,
偶尔投影在你的波心──
你不必讶异,
更无须欢喜──
在转瞬间消灭了踪影。
你我相逢在黑夜的海上,
你有你的,我有我的,方向;
你记得也好,
最好你忘掉
在这交会时互放的光亮!
1.了解QJson
Qt提供了多个类来分别表示JSON文档、节点、迭代器,这些类都是隐式共享的。
QJsonDocument:用于读写JSON文档。它包装完整的JSON文档,并且可以从基于UTF-8编码的文本表示形式以及Qt自己的二进制格式读取和写入此文档。可以使用QJsonDocument::fromJson() 将JSON文档从其基于文本的表示形式转换为QJsonDocument.toJson() 将其转换回文本。
QJsonParseError:用于表示JSON解析时发生的错误。
QJsonObject:表示键值对列表,其中键是唯一的字符串,而值由QJsonValue表示。QJson还提供了iterator和const_iterator来对QJsonObject进行迭代。要注意的是,一般的JSON结构,object键值对列表是无序的,array才有序,QJSON实现没有提供object有序的设置。
QJsonArray:表示值的列表,值由QJsonValue表示。
QJsonValue:存储数据的值,具有六种基本类型,可通过type() 方法获取其类型枚举:Bool、Double、String、Array、Object、Null,外加一个Undefined表示不确定的值。
在使用这些类时,先添加上对应的头文件。
2.解析JSON
一般常用的操作是读取文件用QByteArray初始化一个QJsonDocument,或者接收来自Http的object、array。下面的代码操作步骤为:读取文件,解析为QJsonDocument,遍历其中的QJsonObject和QJsonArray并获取值。操作起来相当的简单。
#include <QFile>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QDebug>
//读取并解析JSON文档
bool LoadJson(const QString &filepath)
{
//判断路径以及是否正常打开
if(filepath.isEmpty())
return false;
QFile file(filepath);
if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
return false;
//读取数据后关闭文件
const QByteArray raw_data=file.readAll();
file.close();
//解析为Json文档
QJsonParseError json_error;
QJsonDocument json_doc=QJsonDocument::fromJson(raw_data,&json_error);
//是否正常解析Json数据
if(json_doc.isNull()||json_doc.isEmpty()||json_error.error!=QJsonParseError::NoError)
return false;
//判断是object{}还是array[]的Json
if(json_doc.isObject()){
//解析文档中的Object
const QJsonObject json_obj=json_doc.object();
//[1]可以遍历object
QJsonObject::const_iterator iter;
for (iter = json_obj.constBegin(); iter != json_obj.constEnd(); ++iter){
qDebug()<<iter.key();
const QJsonValue iter_val=iter.value();
switch (iter_val.type()) {
case QJsonValue::Object:
//如果是Object继续递归解析
break;
case QJsonValue::Array:
//如果是Array继续递归解析
break;
case QJsonValue::Bool:
qDebug()<<iter_val.toBool();
break;
case QJsonValue::Double:
qDebug()<<iter_val.toDouble();
break;
case QJsonValue::String:
qDebug()<<iter_val.toString();
break;
case QJsonValue::Null: break;
case QJsonValue::Undefined: break;
default: break;
}
}
//[2]也可以根据key查询object中的值
if(json_obj.contains("key_name")){
const QJsonValue json_val=json_obj.value("key_name");
if(json_val.isString()){
const QString str_val=json_val.toString();
}else if(json_val.isArray()){
//遍历数组
}
}
}else if(json_doc.isArray()){
//解析文档中的Array
const QJsonArray json_arr=json_doc.array();
//遍历array
QJsonArray::const_iterator iter;
for (iter = json_arr.constBegin(); iter != json_arr.constEnd(); ++iter){
qDebug()<<iter.i;
const QJsonValue iter_val=*iter;
if(iter_val.isString()){
qDebug()<<iter_val.toString();
}
}
for(int i=0;i<json_arr.count();i++){
qDebug()<<i;
const QJsonValue iter_val=json_arr.at(i);
if(iter_val.isString()){
qDebug()<<iter_val.toString();
}
}
}
return true;
}
3.生成JSON
生成和解析操作一样也很简单。
//生成JSON文档并导出
bool DumpJson(const QString &filepath)
{
//判断路径
if(filepath.isEmpty())
return false;
//这里构造一个Json,并添加模拟的数据
QJsonDocument json_doc;
QJsonArray json_arr;
json_arr.insert(0,"Gong");
json_arr.insert(1,"Jian");
json_arr.insert(2,"Bo");
json_arr.append("1992");
QJsonObject json_obj;
json_obj.insert("name","Qt");
QJsonObject json_subobj{
{"major",5},
{"minor",12},
{"patch",6}
};
//可以嵌套
json_obj.insert("version",json_subobj);
json_obj.insert("array",json_arr);
//可以是object或者array
json_doc.setObject(json_obj);
//转换为bytearray,Compact没有换行,Indented有换行可读性更强
QByteArray json_data=json_doc.toJson(QJsonDocument::Indented);
//判断是否正常打开
QFile file(filepath);
if(!file.open(QIODevice::WriteOnly))
return false;
file.write(json_data);
file.close();
return true;
}
QJsonDocument的toJson()接口有两个格式化参数,QJsonDocument::Compact更紧凑没有换行缩进等:
QJsonDocument::Indented 是更容易查看的带换行的格式:
4.利用QTreeView展示和编辑JSON
用QTreeView来展示JSON数据还是比较常见的,我写了一个简单的展示和编辑JSON数据的Demo(增删部分因为涉及到delegate,我就懒得写了,但是大致的框架是有的)。
其实思路很简单,解析并生成Tree就像生成二叉树:
void JsonTreeModel::parseObject(const QString &key, const QJsonObject &obj, JsonTreeItem *&item)
{
//构造Object节点
JsonTreeItem *child=new JsonTreeItem({{0,key},{1,"[Object]"}},JsonTreeItem::Object,item);
item->appendChild(child);
//遍历Object的keys
const QStringList keys=obj.keys();
for(const QString &item_key:keys){
//qDebug()<<"key:"<<item_key;
parseValue(item_key,obj.value(item_key),child);
}
}
void JsonTreeModel::parseArray(const QString &key, const QJsonArray &arr, JsonTreeItem *&item)
{
//构造Array节点
JsonTreeItem *child=new JsonTreeItem({{0,key},{1,"[Array]"}},JsonTreeItem::Array,item);
item->appendChild(child);
//遍历Array
for(int i=0;i<arr.count();i++){
parseValue("-",arr.at(i),child);
}
}
void JsonTreeModel::parseValue(const QString &key, const QJsonValue &val, JsonTreeItem *&item)
{
QVariant the_val;
//根据Value的类型进行下一步处理
//如果是Object或Array就继续递归
//如果是值就添加一个节点
switch (val.type()) {
case QJsonValue::Object:
parseObject(key,val.toObject(),item);
return;
break;
case QJsonValue::Array:
parseArray(key,val.toArray(),item);
return;
break;
case QJsonValue::Bool:
the_val=val.toBool();
break;
case QJsonValue::Double:
the_val=val.toDouble();
break;
case QJsonValue::String:
the_val=val.toString();
break;
case QJsonValue::Null: break;
case QJsonValue::Undefined: break;
default: break;
}
//构造子节点
JsonTreeItem *child=new JsonTreeItem({{0,key},{1,the_val}},JsonTreeItem::Value,item);
item->appendChild(child);
}
导出编辑完后的Tree就像遍历二叉树:
QVariantMap JsonTreeModel::dumpObject(JsonTreeItem *&item) const
{
QVariantMap json_obj; //QVariantMap对应QJsonObject
const int child_count=item->childCount();
for(int i=0;i<child_count;i++){
JsonTreeItem *child=item->childItem(i);
if(!child) continue;
//为什么不用一个返回QVariant的函数之类的封装下?
switch (child->type()) {
case JsonTreeItem::Object:
json_obj.insert(child->key(),dumpObject(child));
break;
case JsonTreeItem::Array:
json_obj.insert(child->key(),dumpArray(child));
break;
case JsonTreeItem::Value:
json_obj.insert(child->key(),dumpValue(child));
break;
default:
break;
}
}
return json_obj;
}
QVariantList JsonTreeModel::dumpArray(JsonTreeItem *&item) const
{
QVariantList json_arr; //QVariantList对应QJsonArray
const int child_count=item->childCount();
for(int i=0;i<child_count;i++){
JsonTreeItem *child=item->childItem(i);
if(!child) continue;
switch (child->type()) {
case JsonTreeItem::Object:
json_arr.append(dumpObject(child));
break;
case JsonTreeItem::Array:
json_arr.append(dumpArray(child));
break;
case JsonTreeItem::Value:
json_arr.append(dumpValue(child));
break;
default:
break;
}
}
return json_arr;
}
QVariant JsonTreeModel::dumpValue(JsonTreeItem *&item) const
{
//QVariant对应QJsonValue
return item->value();
}
对于TreeView的操作,我主要参考的官方示例:
每个节点由TreeItem类构成,相当于链表的一个节点,而每个节点有一个父节点和多个子节点。需要设计的就是对于每个节点,需要标记他的类型,是array、object还是value,这样便于和JSON文档转换。
待改进的地方:除了完善增删节点的功能,还需要使用delegate委托一个ComboBox来编辑节点类型是array、object还是value。
代码链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/QJsonAndTreeView
5.参考
Qt文档:https://doc.qt.io/qt-5/json.html
讨论:https://forum.qt.io/topic/49771/jsonmodel-for-qtreeview
别人的实现:https://github.com/anjinkristou/QJsonTreeWidget
别人关于Model的:https://github.com/benlau/qsyncable
QML felgo框架的:https://felgo.com/apps/avoid-cpp-models-qt
博客QJson:http://blog.sina.com.cn/s/blog_a6fb6cc90101gnxm.html
博客QJson:https://www.cnblogs.com/lifan3a/articles/7811434.html