服务器开发28:rapidjson充当http服务器与运营中台服务器之间解析json数据的工具,rapidjson接口和坑

文章目录

一、背景知识

  • rapidjson官方文档
  • 版本
    RapidJSON 是一个高效的 C++ JSON 解析/生成器,提供 SAX 及 DOM 风格 API。
    1.0 为最早版本,发布于 2015 年。
    1.1 为最新版本,发布于 2016 年。
  • 官网地址

1)DOM 与 SAX

1、什么是 DOM 风格 API?

Document Object Model(DOM)是一个储存于内存的 JSON 表示方式,用于查询及修改 JSON。

2、什么是 SAX 风格 API?

SAX 是一个事件驱动的 API,用于解析及生成 JSON。

3、我应用 DOM 还是 SAX?

DOM 易于查询及修改。SAX 则是非常快及省内存的,但通常较难使用。

4、什么是原位(in situ)解析?

原位解析会把 JSON 字符串直接解码至输入的 JSON 中。这是一个优化,可减少内存消耗及提升性能,但输入的 JSON 会被更改。

5、什么时候会产生解析错误?

当输入的 JSON 包含非法语法,或不能表示一个值(如 Number 太大),或解析器的处理器中断解析过程,解析器都会产生一个错误。

6、有什么错误信息?

错误信息存储在 ParseResult,它包含错误代号及偏移值(从 JSON 开始至错误处的字符数目)。可以把错误代号翻译为人类可读的错误讯息。

7、为何不只使用 double 去表示 JSON number?

一些应用需要使用 64 位无号/有号整数。这些整数不能无损地转换成 double。因此解析器会检测一个 JSON number 是否能转换至各种整数类型及 double。

8、如何清空并最小化 document 或 value 的容量?

调用 SetXXX() 方法 - 这些方法会调用析构函数,并重建空的 Object 或 Array:

Document d;
...
d.SetObject();  // clear and minimize

另外,也可以参考在 C++ swap with temporary idiom 中的一种等价的方法:

Value(kObjectType).Swap(d);

或者,使用这个稍微长一点的代码也能完成同样的事情:

d.Swap(Value(kObjectType).Move()); 

2)解析json

特别注意rapidjson::Document可以为object、array、number、string、boolean和null中任意一种类型。只有为object时才可以调用HasMember等与object有关的方法。

#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    
    
    std::string str;
    rapidjson::Document doc;
    doc.Parse(argv[1]);
    if (doc.HasParseError())
        printf("parse error\n");
    // 注意doc可为object, array, number, string, boolean, null中任意一种类型
    if (!doc.IsObject())

        printf("not object\n");
    else
    {
    
    
        printf("parse ok\n");
        if (doc.IsNumber())
        printf("%d\n", doc.GetInt());
        
        // doc为object类型时,才能调用HasMember
        if (doc.HasMember("x"))
            printf("has x\n");
        else
            printf("without x\n");
    }
    return 0;
}

二、使用接口文档

1)序列化与反序列化

(1)字符串转 json

const char* json = "{\"a\":\"1\",\"b\":2}";
Document d;
d.Parse(json);
if(d.HasParseError()){
    printf("ret=%d\n", d.GetParseError());
}

需要定义一个 Document 对象,通过 Parse 函数解析。

  • 备注
    需要注意的是,Parse 函数的参数不支持 std::string

(2)json 转字符串

StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
d.Accept(writer);
printf("%d\n", buffer.GetSize());
printf("%s\n", buffer.GetString());

由于架构上设计的解耦,这里序列化比较麻烦。

2)数据类型

与 JSON RFC7159 标准一致,支持 字符串、整数、浮点数、true、false、数组、对象、NULL 等。

  • 接口解释
    可以通过 isXXX 来判断当前 value 是不是某个类型
  • 使用接口
    对于每个 value,我们还可以通过 set/get 来取值与设置值。
Value  a(1);
a = 2;
a.SetInt(3);
a.GetInt();
a.SetInt64(4);
a.SetBool(true);
a.SetNull();

3)字符串

  • 备注
    RapidJSON 为了高性能,默认所有操作都是引用或者 Move 操作。
  • 使用举例
    比如对于字符串,设置值的时候,如果想要复制而不是引用,要显示的声明出来
Value author
char buffer[10] = "hello word";
// 常量字符串只储存指针
author.("hello word");
//下面这句会编译错误
author.(buffer);

//强制储存指针
author.(StringRef(buffer));
// 复制字符串--这里是复制
author.SetString(buffer, len, document.GetAllocator());
  • 使用说明
    ①对于常量字符串,可以确定整个生命周期都是安全的,所以直接储存指针。
    ②对于指针字符串,语法上就不能编译通过(生命周期不能保证)。 如果确定指针常量字符串,可以主动声明,从而避免复制。
    ③其他情况,只能主动声明需要申请内存了。

4)数组

  • 使用介绍
    数组的操作与 std::vector 类似。唯一的区别是需要自己传一个内存管理类,这点使得数组的语法非常丑陋。

  • 代码举例

Value a(kArrayType);
Document::AllocatorType& allocator = document.GetAllocator();
a.Clear();
a.Reserve(10, allocator);
a.PushBack(1, allocator);
a.PopBack();
a.Erase(a.Begin());
  • 补充
    另外 RapidJSON 支持 java 的 Fluent interface 功能。
a.PushBack(1, allocator).PushBack(2, allocator);
  • 其他三种方法遍历数组
    ①索引法
const Value& a = document["a"];
assert(a.IsArray());
for (SizeType i = 0; i < a.Size(); i++) {
    
    
    printf("a[%d] = %d\n", i, a[i].GetInt())
}

②迭代器法

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());

5)对象

  • 介绍
    对象与 std::map 类似。
  • 坑点
    这里有一个大坑,Rapidjson 的对象查找复杂度是 O(n) 的,注意被坑。

(1)查找对象

  • 代码
Value contact(kObject);
Document::AllocatorType& allocator = document.GetAllocator();
contact.AddMember("name", "tiankonguse", allocator);
contact.AddMember("sex", "male", allocator);
contact.AddMember("wx", "", allocator);
contact["wx"] = "tiankonguse-code";
contact.RemoveMember("name");
auto itr = d.FindMember("hello");
if(itr == d.MemberEnd()) {
    
    
    printf("no find\n");
}

(2)COPY对象

有点麻烦,我们需要先手动构造出 key 和 value。

  • 代码
// 显式 copy,隐式 move
Value key("name", allocator );
Value val("tiankonguse", allocator );
contact.AddMember(key, val, allocator );
// 参数中进行 copy 与 move
contact.AddMember(
Value("sex", allocator).Move(), 
Value("male", allocator).Move(),
allocator);

(3)遍历对象两种方法

①迭代器法
STL 中,迭代器 key 的名字是 first,value 的名字是 second。
RapidJSON 中,key 的名字是 name,value 的名字是 value。

for (auto itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) {
    
    
    printf("name=%s, valyetype=%d\n", itr->name.GetString(), itr->value.GetType());
}

②c++11法

for (auto& m : d.GetObject())
    printf("Tname=%s valueType=%d\n", m.name.GetString(), m.value.GetType());

三、使用示例

1)解析一个字符串

  • 结果
count=2
name=zhangsan
name=wangwu
  • 代码
void x1()
{
    
    
    rapidjson::Document document; // 定义一个Document对象
    std::string str = "{\"count\":2,\"names\":[\"zhangsan\",\"wangwu\"]}";
    document.Parse(str.c_str()); // 解析,Parse()无返回值,也不会抛异常
    if (document.HasParseError()) // 通过HasParseError()来判断解析是否成功
    {
    
    
        // 可通过GetParseError()取得出错代码,
        // 注意GetParseError()返回的是一个rapidjson::ParseErrorCode类型的枚举值
        // 使用函数rapidjson::GetParseError_En()得到错误码的字符串说明,这里的En为English简写
        // 函数GetErrorOffset()返回出错发生的位置
        printf("parse error: (%d:%d)%s\n", document.GetParseError(), document.GetErrorOffset(), rapidjson::GetParseError_En(document.GetParseError()));
    }
    else
    {
    
    
        // 判断某成员是否存在
        if (!document.HasMember("count") || !document.HasMember("names"))
        {
    
    
            printf("invalid format: %s\n", str.c_str());
        }
        else
        {
    
    
            // 如果count不存在,则运行程序会挂,DEBUG模式下直接abort
            rapidjson::Value& count_json = document["count"];
            // 如果count不是整数类型,调用也会挂,DEBUG模式下直接abort
            // GetInt()返回类型为int
            // GetUint()返回类型为unsigned int
            // GetInt64()返回类型为int64_t
            // GetUint64()返回类型为uint64_t
            // GetDouble()返回类型为double
            // GetString()返回类型为char*
            // GetBool()返回类型为bool
            int count = count_json.GetInt();
            printf("count=%d\n", count);
            
            // 方法GetType()返回枚举值: kNullType,kFalseType,kTrueType,kObjectType,kArrayType,kStringType,kNumberType
            // 可用IsArray()判断是否为数组,示例: { "a": [1, 2, 3, 4] }
            // 用IsString()判断是否为字符串值
            // 用IsDouble()判断是否为double类型的值,示例: { "pi": 3.1416 }
            // 用IsInt()判断是否为int类型的值
            // 用IsUint()判断是否为unsigned int类型的值
            // 用IsInt64()判断是否为int64_t类型的值
            // 用IsUint64()判断是否为uint64_t类型的值
            // 用IsBool()判断是否为bool类型的值
            // 用IsFalse()判断值是否为false,示例: { "t": true, "f": false }
            // 用IsTrue()判断值是否为true
            // 用IsNull()判断值是否为NULL,示例: { "n": null }
            // 更多说明可浏览:
            // https://miloyip.gitbooks.io/rapidjson/content/zh-cn/doc/tutorial.zh-cn.html

            const rapidjson::Value& names_json = document["names"];
            for (rapidjson::SizeType i=0; i<names_json.Size(); ++i)
            {
    
    
                std::string name = names_json[i].GetString();
                printf("name=%s\n", name.c_str());
            }
        }
    }
}

2)构造一个json并转成字符串

  • 运行结果
{
    
    "count":2,"names":[{
    
    "name":"zhangsan"},{
    
    "name":"wangwu"}]}
  • 代码
void x2()

{
    
    
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    writer.StartObject();

 
    // count
    writer.Key("count");
    writer.Int(2);
    // 写4字节有符号整数: Int(int32_t x)
    // 写4字节无符号整数: Uint(uint32_t x)
    // 写8字节有符号整数: Int64(int64_t x)
    // 写8字节无符号整数: Uint64(uint64_t x)
    // 写double值: Double(double x)
    // 写bool值: Bool(bool x)
 
    // names
    writer.Key("names");
    writer.StartArray();
    writer.StartObject();
    writer.Key("name");
    writer.String("zhangsan");
    writer.EndObject();

 
    writer.StartObject();
    writer.Key("name");
    writer.String("wangwu");
    writer.EndObject();
    writer.EndArray();

    writer.EndObject();

    // 以字符串形式打印输出
    printf("%s\n", buffer.GetString());
}

3)修改一个已有的json字符串

  • 运行结果
{
    
    "name":"wangwu","age":22}
  • 代码
void x3()

{
    
    
    rapidjson::Document document;
    std::string str = "{\"name\":\"zhangsan\",\"age\":20}";
    document.Parse(str.c_str());

 

    rapidjson::Value& name_json = document["name"];
    rapidjson::Value& age_json = document["age"];
    std::string new_name = "wangwu";
    int new_age = 22;

 
    // 注意第三个参数是document.GetAllocator(),相当于深拷贝,rapidjson会分配一块内存,然后复制new_name.c_str(),
    // 如果不指定第三个参数,则是浅拷贝,也就是rapidjson不会分配一块内存,而是直接指向new_name.c_str(),省去复制提升了性能
    // 官方说明:
    // http://rapidjson.org/zh-cn/md_doc_tutorial_8zh-cn.html#CreateString
    name_json.SetString(new_name.c_str(), new_name.size(), document.GetAllocator());
    age_json.SetInt(new_age);

 

    // 转成字符串输出
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    document.Accept(writer);
    printf("%s\n", buffer.GetString());
}

4)读数组

  • 运行结果
zhangsan wangwu
  • 代码
void x4()
{
    
    
    rapidjson::Document document;
    std::string str = "{\"count\":2,\"names\":[{\"name\":\"zhangsan\"},{\"name\":\"wangwu\"}]}";
    document.Parse(str.c_str());
    if (document.HasParseError())
    {
    
    
        printf("parse error: %d\n", document.GetParseError());
    }
    else
    {
    
    
        rapidjson::Value& names_json = document["names"];
        for (rapidjson::SizeType i=0; i<names_json.Size(); ++i)
        {
    
    
            if (names_json[i].HasMember("name"))
            {
    
    
                rapidjson::Value& name_json = names_json[i]["name"];
                printf("%s ", name_json.GetString());
            }
        }
        printf("\n");
    }
}

5)以Writer构造一个json,然后修改它,最后转成字符串

  • 运行结果
{
    
    "count":2}
{
    
    "count":8}
  • 代码
void x5()
{
    
    
    rapidjson::StringBuffer buffer1;
    rapidjson::Writer<rapidjson::StringBuffer> writer1(buffer1);

    writer1.StartObject();
    writer1.Key("count");
    writer1.Int(2);    
    writer1.EndObject();

    printf("%s\n", buffer1.GetString());

 

    // 转成Document对象

    rapidjson::Document document;
    document.Parse(buffer1.GetString());
    // 修改
    rapidjson::Value& count_json = document["count"];
    count_json.SetInt(8);

    // 转成字符串
    rapidjson::StringBuffer buffer2;
    rapidjson::Writer<rapidjson::StringBuffer> writer2(buffer2);
    document.Accept(writer2);
    printf("%s\n", buffer2.GetString());
}

6)以Document构造一个json,然后修改它,最后转成字符串

  • 运行结果
{
    
    "count":3,"names":[{
    
    "id":1,"name":"zhangsan"}]}
{
    
    "count":9,"names":[{
    
    "id":1,"name":"zhangsan"}]}
  • 代码
void x6()
{
    
    
    rapidjson::Document document;
    std::string str = "{}"; // 这个是必须的,且不能为"",否则Parse出错
    document.Parse(str.c_str());

    // 新增成员count
    // AddMember第一个参数可以为字符串常,如“str”,不能为“const char*”和“std::string”,
    // 如果使用“const char*”,则需要使用StringRefType转换:StringRefType(str.c_str())
    document.AddMember("count", 3, document.GetAllocator());

 

    // 新增数组成员
    rapidjson::Value array(rapidjson::kArrayType);
    rapidjson::Value object(rapidjson::kObjectType); // 数组成员
    object.AddMember("id", 1, document.GetAllocator());
    object.AddMember("name", "zhangsan", document.GetAllocator());

    // 如果数组添加无名字的成员,定义Value时应当改成相应的类型,如:
    //rapidjson::Value value(rapidjson::kStringType);
    //rapidjson::Value value(rapidjson::kNumberType);
    //rapidjson::Value value(rapidjson::kFalseType);
    //rapidjson::Value value(rapidjson::kTrueType);
    //array.PushBack(value, document.GetAllocator());
    //效果将是这样:'array':[1,2,3,4,5]
    

    // 注意下面用法编译不过:
    //std::string str1 = "hello";
    //object.AddMember("name", str1.c_str(), document.GetAllocator());
    //const char* str2 = "hello";
    //object.AddMember("name", str2, document.GetAllocator());
    //

    // 下面这样可以:
    //object.AddMember("name", "hello", document.GetAllocator());
    //const char str3[] = "hello";
    //object.AddMember("name", str3, document.GetAllocator());

    //    
    //std::string str4 = "#####";
    //rapidjson::Value v(str4.c_str(), document.GetAllocator());
    //obj.AddMember("x", v, document.GetAllocator());
    // 上面两行也可以写在一行:
    //obj.AddMember("x", rapidjson::Value(str4.c_str(), document.GetAllocator()).Move(), document.GetAllocator());

 

    // 添加到数组中
    array.PushBack(object, document.GetAllocator());

    // 添加到document中
    document.AddMember("names", array, document.GetAllocator());

 

    // 转成字符串输出
    rapidjson::StringBuffer buffer1;
    rapidjson::Writer<rapidjson::StringBuffer> writer1(buffer1);
    document.Accept(writer1);
    printf("%s\n", buffer1.GetString());
    

    // 修改值
    rapidjson::Value& count_json = document["count"];
    count_json.SetInt(9);

 

    // 再次输出
    rapidjson::StringBuffer buffer2;
    rapidjson::Writer<rapidjson::StringBuffer> writer2(buffer2);
    document.Accept(writer2);
    printf("%s\n", buffer2.GetString());
}

7)以Document构造一个json,然后修改它,最后转成字符串(转移ASCII码输出)

  • 运行结果
x7=>
{
    
    "title":"\u8D2B\u56F0\u5B64\u513F\u52A9\u517B"}
  • 代码
void x7()
{
    
    
    std::string root = "{}";
    rapidjson::Document document;
    document.Parse(root.c_str());
 
    std::string title = "\u8D2B\u56F0\u5B64\u513F\u52A9\u517B";
    document.AddMember("title", rapidjson::Value(title.c_str(), document.GetAllocator()).Move(), document.GetAllocator());

 

    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<> > writer(buffer);

    // 如果上面一句改成普通的:
    // rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    // 则输出将变成:
    // x7=>

    document.Accept(writer);
    printf("x7=>\n%s\n", buffer.GetString());
}

8)构造空对象和数组

  • 运行结果
{
    
    "age":{
    
    },"times":{
    
    },"names":[],"urls":[],"books":[]}
{
    
    "age":6,"times":{
    
    },"names":[],"urls":[],"books":[]}
  • 代码
void x8()
{
    
    
    rapidjson::Document document;
    document.Parse("{}"); // 这里换成document.SetObject()也可以

    // 下面为2种构造空对象的方法
    document.AddMember("age", rapidjson::Value(rapidjson::kObjectType).Move(), document.GetAllocator());
    document.AddMember("times", rapidjson::Value().SetObject(), document.GetAllocator());

 
    // 下面为2种构造空数组的方法
    document.AddMember("names", rapidjson::Value(rapidjson::kArrayType).Move(), document.GetAllocator());
    document.AddMember("urls", rapidjson::Value(rapidjson::kArrayType).Move(), document.GetAllocator());
    document.AddMember("books", rapidjson::Value().SetArray(), document.GetAllocator());

 

    rapidjson::StringBuffer buffer1;
    rapidjson::Writer<rapidjson::StringBuffer> writer1(buffer1);
    document.Accept(writer1);
    printf("%s\n", buffer1.GetString());
 

    rapidjson::Value& age = document["age"];
    age.SetInt(6);

    rapidjson::StringBuffer buffer2;
    rapidjson::Writer<rapidjson::StringBuffer> writer2(buffer2);
    document.Accept(writer2);
    printf("%s\n", buffer2.GetString());
}

9)删除数组元素

  • 运行结果
{
    
     "names": [ {
    
    "name":"zhangsan","age":100}, {
    
    "name":"wangwu","age":90}, {
    
    "name":"xiaozhang","age":20} ]}

{
    
    "names":[{
    
    "name":"zhangsan","age":100},{
    
    "name":"wangwu","age":90}]}
  • 代码
void x9()
{
    
    
    std::string str = "{ \"names\": [ {\"name\":\"zhangsan\",\"age\":100}, {\"name\":\"wangwu\",\"age\":90}, {\"name\":\"xiaozhang\",\"age\":20} ]}";
    rapidjson::Document document;
    document.Parse(str.c_str());

    rapidjson::Value& names_json = document["names"];
    for (rapidjson::Value::ValueIterator iter=names_json.Begin(); iter!=names_json.End();)
    {
    
    
        std::string name = (*iter)["name"].GetString();
        // 不要小张了
        if (name == "xiaozhang")
            iter = names_json.Erase(iter);
        else
            ++iter;
    }
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    document.Accept(writer);

    printf("%s\n", str.c_str());
    printf("%s\n", buffer.GetString());
}

10)不转义中文

  • 输出结果
{
    
    "title":"贫困孤儿助养"}

{
    
    "title":"\u8D2B\u56F0\u5B64\u513F\u52A9\u517B"}
  • 代码
//g++ -g -o b b.cpp -I/usr/local/thirdparty/rapidjson/include

#include <rapidjson/document.h>

#include <rapidjson/stringbuffer.h>

#include <rapidjson/writer.h>

#include <string>

#include <stdio.h>

 

int main()

{
    
    
    std::string str = "{\"title\":\"\u8d2b\u56f0\u5b64\u513f\u52a9\u517b\"}";
    rapidjson::Document document;
    document.Parse(str.c_str());
    if (document.HasParseError())
    {
    
    
        printf("parse %s failed\n", str.c_str());
        exit(1);
    }

    rapidjson::StringBuffer buffer1;
    rapidjson::Writer<rapidjson::StringBuffer> writer1(buffer1);
    document.Accept(writer1);
    printf("%s\n", buffer1.GetString());

 

    rapidjson::StringBuffer buffer2;
    rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<> > writer2(buffer2);

    document.Accept(writer2);
    printf("%s\n", buffer2.GetString());
    return 0;
}

11)schema使用示例

  • 背景前提
    json的schema用来检验json数据,它也采用了json格式。
  • 代码
rapidjson::Document schema_document;
schema_document.Parse(schema.c_str());
if (!schema_document.HasParseError())
{
    
    
    rapidjson::Document document;
    document.Parse(str.c_str());
    if (!document.HasParseError())
    {
    
    
        SchemaDocument schema(schema_document);
        SchemaValidator validator(schema);
        if (!document.Accept(validator))
        {
    
    
             // 检验出错,输出错误信息
             StringBuffer sb;
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);

             printf("Invalid schema: %s\n", sb.GetString());
             printf("Invalid keyword: %s\n", 					    validator.GetInvalidSchemaKeyword());
             sb.Clear();

             validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
             printf("Invalid document: %s\n", sb.GetString());
        }
    }
}
  • 示例json
{
    
    
    "id": 1,
    "name": "A green door",
    "price": 12.50,
    "tags": ["home", "green"]
}
  • 上段json对应的schema
{
    
    
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Product",
    "description": "A product from Acme's catalog",
    "type": "object",
    "properties": {
    
    
        "id": {
    
    
            "description": "The unique identifier for a product",
            "type": "integer"
        },
        "name": {
    
    
            "description": "Name of the product",
            "type": "string"
        },
        "price": {
    
    
            "type": "number",
            "minimum": 0,
            "exclusiveMinimum": true
        },
        "tags": {
    
    
            "type": "array",
            "items": {
    
    
                "type": "string"
            },
            "minItems": 1,
            "uniqueItems": true
        }
    },
    "required": ["id", "name", "price"]
}
  • 补充
    title”和“description”是描述性的,可以不写。$schema也是可选的,依据的是《JSON Schema Draft v4》。

12)schema完整示例

  • 代码
#include <rapidjson/document.h>
#include <rapidjson/schema.h>
#include <rapidjson/stringbuffer.h>
int main()
{
    
    
    std::string str = "\{\"aaa\":111,\"aaa\":222}"; // "\{\"aaa\":111,\"a\":222}"
#if 0
    std::string schema_str = "{\"type\":\"object\",\"properties\":{\"aaa\":{\"type\":\"integer\"},\"bbb\":{\"type\":\"string\"}},\"required\":[\"aaa\",\"bbb\"]}";
#else
    std::string schema_str = "{\"type\":\"object\",\"properties\":{\"aaa\":{\"type\":\"integer\"},\"bbb\":{\"type\":\"integer\"}},\"required\":[\"aaa\",\"bbb\"]}";
#endif

    printf("%s\n", str.c_str());
    printf("%s\n", schema_str.c_str());
 

    rapidjson::Document doc;
    rapidjson::Document schema_doc;
    schema_doc.Parse(schema_str.c_str());
    doc.Parse(str.c_str());

 

    rapidjson::SchemaDocument schema(schema_doc);
    rapidjson::SchemaValidator validator(schema);
    if (doc.Accept(validator))
    {
    
    
        printf("data ok\n");
    }
    else
    {
    
    
        rapidjson::StringBuffer sb;
        validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
 
        printf("Invalid schema: %s\n", sb.GetString());
        printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());
 
        sb.Clear();
validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
        printf("Invalid document: %s\n", sb.GetString());
    }
    return 0;
}

四、辅助函数和遍历数组操作示例

1)FindMember整数值

int age;
const rapidjson::Value::ConstMemberIterator iter =
    doc.FindMember("age");
if (iter!=doc.MemberEnd() && iter->value.IsInt())
    age = iter->value.GetInt();

2)FindMember字符串值

std::string name;
const rapidjson::Value::ConstMemberIterator iter =
    doc.FindMember("name");
if (iter!=doc.MemberEnd() && iter->value.IsString())
    name.assign(iter->value.GetString(), iter->value.GetStringLength());

3)遍历数组1:字符串数组

// 示例数组:
// {"k":["k1","k2","k3"]}
rapidjson::Document doc;
doc.Parse(str.c_str());

const rapidjson::Value& k = doc["k"];
// 遍历数组

for (rapidjson::Value::ConstValueIterator v_iter=k.Begin();
    v_iter!=k.End(); ++v_iter)
{
    
    
    // k1
    // k2
    // k3
    printf("%s\n", (*v_iter).GetString());
}

4)遍历数组2:一级对象数组

// 数组示例:
// {"h":[{"k1":"f1"},{"k2":"f2"}]}
rapidjson::Document doc;
doc.Parse(str.c_str());
 
const rapidjson::Value& h = doc["h"];
// 遍历数组
for (rapidjson::Value::ConstValueIterator v_iter=h.Begin();
    v_iter!=h.End(); ++v_iter)
{
    
    
    const rapidjson::Value& field = *v_iter;
    for (rapidjson::Value::ConstMemberIterator m_iter=field.MemberBegin();
        m_iter!=field.MemberEnd(); ++m_iter) // kf对
    {
    
    
        // k1 => f1
        // k2 => f2
        const char* key = m_iter->name.GetString();
        const char* value = m_iter->value.GetString();
        printf("%s => %s\n", key, value);
        break;
    }
}

5)遍历数组3:两级对象数组

// 数组示例:
// {"h":[{"k1":["f1","f2"]},{"k2":["f1","f2"]}]}
rapidjson::Document doc;
doc.Parse(str.c_str());
const rapidjson::Value& h = doc["h"];
// 遍历第一级数组
for (rapidjson::Value::ConstValueIterator v1_iter=h.Begin();
    v1_iter!=h.End(); ++v1_iter)
{
    
    
    const rapidjson::Value& k = *v1_iter; // k1,k2,k3
    // 成员遍历
    for (rapidjson::Value::ConstMemberIterator m_iter=k.MemberBegin();
        m_iter!=k.MemberEnd(); ++m_iter)
    {
    
    
        const char* node_name = m_iter->name.GetString();
        printf("hk: %s\n", node_name);
                    
        const rapidjson::Value& node = m_iter->value;
        // 遍历第二级数组
        for (rapidjson::Value::ConstValueIterator v2_iter=node.Begin();
            v2_iter!=node.End(); ++v2_iter)  
        {
    
    
            const char* field = (*v2_iter).GetString();
            printf("field: %s\n", field); // f1,f2,f3
        }
    }
}

6)辅助函数1:任意类型都以字符串返回

// 如果不存在,或者为数组则返回空字符串。
std::string rapidjson_string_value(rapidjson::Value& value, const std::string& name)
{
    
    
    if (!value.HasMember(name.c_str()))
        return std::string("");
    const rapidjson::Value& child = value[name.c_str()];
    if (child.IsString())
        return child.GetString();
    char str[100];
    if (child.IsInt())
    {
    
    
        snprintf(str, sizeof(str), "%d", child.GetInt());
    }
    else if (child.IsInt64())
    {
    
    
        // 为使用PRId64,需要#include <inttypes.h>,
        // 同时编译时需要定义宏__STDC_FORMAT_MACROS
        snprintf(str, sizeof(str), "%"PRId64, child.GetInt64());
    }
    else if (child.IsUint())
    {
    
    
        snprintf(str, sizeof(str), "%u", child.GetUint());
    }
    else if (child.IsUint64())
    {
    
    
        snprintf(str, sizeof(str), "%"PRIu64, child.GetUint64());
    }
    else if (child.IsDouble())
    {
    
    
        snprintf(str, sizeof(str), "%.2lf", child.GetDouble());
    }
    else if (child.IsBool())
    {
    
    
        if (child.IsTrue())
            strcpy(str, "true");
        else
            strcpy(str, "false");
    }
    else
    {
    
    
        str[0] = '\0';
    }
    return str;
}

7)辅助函数2:取int32_t值

当为int32_t值或字符串实际为int32_t值时,返回对应的int32_t值,其它情况返回0。

// 当为int32_t值,或字符串实际为int32_t值时,返回对应的int32_t值,其它情况返回0
int32_t rapidjson_int32_value(rapidjson::Value& value, const std::string& name)
{
    
    
    if (!value.HasMember(name.c_str()))
        return 0;
    const rapidjson::Value& child = value[name.c_str()];
    if (child.IsInt())
    {
    
    
        return child.GetInt();
    }
    else if (child.IsString())
    {
    
    
        return atoi(child.GetString());
    }
    return 0;
}

8)辅助函数3:取int64_t值

int64_t rapidjson_int64_value(rapidjson::Value& value, const std::string& name)
{
    
    
    if (!value.HasMember(name.c_str()))
        return 0;
    const rapidjson::Value& child = value[name.c_str()];
    if (child.IsInt64())
    {
    
    
        return child.GetInt64();
    }
    else if (child.IsString())
    {
    
    
        return (int64_t)atoll(child.GetString());
    }
    return 0;
}

9)辅助函数4:取uint32_t值

uint32_t rapidjson_uint32_value(rapidjson::Value& value, const std::string& name)
{
    
    
    if (!value.HasMember(name.c_str()))
        return 0;
    const rapidjson::Value& child = value[name.c_str()];
    if (child.IsUint())
    {
    
    
        return child.GetUint();
    }
    else if (child.IsString())
    {
    
    
        return (uint32_t)atoll(child.GetString());
    }
    return 0;
}

10)辅助函数5:取uint64_t值

uint64_t rapidjson_uint64_value(rapidjson::Value& value, const std::string& name)
{
    
    
    if (!value.HasMember(name.c_str()))
        return 0;
    const rapidjson::Value& child = value[name.c_str()];
    if (child.IsUint64())
    {
    
    
        return child.GetUint64();
    }
    else if (child.IsString())
    {
    
    
        return (uint64_t)atoll(child.GetString());
    }
    return 0;
}

11)辅助函数6:对象转字符串

std::string& to_string(const rapidjson::Value& value, std::string* str)
{
    
    
    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    value.Accept(writer);
    str->assign(buffer.GetString(), buffer.GetSize());
    return *str;
}

std::string to_string(const rapidjson::Value& value)
{
    
    
    std::string str;
    to_string(value, &str);
#if __cplusplus < 201103L
    return str;
#else
    return std::move(str);
#endif // __cplusplus < 201103L
}

12)辅助函数7:字符串转对象

bool to_rapidjson(const std::string& str, rapidjson::Document* doc)
{
    
    
    doc->Parse(str.c_str());
    return !doc->HasParseError();
}

 
void to_rapidjson(const std::string& str, rapidjson::Document& doc)
{
    
    
    doc.Parse(str.c_str());
    if (doc.HasParseError())
        doc.Parse("{}");
}

五、bug和坑点相关

1)修复一个由宏Unit引起的rapidjson编译失败问题

  • 引入的头文件
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/prettywriter.h"
  • 报错信息(此时编译后出现以下错误(只挑选了关键的)
bc_out/public/protobuf-json/output/include/rapidjson/reader.h: At global scope:
bc_out/public/protobuf-json/output/include/rapidjson/reader.h:84: error: expected unqualified-id before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/reader.h:84: error: expected `)' before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/reader.h: In member function `void rapidjson::GenericReader<SourceEncoding, TargetEncoding, Allocator>::ParseNumber(InputStream&, Handler&)':
bc_out/public/protobuf-json/output/include/rapidjson/reader.h:632: error: expected unqualified-id before '(' token
bc_out/public/protobuf-json/output/include/rapidjson/reader.h:632: error: expected primary-expression before "unsigned"
In file included from baidu/xfire/xcore2/plugins/material_search/src/material_search_proc_query_db.cpp:15:
bc_out/public/protobuf-json/output/include/rapidjson/document.h: In member function `const rapidjson::GenericValue<Encoding, Allocator>& rapidjson::GenericValue<Encoding, Allocator>::Accept(Handler&) const':
bc_out/public/protobuf-json/output/include/rapidjson/document.h:549: error: expected unqualified-id before '(' token
bc_out/public/protobuf-json/output/include/rapidjson/document.h:549: error: expected primary-expression before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/document.h: At global scope:
bc_out/public/protobuf-json/output/include/rapidjson/document.h:791: error: expected unqualified-id before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/document.h:791: error: expected `)' before "unsigned"
In file included from bc_out/public/protobuf-json/output/include/rapidjson/prettywriter.h:4,
                 from baidu/xfire/xcore2/plugins/material_search/src/material_search_proc_query_db.cpp:17:
bc_out/public/protobuf-json/output/include/rapidjson/writer.h:45: error: expected unqualified-id before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/writer.h:45: error: expected `)' before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/writer.h:45: error: abstract declarator `rapidjson::Writer<OutputStream, SourceEncoding, TargetEncoding, Allocator>&' used as declaration
bc_out/public/protobuf-json/output/include/rapidjson/writer.h:45: error: expected `;' before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/writer.h:46: error: expected `;' before "Writer"
In file included from baidu/xfire/xcore2/plugins/material_search/src/material_search_proc_query_db.cpp:17:
bc_out/public/protobuf-json/output/include/rapidjson/prettywriter.h:46: error: expected unqualified-id before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/prettywriter.h:46: error: expected `)' before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/prettywriter.h:46: error: abstract declarator `rapidjson::PrettyWriter<OutputStream, SourceEncoding, TargetEncoding, Allocator>&' used as declaration
bc_out/public/protobuf-json/output/include/rapidjson/prettywriter.h:46: error: expected `;' before "unsigned"
bc_out/public/protobuf-json/output/include/rapidjson/prettywriter.h:47: error: expected `;' before "PrettyWriter"

直接的意思是说reader.h头文件出错了,但显然rapid库本身没有错。

  • 分析解决
    reader.h出问题的代码84行如下:
    void Uint(unsigned i);

定义了一个Uint的函数,根据经验有可能是某个地方也定义了相同的函数,且命名空间相同导致冲突,经过查找并没有,这时google到github上有人提出类似问题。可能是宏和Uint冲突了,于是做如下修改

#ifdef Uint
#undef Uint
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/prettywriter.h"
#endif
  • 查找相同定义的宏
    此时编译可以通过,但是毕竟我们强行undef了一个宏的定义,于是在依赖的文件中搜索Uint宏的定义
find . -name "*.h" | xargs grep "#define Uint"

./bc_out/lib2-64/ullib/output/include/ul_def.h:#define Uint(inp)    (unsigned int)(inp)
./lib2-64/ullib/include/ul_def.h:#define Uint(inp)  (unsigned int)(inp)

找到了该宏的定义,最后把其补充上,防止某些地方出现问题,最终解决后引用修改如下

#ifdef Uint
#undef Uint
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/prettywriter.h"
#define Uint(inp)   (unsigned int)(inp)
#endif

此时编译通过,并且可以顺利执行。

rapid json的确比Boost快,8w行的数据,boost用了近30s,rapid只用了不到2s。

  • 其他补充
    了解到了宏是全局作用的,所以就会和其他函数、变量冲突,对于函数,还有个解决方案是,把函数名用括号括起来,比如
(std::min)(x, y); 

此时min就不会被其他的定义为min的宏覆盖。
当然,这只对自己的代码有用,对于第三方库就不能这个改了。

2)怎么把一个json当作一个节点插入另外一个json?

(如何将一个 document 节点插入到另一个 document 中?)
比如有以下两个 document(DOM):

Document person;
person.Parse("{\"person\":{\"name\":{\"first\":\"Adam\",\"last\":\"Thomas\"}}}");
 
Document address;
address.Parse("{\"address\":{\"city\":\"Moscow\",\"street\":\"Quiet\"}}");

假设我们希望将整个 address 插入到 person 中,作为其的一个子节点:

{
    
     "person": {
    
    
   "name": {
    
     "first": "Adam", "last": "Thomas" },
   "address": {
    
     "city": "Moscow", "street": "Quiet" }
   }
}

在插入节点的过程中需要注意 document 和 value 的生命周期并且正确地使用 allocator 进行内存分配和管理。一个简单有效的方法就是修改上述 address 变量的定义,让其使用 person 的 allocator 初始化然后将其添加到根节点。

Documnet address(&person.GetAllocator());
...
person["person"].AddMember("address", address["address"], person.GetAllocator());

当然,如果你不想通过显式地写出 address 的 key 来得到其值,可以使用迭代器来实现:

auto addressRoot = address.MemberBegin();
person["person"].AddMember(addressRoot->name, addressRoot->value, person.GetAllocator());

此外,还可以通过深拷贝 address document 来实现:

Value addressValue = Value(address["address"], person.GetAllocator());
person["person"].AddMember("address", addressValue, person.GetAllocator());

3)不正确使用接口会内存泄漏

  1. 重复使用Document对象
    重复使用Document可能导致内存泄漏,如下段代码即存在内存泄漏:
#include <rapidjson/document.h>
int main() {
    
    
  rapidjson::Document doc;
  for (int i=0; i<1000000; ++i) {
    
    
      std::string a = "{\"b\":1" + std::to_string(i) + "}";
      doc.Parse(a.c_str());
  }
  return 0;
}

参考:https://github.com/Tencent/rapidjson/issues/1333。解决办法:

#include <rapidjson/document.h>
int main() {
    
    
  rapidjson::Document doc;
  for (int i=0; i<1000000; ++i) {
    
    
      std::string a = "{\"b\":1" + std::to_string(i) + "}";
      doc.Parse(a.c_str());
      rapidjson::Document tmpdoc;
      doc.Swap(tmpdoc);
  }
  return 0;

}
  1. 使用Value类型指针

把Document类型指针当Value类型指针使用,出现内存泄漏,原因是Value的析构不是虚拟的,打开-Wall编译器也不会告警。

4)浅拷贝添加字符串元素

  • 代码举例
#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>

using namespace std;
using namespace rapidjson;

int main() {
    
    
    Document doc;
    doc.SetObject();
    Document::AllocatorType &allocator = doc.GetAllocator();

    string value1 = "value1";
    doc.AddMember("key1", StringRef(value1.c_str(), value1.size()), allocator);
    string v = doc["key1"].GetString();
    cout << v.c_str() << endl; // 輸出爲value1

    value1 = "abc";
    v = doc["key1"].GetString();
    cout << v.c_str() << endl; // 輸出爲abc

    system("pause");
    return 0;
}

輸出結果證明,上述操作爲字符串淺拷貝。所以如果有下面這樣的代碼,輸出將是不確定的值。

void addMem(Document& doc) {
    
    
    string value = "123456";
	doc.AddMember("key", StringRef(value.c_str(), value.size()), doc.GetAllocator());
}

int main() {
    
    
	Document doc;
	doc.SetObject();
	addMem(doc);

	string v = doc["key"].GetString();
	cout << v.c_str() << endl; //此處由於局部變量被釋放,將輸出亂碼

	system("pause");
	return 0;
}
  • 问题点
    從始至終,一直是淺拷貝的AddMember。所以當給doc添加字符串value時,不要添加局部變量作爲入參。同理,key也不要。

  • 正确写法

void addMem(Document& doc) {
    string value = "123456";
	Value v(kStringType);
	v.SetString(value.c_str(), value.size(), doc.GetAllocator()); //這裏很重要,必須要傳遞allocator作爲參數,否則依然爲淺拷貝。
	doc.AddMember("key", v, doc.GetAllocator());
}

5)其他坑点

1、赋值是 move 操作。

rapidjson::Value a(123);
rapidjson::Value b(456);
b = a; // a变成Null,b变成数字123,这样的做法是基于性能考虑

2、常量数组只储存了指针。
3、map 使用数组实现,查询性能低。
4、使用map的[]操作符时,对应的 key 必须存在,否则 coredump
5、AddMember()和PushBack()也采用了Move语意。深复制Value:

Value v1("foo");
// Value v2(v1); // 不容许
Value v2(v1, a); // 制造一个克隆,v1不变

Document d;
v2.CopyFrom(d, a); // 把整个document复制至v2,d不变

六、json性能评测文档

https://github.com/miloyip/nativejson-benchmark

七、rapidjson设计与实现

文档传送门

八、cjson、rapidjson、yyjson大整数精度对比

文档传送门

猜你喜欢

转载自blog.csdn.net/weixin_43679037/article/details/128347621
今日推荐