RapidJSON是一个 C++ 的 JSON 解析器及生成器。它的灵感来自 RapidXml。
- RapidJSON 小而全。它同时支持 SAX 和 DOM 风格的 API。SAX 解析器只有约 500 行代码。
- RapidJSON 快。它的性能可与 strlen() 相比。可支持 SSE2/SSE4.2 加速。
- RapidJSON 独立。它不依赖于 BOOST 等外部库。它甚至不依赖于 STL。
- RapidJSON 对内存友好。在大部分 32/64 位机器上,每个 JSON 值只占 16 字节(除字符串外)。它预设使用一个快速的内存分配器,令分析器可以紧凑地分配内存。
- RapidJSON 对 Unicode 友好。它支持 UTF-8、UTF-16、UTF-32 (大端序/小端序),并内部支持这些编码的检测、校验及转码。例如,RapidJSON 可以在分析一个 UTF-8 文件至 DOM 时,把当中的 JSON 字符串转码至 UTF-16。它也支持代理对(surrogate pair)及 “\u0000”(空字符)。
安装
RapidJSON 是只有头文件的 C++ 库。只需把 include/rapidjson 目录复制至系统或项目的 include 目录中。
RapidJSON 依赖于以下软件:
- CMake 作为通用生成工具
- (optional) Doxygen 用于生成文档
- (optional) googletest 用于单元及性能测试
生成测试及例子的步骤:
- 执行
git submodule update --init
去获取 thirdparty submodules (google test)。 - 在 rapidjson 目录下,建立一个 build 目录。
- 在 build 目录下执行 cmake … 命令以设置生成。Windows 用户可使用 cmake-gui 应用程序。
- 在 Windows 下,编译生成在 build 目录中的 solution。在 Linux 下,于 build 目录运行 make。
成功生成后,你会在 bin 的目录下找到编译后的测试及例子可执行文件。而生成的文档将位于 build 下的 doc/html 目录。要执行测试,请在 build 下执行 make test 或 ctest。使用 ctest -V 命令可获取详细的输出。
我们也可以把程序库安装至全系统中,只要在具管理权限下从 build 目录执行 make install 命令。这样会按系统的偏好设置安装所有文件。当安装 RapidJSON 后,其他的 CMake 项目需要使用它时,可以通过在 CMakeLists.txt 加入一句 find_package(RapidJSON)
教程
构造json
Addmember构造
int main()
{
rapidjson::Document document;
document.SetObject();
// 添加name, value
const char* name = "success_url";
const char* value = "https://www.google.com";
document.AddMember(rapidjson::StringRef(name), rapidjson::StringRef(value), document.GetAllocator());
// 添加json object
rapidjson::Value info_objects(rapidjson::kObjectType);
std::string jsonObject = "json_object";
info_objects.AddMember(rapidjson::StringRef("class_room"), rapidjson::StringRef("NO. 6110"), document.GetAllocator());
info_objects.AddMember(rapidjson::StringRef("teacher_name"), rapidjson::StringRef("ZhangSanfeng"), document.GetAllocator());
document.AddMember(rapidjson::StringRef(jsonObject.c_str()), info_objects, document.GetAllocator());
// 添加json array
rapidjson::Value array_objects(rapidjson::kArrayType);
for (int i = 0; i < 2; i++)
{
rapidjson::Value object(rapidjson::kObjectType);
rapidjson::Value nobject(rapidjson::kNumberType);
nobject.SetInt(i);
object.AddMember(rapidjson::StringRef("id"), nobject, document.GetAllocator());
object.AddMember(rapidjson::StringRef("name"), rapidjson::StringRef("zhangsan"), document.GetAllocator());
array_objects.PushBack(object, document.GetAllocator());
}
char * jsonArrayName = "jsonArrayName";
document.AddMember(rapidjson::StringRef(jsonArrayName), array_objects, document.GetAllocator());
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);
std::string json = std::string(buffer.GetString());
printf("testAddMember = %s\r\n", json.c_str());
return 0;
}
用writer构造
rapidjson::StringBuffer s;
rapidjson::Writer<StringBuffer> writer(s);
writer.StartObject(); // Between StartObject()/EndObject(),
writer.Key("hello"); // output a key,
writer.String("world"); // follow by a value.
writer.Key("t");
writer.Bool(true);
writer.Key("f");
writer.Bool(false);
writer.Key("n");
writer.Null();
writer.Key("i");
writer.Uint(123);
writer.Key("pi");
writer.Double(3.1416);
writer.Key("a");
writer.StartArray(); // Between StartArray()/EndArray(),
for (unsigned i = 0; i < 4; i++)
writer.Uint(i); // all values are elements of the array.
writer.EndArray();
writer.EndObject();
std::cout << s.GetString() << std::endl;
第一个例子
// rapidjson/example/simpledom/simpledom.cpp`
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>
using namespace rapidjson;
int main() {
// 1. 把 JSON 解析至 DOM。
const char* json = "{\"project\":\"rapidjson\",\"stars\":10}";
Document d;
d.Parse(json);
// 2. 利用 DOM 作出修改。
Value& s = d["stars"];
s.SetInt(s.GetInt() + 1);
// 3. 把 DOM 转换(stringify)成 JSON。
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);
// Output {"project":"rapidjson","stars":11}
std::cout << buffer.GetString() << std::endl;
return 0;
}
下图展示执行过程。
可以看出,可以解析一个Json置DOM,然后就可以轻松查询以及修改DOM,并最终转换回JSON
每个Json值都存储为Value类,而Document表示整个DOM,它存储了一个DOM树的根Value。RapidJSON 的所有公开类型及函数都在 rapidjson 命名空间中。
查询值
查询 Value
假设我们用 C 语言的字符串储存一个 JSON(const char* json):
{
"hello": "world",
"t": true ,
"f": false,
"n": null,
"i": 123,
"pi": 3.1416,
"a": [1, 2, 3, 4]
}
把它解析至一个 Document:
#include "rapidjson/document.h"
using namespace rapidjson;
// ...
Document document;
document.Parse(json);
那么现在该 JSON 就会被解析至 document 中,成为一棵 DOM 树:
自从 RFC 7159 作出更新,合法 JSON 文件的根可以是任何类型的 JSON 值。而在较早的 RFC 4627 中,根值只允许是 Object 或 Array。而在上述例子中,根是一个 Object。
assert(document.IsObject());
让我们查询一下根 Object 中有没有 “hello” 成员。由于一个value
中可以包含不同类型的值,我们可能需要验证它的类型,并使用合适的API去获取其值。在这里,“hello” 成员关联到一个 JSON String。
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());
// 在此情况下,IsUint()/IsInt64()/IsUint64() 也会返回 true
assert(document["i"].IsInt());
printf("i = %d\n", document["i"].GetInt());
// 另一种用法: (int)document["i"]
assert(document["pi"].IsNumber());
assert(document["pi"].IsDouble());
printf("pi = %g\n", document["pi"].GetDouble());
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 的 typedef。在多数系统中,Array 最多能存储 2^32-1 个元素。
你可以用整数字面量访问元素,如 a[0]、a[1]、a[2]。
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
和 Array 相似,我们可以用迭代器去访问所有 Object 成员:
static const char* 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()]);
}
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
注意,当 operator[](const char*)
找不到成员,它会断言失败。
若我们不确定一个成员是否存在,便需要在调用 operator[](const char*)
前先调用 HasMember()
。然而,这会导致两次查找。更好的做法是调用 FindMember()
,它能同时检查成员是否存在并返回它的 Value:
Value::ConstMemberIterator itr = document.FindMember("hello");
if (itr != document.MemberEnd())
printf("%s\n", itr->value.GetString());
当使用 C++11 功能时,你可使用范围 for 循环去访问 Object 内的所有成员。
for (auto& m : document.GetObject())
printf("Type of member %s is %s\n",
m.name.GetString(), kTypeNames[m.value.GetType()]);
查询 Number
JSON 只提供一种数值类型──Number。数字可以是整数或实数。
当解析一个 Number 时, 它会被存储在 DOM 之中,成为下列其中一个类型:
当查询一个 Number 时, 你可以检查该数字是否能以目标类型来提取:
注意,一个整数可能用几种类型来提取,而无需转换。例如,一个名为 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 可能会丧失精度(因为 double 的尾数只有 52 位)。
查询 String
除了 GetString(),Value 类也有一个 GetStringLength()。
- 根据 RFC 4627,JSON String 可包含 Unicode 字符 U+0000,在 JSON 中会表示为 “\u0000”。问题是,C/C++ 通常使用空字符结尾字符串(null-terminated string),这种字符串把
‘\0’
作为结束符号。 - 为了符合 RFC 4627,RapidJSON 支持包含 U+0000 的 String。若你需要处理这些 String,便可使用 GetStringLength() 去获得正确的字符串长度。
例如,当解析以下的 JSON 至 Document d 之后:
{ "s" : "a\u0000b" }
“a\u0000b” 值的正确长度应该是 3。但 strlen() 会返回 1。
GetStringLength() 也可以提高性能,因为用户可能需要调用 strlen() 去分配缓冲。
此外,std::string 也支持这个构造函数:
string(const char* s, size_t count);
此构造函数接受字符串长度作为参数。它支持在字符串中存储空字符,也应该会有更好的性能。
比较两个 Value
你可使用 == 及 != 去比较两个 Value。当且仅当两个 Value 的类型及内容相同,它们才当作相等
Array/Object 顺序以它们的元素/成员作比较。当且仅当它们的整个子树相等,它们才当作相等。
创建/修改值
有多种方法去创建值。 当一个 DOM 树被创建或修改后,可使用 Writer 再次存储为 JSON。
改变 Value 类型
当使用默认构造函数创建一个 Value 或 Document,它的类型便会是 Null。要改变其类型,需调用 SetXXX() 或赋值操作,例如:
Document d; // Null
d.SetObject();
Value v; // Null
v.SetInt(10);
v = 10; // 简写,和上面的相同
几个类型也有重载构造函数:
Value b(true); // 调用 Value(bool)
Value i(-123); // 调用 Value(int)
Value u(123u); // 调用 Value(unsigned)
Value d(1.5); // 调用 Value(double)
转移语义
在设计 RapidJSON 时有一个非常特别的决定,就是 Value 赋值并不是把来源 Value 复制至目的 Value,而是把来源 Value 转移(move)至目的 Value。例如:
Value a(123);
Value b(456);
b = a; // a 变成 Null,b 变成数字 123。