C++ rapidjson 基础入门

C++ rapidjson 基础入门此篇文章中对于 rapidjson 的使用方法基本上来源于 rapidjson 中文使用手册 rapidjson 的基本介绍 使用好处 解析速度等不在此篇讲述 因为在官网上已经讲得非常详细了 这里写的都是本人拙劣的见解 如有不足之处 烦请各位指出 本文结构 1 基本单元 2 构建 3 增 4 删 5 查 6 改 7 输出 8 拷贝 9 总结 1 基本单元 rapidjson 的基本操作单元 Document 以及 Value 例 当有

此篇文章中对于rapidjson的使用方法基本上来源于 rapidjson中文使用手册

rapidjson的基本介绍、使用好处、解析速度等不在此篇讲述,因为在官网上已经讲得非常详细了,这里写的都是本人拙劣的见解,如有不足之处,烦请各位指出。

本文结构:

1、基本单元;

2、构建

3、增

4、删

5、查

6、改

7、输出

8、拷贝

1、基本单元

rapidjson的基本操作单元:Document以及Value

例:当有一个json案例,请让我们称之为test.json

{ "test_int": 100, "test_float": 100.9, "test_string": "asd", "test_float_array": [], "test_object": { "vec_key": [9, 3, 5, 7, 2, 0], "i_key": 789, "f_key": 111.3, "s_key": "qwe" } }

在rapidjson中的Document就相当于test.json本身,而Value就相当于test.json里面的那些key对应的value数据,可以是int、float、string、vector亦或是object类型

2、构建

1)构建document:如果我们手中有test.json所示的字符串数据,我们可以直接将这个数据装载进Document中:

#include "rapidjson/document.h" //引入rapidjson源文件 using namespace rapidjson; //使用rapidjson命名空间,如果没有这个语句,将无法使用Document等数据类型, //除非你想以rapidjson::Document document;这样的方式命名变量 // ... Document document; //创建Document变量document document.Parse(json); //将已有的json变量导入document

当然你也可以不用Parse方法去装填数据到document,在本篇文章后面会有直接把自己想要的数据装填进document的方法教程。

2)构建Value:构建Value的方法比较多,最简单是利用作者写好的重载方法:

Value b(true); // 调用 Value(bool) Value i(-123); // 调用 Value(int) Value u(123u); // 调用 Value(unsigned) Value d(1.5); // 调用 Value(double)

也可以采用各种set方法,或者是直接赋值的方式进行设置:

Value v; // Null v.SetBool(true); //调用set方法进行设置 v.SetInt(10); v.SetFloat(12.3); v.SetString("qwe"); v = true; // 简写,和上面的相同 v = 10; v = 12.3; v = "qwe";

以上所示的方法,可以将value设置成各种类型的数据

3、增

需要注意的是,虽然我将普通数据和复杂数据分开讲述,但是对于document来说,无论你添加什么数据进去,使用的都是同一个函数(AddMember(key,value,allocator)),使用者只需要将value设置成想要的数据,然后再调用这个函数就可以了,之所以分开讲述是因为构建的难度稍微有些不同。

1)添加普通类型数据

这里将bool、int、float、string称为普通数据类型,方法如下面的代码3.1所示:

#include "rapidjson/document.h" #include "rapidjson/writer.h" using namespace rapidjson; //... Document document; // Null document.SetObject(); //如果不使用这个方法,AddMember将会报错 Value value; // Null value.SetBool(true); //调用set方法进行设置 //下面就是装填value到document的方法,对于所有数据类型都是一样的 document.AddMember("bool_key",value,document.GetAllocator()); //...

这里的代码使用bool类型作为例子,其他的类型都是一样的道理,不再举例。代码3.1得到的json数据为:

{ "bool_key": true }

2)添加数组

添加数据的方法,我已经把需要注意的地方都写到代码注释,这里不再赘述,代码3.2

#include "rapidjson/document.h" #include "rapidjson/writer.h" using namespace rapidjson; //... Document document; // Null document.SetObject(); //如果不使用这个方法,AddMember将会报错 //下面会遇到多次使用allocator的情况,采用这种方式避免多次调用GetAllocator()去获取allocator Document::AllocatorType& allocator = document.GetAllocator(); Value value(kArrayType); // 创建一个array类型的value数据,如果没有kArrayType这个类型说明,后面使用PushBack方法操作value的时候会报错 for (int i = 5; i <= 10; i++) value.PushBack(i, allocator); // 可能需要调用 realloc() 所以需要 allocator // 流畅接口(Fluent interface)直接将相关数据push到value中 value.PushBack("Lua", allocator).PushBack("Mio", allocator); //下面就是装填value到document的方法,对于所有数据类型都是一样的 document.AddMember("vec_key",value,allocator); //...

为什么添加普通类型的时候不用在创建Value类型变量时进行类型说明呢?因为对于普通的数据类型,rapidjson都做了相关的方法覆盖,对于操作者来说,这是不必关心的事情,但是对于复杂数据类型,就不一样了,将添加数组到value也做一样的无差别接口,可能是一件比较困难的事情。

另外,为什么不添加document.SetObject();语句就会报错?

为什么不添加Value value(kArrayType);这样的类型说明就会报错?

C++ rapidjson 基础入门

很明显的,函数中做了一个IsObject()的断言,只要传入的document不是object类型就会出错。

而调用value.PushBack()方法的时候,请看这个函数的实现:

C++ rapidjson 基础入门

代码3.2得到的json数据为:

{ "vec_key": [5, 6, 7, 8, 9, 10, "Lua", "Mio"] }

3)添加object类型数据

事实上,rapidjson定义的json数据类型有7种,如下所示:

//! Type of JSON value enum Type { kNullType = 0, //!< null kFalseType = 1, //!< false kTrueType = 2, //!< true kObjectType = 3, //!< object kArrayType = 4, //!< array kStringType = 5, //!< string kNumberType = 6 //!< number };

这次用到的是 kObjectType类型,废话不多说,下面请看 代码3.3

#include "rapidjson/document.h" #include "rapidjson/writer.h" using namespace rapidjson; //... Document document; // Null document.SetObject(); //如果不使用这个方法,AddMember将会报错 //下面会遇到多次使用allocator的情况,采用这种方式避免多次调用GetAllocator()去获取allocator Document::AllocatorType& allocator = document.GetAllocator(); Value value(kObjectType); // 创建一个array类型的value数据,如果没有kArrayType这个类型说明,后面是用PushBack方法操作value的时候会报错 value.AddMember("int_key",123,allocator); //value添加int类型数据 value.AddMember("float_key",1.23,allocator); //value添加float类型数据 value.AddMember("string_key","Lua",allocator); //value添加string类型数据 //下面就是装填value到document的方法,对于所有数据类型都是一样的 document.AddMember("obj_key",value,allocator); value.SetInt(123); document.AddMember("i_key",value,allocator); //document添加int类型数据 value.SetFloat(1.23); document.AddMember("f_key",value,allocator); //document添加float类型数据 value.SetString("Mio"); document.AddMember("s_key",value,allocator); //document添加string类型数据 //...

代码3.3得到的json数据为:

{ "obj_key": { "int_key": 123, "float_key": 1.23, "string_key": "Lua" }, "i_key": 123, "f_key": 1.23, "s_key": "Mio" }

可以看到,通过上述代码生成了一个含有普通数据以及object数据的json


4、删

rapidjson中提供了两个删除接口,一个是:RemoveAllMembers(),这个函数用来删除document或value中的所有数据,在想要清空json,并添加新数据的时候可以用。

终于写到了查询的方法,rapidjson中的查询方法多种多样,涉及到普通value查询、array、object查询等。一般来说,查询数据也分为两种情况,一种是已知数据肯定存在的情况(一般来说这不太严谨),另一种是不清楚数据是否存在的情况,这里举的例子一般为第二种情况,即不清楚数据是否存在,因此查询之前,还会对数据是否存在做一次判断。

1)普通查询

如果只是对普通数据进行查询,那么可以通过HasMember(key) 方法先判断数据是否存在,如果存在,再取出数据:

 if(document.HasMember("i_key")) { printf("%d\n",document["i_key"].GetInt()); }

需要注意的是,HasMember作为判断数据是否存在的方法,事实上已经对数据进行了一次查找。

当然,我们也可以用迭代器的方式来获取普通数据,下面是官网的例子:

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

2)查询(遍历)array数组

查询数据是否存在,依然采用HasMember方法,查询到这个数据存在之后,如何把数据提取出来呢?方法很简单:我们在代码3.2中曾经构造了一个数组,现在我们要将这个数组的数据提取出来,这里也有多种方法,第一种是考虑数组长度,遍历这个数组,请看代码5.2.1:

// 使用引用来连续访问,方便之余还更高效。 const Value& a = document["vec_key"]; assert(a.IsArray()); for (SizeType i = 0; i < a.Size(); i++) // 使用 SizeType 而不是 size_t { if(a[i].IsInt()) { printf("a[%d] = %d\n", i, a[i].GetInt()); }else if(a[i].IsFloat()) { printf("a[%d] = %f\n", i, a[i].GetFloat()); }else if(a[i].IsString()) { printf("a[%d] = %s\n", i, a[i].GetString()); } }

第二种方法是采用迭代器去遍历数组,请看代码5.2.2:

for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) { if(itr->IsInt()) { printf("%d\n", itr->GetInt()); }else if(itr->IsFloat()) { printf("%f\n", itr->GetFloat()); }else if(itr->IsString()) { printf("%s\n", itr->GetString()); } }

第三种是利用C++11的特性,使用范围 for 循环去访问 Array 内的所有元素,代码有点冗余,我就贴上官网的例子:

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

3)查询(遍历)object

查询object,还是采用迭代器的方法,先调用FindMember方法,它能同时检查成员是否存在并返回它的 Value,然后再取出具体数据,就像普通查询的代码一样使用迭代器就可以了:

Value::ConstMemberIterator itr = document.FindMember("hello"); if (itr != document.MemberEnd()) //itr->value就是object的值,itr->name即为object的key的值

关于遍历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()]); }

可以看到,迭代器itr里面有两个变量,一个是name(即通常意义上的key),一个是value(如其名),采用迭代器的方法可以轻松遍历object内容,

6、改

1)修改value

修改value的方法其实在创建的那个章节就提到了,直接调用各种set函数,或者赋值语句,都是可以的。特别需要提出来的一点是修改string类型,上面举的例子都是常量,如果你想传入一个string变量,需要使用StringRef函数,官网的说法是:对于字符指针,RapidJSON 需要作一个标记,代表它不复制也是安全的。这里直接贴官网的例子:

const char * cstr = getenv("USER"); size_t cstr_len = ...; // 如果有长度 Value s; // s.SetString(cstr); // 这不能通过编译 s.SetString(StringRef(cstr)); // 可以,假设它的生命周期安全,并且是以空字符结尾的 s = StringRef(cstr); // 上行的缩写 s.SetString(StringRef(cstr, cstr_len));// 更快,可处理空字符 s = StringRef(cstr, cstr_len); // 上行的缩写

2)修改array数组

其实谈不上什么修改,因为数组的修改方法,一共就两种,一个是把数据push进去,一种将数据pop出来。

而且push数据的时候,需要提供key-value键值对,但是pop数据的时候,是不用传参数进去的,默认就是删除数组的最后一个数据,所以如何修改array数组?我的建议是删掉原有数据,并新建一个array。

3)修改object

举个栗子:在代码3.3中我们构建了一个json数据,实际上这个json数据本身就是一个object,如果想要修改这个object中的其中一个数据,如“i_key”,那你可能会想到先为value赋新的值,然后调用document的AddMember将这个value添加进去,key的名称就采用原来的“i_key”。

结果,如果你去尝试了,你会发现你的json数据中出现了两个“i_key”数据。

这是因为rapidjson允许数据出现相同的key名称,尽管传统json并不允许出现相同的key,但是,如果你采用rapidjson去操作json数据,你就不得不遵守rapidjson的规定。

所以,回到原来的问题,如果你想修改object中的数据,有两种方法:

第一种方法比较粗暴,那就是先调用removeValue函数,删除掉原有的key,然后再将新的key添加进去,你甚至可以不用判断这个key是否存在,就调用removeValue函数去删除,因为就算key不存在,removeValue也不会报错。

第二种方法是采用引用的方式,将object中的数据提取到Value数据中,然后修改这个Value数据,从而达到修改数据的目的,代码如下:

Value& value = document["i_key"]; value.SetInt(999);

7、输出

如何输出document中的数据?这里用的是rapidjson的write方法,将数据写入到StringBuffer中:

StringBuffer jsonBuffer; Writer 
     
       writer(jsonBuffer); document.Accept(writer); printf("%s\n",jsonBuffer.GetString()); 
     

8、拷贝

这一节讨论document和value(kObjectType类型)如何交换各自的object数据。

方法是调用rapidjson的CopyFrom函数

1)从document拷贝到document

Document copy_doc; copy_doc.CopyFrom(document,copy_doc.GetAllocator());

这样就将document中的所有数据拷贝到了copy_doc中

2)从value拷贝到value

Value copy_value(kObjectType); copy_value.CopyFrom(value,document.GetAllocator());

3)从document拷贝到value

Value copy_value(kObjectType); copy_value.CopyFrom(document,document.GetAllocator());

4)从value拷贝到document

Document copy_doc; copy_doc.CopyFrom(value,copy_doc.GetAllocator());

9、总结

编程是不断学习与进步的过程,此次学习rapidjson,虽然只是将用法简单理清了一下还没有怎么看源码,但还是受益良多,让我明白了一些C++的用法,在此记录下自己学习的过程,希望以后也不断进步。最后,这篇文章花了我一整天的时间去编写和修改,虽然不是什么非常有营养的东西,大部分的内容还是照搬的官网,但是对我自己来说,这是自己学习rapidjson的过程,是帮助自己理解与进步的一个阶段,希望各位小伙伴们共勉。








版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207289.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午2:08
下一篇 2026年3月19日 下午2:09


相关推荐

  • 51单片机的毕业设计题目_51单片机新颖毕业设计题目

    51单片机的毕业设计题目_51单片机新颖毕业设计题目以下是学长亲手整理的C51单片机相关的毕业设计选题,都是经过学长精心审核的题目,适合作为毕设,难度不高,工作量达标,对毕设有任何疑问都可以问学长哦!相对容易工作量达标题目新颖,含创新点httpshttpshttpshttpshttpshttps。…

    2022年10月3日
    3
  • Dagger2_daggerlimit

    Dagger2_daggerlimit参考Android_Dagger2篇——从小白最易上手的角度+最新dagger.android步骤1.在需要实例化的类中,构造无参构造方法,注解@Inject@InjectpublicStudent(){}构造Component接口,有inject方法@ComponentpublicinterfaceDaggerComponent{voi…

    2025年8月24日
    4
  • 反演定理及其应用_反演定理的证明

    反演定理及其应用_反演定理的证明一、莫比乌斯反演莫比乌斯反演公式:若F(n)=∑i|nf(i)F(n)=∑i|nf(i)F(n)=\sum_{i|n}f(i)则f(n)=∑i|nμ(i)F(ni)f(n)=∑i|nμ(i)F(ni)f(n)=\sum_{i|n}\mu(i)F(\frac{n}{i})为什么呢?请看证明:由于莫比乌斯函数具有性质∑i|nμ(i)=[n=1]∑i|nμ(i)=[n=1]\…

    2025年8月21日
    5
  • J1939TP「建议收藏」

    J1939TP「建议收藏」J1939TP给上层、下层提供的服务,和它本身内部的行为。1939协议定义了一些参数组,每个参数组包含确定的内容和信号。并提供以下PG:负载的长度类型:最大字节数、可变或固定大小参数组号:18位包含以下信息:2bit数据页信息8bitPDU格式8bitPDU细节PF小于240的为PDU1格式,用于点对点通信;大于等于240的为PDU2格式,用于广播通信。PDU细节仅与PDU2格式有关。在PDU1格式下的点对点通信,PS总为0。J1939使用29位CANid作为消…

    2022年5月5日
    44
  • Linux 使用fdisk和gdisk对磁盘进行分区和删除分区操作

    Linux 使用fdisk和gdisk对磁盘进行分区和删除分区操作

    2026年3月15日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号