Content Table

Qt 的 Json 读写工具类 Json

读取下面 Json 文件 x.json 中 admin 属性下的 roles 属性,使用工具类 Json,支持带 “.” 的路径格式,代码如下

1
2
Json json("x.json", true);
qDebug() << json.getStringList("admin.roles");

如果使用原生的 QJsonDocument 来读的话,则代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
QByteArray json; // json 的内容
QFile file("x.json"); // Json 的文件

// [1] 读取 Json 文件内容
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
json = file.readAll();
} else {
return -1;
}

// [2] 解析 Json 得到根节点的 QJsonObject
QJsonParseError error;
QJsonDocument jsonDocument = QJsonDocument::fromJson(json, &error);
QJsonObject root = jsonDocument.object();

if (QJsonParseError::NoError != error.error) {
qDebug() << error.errorString() << ", Offset: " << error.offset;
}

// [3] 按路径访问得到 roles 的数组
QJsonArray roles = root.value("admin").toObject().value("roles").toArray();

QStringList result;

// [4] 遍历 roles 数组得到数组中的所有字符串
for (QJsonArray::const_iterator iter = roles.begin(); iter != roles.end(); ++iter) {
QJsonValue value = *iter;
result << value.toString();
}

qDebug() << result;

相比之下,类 Json 简单很多,省去了很多繁杂的步骤。

x.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"status": 0,
"message": "Congratulation",
"admin": {
"username": "Alice",
"password": "Secret",
"roles": ["ADMIN", "USER", "SUPERADMIN"]
},
"roomEnrollmentList": [{
"enrollmentId": 5094,
"examUId": "10000091",
"examineeName": "马超",
"subjectName": "普通逻辑"
}, {
"enrollmentId": 5103,
"examUId": "10000100",
"examineeName": "肖俊彦",
"subjectName": "数值计算"
}]
}

Json 的使用

类 Json 支持从文件中读取 Json,也可以使用 Json 字符串初始化,支持使用带 . 的路径级连的读写 Json 的属性

  • 使用 Json 文件创建 Json 对象

    1
    Json json("x.json", true);
  • 使用 Json 字符串创建 Json 对象

    1
    Json json("{\"data\": {\"userId\": 12345}}");
  • 读取 Json 使用 Json.getInt(), Json.getString() 等

  • 写入 Json 使用 Json.set()

  • 参考 main.cpp 中 Json 的使用用例

文件说明

  • main.cpp 和 x.json 用于测试类 Json
  • 类 Json 的实现文件为 Json.h 和 Json.cpp

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include "Json.h"

int main(int argc, char *argv[]) {
Q_UNUSED(argc)
Q_UNUSED(argv)

// 使用字符串创建 Json
Json j("{\"message\": \"Welcome\", \"code\": 200, \"inner\": {\"ok\": true}}");
qDebug().noquote() << j.getString("message");

// 删除
qDebug().noquote() << j.toString();
j.remove("code");
qDebug().noquote() << j.toString();
j.remove("inner.ok");
qDebug().noquote() << j.toString();

// 从文件读取 Json
Json json("/Users/Biao/Documents/workspace/Qt/Json/x.json", true);

// 第一级
qDebug() << json.getString("message");
qDebug() << json.getString("non-exit", "不存在");

// 第二级
qDebug() << json.getString("admin.username");
qDebug() << json.getString("admin.password");

// 第二级的数组
qDebug() << json.getStringList("admin.roles");

// 第二级的数组中的值
QJsonArray array = json.getJsonArray("roomEnrollmentList");
QJsonObject fromNode = array.at(0).toObject();
qDebug() << json.getString("examineeName", "", fromNode); // 传入开始查找的节点

// 修改 Json,创建 foo.bar.avatar
json.set("foo.bar.avatar", "Sparta");
qDebug() << json.getJsonValue("foo.bar");

// 修改 Json,給 foo.bar 创建 fruit,和給 foo 创建 names 数组
QJsonObject bar = json.getJsonObject("foo.bar");
bar.insert("fruit", "Apple");
json.set("foo.bar", bar);
json.set("foo.names", QStringList() << "One" << "Two" << "Three");
qDebug().noquote() << json.toString(QJsonDocument::Compact);

// 保存到文件
json.save("/Users/Biao/Desktop/xr.json");

return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Welcome
{
"code": 200,
"inner": {
"ok": true
},
"message": "Welcome"
}

{
"inner": {
"ok": true
},
"message": "Welcome"
}

{
"inner": {
},
"message": "Welcome"
}

"Congratulation"
"不存在"
"Alice"
"Secret"
("ADMIN", "USER", "SUPERADMIN")
"马超"
QJsonValue(object, QJsonObject({"avatar":"Sparta"}))
{
"admin": {
"password": "Secret",
"roles": [
"ADMIN",
"USER",
"SUPERADMIN"
],
"username": "Alice"
},
"foo": {
"bar": {
"avatar": "Sparta",
"fruit": "Apple"
},
"names": [
"One",
"Two",
"Three"
]
},
"message": "Congratulation",
"roomEnrollmentList": [
{
"enrollmentId": 5094,
"examUId": "10000091",
"examineeName": "马超",
"subjectName": "普通逻辑"
},
{
"enrollmentId": 5103,
"examUId": "10000100",
"examineeName": "肖俊彦",
"subjectName": "数值计算"
}
],
"status": 0
}

查看保存的 Json 文件,比 x.json 中多出了下面的内容,说明修改与保存成功

1
2
3
4
5
6
7
8
9
10
11
"foo": {
"bar": {
"avatar": "Sparta",
"fruit": "Apple"
},
"names": [
"One",
"Two",
"Three"
]
}

Json.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#ifndef JSON_H
#define JSON_H

#include <QJsonArray>
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonDocument>

struct JsonPrivate;

/**
* Qt 的 JSON API 读写多层次的属性不够方便,这个类的目的就是能够使用带 "." 的路径格式访问 Json 的属性,例如
* "id" 访问的是根节点下的 id,"user.address.street" 访问根节点下 user 的 address 的 street 的属性。
*
* JSON 例子 (JSON 的 key 必须用双引号括起来,值有不同的类型,数值类型不用双引号括起来,字符类型的才用):
* {
* "id": 18191,
* "user": {
* "address": {
* "street": "Wiessenstrasse",
* "postCode": "100001"
* },
* "childrenNames": ["Alice", "Bob", "John"]
* }
* }
*
* 创建 Json 对象: Json json(jsonString) or Json json(jsonFilePath, true)
* 保存 Json 对象到文件: json.save("xxx.json")
*
* 访问 id: json.getInt("id"),返回 18191
* 访问 street: json.getString("user.address.street"),返回 "Wiessenstrasse"
* 访问 childrenNames: json.getStringList("user.childrenNames") 得到字符串列表("Alice", "Bob", "John")
* 设置 "user.address.postCode" 则可以使用 json.set("user.address.postCode", "056231")
* 如果根节点是数组,则使用 json.getJsonArray(".") 获取
*
* 如果读取的属性不存在,则返回指定的默认值,如 "database.username.firstName" 不存在,
* 调用 json.getString("database.username.firstName", "defaultName"),由于要访问的属性不存在,
* 得到的是一个空的 QJsonValue,所以返回我们指定的默认值 "defaultName"。
*
* 如果要修改的属性不存在,则会自动的先创建属性,然后设置它的值。
*
* 注意: JSON 文件要使用 UTF-8 编码。
*/
class Json {
public:
/**
* 使用 JSON 字符串或者从文件读取 JSON 内容创建 Json 对象。
* 如果 fromFile 为 true, 则 jsonOrJsonFilePath 为 JSON 文件的路径
* 如果 fromFile 为 false,则 jsonOrJsonFilePath 为 JSON 的字符串内容
*
* @param jsonOrJsonFilePath JSON 的字符串内容或者 JSON 文件的路径
* @param fromFile 为 true,则 jsonOrJsonFilePath 为 JSON 文件的路径,为 false 则 jsonOrJsonFilePath 为 JSON 的字符串内容
*/
explicit Json(const QString &jsonOrJsonFilePath = "{}", bool fromFile = false);
~Json();

// 禁止复制构造和赋值, 因为 ~Json() 中会删除 JSON 的数据对象 JsonPrivate
Json(const Json &other) = delete;
Json& operator=(const Json &other) = delete;

bool isValid() const; // JSON 是否有效,有效的 JSON 返回 true,否则返回 false
QString errorString() const; // JSON 无效时的错误信息

/**
* 读取路径 path 对应属性的整数值
*
* @param path 带 "." 的路径格
* @param def 如果要找的属性不存在时返回的默认值
* @param fromNode 从此节点开始查找,如果为默认值 QJsonObject(),则从 Json 的根节点开始查找
* @return 整数值
*/
int getInt(const QString &path, int def = 0, const QJsonObject &fromNode = QJsonObject()) const;
bool getBool(const QString &path, bool def = false, const QJsonObject &fromNode = QJsonObject()) const;
double getDouble(const QString &path, double def = 0.0, const QJsonObject &fromNode = QJsonObject()) const;
QString getString(const QString &path, const QString &def = QString(), const QJsonObject &fromNode = QJsonObject()) const;
QStringList getStringList(const QString &path, const QJsonObject &fromNode = QJsonObject()) const;

QJsonArray getJsonArray( const QString &path, const QJsonObject &fromNode = QJsonObject()) const;
QJsonValue getJsonValue( const QString &path, const QJsonObject &fromNode = QJsonObject()) const;
QJsonObject getJsonObject(const QString &path, const QJsonObject &fromNode = QJsonObject()) const;

/**
* @brief 设置 path 对应的 Json 属性的值
* @param path path 带 "." 的路径格
* @param value 可以是整数,浮点数,字符串,QJsonValue, QJsonObject 等,具体请参考 QJsonValue 的构造函数
*/
void set(const QString &path, const QJsonValue &value);
void set(const QString &path, const QStringList &strings);

/**
* @brief 删除 path 对应的属性
* @param path 带 "." 的路径格
*/
void remove(const QString &path);

/**
* @brief 把 JSON 保存到 path 指定的文件
*
* @param path 文件的路径
* @param pretty 为 true 时格式化 JSON 字符串,为 false 则使用压缩格式去掉多余的空白字符
*/
void save(const QString &path, bool pretty = true) const;

/**
* @brief 把 Json 对象转换为 JSON 字符串
* @param pretty 为 true 时格式化 JSON 字符串,为 false 则使用压缩格式去掉多余的空白字符
* @return Json 对象的字符串表示
*/
QString toString(bool pretty = true) const;

public:
JsonPrivate *d;
};

#endif // JSON_H

Json.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#include "Json.h"

#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#include <QJsonParseError>

/*-----------------------------------------------------------------------------|
| JsonPrivate implementation |
|----------------------------------------------------------------------------*/
struct JsonPrivate {
JsonPrivate(const QString &jsonOrJsonFilePath, bool fromFile);

void remove(QJsonObject &parent, const QString &path); // 删除 path 对应的属性
void setValue(QJsonObject &parent, const QString &path, const QJsonValue &newValue); // 设置 path 的值
QJsonValue getValue(const QString &path, const QJsonObject &fromNode) const; // 获取 path 的值

QJsonObject root; // Json 的根节点
QJsonDocument doc; // Json 的文档对象
bool valid = true; // Json 是否有效
QString errorString; // Json 无效时的错误信息
};

JsonPrivate::JsonPrivate(const QString &jsonOrJsonFilePath, bool fromFile) {
QByteArray json("{}"); // json 的内容

// 如果传人的是 Json 文件的路径,则读取内容
if (fromFile) {
QFile file(jsonOrJsonFilePath);

if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
json = file.readAll();
} else {
valid = false;
errorString = QString("Cannot open the file: %1").arg(jsonOrJsonFilePath);
qDebug() << errorString;
return;
}
} else {
json = jsonOrJsonFilePath.toUtf8();
}

// 解析 Json
QJsonParseError error;
doc = QJsonDocument::fromJson(json, &error);

if (QJsonParseError::NoError == error.error) {
root = doc.object();
} else {
valid = false;
errorString = QString("%1\nOffset: %2").arg(error.errorString()).arg(error.offset);
qDebug() << errorString;
}
}

// 删除 path 对应的属性
void JsonPrivate::remove(QJsonObject &parent, const QString &path) {
const int indexOfDot = path.indexOf('.'); // 第一个 . 的位置
const QString property = path.left(indexOfDot); // 第一个 . 之前的内容,如果 indexOfDot 是 -1 则返回整个字符串
const QString restPath = (indexOfDot>0) ? path.mid(indexOfDot+1) : QString(); // 第一个 . 后面的内容

if(restPath.isEmpty()) {
// restPath 为空, 说明 property 就是 path 中最后一个 . 右边的部分, 也就是要删除的属性
parent.remove(property);
} else {
// 路径中间的属性,递归访问它的子属性
QJsonObject child = parent[property].toObject();
remove(child, restPath);
parent[property] = child;
}
}

// 使用递归+引用设置 Json 的值,因为 toObject() 等返回的是对象的副本,对其修改不会改变原来的对象,所以需要用引用来实现
void JsonPrivate::setValue(QJsonObject &parent, const QString &path, const QJsonValue &newValue) {
const int indexOfDot = path.indexOf('.'); // 第一个 . 的位置
const QString property = path.left(indexOfDot); // 第一个 . 之前的内容,如果 indexOfDot 是 -1 则返回整个字符串
const QString restPath = (indexOfDot>0) ? path.mid(indexOfDot+1) : QString(); // 第一个 . 后面的内容

QJsonValue fieldValue = parent[property];

if(restPath.isEmpty()) {
// restPath 为空, 说明 property 就是 path 中最后一个 . 右边的部分, 也就是要设置的属性
parent[property] = newValue; // 如果不存在则会创建
} else {
// 路径中间的属性,递归访问它的子属性
QJsonObject child = parent[property].toObject();
setValue(child, restPath, newValue);
parent[property] = child; // 因为 QJsonObject 操作的都是对象的副本,所以递归结束后需要保存起来再次设置回 parent
}
}

// 读取属性的值,如果 fromNode 为空,则从跟节点开始访问
QJsonValue JsonPrivate::getValue(const QString &path, const QJsonObject &fromNode) const {
// 1. 确定搜索的根节点,如果 fromNode 为空则搜索的根节点为 root
// 2. 把 path 使用分隔符 . 分解成多个属性名字
// 3. 从搜索的根节点开始向下查找到倒数第二个属性名字对应的 QJsonObject parent
// 如 "user.address.street",要设置的属性为 street,它的 parent 是 address
// 4. 返回 parent 中属性名为倒数第一个属性名字对应的属性值

// [1] 确定搜索的根节点,如果 fromNode 为空则搜索的根节点为 root
// [2] 把 path 使用分隔符 . 分解成多个属性名字
QJsonObject parent = fromNode.isEmpty() ? root : fromNode;
QStringList names = path.split(QRegularExpression("\\."));

// [3] 从搜索的根节点开始向下查找到倒数第二个属性名字对应的 QJsonObject parent
int size = names.size();
for (int i = 0; i < size - 1; ++i) {
if (parent.isEmpty()) {
return QJsonValue();
}

parent = parent.value(names.at(i)).toObject();
}

// [4] 返回 parent 中属性名为倒数第一个属性名字对应的属性值
return parent.value(names.last());
}

/*-----------------------------------------------------------------------------|
| Json implementation |
|----------------------------------------------------------------------------*/
Json::Json(const QString &jsonOrJsonFilePath, bool fromFile) : d(new JsonPrivate(jsonOrJsonFilePath, fromFile)) {
}

Json::~Json() {
delete d;
}

// JSON 是否有效,有效的 JSON 返回 true,否则返回 false
bool Json::isValid() const {
return d->valid;
}

// JSON 无效时的错误信息
QString Json::errorString() const {
return d->errorString;
}

int Json::getInt(const QString &path, int def, const QJsonObject &fromNode) const {
return getJsonValue(path, fromNode).toInt(def);
}

bool Json::getBool(const QString &path, bool def, const QJsonObject &fromNode) const {
return getJsonValue(path, fromNode).toBool(def);
}

double Json::getDouble(const QString &path, double def, const QJsonObject &fromNode) const {
return getJsonValue(path, fromNode).toDouble(def);
}

QString Json::getString(const QString &path, const QString &def, const QJsonObject &fromNode) const {
return getJsonValue(path, fromNode).toString(def);
}

QStringList Json::getStringList(const QString &path, const QJsonObject &fromNode) const {
QStringList result;
QJsonArray array = getJsonValue(path, fromNode).toArray();

for (QJsonArray::const_iterator iter = array.begin(); iter != array.end(); ++iter) {
QJsonValue value = *iter;
result << value.toString();
}

return result;
}

QJsonArray Json::getJsonArray(const QString &path, const QJsonObject &fromNode) const {
// 如果根节点是数组时特殊处理
if (("." == path || "" == path) && fromNode.isEmpty()) {
return d->doc.array();
}

return getJsonValue(path, fromNode).toArray();
}

QJsonObject Json::getJsonObject(const QString &path, const QJsonObject &fromNode) const {
return getJsonValue(path, fromNode).toObject();
}

QJsonValue Json::getJsonValue(const QString &path, const QJsonObject &fromNode) const {
return d->getValue(path, fromNode);
}


void Json::set(const QString &path, const QJsonValue &value) {
d->setValue(d->root, path, value);
}

void Json::set(const QString &path, const QStringList &strings) {
QJsonArray array;

for (const QString &str : strings) {
array.append(str);
}

d->setValue(d->root, path, array);
}

// 删除 path 对应的属性
void Json::remove(const QString &path) {
d->remove(d->root, path);
}

// 把 JSON 保存到 path 指定的文件
void Json::save(const QString &path, bool pretty) const {
QFile file(path);

if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
return;
}

QTextStream out(&file);
out << toString(pretty);
out.flush();
file.close();
}

// 把 Json 对象转换为 JSON 字符串
QString Json::toString(bool pretty) const {
return QJsonDocument(d->root).toJson(pretty ? QJsonDocument::Indented : QJsonDocument::Compact);
}