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. json字符串
    { "hello": "world",
        "t": true ,
        "f": false,
        "n": null,
        "i": 123,
        "pi": 3.1416,
        "a": [1, 2, 3, 4]
    }
    
    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. 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 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  #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 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 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 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 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) 使用了“转移最后”手法来达成常数时间复杂度。基本上就是析构迭代器位置的成员,然后把最后的成员转移至迭代器位置。因此,成员的次序会被改变。