简介
RapidJSON是一个高效的C++ JSON解析器和生成器。它专注于性能和易用性,使得处理JSON数据变得简单和快速。RapidJSON支持现代的JSON特性,如嵌套对象、数组、Unicode编码和注释。它的API简洁易用,可以轻松解析和生成JSON数据。无论你的项目需要处理大量的JSON数据,还是只需要解析或生成少量的JSON数据,RapidJSON都能提供出色的性能和便利的API,成为你的理想选择。
说明文档
https://rapidjson.org/zh-cn/md_doc_pointer_8zh-cn.html
下载地址
https://github.com/Tencent/rapidjson/
https://gitcode.com/mirrors/Tencent/rapidjson/tree/master
安装
RapidJSON 是只有头文件的 C++ 库。只需把 include/rapidjson 目录复制至系统或项目的 include 目录中。或者如果是用vs可以设置包含目录
Value 及 Document
每个 JSON 值都储存为 Value 类,而 Document 类则表示整个 DOM,它存储了一个 DOM 树的根 Value。
RapidJSON 的所有公开类型及函数都在 rapidjson 命名空间中。
查询 Value
- 头文件和命名空间
#include "rapidjson/document.h"
using namespace rapidjson;
- json字符串
{
"hello": "world",
"t": true ,
"f": false,
"n": null,
"i": 123,
"pi": 3.1416,
"a": [1, 2, 3, 4]
}
- 代码
将JSON字符串,解析至document 中,成为一棵 DOM 树
#include <iostream>
#include "rapidjson/document.h"
using namespace std;
using namespace rapidjson;
int main()
{
string json = "{ \"hello\": \"world\", \"t\": true, \"f\": false, \"n\": null, \"i\": 123, \"pi\": 3.1416, \"a\": [1, 2, 3, 4] }";
Document document;
document.Parse(json.c_str());
cin.get();
return 0;
}
- DOM树
- 判断根是不是 Object
assert(document.IsObject());
assert 是一个判断语句。参数为false时,会导致程序终止。在生产环境中,要使用其他方法来处理这种情况,例如通过返回错误代码或抛出异常。
- 获取成员值
- 让我们查询一下根 Object 中有没有 “hello” 成员。
assert(document.HasMember("hello"));
- 验证类型
assert(document["hello"].IsString());
- 根据类型获取其值
printf("hello = %s\n", document["hello"].GetString());
输出:world
- JSON True/False 值是以 bool 表示的。
assert(document["t"].IsBool());
printf("t = %s\n", document["t"].GetBool() ? "true" : "false");
输出:true
- JSON Null 值可用 IsNull() 查询。
printf("n = %s\n", document["n"].IsNull() ? "null" : "?");
输出:null
- JSON Number 类型表示所有数值。然而,C++ 需要使用更专门的类型。
assert(document["i"].IsNumber());
assert(document["pi"].IsNumber());
assert(document["i"].IsInt());
printf("i = %d\n", document["i"].GetInt());
assert(document["pi"].IsDouble());
printf("pi = %g\n", document["pi"].GetDouble());
整型123、浮点型3.1416使用
IsNumber()
判断都是true
。
输出:
i = 123
pi = 3.1416
- JSON Array 包含一些元素。
// 使用引用来连续访问,方便之余还更高效。
const Value& a = document["a"];
assert(a.IsArray());
for (SizeType i = 0; i < a.Size(); i++) // 使用 SizeType 而不是 size_t
printf("a[%d] = %d\n", i, a[i].GetInt());
输出:
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
注意,RapidJSON 并不自动转换各种 JSON 类型。例如,对一个 String 的 Value 调用 GetInt() 是非法的,其行为是未定义的.
查询 Array
SizeType 是 unsigned int 的别名。在多数系统中,Array 最多能存储 2^32-1 个元素。
Array 与 std::vector 相似,除了使用索引,也可使用迭代器来访问所有元素。
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
printf("%d ", itr->GetInt());
当使用 C++11 功能时,你可使用范围 for 循环去访问 Array 内的所有元素。
for (auto& v : a.GetArray())
printf("%d ", v.GetInt());
查询 Object
用迭代器去访问所有 Object 成员:
vector<string> kTypeNames = {"Null", "False", "True", "Object", "Array", "String", "Number"};
for (Value::ConstMemberIterator itr = document.MemberBegin();
itr != document.MemberEnd(); ++itr)
{
printf("Type of member %s is %s\n",itr->name.GetString(), kTypeNames[itr->value.GetType()].c_str());
}
输出:
Type of member hello is String
Type of member t is True
Type of member f is False
Type of member n is Null
Type of member i is Number
Type of member pi is Number
Type of member a is Array
当使用 C++11 功能时,你可使用范围 for 循环去访问 Object 内的所有成员。
for (auto& m : document.GetObject())
printf("Type of member %s is %s\n",
m.name.GetString(), kTypeNames[m.value.GetType()]);
判断对象是否存在
HasMember()
方法,会导致两次查找:
if(document.HasMember("hello"))
printf("%s\n", document["hello"].GetString());
FindMember()
方法,更好:
Value::ConstMemberIterator itr = document.FindMember("hello");
if (itr != document.MemberEnd())
printf("%s\n", itr->value.GetString());
查询 Number
|
|
|
---|---|---|
bool IsUint() | unsigned GetUint() | 32 位无符号整数 |
bool IsInt() | int GetInt() | 32 位有符号整数 |
bool IsUint64() | uint64_t GetUint64() | 64 位无符号整数 |
bool IsInt64() | int64_t GetInt64() | 64 位有符号整数 |
bool IsDouble() | double GetDouble() | 64 位双精度浮点数 |
注意,一个整数可能用几种类型来提取,而无需转换。例如,一个名为 x 的 Value 包含 123,那么 x.IsInt() == x.IsUint() == x.IsInt64() == x.IsUint64() == true
。但如果一个名为 y 的 Value 包含 -3000000000,那么仅会令 x.IsInt64() == true
。
当要提取 Number 类型,GetDouble() 是会把内部整数的表示转换成 double。注意 int 和 unsigned 可以安全地转换至 double,但 int64_t 及 uint64_t 可能会丧失精度( int64_t 最大值有19 位有效数字,uint64_t最大值有 20 位有效数字,都超过了 double 15位有效数字的限制)。
查询 String
c++string字符串把 '\0'
作为结束符号,如果json的值中带有这个字符,则需要用GetStringLength()
获取正确的长度。
if (document.HasMember("hello") && document["hello"].IsString())
{
SizeType len = document["hello"].GetStringLength();
string str(document["hello"].GetString(), len);
printf("%s %d\n", str.c_str(), len);
}
输出:
world 5
比较两个 Value
直接使用 == 及 != 比较两个 Value。当两个 Value 的类型及内容相同,它们才当作相等。也可以比较 Value 和它的原生类型值。例子:
if (document["hello"] == document["n"]) /*...*/; // 比较两个值
if (document["hello"] == "world") /*...*/; // 与字符串字面量作比较
if (document["i"] != 123) /*...*/; // 与整数作比较
if (document["pi"] != 3.14) /*...*/; // 与 double 作比较
Array/Object 顺序以它们的元素/成员作比较。当且仅当它们的整个子树相等,它们才当作相等。
另外需要注意的是若一个 Object 含有重复命名的成员,它与任何 Object 作比较都总会返回 false。
创建/修改值
当一个 DOM 树被创建或修改后,可使用 Writer 再次存储为 JSON。
改变 Value 类型和值
代码
document["t"].SetInt(666);
document["t"] = 666; // 简写,和上面的相同
Value t(666); //使用Value的构造函数
document["t"] = t;
完整代码
#include <iostream>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
using namespace std;
using namespace rapidjson;
int main() {
string json = "{ \"hello\": \"world\", \"t\": true, \"f\": false, \"n\": null, \"i\": 123, \"pi\": 3.1416, \"a\": [1, 2, 3, 4] }";
Document document;
document.Parse(json.c_str());
修改值
document["hello"] = "LiHai"; // 修改 "hello" 的值为 "earth"
document["t"] = 666; // 修改 "t" 的值为666,同时修改类型
//使用 Writer 再次存储为 JSON
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
document.Accept(writer);
string jsonNew = buffer.GetString();
//打印输出
cout << json << endl;
cout << jsonNew << endl;
cin.get();
return 0;
}
输出:
{ “hello”:"world"
, “t”:true
, “f”: false, “n”: null, “i”: 123, “pi”: 3.1416, “a”: [1, 2, 3, 4] }
{“hello”:"LiHai"
,“t”:666
,“f”:false,“n”:null,“i”:123,“pi”:3.1416,“a”:[1,2,3,4]}
构造函数的各个重载
几个类型也有重载构造函数:
Value b(true); // 调用 Value(bool)
Value i(-123); // 调用 Value(int)
Value u(123u); // 调用 Value(unsigned)
Value d(1.5); // 调用 Value(double)
要重建空 Object 或 Array,可在默认构造函数后使用 SetObject()/SetArray(),或一次性使用 Value(Type):
Value o(kObjectType);
Value a(kArrayType);
转移语义(Move Semantics)
在设计 RapidJSON 时有一个非常特别的决定,就是 Value 赋值并不是把来源 Value 复制至目的 Value,而是把来源 Value 转移(move)至目的 Value。例如:
Value a(123);
Value b(456);
b = a; // a 变成 Null,b 变成数字 123。
- 为什么?此语义有何优点?
赋值时转移拥有权。转移快得多简单得多,只需要析构原来的 Value,把来源 memcpy() 至目标,最后把来源设置为 Null 类型。
深复制 Value
Value a(123);
Value b(456);
b.CopyFrom(a, document.GetAllocator()); // a 仍然是数字 123,b 现在是数字 123。
交换 Value
RapidJSON 也提供 Swap()。
Value a(123);
Value b("Hello");
a.Swap(b);
修改 Array
Array 类型的 Value 提供与 std::vector 相似的 API。
Clear()
Reserve(SizeType, Allocator&)
Value& PushBack(Value&, Allocator&)
template <typename T> GenericValue& PushBack(T, Allocator&)
Value& PopBack()
ValueIterator Erase(ConstValueIterator pos)
ValueIterator Erase(ConstValueIterator first, ConstValueIterator last)
注意,Reserve(...)
及 PushBack(...)
可能会为数组元素分配内存,所以需要一个 allocator
。
以下是 PushBack() 的例子:
#include <iostream>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
using namespace std;
using namespace rapidjson;
int main()
{
string json = "{ \"hello\": \"world\", \"t\": true, \"f\": false, \"n\": null, \"i\": 123, \"pi\": 3.1416, \"a\": [1, 2, 3, 4] }";
Document document;
document.Parse(json.c_str());
Value a(kArrayType);
Document::AllocatorType& allocator = document.GetAllocator();
for (int i = 5; i <= 10; i++)
a.PushBack(i, allocator); // 可能需要调用 realloc() 所以需要 allocator
// 流畅接口(Fluent interface)
a.PushBack("Li", allocator).PushBack("Hai", allocator);
document["a"] = a;
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
document.Accept(writer);
string jsonNew = buffer.GetString();
cout << json << endl;
cout << jsonNew << endl;
cin.get();
return 0;
}
输出:
{ …, “a”: [1, 2, 3, 4] }
{…,“a”:[5,6,7,8,9,10,“Li”,“Hai”]}
流畅接口(fluent interface)
与 STL 不一样的是,PushBack()
/PopBack()
返回 Array 本身的引用。这称为流畅接口(fluent interface)。
a.PushBack("Li", allocator).PushBack("Hai", allocator);
修改 Object
Object 是键值对的集合。每个键必须为 String。要修改 Object,方法是增加或移除成员。以下的 API 用来增加成员:
Value& AddMember(Value&, Value&, Allocator& allocator)
Value& AddMember(StringRefType, Value&, Allocator&)
template <typename T> Value& AddMember(StringRefType, T value, Allocator&)
以下是一个例子。
Value contact(kObject);
contact.AddMember("name", "Milo", document.GetAllocator());
contact.AddMember("married", true, document.GetAllocator());
使用 StringRefType
作为 name
参数的重载版本与字符串的 SetString
的接口相似。 这些重载是为了避免复制 name
字符串,因为 JSON object 中经常会使用常数键名。
如果你需要从非常数字符串或生命周期不足的字符串创建键名(见 创建 String),你需要使用 copy-string API。为了避免中间变量,可以就地使用 临时值:
// 就地 Value 参数
contact.AddMember(Value("copy", document.GetAllocator()).Move(), // copy string
Value().Move(), // null value
document.GetAllocator());
// 显式参数
Value key("key", document.GetAllocator()); // copy string name
Value val(42); // 某 Value
contact.AddMember(key, val, document.GetAllocator());
移除成员有几个选择:
bool RemoveMember(const Ch* name)
:使用键名来移除成员(线性时间复杂度)。
bool RemoveMember(const Value& name)
:除了 name 是一个 Value,和上一行相同。
MemberIterator RemoveMember(MemberIterator)
:使用迭代器移除成员(_ 常数 _ 时间复杂度)。
MemberIterator EraseMember(MemberIterator)
:和上行相似但维持成员次序(线性时间复杂度)。
MemberIterator EraseMember(MemberIterator first, MemberIterator last)
:移除一个范围内的成员,维持次序(线性时间复杂度)。
MemberIterator RemoveMember(MemberIterator)
使用了“转移最后”手法来达成常数时间复杂度。基本上就是析构迭代器位置的成员,然后把最后的成员转移至迭代器位置。因此,成员的次序会被改变。