首页 前端知识 C\C 使用RapidJSON库,轻松解析和生成JSON

C\C 使用RapidJSON库,轻松解析和生成JSON

2024-04-30 12:04:10 前端知识 前端哥 555 467 我要收藏

简介

  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;
  1. json字符串
{
    "hello": "world",
    "t": true ,
    "f": false,
    "n": null,
    "i": 123,
    "pi": 3.1416,
    "a": [1, 2, 3, 4]
}
  1. 代码
    将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;
}
  1. DOM树
    在这里插入图片描述
  2. 判断根是不是 Object
assert(document.IsObject());

assert 是一个判断语句。参数为false时,会导致程序终止。在生产环境中,要使用其他方法来处理这种情况,例如通过返回错误代码或抛出异常。

  1. 获取成员值
  • 让我们查询一下根 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) 使用了“转移最后”手法来达成常数时间复杂度。基本上就是析构迭代器位置的成员,然后把最后的成员转移至迭代器位置。因此,成员的次序会被改变。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/6761.html
标签
RapidJSON
评论
会员中心 联系我 留言建议 回顶部
复制成功!