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


    简介

      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
    • 2
    1. json字符串
    {
        "hello": "world",
        "t": true ,
        "f": false,
        "n": null,
        "i": 123,
        "pi": 3.1416,
        "a": [1, 2, 3, 4]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 代码
      将JSON字符串,解析至document 中,成为一棵 DOM 树
    #include   
    #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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. DOM树
      在这里插入图片描述
    2. 判断根是不是 Object
    assert(document.IsObject());
    
    • 1

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

    1. 获取成员值
    • 让我们查询一下根 Object 中有没有 “hello” 成员。
    assert(document.HasMember("hello"));
    
    • 1
    • 验证类型
    assert(document["hello"].IsString());
    
    • 1
    • 根据类型获取其值
    printf("hello = %s\n", document["hello"].GetString());
    
    • 1

    输出:world

    • JSON True/False 值是以 bool 表示的。
    assert(document["t"].IsBool());
    printf("t = %s\n", document["t"].GetBool() ? "true" : "false");
    
    • 1
    • 2

    输出:true

    • JSON Null 值可用 IsNull() 查询。
    printf("n = %s\n", document["n"].IsNull() ? "null" : "?");
    
    • 1

    输出: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());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    整型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());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出:
    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());
    
    • 1
    • 2

    当使用 C++11 功能时,你可使用范围 for 循环去访问 Array 内的所有元素。

    for (auto& v : a.GetArray())
    		printf("%d ", v.GetInt());
    
    • 1
    • 2

    查询 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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    输出:
    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()]);
    
    • 1
    • 2
    • 3

    判断对象是否存在

    HasMember()方法,会导致两次查找:

    	if(document.HasMember("hello"))
    		printf("%s\n", document["hello"].GetString());
    
    • 1
    • 2

    FindMember()方法,更好:

    	Value::ConstMemberIterator itr = document.FindMember("hello");
    	if (itr != document.MemberEnd())
    		printf("%s\n", itr->value.GetString());
    
    • 1
    • 2
    • 3

    查询 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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输出:
    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 作比较
    
    • 1
    • 2
    • 3
    • 4

    Array/Object 顺序以它们的元素/成员作比较。当且仅当它们的整个子树相等,它们才当作相等。
    另外需要注意的是若一个 Object 含有重复命名的成员,它与任何 Object 作比较都总会返回 false。

    创建/修改值

    当一个 DOM 树被创建或修改后,可使用 Writer 再次存储为 JSON。

    改变 Value 类型和值

    代码

    document["t"].SetInt(666); 
    
    • 1
    document["t"] = 666;     // 简写,和上面的相同
    
    • 1
    Value t(666);           //使用Value的构造函数
    document["t"] = t;
    
    • 1
    • 2

    完整代码

    #include   
    #include   
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出:
    { “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)
    
    • 1
    • 2
    • 3
    • 4

    要重建空 Object 或 Array,可在默认构造函数后使用 SetObject()/SetArray(),或一次性使用 Value(Type):

    Value o(kObjectType);
    Value a(kArrayType);
    
    • 1
    • 2

    转移语义(Move Semantics)

    在设计 RapidJSON 时有一个非常特别的决定,就是 Value 赋值并不是把来源 Value 复制至目的 Value,而是把来源 Value 转移(move)至目的 Value。例如:

    Value a(123);
    Value b(456);
    b = a;         // a 变成 Null,b 变成数字 123。
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    • 为什么?此语义有何优点?

    赋值时转移拥有权。转移快得多简单得多,只需要析构原来的 Value,把来源 memcpy() 至目标,最后把来源设置为 Null 类型。

    深复制 Value

    Value a(123);  
    Value b(456);  
    b.CopyFrom(a, document.GetAllocator());  // a 仍然是数字 123,b 现在是数字 123。
    
    • 1
    • 2
    • 3

    交换 Value

    RapidJSON 也提供 Swap()。

    Value a(123);
    Value b("Hello");
    a.Swap(b);
    
    • 1
    • 2
    • 3

    修改 Array

    Array 类型的 Value 提供与 std::vector 相似的 API。

    Clear()
    Reserve(SizeType, Allocator&)
    Value& PushBack(Value&, Allocator&)
    template GenericValue& PushBack(T, Allocator&)
    Value& PopBack()
    ValueIterator Erase(ConstValueIterator pos)
    ValueIterator Erase(ConstValueIterator first, ConstValueIterator last)
    注意,Reserve(...) PushBack(...) 可能会为数组元素分配内存,所以需要一个 allocator

    以下是 PushBack() 的例子:

    #include   
    #include   
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    输出:
    { …, “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);
    
    • 1

    修改 Object

    Object 是键值对的集合。每个键必须为 String。要修改 Object,方法是增加或移除成员。以下的 API 用来增加成员:

    Value& AddMember(Value&, Value&, Allocator& allocator)
    Value& AddMember(StringRefType, Value&, Allocator&)
    template Value& AddMember(StringRefType, T value, Allocator&)
    以下是一个例子。

    Value contact(kObject);
    contact.AddMember("name", "Milo", document.GetAllocator());
    contact.AddMember("married", true, document.GetAllocator());
    
    • 1
    • 2
    • 3

    使用 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());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    移除成员有几个选择:

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

  • 相关阅读:
    剑指offer 22. 链表中环的入口结点
    智能巡检软件怎么选?企业设备管理需要做什么?
    关于 国产系统UOS系统Qt开发Tcp服务器外部连接无法连接上USO系统 的解决方法
    win11安装ubuntu(by wsl2)
    牛只识别 牛脸识别 个体识别 身份识别
    Casbin——Java版本(笔记)
    Jetson系统烧录环境搭建
    Git版本控制管理——补丁
    在Linux上部署Servlet程序
    【PAT甲级 - C++题解】1045 Favorite Color Stripe
  • 原文地址:https://blog.csdn.net/WangPaiFeiXingYuan/article/details/133981815