C/C++编程:RapidJSON

本文介绍了RapidJSON,一个轻量级、快速且独立的C++ JSON库,支持SAX和DOM模式,内存友好,对Unicode友好。讲解了其安装、使用和关键功能,如DOM操作、Value查询和修改。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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。

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值