在前面的文章中为大家介绍了C语言中如何使用cjson处理json数据,接下来讲一下json在C++中的处理,这里给大家介绍一个开源的库jsoncpp

1. 下载和编译

1.1 下载

下载 jsoncpp

Jsoncpp是个跨平台的C++开源库,提供的类为我们提供了很便捷的操作,而且使用的人也很多。在使用之前我们首先要从github仓库下载源码,地址如下:

1
https://github.com/open-source-parsers/jsoncpp

下载 cmake 工具

由于C++程序猿都是基于VS进行项目开发,下载的源码我们一般不会直接使用,而且将其编译成相应的库文件(动态库或者静态库),这样不论是从使用或者部署的角度来说,操作起来都会更方便一些。

但是,从github下载的源码不能直接通过VS打开,编译就更谈不上了。它提供的默认编译方式是cmake。我们可以通过使用cmake工具将下载的jsoncpp源码生成一个VS项目,这样就可以通过VS编译出需要的库文件了。

CMake工具的官方下载地址如下:

1
https://cmake.org/download/

image-20220305161450632

在这最新的安装包,根据向导完成安装即可。

1.2 生成VS项目

打开安装好的CMake工具

image-20220305161755824

需要在工具中指定本地的jsoncpp路径(git clone 之后就会得到这个目录),这是我本地的目录:

image-20220305162106386

第二个需要指定的是一个文件存储路径(生成的VS项目会保存到这个目录下),保证这是一个本地的有效目录即可。

image-20220305162412442

设置好之后进行配置,点击Configure按钮

image-20220305162614605

此处需要设置一下,VS的版本以及生成器的平台位数,不填默认就是64位。

image-20220305163029229

配置完成,开始生成VS项目。

image-20220305163244014

打开在CMake工具中指定的生成目录,我这里是D:\output-project,基于项目文件jsoncpp.sln打开这个VS项目。

1.3 编译

基于生成的项目文件打开VS项目之后,可以看到里边有很多子项目

image-20220305163731357

我们只需要编译上图标记的那一个就可以了,编译成功之后就可以得到我们需要的库文件了。

image-20220305173344150

通过输出的日志信息,就能找到我们想要的动态库了,把这两个文件收集起来备用。

2. jsoncpp 的使用

jsoncpp库中的类被定义到了一个Json命名空间中,建议在使用这个库的时候先声明这个命名空间:

1
using namespace Json;

使用jsoncpp库解析json格式的数据,我们只需要掌握三个类:

  1. Value 类:将json支持的数据类型进行了包装,最终得到一个Value类型
  2. FastWriter类:将Value对象中的数据序列化为字符串
  3. Reader类:反序列化, 将json字符串 解析成 Value 类型

2.1 Value类

这个类可以看做是一个包装器,它可以封装Json支持的所有类型,这样我们在处理数据的时候就方便多了。

枚举类型 说明 翻译
nullValue ‘null’ value 不表示任何数据,空值
intValue signed integer value 表示有符号整数
uintValue unsigned integer value 表示无符号整数
realValue double value 表示浮点数
stringValue UTF-8 string value 表示utf8格式的字符串
booleanValue bool value 表示布尔数
arrayValue array value (ordered list) 表示数组,即JSON串中的[]
objectValue object value (collection of name/value pairs) 表示键值对,即JSON串中的{}

构造函数

Value类为我们提供了很多构造函数,通过构造函数来封装数据,最终得到一个统一的类型。

1
2
3
4
5
6
7
8
9
10
11
12
// 因为Json::Value已经实现了各种数据类型的构造函数
Value(ValueType type = nullValue);
Value(Int value);
Value(UInt value);
Value(Int64 value);
Value(UInt64 value);
Value(double value);
Value(const char* value);
Value(const char* begin, const char* end);
Value(bool value);
Value(const Value& other);
Value(Value&& other);

检测保存的数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
// 检测保存的数据类型
bool isNull() const;
bool isBool() const;
bool isInt() const;
bool isInt64() const;
bool isUInt() const;
bool isUInt64() const;
bool isIntegral() const;
bool isDouble() const;
bool isNumeric() const;
bool isString() const;
bool isArray() const;
bool isObject() const;

将Value对象转换为实际类型

1
2
3
4
5
6
7
8
9
10
11
Int asInt() const;
UInt asUInt() const;
Int64 asInt64() const;
UInt64 asUInt64() const;
LargestInt asLargestInt() const;
LargestUInt asLargestUInt() const;
JSONCPP_STRING asString() const;
float asFloat() const;
double asDouble() const;
bool asBool() const;
const char* asCString() const;

对json数组的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
ArrayIndex size() const;
Value& operator[](ArrayIndex index);
Value& operator[](int index);
const Value& operator[](ArrayIndex index) const;
const Value& operator[](int index) const;
// 根据下标的index返回这个位置的value值
// 如果没找到这个index对应的value, 返回第二个参数defaultValue
Value get(ArrayIndex index, const Value& defaultValue) const;
Value& append(const Value& value);
const_iterator begin() const;
const_iterator end() const;
iterator begin();
iterator end();

对json对象的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Value& operator[](const char* key);
const Value& operator[](const char* key) const;
Value& operator[](const JSONCPP_STRING& key);
const Value& operator[](const JSONCPP_STRING& key) const;
Value& operator[](const StaticString& key);

// 通过key, 得到value值
Value get(const char* key, const Value& defaultValue) const;
Value get(const JSONCPP_STRING& key, const Value& defaultValue) const;
Value get(const CppTL::ConstString& key, const Value& defaultValue) const;

// 得到对象中所有的键值
typedef std::vector<std::string> Members;
Members getMemberNames() const;

将Value对象数据序列化为string

1
2
3
// 序列化得到的字符串有样式 -> 带换行 -> 方便阅读
// 写配置文件的时候
std::string toStyledString() const;

2.2 FastWriter 类

1
2
3
// 将数据序列化 -> 单行
// 进行数据的网络传输
std::string Json::FastWriter::write(const Value& root);

2.3 Reader 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool Json::Reader::parse(const std::string& document,
Value& root, bool collectComments = true);
参数:
- document: json格式字符串
- root: 传出参数, 存储了json字符串中解析出的数据
- collectComments: 是否保存json字符串中的注释信息

// 通过begindoc和enddoc指针定位一个json字符串
// 这个字符串可以是完成的json字符串, 也可以是部分json字符串
bool Json::Reader::parse(const char* beginDoc, const char* endDoc,
Value& root, bool collectComments = true);

// write的文件流 -> ofstream
// read的文件流 -> ifstream
// 假设要解析的json数据在磁盘文件中
// is流对象指向一个磁盘文件, 读操作
bool Json::Reader::parse(std::istream& is, Value& root, bool collectComments = true);

3. VS的配置

如果想要在VS中使用编译出的jsoncpp库,我们还需要做如下配置:

3.1 头文件

在编码过程中需要在项目文件中包含从github下载得到的头文件,有两种处理方式:

  1. 将头文件放到项目目录下,直接被项目包含引用

  2. 将头文件放到一个本地固定目录,以后就不再动了,在VS项目属性中设置包含这个目录,我推荐这种

    image-20220305172149309

另外,在这个include目录中还有一个json子目录,所有的头文件都在这个子目录中,我们不要破坏这个目录结构:

image-20220305172446116

在包含需要的头文件的时候,使用如下这种方式:

1
#include <json/json.h>

把本地的头文件目录在项目属性窗口中进行配置:

image-20220305173614000

我这里头文件是放到了C盘jsoncpp目录中:

image-20220305173715283

3.2 库文件

我这里也是将生成的jsoncpp.libjsoncpp.dll放到了C盘(C:\jsoncpp\lib),在VS项目中需要指定这个库路径:

image-20220305174054294

image-20220305174136447

另外,还需要告诉VS需要加载的动态库是哪一个

image-20220305174409112

此处指定的是动态库对应的lib文件,也就是jsoncpp.lib

image-20220305174503539

配置完成之后,如果项目中使用了jsoncpp就可以编译通过了。在程序执行的时候,如果提示找不到jsoncpp的动态库,把 jsoncpp.dll 拷贝到可执行所在的目录下就可以解决这个问题了。

3. 示例代码

比如:我们要将下面这个Json数组写入的一个文件中

1
2
3
4
5
6
7
8
[
12,
12.34,
true,
"tom",
["jack", "ace", "robin"],
{"sex":"man", "girlfriend":"lucy"}
]
1
2
3
4
5
6
7
8
9
#include <json/json.h>
#include <fstream>
using namespace Json;

int main()
{
writeJson();
readJson();
}

3.1 写json文件

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
void writeJson()
{
// 将最外层的数组看做一个Value
// 最外层的Value对象创建
Value root;
// Value有一个参数为int 行的构造函数
root.append(12); // 参数进行隐式类型转换
root.append(12.34);
root.append(true);
root.append("tom");

// 创建并初始化一个子数组
Value subArray;
subArray.append("jack");
subArray.append("ace");
subArray.append("robin");
root.append(subArray);

// 创建并初始化子对象
Value subObj;
subObj["sex"] = "woman"; // 添加键值对
subObj["girlfriend"] = "lucy";
root.append(subObj);

// 序列化
#if 1
// 有格式的字符串
string str = root.toStyledString();
#else
FastWriter f;
string str = f.write(root);
#endif
// 将序列化的字符串写磁盘文件
ofstream ofs("test.json");
ofs << str;
ofs.close();
}

3.2 读json文件

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
void readJson()
{
// 1. 将磁盘文件中的json字符串读到磁盘文件
ifstream ifs("test.json");
// 2. 反序列化 -> value对象
Value root;
Reader r;
r.parse(ifs, root);
// 3. 从value对象中将数据依次读出
if (root.isArray())
{
// 数组, 遍历数组
for (int i = 0; i < root.size(); ++i)
{
// 依次取出各个元素, 类型是value类型
Value item = root[i];
// 判断item中存储的数据的类型
if (item.isString())
{
cout << item.asString() << ", ";
}
else if (item.isInt())
{
cout << item.asInt() << ", ";
}
else if (item.isBool())
{
cout << item.asBool() << ", ";
}
else if (item.isDouble())
{
cout << item.asFloat() << ", ";
}
else if (item.isArray())
{
for (int j = 0; j < item.size(); ++j)
{
cout << item[j].asString() << ", ";
}
}
else if (item.isObject())
{
// 对象
// 得到所有的key
Value::Members keys = item.getMemberNames();
for (int k = 0; k < keys.size(); ++k)
{
cout << keys.at(k) << ":" << item[keys[k]] << ", ";
}
}

}
cout << endl;
}
}

在上面读Json文件的这段代码中,对读出的每个Value类型的节点进行了类型判断,其实一般情况下是不需要做这样的判断的,因为我们在解析的时候是明确地知道该节点的类型的。

虽然Json这种格式无外乎数组和对象两种,但是需求不同我们设计的Json文件的组织方式也不同,一般都是特定的文件对应特定的解析函数,一个解析函数可以解析任何的Json文件这种设计思路是坚决不推荐的。