本文主要详细介绍如何使用rapidjson库来实现.json文件的读取,分为相关基础介绍、结合简单示例进行基础介绍、结合复杂示例进行详细的函数实现介绍等三部分。
一、相关基础
1、Json文件中的{}
和 []
在 JSON 文件中,{}
和 []
分别表示不同的数据结构:
(1) {}
表示对象(Object)
- 语义 :
{}
表示键值对的集合。 - 结构 :每个键是一个字符串,键与值之间用冒号(
:
)分隔,键值对之间用逗号(,
)分隔。 - 特点 :
- 键是唯一的,值可以是任意类型(如字符串、数字、布尔值、数组、对象等)。
- 对象类似于 C++ 中的
std::map
或 Python 中的字典(dict
)。
示例:
json
{
"name": "John",
"age": 30,
"married": true
}
- 解析:
name
是键,值为字符串"John"
。age
是键,值为整数30
。married
是键,值为布尔值true
。
(2) []
表示数组(Array)
- 语义 :
[]
表示有序的值列表。 - 结构 :值之间用逗号(
,
)分隔,值可以是任意类型(如字符串、数字、布尔值、数组、对象等)。 - 特点 :
- 数组中的值没有键,只有索引(从 0 开始)。
- 数组类似于 C++ 中的
std::vector
或 Python 中的列表(list
)。
示例:
json
[10, 20, 30, 40]
- 解析:
- 数组包含 4 个整数值:
10
、20
、30
和40
。
- 数组包含 4 个整数值:
(3)综合示例
一个 JSON 文件可以同时包含对象和数组的嵌套:
json
{
"person": {
"name": "Alice",
"age": 25,
"hobbies": ["reading", "cycling", "traveling"]
},
"scores": [85, 90, 78]
}
- 解析 :
person
是一个对象,包含键值对:name
:"Alice"
(字符串)age
:25
(整数)hobbies
: 一个数组,包含 3 个字符串。
scores
是一个数组,包含 3 个整数。
(4)总结
{}
:用于表示对象(键值对的集合)。[]
:用于表示数组(有序值列表)。
2、常用函数
JSON 文件中常用的函数分类及详细列表如下所示,基于 RapidJSON 库进行了分类整理。
注:下面示例中的doc是用来存储解析后的JSON数据的主要对象,可通过rapidjson::Document doc
声明
(1) 文件处理相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
ParseStream |
解析输入流中的 JSON 数据 | doc.ParseStream(isw); |
Parse |
解析字符串中的 JSON 数据 | doc.Parse(jsonStr.c_str()); |
HasParseError |
检查解析是否出错 | if (doc.HasParseError()) { /* 错误处理 */ } |
(2)解析与验证相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
HasMember |
检查对象是否包含指定键 | if (doc.HasMember("key")) { /* 存在键 */ } |
IsObject |
检查 JSON 节点是否为对象 | if (doc["key"].IsObject()) { /* 是对象 */ } |
IsArray |
检查 JSON 节点是否为数组 | if (doc["key"].IsArray()) { /* 是数组 */ } |
IsString |
检查 JSON 节点是否为字符串 | if (doc["key"].IsString()) { /* 是字符串 */ } |
IsInt |
检查 JSON 节点是否为整数 | if (doc["key"].IsInt()) { /* 是整数 */ } |
IsDouble |
检查 JSON 节点是否为浮点数 | if (doc["key"].IsDouble()) { /* 是浮点数 */ } |
IsBool |
检查 JSON 节点是否为布尔值 | if (doc["key"].IsBool()) { /* 是布尔值 */ } |
IsNull |
检查 JSON 节点是否为空 | if (doc["key"].IsNull()) { /* 是空值 */ } |
函数名 | 函数用途 | 使用示例 |
---|---|---|
IsNumber |
检查 JSON 节点是否为数字(整数或浮点数) | if (doc["key"].IsNumber()) { /* 是数字 */ } |
IsUint |
检查 JSON 节点是否为无符号整数 | if (doc["key"].IsUint()) { /* 是无符号整数 */ } |
IsUint64 |
检查 JSON 节点是否为 64 位无符号整数 | if (doc["key"].IsUint64()) { /* 是64位无符号整数 */ } |
IsInt64 |
检查 JSON 节点是否为 64 位整数 | if (doc["key"].IsInt64()) { /* 是64位整数 */ } |
IsLosslessDouble |
检查浮点数是否能无损转换为整数 | if (doc["key"].IsLosslessDouble()) { /* 无损转换 */ } |
(3) 对象操作相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
operator[] |
获取对象中指定键的值 | auto value = doc["key"]; |
MemberBegin |
获取对象的第一个键值对迭代器 | for (auto itr = obj.MemberBegin(); itr != obj.MemberEnd(); ++itr) { std::cout << itr->name.GetString(); } |
MemberEnd |
获取对象的最后一个键值对迭代器 | 同上 |
(4)数组操作相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
operator[] |
获取数组中指定索引的值 | auto value = doc["key"][0]; |
Begin |
获取数组的第一个元素迭代器 | for (auto itr = array.Begin(); itr != array.End(); ++itr) { std::cout << itr->GetInt(); } |
End |
获取数组的最后一个元素迭代器 | 同上 |
Size |
获取数组大小 | size_t len = doc["key"].Size(); |
(5) 获取操作相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
GetString |
获取字符串值 | std::string str = doc["key"].GetString(); |
GetInt |
获取整数值 | int val = doc["key"].GetInt(); |
GetDouble |
获取浮点数值(包括 float 类型) | double val = doc["key"].GetDouble(); |
GetBool |
获取布尔值 | bool flag = doc["key"].GetBool(); |
GetArray |
获取数组值(作为数组对象) | const auto& arr = doc["key"].GetArray(); |
GetObject |
获取对象值(作为对象) | const auto& obj = doc["key"].GetObject(); |
函数名 | 函数用途 | 使用示例 |
---|---|---|
GetUint |
获取无符号整数值 | unsigned int val = doc["key"].GetUint(); |
GetInt64 |
获取 64 位整数值 | int64_t val = doc["key"].GetInt64(); |
GetUint64 |
获取 64 位无符号整数值 | uint64_t val = doc["key"].GetUint64(); |
关于浮点数
-
基础的RapidJSON 中没有
GetFloat
函数。 -
浮点数统一通过
GetDouble
获取。即使是 JSON 文件中的浮点数值可以存储为单精度(float),也会被解释为双精度(double)。 -
若需要
float
类型的值,可以使用强制类型转换:cppfloat val = static_cast<float>(doc["key"].GetDouble());
-
即使在某些RapidJSON中,对
GetFloat
函数也进行了封装,比如我使用的这个版本的document.h文件中,进行了以下封装,所以也可以直接用GetFloat
函数。但其本质还是通过GetDouble
函数获取,然后进行类型转换后返回的。
[[_resources/详细介绍如何使用rapidjson读取json文件/bc2fcd9db0921f81d29d3018268dc70e_MD5.jpeg|Open: Pasted image 20241206092810.png]]
![[_resources/详细介绍如何使用rapidjson读取json文件/bc2fcd9db0921f81d29d3018268dc70e_MD5.jpeg]]
C++
float GetFloat() const {
return static_cast<float>(GetDouble());
}
综合示例
示例JSON文件如下:
json
{
"Info": {
"author": "JZX-MY",
"number": 7,
},
"regions": [
{
"name": "Area 1",
"points": [
{
"x": 1.0,
"y": 2.0
},
{
"x": 3.0,
"y": 4.0
}
]
},
{
"name": "Area 2",
"points": [
{
"x": 5.0,
"y": 6.0
},
{
"x": 7.0,
"y": 8.0
}
]
}
]
}
读取该示例JSON文件的代码片段如下:
cpp
#include <iostream>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <fstream>
int main() {
// 读取 JSON 文件
std::ifstream ifs("data.json");
rapidjson::IStreamWrapper isw(ifs);
// 解析 JSON 文件
rapidjson::Document doc;
doc.ParseStream(isw);
// 检查是否存在错误
if (doc.HasParseError()) {
std::cerr << "解析失败!" << std::endl;
return -1;
}
// 获取对象中的值
std::string author = doc["Info"]["author"].GetString();
int number = doc["Info"]["number"].GetInt();
// 遍历数组
const auto& regions = doc["regions"].GetArray();
for (const auto& region : regions) {
std::cout << "Region Name: " << region["name"].GetString() << std::endl;
const auto& points = region["points"].GetArray();
for (const auto& point : points) {
std::cout << " Point: (" << point["x"].GetDouble() << ", " << point["y"].GetDouble() << ")" << std::endl;
}
}
return 0;
}
二、结合简单示例进行详细的基础介绍
0. 示例
本部分内容以读取如下所示非常简单的test.json文件为例
json
{
"points": [-17, -15, 0],
"cost": 10,
"rate": 0.6,
}
对应的读取示例程序如下:
C++
bool readJson(std::string& file_path, int& cost, double& occ_th, double& free_th) {
std::string read_path = file_path + "/test.json";
std::cout << "Read Json: " << read_path << std::endl;
// 加载 JSON 文件
std::ifstream ifs(read_path);
if (!ifs.is_open()) {
std::cout << " could not open " << read_path << std::endl;
return false;
}
rapidjson::IStreamWrapper isw(ifs);
rapidjson::Document doc;
doc.ParseStream(isw);
if (doc.HasParseError()) {
std::cerr << "Error JSON file " << read_path << std::endl;
return false;
}
// 解析 points 数组
if (doc.HasMember("points") && doc["points"].IsArray() && doc["points"].Size() == 3) {
points_.x() = doc["points"][0].GetDouble();
points_.y() = doc["points"][1].GetDouble();
std::cout << "points x: " << points_.x() << " points y: " << points_.y()
<< " points th: " << doc["points"][2].GetDouble() << std::endl;
} else {
std::cerr << "Invalid points data in map_.json" << std::endl;
return false;
}
// 读取 cost
if (doc.HasMember("cost") && doc["cost"].IsInt()) {
cost = doc["cost"].GetInt();
std::cout << "cost: " << cost << std::endl;
} else {
std::cerr << "The JSON does not contain a cost tag or it is invalid." << std::endl;
return false;
}
// 读取 rate
if (doc.HasMember("rate") && doc["rate"].IsDouble()) {
occ_th = doc["rate"].GetDouble();
std::cout << "rate: " << occ_th << std::endl;
} else {
std::cerr << "The JSON does not contain an rate tag or it is invalid." << std::endl;
return false;
}
return true;
}
在上述代码中,函数 readJson
使用了 RapidJSON 读取和解析一个 JSON 文件。以下是分步骤的详细介绍,以及如何正确使用 RapidJSON 读取 JSON 文件并提取数据:
1. 准备工作
- 路径声明 :std::string read_path = file_path + "/test.json";
- 定义要解析的 JSON 文件路径。
示例如下:
C++
std::string read_path = file_path + "/test.json";
std::cout << "Read Json: " << read_path << std::endl;
- 加载文件 :通过
std::ifstream
打开文件流,确保文件可用。- 如果文件无法打开,返回错误。
示例如下:
C++
std::ifstream ifs(read_path);
if (!ifs.is_open()) {
std::cout << "test.json could not open " << read_path << std::endl;
return false;
}
2. 创建 RapidJSON 的流包装器
RapidJSON 使用 IStreamWrapper
将标准的 C++ 输入流包装为可供 RapidJSON 解析的流。
c++
rapidjson::IStreamWrapper isw(ifs);
- 文档对象 :
rapidjson::Document doc
是用来存储解析后 JSON 数据的主要对象。 - 解析 JSON 流 :通过
doc.ParseStream(isw)
解析 JSON 文件内容。
cpp
rapidjson::Document doc;
doc.ParseStream(isw);
if (doc.HasParseError()) {
std::cerr << "Error JSON file " << read_path << std::endl;
return false;
}
3. 验证和提取 JSON 数据
(1)验证 JSON 数据 【非必要】
- 可以使用
doc.HasParseError()
检查 JSON 文件的解析是否出错。 - 对每个 JSON 字段,使用
doc.HasMember("key")
检查该字段是否存在,并验证字段类型,例如IsArray()
、IsDouble()
等。
(2)解析 JSON 字段
json
{
"points": [-17, -15, 0],
"cost": 10,
"rate": 0.6,
}
数组字段解析示例------points
字段解析
已知points
是一个含有3个数字的数组字段,这里代码验证json文件中是否含有points
成员、它是否为数组,长度是否为 3,若验证成功,则提取数据,并保存至合适的变量中(示例中保存到了类内变量中)。
对于名为points
的数组字段,且数据类型为double,可以使用 doc[ "points" ][ 0 ].GetDouble();
来读取其第一个元素,以此类推
cpp
if (doc.HasMember("points") && doc["points"].IsArray() && doc["points"].Size() == 3) {
points_.x() = doc["points"][0].GetDouble();
points_.y() = doc["points"][1].GetDouble();
std::cout << "points x: " << points_.x() << " points y: " << points_.y()
<< " points th: " << doc["points"][2].GetDouble() << std::endl;
} else {
std::cerr << "Invalid points data in map_.json" << std::endl;
return false;
}
整数字段解析示例------cost
字段解析
cost
是一个整数字段,代码doc.HasMember("cost") 验证是否含有成员cost, 使用 IsInt()
验证其类型是否为int,并通过 GetInt()
获取值。
cpp
if (doc.HasMember("cost") && doc["cost"].IsInt()) {
cost = doc["cost"].GetInt();
std::cout << "cost: " << cost << std::endl;
} else {
std::cerr << "The JSON does not contain a cost tag or it is invalid." << std::endl;
return false;
}
浮点数字段解析示例------rate
字段解析
rate
是一个浮点数字段,代码使用 IsDouble()
验证类型,并通过 GetDouble()
获取值。
cpp
if (doc.HasMember("rate") && doc["rate"].IsDouble()) {
occ_th = doc["rate"].GetDouble();
std::cout << "rate: " << occ_th << std::endl;
} else {
std::cerr << "The JSON does not contain an rate tag or it is invalid." << std::endl;
return false;
}
4. 返回解析结果
在所有字段都成功解析后,函数返回 true
,否则返回 false
。
5. 总结 RapidJSON 的使用
- 文件读取 :通过
std::ifstream
和rapidjson::IStreamWrapper
将文件内容传递给 RapidJSON。 - 文档解析 :通过
rapidjson::Document
和ParseStream
将 JSON 数据加载到内存中。 - 字段验证 :使用
HasMember
和字段类型检查确保数据完整性。 - 字段提取 :通过
Get<Type>()
函数提取具体数据。
这种方法适用于处理结构明确的 JSON 文件,提供高效且安全的解析能力。
三、结合复杂示例进行详细的函数实现介绍
本部分以如下所示的示例为例子展开介绍
json
{
"Info": {
"author": "JZX_MY",
"version": "v1.0.96",
"number": 66,
"position": [-9, 60.0, 10.0],
"rate": 0.99
},
"scale": 77,
"regions": [
{
"index": 0,
"name": "free_regions",
"points": [
{
"x": -2.03,
"y": 2.25
},
{
"x": 1.39,
"y": 5.82
},
{
"x": 7.47,
"y": 2.35
},
{
"x": 5.50,
"y": -1.36
}
]
},
{
"index": 1,
"name": "occupy_regions",
"points": [
{
"x": -2.03,
"y": 27.25
},
{
"x": 10.39,
"y": 5.82
},
{
"x": 78.47,
"y": 2.35
}
]
}
],
"param": {
"max_threshold": 0.05,
"max_num" : 10240,
"success" : true
},
"robot": {
"width": 1510,
"length": 5180,
"radius": 3420
},
"other": []
}
1、编写通用的基本读取框架
根据第二部分的介绍,我们先编写读取一个JSON所需的一些基本框架,如下所示,
C++
/**
* 使用rapidJSON解析JSON文件的函数
* @param directory_path 文件所在的目录路径
* @param file_name 文件名
*/
bool readJSON(const std::string& directory_path, const std::string& file_name) {
// (1)拼接完整的文件路径
std::string full_file_path = directory_path + "/" + file_name;
std::cout << "Read Json file: " << full_file_path << std::endl;
// (2)打开JSON文件流
std::ifstream ifs(full_file_path);
if (!ifs.is_open()) {
// 如果文件无法打开,输出错误信息并返回
std::cerr << "could not open: " << full_file_path << std::endl;
return false;
}
// (3)使用rapidJSON的IStreamWrapper包装文件流,方便解析
rapidjson::IStreamWrapper isw(ifs);
// (4)定义rapidJSON的Document对象,用于存储解析后的JSON数据
rapidjson::Document doc;
doc.ParseStream(isw);
// (5)检查JSON文件解析是否成功
if (doc.HasParseError()) {
// 如果解析失败,输出错误信息并返回
std::cerr << "Error JSON file!" << std::endl;
return false;
}
// ------------------------开始读取------------------------------
// 开始逐个解析读取JSON字段 [后面的步骤补充]
...
...
...
...
...
...
// ------------------------读取完成------------------------------
// 关闭文件流
ifs.close();
return true;
}
其详细介绍如下:
(1)为了方便对多个类似结构的文件进行读取,下面的示例采用了将文件所在路径directory_path和文件名file_name以形参传入的方式,将其拼接后得到完整的要读取的JSON文件的路径full_file_path
C++
// (1)拼接完整的文件路径
std::string full_file_path = directory_path + "/" + file_name;
std::cout << "Read Json file: " << full_file_path << std::endl;
(2)使用std::ifstream(输入文件流)创建一个文件流对象ifs。传入full_file_path作为参数,表示要打开的文件路径。std::ifstream会尝试打开指定路径的文件,并准备从文件中读取数据。【注:文件路径需要是完整路径,只有当路径正确且文件存在时,文件才能成功打开,没有指定模式时,默认以只读模式打开文件(std::ios::in)】
C++
// (2)打开JSON文件流
std::ifstream ifs(full_file_path);
if (!ifs.is_open()) {
// 如果文件无法打开,输出错误信息并返回
std::cerr << "could not open: " << full_file_path << std::endl;
return false;
}
(3)使用RapidJSON 的IStreamWrapper
将标准的 C++ 输入流对象ifs包装为可供 RapidJSON 解析的流isw。
C++
// (3)使用rapidJSON的IStreamWrapper包装文件流,方便解析
rapidjson::IStreamWrapper isw(ifs);
(4)定义rapidJSON的Document对象doc,用于存储解析后的JSON数据文档对象,然后通过 doc.ParseStream(isw)
解析 JSON 文件内容。
C++
// (4)定义rapidJSON的Document对象,用于存储解析后的JSON数据
rapidjson::Document doc;
doc.ParseStream(isw);
(5)检查JSON文件解析是否成功
C++
// (5)检查JSON文件解析是否成功
if (doc.HasParseError()) {
// 如果解析失败,输出错误信息并返回
std::cerr << "Error JSON file!" << std::endl;
return false;
}
(6)若解析成功,则开始根据JSON文件的结构内容,依次进行读取【在下一节中介绍】
(7)成功读取完成后,关闭C++文件流ifs,并返回true
到这里读取一个JSON文件的常用框架就介绍完了,下面根据示例JSON文件的结构补充读取部分
2、在通用框架的基础上补充实际要读取的内容
(1)示例中的Info部分
首先回顾第一部分中的内容:{}
是对象,表示键值对的集合。每个键是一个字符串,键与值之间用冒号(:
)分隔,键值对之间用逗号(,
)分隔。键是唯一的,值可以是任意类型(如字符串、数字、布尔值、数组、对象等 )。[]
是数组,表示有序的值列表。值之间用逗号(,
)分隔,值可以是任意类型(如字符串、数字、布尔值、数组、对象等)。 数组中的值没有键,只有索引(从 0 开始)。对象和数组可以互相嵌套,产生一些比较复杂的结构
本部分内容的结构相对简单,Info作为总的JSON文件对象的一个键值对中的键,值是一个对象,该对象中又包含了author、version、number、position、rate这五个键值对,除了position之外,其他键值对的值都是单个数据,position的值又是一个数组,数组中每个元素也都是普通的单个数据,没有再嵌套下去。
json
{
"Info": {
"author": "JZX_MY",
"version": "v1.0.96",
"number": 66,
"position": [-9, 60.0, 10.0],
"rate": 0.99
}
}
我们先来看Info对象的第一个键值对"author": "JZX_MY",常见的读取方式有以下三种
第一种:通过最简化的直接链式访问
可以直接通过链式访问来读取,如下所示,根据第一部分中常用函数的介绍,读取字符串类型用GetString()函数即可
C++
std::string author = doc["Info"]["author"].GetString();
std::cout << "Author: " << author << std::endl;
在确定 JSON 的结构是固定的,并且不会缺少任何字段的情况下,可以直接使用类似于 doc["Info"]["author"]
的链式访问方式,代码更简洁。缺点是,如果 Info 不存在、不是对象,或者 author 不存在、不是字符串,则会导致程序崩溃。
第二种:通过分层安全访问
这种方式的核心是进行逐步检查,确保每一步的操作都符合预期的条件,然后再执行具体的逻辑。不会因为 JSON 结构问题导致程序崩溃。 能够在异常情况(例如字段缺失或类型不匹配)时,自定义处理逻辑(如打印错误信息)。但代码更长,显得繁琐。如下所示:
JSON
// 检查 JSON 对象是否包含名为 "Info" 的成员,并且该成员是一个对象
if (doc.HasMember("Info") && doc["Info"].IsObject()) {
// 获取 "Info" 成员,并存储为一个常量引用
const rapidjson::Value& info = doc["Info"];
// 检查 "Info" 对象是否包含名为 "author" 的成员,并且该成员是一个字符串
if (info.HasMember("author") && info["author"].IsString()) {
// 获取 "author" 成员的字符串值,并存储到变量 author 中
std::string author = info["author"].GetString();
// 输出 "author" 的值到控制台
std::cout << "Author: " << author << std::endl;
}
}
第三种:通过链式安全访问
可以将前两种结合一下,先检查,然后再通过链式访问,省去了临时中间变量'const rapidjson::Value& info = doc["Info"];'
json
// 检查 JSON 对象是否包含名为 "Info" 的成员,并且该成员是一个对象
if (doc.HasMember("Info") && doc["Info"].IsObject()) {
// 直接链式访问检查 "Info" 对象中的 "author" 成员
if (doc["Info"].HasMember("author") && doc["Info"]["author"].IsString()) {
// 直接链式获取 "author" 的字符串值
std::string author = doc["Info"]["author"].GetString();
// 输出 "author" 的值到控制台
std::cout << "Author: " << author << std::endl;
}
}
该方式虽然看起来稍微简洁了一些,但是 每次链式访问都会重新解析路径,因此可能会稍微影响性能,尤其在大规模或复杂 JSON 时。使用临时中间变量(如 info
)会在逻辑上缓存某个子节点的引用,从而提高性能。如果后续多次访问 "Info"
的内容,建议保留 const rapidjson::Value& info
方式以减少重复解析。【即第二种方式】
综合来看,第二种分层安全访问的方式是最值得推荐的,version、number、rate项与author项类似(换成对应类型的函数即可),这里就不展开介绍了,我们再来看一下position项,给出带检查与不带检查的写法示例
第一种:通过最简化的直接链式访问
如果完全确定数据结构正确,并希望最简化代码,可以直接链式访问:
cpp
float x = doc["Info"]["position"][0].GetFloat();
float y = doc["Info"]["position"][1].GetFloat();
float z = doc["Info"]["position"][2].GetFloat();
std::cout << "Position: (" << x << ", " << y << ", " << z << ")" << std::endl;
缺点:
- 缺乏安全性检查,一旦 JSON 数据结构有误(例如
position
缺失、不是数组或者长度不足),会导致程序崩溃。
第二种:通过分层安全访问
这种方法逐层检查 JSON 的结构,确保安全性:
cpp
if (doc.HasMember("Info") && doc["Info"].IsObject()) {
const rapidjson::Value& info = doc["Info"]; // 获取 "Info" 对象
if (info.HasMember("position") && info["position"].IsArray()) {
const rapidjson::Value& position = info["position"]; // 获取 "position" 数组
// 检查数组长度并逐个读取元素
if (position.Size() == 3) { // 假设数组有3个元素
float x = position[0].GetFloat(); // 获取第一个元素
float y = position[1].GetFloat(); // 获取第二个元素
float z = position[2].GetFloat(); // 获取第三个元素
// 输出读取结果
std::cout << "Position: (" << x << ", " << y << ", " << z << ")" << std::endl;
} else {
std::cerr << "Position array size is not 3!" << std::endl;
}
} else {
std::cerr << "Position does not exist or is not an array!" << std::endl;
}
}
优点:
- 每一步都进行了显式检查,适用于不确定 JSON 数据结构是否完全符合预期的情况。
- 对数组的操作更清晰,容易扩展到更复杂的逻辑。
带检查的Info部分读取示例(仅打印未存储):
C++
// 解析Info部分
if (document.HasMember("Info")) { // 检查是否包含"Info"字段
const rapidjson::Value& info = doc["Info"];
if (info.IsObject()) { // 确保"Info"是一个对象
std::cout << "Info 部分解析结果:" << std::endl;
if (info.HasMember("author") && info["author"].IsString()) {
std::cout << "作者: " << info["author"].GetString() << std::endl; // 输出作者名称
}
if (info.HasMember("version") && info["version"].IsString()) {
std::cout << "版本: " << info["version"].GetString() << std::endl; // 输出版本号
}
if (info.HasMember("number") && info["number"].IsInt()) {
std::cout << "编号: " << info["number"].GetInt() << std::endl; // 输出编号
}
if (info.HasMember("position") && info["position"].IsArray()) {
std::cout << "位置: ";
for (auto& pos : info["position"].GetArray()) {
std::cout << pos.GetFloat() << " "; // 输出位置坐标
}
std::cout << std::endl;
}
if (info.HasMember("rate") && info["rate"].IsDouble()) {
std::cout << "速率: " << info["rate"].GetDouble() << std::endl; // 输出速率
}
}
}
(2)示例中的scale部分
本部分结构很简单,scale作为总的JSON文件对象的一个键值对中的键,值是一个普通的单个数据,没有嵌套。
JSON
{
"scale": 77
}
直接访问:
json
int scale = doc["scale"].GetInt();
std::cout << "Scale: " << scale << std::endl;
安全的访问:
json
if (doc.HasMember("scale") && doc["scale"].IsInt()) {
int scale = doc["scale"].GetInt(); // 读取 "scale" 的值
std::cout << "Scale: " << scale << std::endl; // 输出结果
} else {
std::cerr << "Scale is not present or not an integer!" << std::endl;
}
(3)示例中的regions部分
本部分结构相对复杂一点,regions作为总的JSON文件对象的一个键值对中的键,值是一个数组,数组中每个成员又是一个对象,该对象中含有index、name、points三个成员,其中points的值又是一个数组,数组中每个成员又是一个对象,该对象包含x和y两个键值对,值为普通变量,嵌套结束。
json
{
"regions": [
{
"index": 0,
"name": "free_regions",
"points": [
{
"x": -2.03,
"y": 2.25
},
{
"x": 1.39,
"y": 5.82
},
{
"x": 7.47,
"y": 2.35
},
{
"x": 5.50,
"y": -1.36
}
]
},
{
"index": 1,
"name": "occupy_regions",
"points": [
{
"x": -2.03,
"y": 27.25
},
{
"x": 10.39,
"y": 5.82
},
{
"x": 78.47,
"y": 2.35
}
]
}
]
}
第一种:最简洁的无检查写法
json
// 遍历regions数组中每个region
for (const auto& region : doc["regions"].GetArray()) {
std::cout << "Region index: " << region["index"].GetInt() << std::endl;
std::cout << "Region name: " << region["name"].GetString() << std::endl;
// 遍历points数组中每个point
for (const auto& point : region["points"].GetArray()) {
std::cout << " Point: (x: " << point["x"].GetDouble()
<< ", y: " << point["y"].GetDouble() << ")" << std::endl;
}
}
第二种:逐层检查的最安全的写法
json
if (doc.HasMember("regions") && doc["regions"].IsArray()) {
const rapidjson::Value& regions = doc["regions"];
for (rapidjson::SizeType i = 0; i < regions.Size(); ++i) {
const rapidjson::Value& region = regions[i];
if (region.HasMember("index") && region["index"].IsInt()) {
int index = region["index"].GetInt();
std::cout << "Region index: " << index << std::endl;
}
if (region.HasMember("name") && region["name"].IsString()) {
std::string name = region["name"].GetString();
std::cout << "Region name: " << name << std::endl;
}
if (region.HasMember("points") && region["points"].IsArray()) {
const rapidjson::Value& points = region["points"];
for (rapidjson::SizeType j = 0; j < points.Size(); ++j) {
const rapidjson::Value& point = points[j];
if (point.HasMember("x") && point["x"].IsDouble() &&
point.HasMember("y") && point["y"].IsDouble()) {
double x = point["x"].GetDouble();
double y = point["y"].GetDouble();
std::cout << " Point: (x: " << x << ", y: " << y << ")" << std::endl;
}
}
}
}
}
第三种:仅针对顶层结构检查的的写法
json
if (doc.HasMember("regions") && doc["regions"].IsArray()) {
for (const auto& region : doc["regions"].GetArray()) {
int index = region["index"].GetInt();
std::cout << "Region index: " << index << std::endl;
std::string name = region["name"].GetString();
std::cout << "Region name: " << name << std::endl;
for (const auto& point : region["points"].GetArray()) {
double x = point["x"].GetDouble();
double y = point["y"].GetDouble();
std::cout << " Point: (x: " << x << ", y: " << y << ")" << std::endl;
}
}
}
(4)示例中的param部分
json
{
"param": {
"max_threshold": 0.05,
"max_num" : 10240,
"success" : true
}
}
第一种:无检查写法
json
const rapidjson::Value& param = doc["param"];
double max_threshold = param["max_threshold"].GetDouble();
int max_num = param["max_num"].GetInt();
bool success = param["success"].GetBool();
std::cout << "Max Threshold: " << max_threshold << std::endl;
std::cout << "Max Num: " << max_num << std::endl;
std::cout << "Success: " << (success ? "true" : "false") << std::endl;
或
json
double max_threshold = doc["param"]["max_threshold"].GetDouble();
int max_num = doc["param"]["max_num"].GetInt();
bool success = doc["param"]["success"].GetBool();
std::cout << "Max Threshold: " << max_threshold << std::endl;
std::cout << "Max Num: " << max_num << std::endl;
std::cout << "Success: " << (success ? "true" : "false") << std::endl;
第二种:带检查写法
json
if (doc.HasMember("param") && doc["param"].IsObject()) {
const rapidjson::Value& param = doc["param"];
if (param.HasMember("max_threshold") && param["max_threshold"].IsDouble()) {
double max_threshold = param["max_threshold"].GetDouble();
std::cout << "Max Threshold: " << max_threshold << std::endl;
}
if (param.HasMember("max_num") && param["max_num"].IsInt()) {
int max_num = param["max_num"].GetInt();
std::cout << "Max Num: " << max_num << std::endl;
}
if (param.HasMember("success") && param["success"].IsBool()) {
bool success = param["success"].GetBool();
std::cout << "Success: " << (success ? "true" : "false") << std::endl;
}
}
(5)示例中的robot部分
json
{
"robot": {
"width": 1510,
"length": 5180,
"radius": 3420
}
}
第一种:无检查写法
json
const rapidjson::Value& robot = doc["robot"];
int width = robot["width"].GetInt();
int length = robot["length"].GetInt();
int radius = robot["radius"].GetInt();
std::cout << "Width: " << width << std::endl;
std::cout << "Length: " << length << std::endl;
std::cout << "Radius: " << radius << std::endl;
或
json
int width = doc["robot"]["width"].GetInt();
int length = doc["robot"]["length"].GetInt();
int radius = doc["robot"]["radius"].GetInt();
std::cout << "Width: " << width << std::endl;
std::cout << "Length: " << length << std::endl;
std::cout << "Radius: " << radius << std::endl;
第二种:带检查写法
json
if (doc.HasMember("robot") && doc["robot"].IsObject()) {
const rapidjson::Value& robot = doc["robot"];
if (robot.HasMember("width") && robot["width"].IsInt()) {
int width = robot["width"].GetInt();
std::cout << "Width: " << width << std::endl;
}
if (robot.HasMember("length") && robot["length"].IsInt()) {
int length = robot["length"].GetInt();
std::cout << "Length: " << length << std::endl;
}
if (robot.HasMember("radius") && robot["radius"].IsInt()) {
int radius = robot["radius"].GetInt();
std::cout << "Radius: " << radius << std::endl;
}
}
(6)示例中的other部分
json
{
"other": []
}
第一种:仅检查数组是否为空
json
const rapidjson::Value& other = doc["other"];
if (other.Empty()) {
std::cout << "The 'other' array is empty." << std::endl;
} else {
for (rapidjson::SizeType i = 0; i < other.Size(); ++i) {
std::cout << "Element " << i << ": " << other[i].GetString() << std::endl;
}
}
第二种:逐层检查写法
json
if (doc.HasMember("other") && doc["other"].IsArray()) {
const rapidjson::Value& other = doc["other"];
// 检查数组是否为空
if (other.Empty()) {
std::cout << "The 'other' array is empty." << std::endl;
} else {
// 遍历数组元素(如果有的话)
for (rapidjson::SizeType i = 0; i < other.Size(); ++i) {
std::cout << "Element " << i << ": " << other[i].GetString() << std::endl;
}
}
}
3、完整的程序示例 & 运行
(1)将以下内容存储为main.cpp
C++
#include <iostream>
#include <fstream>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/istreamwrapper.h"
/**
* 使用rapidJSON解析JSON文件的函数
* @param directory_path 文件所在的目录路径
* @param file_name 文件名
*/
void parseJSON(const std::string& directory_path, const std::string& file_name) {
// 拼接完整的文件路径,判断 directory_path 是否为空,
std::string full_file_path = directory_path.empty()
? file_name //若为空,说明要读取的json文件与当前可执行文件在同一目录下,则直接将file_name作为路径
: (directory_path.back() == '/' // 判断 directory_path 的最后是否有 '/',若有则直接拼接 file_name,否则加上 '/'
? directory_path + file_name
: directory_path + "/" + file_name);
// 打开JSON文件流
std::ifstream ifs(full_file_path);
if (!ifs.is_open()) {
// 如果文件无法打开,输出错误信息并返回
std::cerr << "无法打开文件: " << full_file_path << std::endl;
return;
}
// 使用rapidJSON的IStreamWrapper包装文件流,方便解析
rapidjson::IStreamWrapper isw(ifs);
// 定义rapidJSON的Document对象,用于存储解析后的JSON数据
rapidjson::Document document;
document.ParseStream(isw);
// 检查JSON文件解析是否成功
if (document.HasParseError()) {
// 如果解析失败,输出错误信息并返回
std::cerr << "JSON解析错误!" << std::endl;
return;
}
// 开始逐个解析JSON字段
// 解析"Info"部分
if (document.HasMember("Info")) { // 检查是否存在"Info"字段
const rapidjson::Value& info = document["Info"];
if (info.IsObject()) { // 确保"Info"是一个对象类型
std::cout << "Info 部分解析结果:" << std::endl;
// 读取作者信息
if (info.HasMember("author") && info["author"].IsString()) {
std::cout << "作者: " << info["author"].GetString() << std::endl; // 输出作者名称
}
// 读取版本号
if (info.HasMember("version") && info["version"].IsString()) {
std::cout << "版本: " << info["version"].GetString() << std::endl; // 输出版本号
}
// 读取编号
if (info.HasMember("number") && info["number"].IsInt()) {
std::cout << "编号: " << info["number"].GetInt() << std::endl; // 输出编号
}
// 读取位置数组
if (info.HasMember("position") && info["position"].IsArray()) {
std::cout << "位置: ";
for (auto& pos : info["position"].GetArray()) {
std::cout << pos.GetFloat() << " "; // 遍历并输出位置坐标
}
std::cout << std::endl;
}
// 读取速率
if (info.HasMember("rate") && info["rate"].IsDouble()) {
std::cout << "速率: " << info["rate"].GetDouble() << std::endl; // 输出速率
}
}
}
// 解析"scale"部分
if (document.HasMember("scale") && document["scale"].IsInt()) {
std::cout << "缩放比例: " << document["scale"].GetInt() << std::endl; // 输出缩放比例
}
// 解析"regions"部分
if (document.HasMember("regions") && document["regions"].IsArray()) {
std::cout << "区域解析:" << std::endl;
const rapidjson::Value& regions = document["regions"];
for (const auto& region : regions.GetArray()) { // 遍历区域数组
// 输出区域索引
if (region.HasMember("index") && region["index"].IsInt()) {
std::cout << "区域索引: " << region["index"].GetInt() << std::endl;
}
// 输出区域名称
if (region.HasMember("name") && region["name"].IsString()) {
std::cout << "区域名称: " << region["name"].GetString() << std::endl;
}
// 输出区域点坐标
if (region.HasMember("points") && region["points"].IsArray()) {
std::cout << "区域点坐标:" << std::endl;
for (const auto& point : region["points"].GetArray()) {
if (point.HasMember("x") && point["x"].IsDouble() &&
point.HasMember("y") && point["y"].IsDouble()) {
std::cout << "x: " << point["x"].GetDouble() << ", y: " << point["y"].GetDouble() << std::endl;
}
}
}
}
}
// 解析"param"部分
if (document.HasMember("param") && document["param"].IsObject()) {
std::cout << "参数解析:" << std::endl;
const rapidjson::Value& param = document["param"];
// 输出最大阈值
if (param.HasMember("max_threshold") && param["max_threshold"].IsDouble()) {
std::cout << "最大阈值: " << param["max_threshold"].GetDouble() << std::endl;
}
// 输出最大数量
if (param.HasMember("max_num") && param["max_num"].IsInt()) {
std::cout << "最大数量: " << param["max_num"].GetInt() << std::endl;
}
// 输出成功状态
if (param.HasMember("success") && param["success"].IsBool()) {
std::cout << "是否成功: " << (param["success"].GetBool() ? "是" : "否") << std::endl;
}
}
// 解析"robot"部分
if (document.HasMember("robot") && document["robot"].IsObject()) {
std::cout << "机器人参数:" << std::endl;
const rapidjson::Value& robot = document["robot"];
// 输出机器人宽度
if (robot.HasMember("width") && robot["width"].IsInt()) {
std::cout << "宽度: " << robot["width"].GetInt() << std::endl;
}
// 输出机器人长度
if (robot.HasMember("length") && robot["length"].IsInt()) {
std::cout << "长度: " << robot["length"].GetInt() << std::endl;
}
// 输出机器人半径
if (robot.HasMember("radius") && robot["radius"].IsInt()) {
std::cout << "半径: " << robot["radius"].GetInt() << std::endl;
}
}
// 解析"other"部分
if (document.HasMember("other") && document["other"].IsArray()) {
std::cout << "其他信息: " << (document["other"].Empty() ? "空" : "存在内容") << std::endl;
}
// 关闭文件流
ifs.close();
}
int main() {
// 调用解析函数,传入JSON文件所在目录路径和文件名
parseJSON("", "test.json");
return 0;
}
(2)在与main.cpp的同一目录下,将以下内容存储为test.json
json
{
"Info": {
"author": "JZX_MY",
"version": "v1.0.96",
"number": 66,
"position": [-9, 60.0, 10.0],
"rate": 0.99
},
"scale": 77,
"regions": [
{
"index": 0,
"name": "free_regions",
"points": [
{
"x": -2.03,
"y": 2.25
},
{
"x": 1.39,
"y": 5.82
},
{
"x": 7.47,
"y": 2.35
},
{
"x": 5.50,
"y": -1.36
}
]
},
{
"index": 1,
"name": "occupy_regions",
"points": [
{
"x": -2.03,
"y": 27.25
},
{
"x": 10.39,
"y": 5.82
},
{
"x": 78.47,
"y": 2.35
}
]
}
],
"param": {
"max_threshold": 0.05,
"max_num" : 10240,
"success" : true
},
"robot": {
"width": 1510,
"length": 5180,
"radius": 3420
},
"other": []
}
(3)在存储以上两个文件的目录下的终端运行以下指令进行编译
bash
g++ main.cpp -o main
(4)继续在该终端下,运行以下指令,运行程序
./main
(5)可以看到如下所示的运行结果
bash
Info 部分解析结果:
作者: JZX_MY
版本: v1.0.96
编号: 66
位置: -9 60 10
速率: 0.99
缩放比例: 77
区域解析:
区域索引: 0
区域名称: free_regions
区域点坐标:
x: -2.03, y: 2.25
x: 1.39, y: 5.82
x: 7.47, y: 2.35
x: 5.5, y: -1.36
区域索引: 1
区域名称: occupy_regions
区域点坐标:
x: -2.03, y: 27.25
x: 10.39, y: 5.82
x: 78.47, y: 2.35
参数解析:
最大阈值: 0.05
最大数量: 10240
是否成功: 是
机器人参数:
宽度: 1510
长度: 5180
半径: 3420
其他信息: 空