C++ Rapidjson 完全指南 - 高效 JSON 解析与性能优化
Rapidjson
是一款 C++ 的
json
库. 支持处理
json
格式的文档. 其设计风格是头文件库, 包含头文件即可使用, 小巧轻便并且性能强悍. 本文结合在线样例来介绍 Rapidjson 一些常见的用法.
作者提了一个 PR 将 Rapidjson 集成到 Compiler Explorer 上. 目前可以使用 Compiler Explorer 来体验 Rapidjson 的功能. 如果想要本地安装, 请参考 最后一节 .
基础用法
解析 json
访问元素
检查并获取
HasMember
查询 key 是否存在, 然后使用
Is
方法来判断类型是否兼容, 最后用
Get
方法来获取对应的值.
使用
FindMember
减少查询开销
上述示例中,
doc["name"]
被使用了两次, 相当于创建了两个临时变量. 使用
FindMember
方法则可以减少这种额外开销.
访问对象(Object)
现在假定我们有下面的 json:
查询方法与前面的基础类型相似. 需要注意的是,
GetObject()
方法返回的是一个
const
引用.
Rapidjson 为了提高效率, 接口的设计上避免使用对象拷贝.
访问数组(Array)
我们用
IsArray()
和
GetArray()
来判断和获取对应的数据.
需要注意的是: json 中的数组是允许多个不同类型的, 如下是一个合法的 json:
但是 C++ 的数组或者容器
vector
仅支持相同的元素, 所以我们在获取数组元素时需要注意判断元素类型.
由于 rapidjson 支持
range based for
, 我们可以这样写:
序列化 json 对象
下面的函数用来将 json 对象序列化成字符串. 后续的示例中将会用到这个函数
生成 json 对象
基础类型
对于基础类型(整型, 布尔值, 浮点数)我们可以直接使用
AddMember
添加, 需要注意的是接口中需要指定一个
Allocator
.
此时
doc
的内容为:
为了减少对
GetAllocator()
的调用, 可以使用一个变量保存该结果, 见后续代码.
刚开始使用 Rapidjson 时会遇到如下场景下的编译失败:
-
value
类型为std::string
-
key
类型为std::string
接下来详述其解决办法.
解决 value 为 std::string 类型的问题
编译器错误:
也就是没有匹配的
AddMember
可以接受
std::string
类型. 这是因为
Rapidjson
默认没有打开
std::string
支持. 解决办法是在项目中增加
RAPIDJSON_HAS_STDSTRING
宏定义. 可以在编译时增加
-DRAPIDJSON_HAS_STDSTRING
参数.
-
手动增加编译器参数:
g++ -DRAPIDJSON_HAS_STDSTRING
-
在
CMakeLists.txt
中增加如下代码: -
代码中在
include
之前增加如下代码:#define RAPIDJSON_HAS_STDSTRING 1
解决 key 为 std::string 类型的问题
编译器错误:
解决办法为创建一个
rapidjson::Value
对象, 并对其赋给动态值.
添加对象
一个 Object 对象可以用
rapidjson::Value
表示. 其添加成员的方法是
AddMember
(
rapidjson::Document
是
rapidjson::Value
的衍生类).
对于特殊值
null
, 我们可以使用
SetNull()
方法或者在构造函数中指定
rapidjson::kNullType
来实现.
此时的
doc
为:
添加数组
Array 类型的创建和添加如下所示.
此时
doc
为:
进阶用法
使用函数模板简化解析
从前面解析的例子我们可以看到, 对每一个字段的解析都有:查找是否存在, 判断类型是否匹配, 然后获取值, 代码接近只是字段和类型有区别, 这会导致大量的冗余代码. 可以通过模板函数来实现一个通用的解析代码.
-
模板签名为:
其中:
-
data
是需要解析的 json 数据 -
name
是字段名 -
target
是存储解析结果的变量, 我们用std::variant
来存储不同的解析类型, 比如:int*
,double*
,std::string*
等.
-
-
在实现中, 先查找字段中是否存在字段, 如果不存在则报错.
-
接着判断字段值的类型是否与
target
预期一致. 我们用std::visit
来访问std::variant
, 针对不同类型做不同的解析, 对目前尚不支持的类型则报错.
下面以解析一个结构体为例展示如何使用. 页面中仅仅展示了一部分关键代码. 点击下面的运行或者访问 parse.cpp 可以获取到完整代码.
处理多重嵌套
在工作中我们有时候会遇到嵌套很深的 json 文档. 比如给定这样一个 json 文档, 现在我们要获取
/data/avatar/image/thumbnail
如何操作?
如果按照之前的写法层层解析, 那么必然是个很深的嵌套.
但是现在有个更好的解决办法, 就是
JSONPath
, 在 rapidjson 里面就是
Pointer
类, 可以轻松获取到嵌套很深的值.
完整的代码请参考: jsonpath.cpp
安装集成
有如何的几种方法可以将 Rapidjson 集成到您的项目中.
-
Vcpkg
安装: 使用vcpkg install rapidjson
即可. 如果不熟悉 vcpkg 请参考我的文章: Vcpkg 使用全攻略: 支持 VS Code, Visual Studio 和 CLion . -
CMake
的FetchContent_Declare
方法. -
源码安装: 下载源码并将其路径加入
include
目录列表中:gcc -I /path/to/rapidjson
参考链接
Tags: