简介
在c++中用来解析Json的库很多,如Jsoncpp。
我之前也使用Jsoncpp来做Json解析,但自从接触rapidjson后,特别是尝试使用之后,便决定以后就使用它了,要与Jsoncpp说拜拜。
使用Jsoncpp的同学不妨尝试一下,不管在易用性还是性能方面,rapidjson都是可圈可点的。
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
目录中。
获取源代码:https://github.com/Tencent/rapidjson 。
一个简单的读写例程如下。
// 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以字符串的形式在程序中给出,包括两个键值对,在c-string中,字符串中的双引号用反斜杠转义。
声明一个DOCUMENT并使用其parse方法把json字符串解析至DOM。利用DOM可取出json中的值,也可以修改其值。
定义一个Writer对象,使用Document的Accept方法写入。最后输出。
前半部分是用来读取Json,后半部分完成写入,非常简单。
解析
RapidJSON 的所有公开类型及函数都在 rapidjson 命名空间中。
这里介绍简单的DOM(Document Object Model, DOM,文件对象模型)。
可以解析一个 JSON 至 DOM,每个 JSON 值都储存为 Value 类,而 Document 类则表示整个 DOM,它存储了一个 DOM 树的根 Value。
待解析的Json格式如下:
{
"hello": "world",
"t": true ,
"f": false,
"n": null,
"i": 123,
"pi": 3.1416,
"a": [1, 2, 3, 4]
}
解析:
#include "rapidjson/document.h"
using namespace rapidjson;
Document document;
document.Parse(json);
// 省略错误处理和类型检查
printf("hello = %s\n", document["hello"].GetString());
printf("t = %s\n", document["t"].GetBool() ? "true" : "false");
printf("n = %s\n", document["n"].IsNull() ? "null" : "?");
printf("i = %d\n", document["i"].GetInt());
printf("pi = %g\n", document["pi"].GetDouble());
// 使用引用来连续访问,方便之余还更高效。
const Value& a = document["a"];
assert(a.IsArray());// 类型检查,异常直接退出
for (SizeType i = 0; i < a.Size(); i++) // 使用 SizeType 而不是 size_t,缺省情况下,SizeType 是 unsigned 的 typedef
printf("a[%d] = %d\n", i, a[i].GetInt());
其他异常检查,如:
assert(document.IsObject());
assert(document.HasMember("hello"));
assert(document["hello"].IsString());
关于数组,Array 与 std::vector 相似,除了使用索引,也可使用迭代器来访问所有元素。
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
printf("%d ", itr->GetInt());
// 也可以使用c++11的新特性
for (auto& v : a.GetArray())
printf("%d ", v.GetInt());
生成
Writer 把事件转换成 JSON。它简单易用,如果只是需要把一些数据直接转换为Json,直接使用Writer即可。
如下程序,将生成这样一个Json串:
{"hello":"world","t":true,"f":false,"n":null,"i":123,"pi":3.1416,"a":[0,1,2,3]}
代码:
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>
using namespace rapidjson;
using namespace std;
void main() {
StringBuffer s;
Writer<StringBuffer> writer(s);
writer.StartObject();
writer.Key("hello");
writer.String("world");
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();
for (unsigned i = 0; i < 4; i++)
writer.Uint(i);
writer.EndArray();
writer.EndObject();
cout << s.GetString() << endl;
}
使用Writer的优点:
- Writer 必然会输出一个结构良好(well-formed)的 JSON。若然有错误的事件次序(如 Int() 紧随 StartObject() 出现),它会在调试模式中产生断言失败。
- Writer::String() 可处理字符串转义(如把码点 U+000A 转换成 \n)及进行 Unicode 转码。
- Writer 一致地处理 number 的输出。
- Writer 实现了事件处理器 concept。可用于处理来自 Reader、Document 或其他事件发生器。
- Writer 可对不同平台进行优化。
Writer 所输出的是没有空格字符的最紧凑 JSON,适合网络传输或储存,但不适合人类阅读。
使用PrettyWriter可以得到格式化的Json输出,它在输出中加入缩进及换行。PrettyWriter 的用法与 Writer 几乎一样,不同之处是 PrettyWriter 提供了一个 SetIndent(Ch indentChar, unsigned indentCharCount) 函数。缺省的缩进是 4 个空格。
总结
个人感觉是rapidjson提供了比Jsoncpp灵活得多的类型,使得编码及解码时更自由随意,不必为了找不到的类型而纠结。
另外,根据资料,rapidjson的性能是Jsoncpp的好多倍,经过实测,确实性能表现较好。
当然,rapidjson的header-only特性也是大大的加分项,不必安装库,移植、部署都很方便。
更多内容在实践继续探索。
参考资料:
- RapidJson教程
- 关于更多c++ Json库的介绍及对比,可以参考 https://github.com/miloyip/nativejson-benchmark 。
- 更简洁的对比可参考 jsoncpp和rapidjson哪个好用。