详细介绍如何使用rapidjson读取json文件

本文主要详细介绍如何使用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 个整数值:10203040

(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 类型的值,可以使用强制类型转换:

    cpp 复制代码
    float 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::ifstreamrapidjson::IStreamWrapper 将文件内容传递给 RapidJSON。
  • 文档解析 :通过 rapidjson::DocumentParseStream 将 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
其他信息: 空
以上综合示例的相关文件,我已经放在了本文的绑定附件中,有需要可以自行获取。
相关推荐
Cachel wood1 小时前
Vue.js前端框架教程8:Vue消息提示ElMessage和ElMessageBox
linux·前端·javascript·vue.js·前端框架·ecmascript
人才程序员3 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
w(゚Д゚)w吓洗宝宝了3 小时前
C vs C++: 一场编程语言的演变与对比
c语言·开发语言·c++
小屁不止是运维3 小时前
麒麟操作系统服务架构保姆级教程(二)ssh远程连接
linux·运维·服务器·学习·架构·ssh
小老鼠不吃猫5 小时前
C++点云大文件读取
开发语言·c++
姚先生975 小时前
LeetCode 35. 搜索插入位置 (C++实现)
c++·算法·leetcode
黑客K-ing5 小时前
网络安全防范
linux·服务器·web安全
CoderCodingNo6 小时前
【GESP】C++二级考试大纲知识点梳理, (4)流程图
开发语言·c++·流程图
王三三6 小时前
群晖利用acme.sh自动申请证书并且自动重载证书的问题解决
linux·自动化·证书·群晖·acme·acme.sh·lets encrypt
路飞雪吖~6 小时前
【Linux】进程控制
linux·运维·服务器