DOM对象是不是似曾相熟,比如常听到浏览器解析http响应构建的DOM对象。DOM对象是个语言无关的,保存XML或者HTML文档的树状结构。
JSON其实是一个网络对象,它比XML、更简洁更方便在网络传输。DOM、和JSON、的关系是DOM、是JOSN串在内存中的表示。
类Document描述了RapidJson的DOM结构。类Document是通用模板GenericDocument类UTF8的特化。
//! GenericDocument with UTF8 encoding
typedef GenericDocument<UTF8<> > Document;
按照Json语法,这里的Document类型可以是Object,Array,Number,Stirng,Boolean和Null的任意一种类型。其他的都是非法的。
//! 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
};
所有的GenericValue都是基于以上合法type的json串做处理,代码中大量使用了 RAPIDJSON_NOEXCEPT做合法性验证。
GenericDocument类继承了GenericValue类。我们先来看看GenericValue都是怎么定义的。
GenericValue定义包括了DOM一些基本生成、替换、删除和查找(增删改查)成员函数。Value类则是用模板特化了更常用UTF-8编码的。
typedef GenericValue<UTF8<> > Value;
GenericValue定义了以下构造函数:
除此还定义了=操作符的函数和CopyFrom深拷贝函数
插入节点的一些样例:
这里有个person类,需要追加一个address属性。一个简单有效的方法就是修改上述 `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());
GenericDocument是继承了GenericValue的封装,提供一套更容易操作的api。Parse函数用于解析,并且提供了一些配套函数以及获取解析结果,解析出错码。
GenericDocument的几个关键成员包括:
`GenericDocument` 的缺省分配器是 `MemoryPoolAllocator`。此分配器实际上会顺序地分配内存,并且不能逐一释放。当要解析一个 JSON 并生成 DOM,这种分配器是非常合适的。
RapidJSON 还提供另一个分配器 `CrtAllocator`,当中 CRT 是 C 运行库(C RunTime library)的缩写。此分配器简单地读用标准的 `malloc()`/`realloc()`/`free()`。当我们需要许多增减操作,这种分配器会更为适合。然而这种分配器远远比 `MemoryPoolAllocator` 低效。
从外部传入一个定义好一个大数组也可以算是内存分配器。一个样例如下:
char valueBuffer[4096];
char parseBuffer[1024];
MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer));
MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer));
DocumentType d(&valueAllocator, sizeof(parseBuffer), &parseAllocator);
d.Parse(json);
若解析时分配总量少于 4096+1024 字节时,这段代码不会造成任何堆内存分配(经 `new` 或 `malloc()`)。
使用者可以通过 `MemoryPoolAllocator::Size()` 查询当前已分的内存大小。那么使用者可以拟定使用者缓冲区的合适大小。
另外需要说明的是,`Allocator` 定义当 `Document`/`Value` 分配或释放内存时使用那个分配类。`Document` 拥有或引用到一个 `Allocator` 实例。而为了节省内存,`Value` 并没有Allocator。如果需要Allocator,需要从Document获取。
许多 DOM 操作 API 中要提供分配器作为参数。由于这些 API 是 `Value` 的成员函数,不希望为每个 `Value` 储存一个分配器指针。
什么是原位解析?
原位解析把分配开销及内存复制减至最小。
原位解析最适合用于短期的、用完即弃的 JSON。实际应用中,这些场合是非常普遍的,例如反序列化 JSON 至 C++ 对象、处理以 JSON 表示的 web 请求等。
使用原位解析的前置限制条件
解析过程顺利完成,`Document` 便会含有解析结果。当过程出现错误,原来的 DOM 会维持不变。可使用 `bool HasParseError()`、`ParseErrorCode GetParseError()` 及 `size_t GetErrorOffset()` 获取解析的错误状态。
获取错误的原因,以及错误开始的位置的一个样例如下:
Document d;
if (d.Parse(json).HasParseError()) {
fprintf(stderr, "\nError(offset %u): %s\n",
(unsigned)d.GetErrorOffset(),
GetParseError_En(d.GetParseErrorCode()));
// ...
}
类似于std::swap语义的接口, GenericDocument::swap(one, other)
源代码有提醒注意的是,GenericDocument没有实现任何虚接口,也包括没有实现析构函数,所以避免使用delete GenericDocument这种写法。
Rapidjson大量使用了浅拷贝,如果采用了浅拷贝,注意局部对象的使用 不超过对象生存范围,防止使用了被析构的对象。
SAX(Simple API for XML)是对XML的简单操作API的集合。其实这里使用了SAX概念集来描述操作JSON(或者内存中DOM,Document)的操作。
这个SAX还包含了以下的特性:
Accept(Handler &) const:bool 使用了Gof访问者设计模式,在不改变对象类的前提下,定义新操作。一个样例如下:
Writer<StringBuffer> writer(buffer);
d.Accept(writer);
常用的场景有输出字符串的字符,或者深拷贝object类型
实际上,`Value::Accept()` 是负责发布该值相关的 SAX 事件至处理器的。通过这个设计,`Value` 及 `Writer` 解除了偶合。`Value` 可生成 SAX 事件,而 `Writer` 则可以处理这些事件。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。