rapidjson官方教程
2016-05-19 15:04
936 查看
原文地址:http://rapidjson.org/zh-cn/md_doc_tutorial_8zh-cn.html
教程
Value 及 Document
查询Value
查询Array
查询Object
查询Number
查询String
创建/修改值
改变Value类型
转移语意(Move Semantics)
转移语意及临时值
创建String
修改Array
修改Object
深复制Value
交换Value
下一部分
本教程简介文件对象模型(Document Object Model, DOM)API。
如用法一览中所示,可以解析一个JSON至DOM,然后就可以轻松查询及修改DOM,并最终转换回JSON。
每个JSON值都储存为
在本节中,我们会使用到
假设我们用C语言的字符串储存一个JSON(
{
"hello":
"world",
"t": true ,
"f":
false,
"n": null,
"i": 123,
"pi": 3.1416,
"a": [1, 2, 3, 4]
}
把它解析至一个
#include "rapidjson/document.h"
using namespace rapidjson;
// ...
Document document;
document.Parse(json);
那么现在该JSON就会被解析至
教程中的DOM
自从RFC 7159作出更新,合法JSON文件的根可以是任何类型的JSON值。而在较早的RFC 4627中,根值只允许是Object或Array。而在上述例子中,根是一个Object。
assert(document.IsObject());
让我们查询一下根Object中有没有
String。
assert(document.HasMember("hello"));
assert(document["hello"].IsString());
printf("hello = %s\n", document["hello"].GetString());
world
JSON True/False值是以
assert(document["t"].IsBool());
printf("t = %s\n", document["t"].GetBool() ?"true" :
"false");
true
JSON Null值可用
printf("n = %s\n", document["n"].IsNull() ?"null" :
"?");
null
JSON Number类型表示所有数值。然而,C++需要使用更专门的类型。
assert(document["i"].IsNumber());
// 在此情况下,IsUint()/IsInt64()/IsUInt64()也会返回 true
assert(document["i"].IsInt());
printf("i = %d\n", document["i"].GetInt());
// 另一种用法: (int)document["i"]
assert(document["pi"].IsNumber());
assert(document["pi"].IsDouble());
printf("pi = %g\n", document["pi"].GetDouble());
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调用
以下将会讨论有关查询各类型的细节。
你可以用整数字面量访问元素,如
Array与
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
printf("%d ", itr->GetInt());
还有一些熟悉的查询函数:
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()]);
}
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
注意,当
若我们不确定一个成员是否存在,便需要在调用
Value::ConstMemberIterator itr = document.FindMember("hello");
if (itr != document.MemberEnd())
printf("%s %s\n", itr->value.GetString());
由于C++提供多种整数及浮点数类型,DOM尝试尽量提供最广的范围及良好性能。
当解析一个Number时, 它会被存储在DOM之中,成为下列其中一个类型:
当查询一个Number时, 你可以检查该数字是否能以目标类型来提取:
注意,一个整数可能用几种类型来提取,而无需转换。例如,一个名为
当要提取Number类型,
根据RFC 4627,JSON String可包含Unicode字符
string),这种字符串把`
为了符合RFC 4627,RapidJSON支持包含
例如,当解析以下的JSON至
{ "s" :
"a\u0000b" }
此外,
string(const
char* s, size_t count);
此构造函数接受字符串长度作为参数。它支持在字符串中存储空字符,也应该会有更好的性能。
if (document["hello"] == document["n"])/*...*/;
// 比较两个值
if (document["hello"] =="world")
/*...*/;
// 与字符串家面量作比较
if (document["i"] != 123)/*...*/;
// 与整数作比较
if (document["pi"] != 3.14)/*...*/;
// 与double作比较
Array/Object顺序以它们的元素/成员作比较。当且仅当它们的整个子树相等,它们才当作相等。
注意,现时若一个Object含有重复命名的成员,它与任何Object作比较都总会返回
有多种方法去创建值。 当一个DOM树被创建或修改后,可使用
Document d;//
Null
d.SetObject();
Value v;// Null
v.SetInt(10);
v = 10; // 简写,和上面的相同
Value b(true);//
调用Value(bool)
Value i(-123);//
调用 Value(int)
Value u(123u);//
调用Value(unsigned)
Value d(1.5);//
调用Value(double)
要重建空Object或Array,可在默认构造函数后使用
Value o(kObjectType);
Value a(kArrayType);
Value a(123);
Value b(456);
b = a; // a变成Null,b变成数字123。
使用移动语意赋值。
为什么?此语意有何优点?
最简单的答案就是性能。对于固定大小的JSON类型(Number、True、False、Null),复制它们是简单快捷。然而,对于可变大小的JSON类型(String、Array、Object),复制它们会产生大量开销,而且这些开销常常不被察觉。尤其是当我们需要创建临时Object,把它复制至另一变量,然后再析构它。
例如,若使用正常*复制*语意:
Value o(kObjectType);
{
Value contacts(kArrayType);
// 把元素加进contacts数组。
// ...
o.AddMember("contacts", contacts, d.GetAllocator());// 深度复制contacts (可能有大量内存分配)
// 析构contacts。
}
复制语意产生大量的复制操作。
那个
有一些方案可避免实质地复制这些数据,例如引用计数(reference counting)、垃圾回收(garbage collection, GC)。
为了使RapidJSON简单及快速,我们选择了对赋值采用*转移*语意。这方法与
因此,使用转移语意后,上面的例子变成:
Value o(kObjectType);
{
Value contacts(kArrayType);
// adding elements to contacts array.
o.AddMember("contacts", contacts, d.GetAllocator());// 只需 memcpy() contacts本身至新成员的Value(16字节)
// contacts在这里变成Null。它的析构是平凡的。
}
转移语意不需复制。
在C++11中这称为转移赋值操作(move assignment operator)。由于RapidJSON 支持C++03,它在赋值操作采用转移语意,其它修改形函数如
Value a(kArrayType);
Document::AllocatorType& allocator = document.GetAllocator();
// a.PushBack(Value(42), allocator); // 不能通过编译
a.PushBack(Value().SetInt(42), allocator);//
fluent API
a.PushBack(Value(42).Move(), allocator);//
和上一行相同
copy-string: 分配缓冲区,然后把来源数据复制至它。
const-string: 简单地储存字符串的指针。
Copy-string总是安全的,因为它拥有数据的克隆。Const-string可用于存储字符串字面量,以及用于在DOM一节中将会提到的in-situ解析中。
为了让用户自定义内存分配方式,当一个操作可能需要内存分配时,RapidJSON要求用户传递一个allocator实例作为API参数。此设计避免了在每个Value存储allocator(或document)的指针。
因此,当我们把一个copy-string赋值时, 调用含有allocator的
Document document;
Value author;
char buffer[10];
int len = sprintf(buffer,"%s %s",
"Milo",
"Yip");// 动态创建的字符串。
author.SetString(buffer, len, document.GetAllocator());
memset(buffer, 0, sizeof(buffer));
// 清空buffer后author.GetString() 仍然包含 "Milo Yip"
在此例子中,我们使用
另外,上面的
最后,对于字符串字面量或有安全生命周期的字符串,可以使用const-string版本的
Value s;
s.SetString("rapidjson");// 可包含空字符,长度在编译萁推导
s = "rapidjson";
// 上行的缩写
对于字符指针,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); // 上行的缩写
注意,
以下是
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("Lua", allocator).PushBack("Mio", allocator);
与STL不一样的是,
interface_)。
如果你想在Array中加入一个非常量字符串,或是一个没有足够生命周期的字符串(见Create String),你需要使用copy-string API去创建一个String。为了避免加入中间变量,可以就地使用一个临时值:
// 就地Value参数
contact.PushBack(Value("copy",
document.GetAllocator()).Move(),
// copy string
document.GetAllocator());
// 显式Value参数
Value val("key",
document.GetAllocator()); // copy string
contact.PushBack(val, document.GetAllocator());
以下是一个例子。
Value contact(kObject);
contact.AddMember("name","Milo", document.GetAllocator());
contact.AddMember("married",true, document.GetAllocator());
使用
这些重载是为了避免复制
如果你需要从非常数字符串或生命周期不足的字符串创建键名(见创建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());
移除成员有几个选择:
Document d;
Document::AllocatorType& a = d.GetAllocator();
Value v1("foo");
// Value v2(v1); // 不容许
Value v2(v1, a);//
制造一个克隆
assert(v1.IsString()); // v1不变
d.SetArray().PushBack(v1, a).PushBack(v2, a);
assert(v1.IsNull() && v2.IsNull());
// 两个都转移动d
v2.CopyFrom(d, a); // 把整个document复制至v2
assert(d.IsArray() && d.Size() == 2);
// d不变
v1.SetObject().AddMember("array", v2, a);
d.PushBack(v1, a);
Value a(123);
Value b("Hello");
a.Swap(b);
assert(a.IsString());
assert(b.IsInt());
无论两棵DOM树有多复杂,交换是很快的(常数时间)。
本教程展示了如何询查及修改DOM树。RapidJSON还有一个重要概念:
流 是读写JSON的通道。流可以是内存字符串、文件流等。用户也可以自定义流。
编码定义在流或内存中使用的字符编码。RapidJSON也在内部提供Unicode转换及校验功能。
DOM的基本功能已在本教程里介绍。还有更高级的功能,如原位(*in situ*)解析、其他解析选项及高级用法。
SAX 是RapidJSON解析/生成功能的基础。学习使用
性能展示一些我们做的及第三方的性能测试。
技术内幕讲述一些RapidJSON内部的设计及技术。
你也可以参考常见问题、API文档、例子及单元测试。
教程
目录
Value 及 Document查询Value
查询Array
查询Object
查询Number
查询String
创建/修改值
改变Value类型
转移语意(Move Semantics)
转移语意及临时值
创建String
修改Array
修改Object
深复制Value
交换Value
下一部分
本教程简介文件对象模型(Document Object Model, DOM)API。
如用法一览中所示,可以解析一个JSON至DOM,然后就可以轻松查询及修改DOM,并最终转换回JSON。
Value 及 Document
每个JSON值都储存为Value类,而
Document类则表示整个DOM,它存储了一个DOM树的根
Value。RapidJSON的所有公开类型及函数都在
rapidjson命名空间中。
查询Value
在本节中,我们会使用到example/tutorial/tutorial.cpp中的代码片段。
假设我们用C语言的字符串储存一个JSON(
const char* json):
{
"hello":
"world",
"t": true ,
"f":
false,
"n": null,
"i": 123,
"pi": 3.1416,
"a": [1, 2, 3, 4]
}
把它解析至一个
Document:
#include "rapidjson/document.h"
using namespace rapidjson;
// ...
Document document;
document.Parse(json);
那么现在该JSON就会被解析至
document中,成为一棵*DOM树*:
教程中的DOM
自从RFC 7159作出更新,合法JSON文件的根可以是任何类型的JSON值。而在较早的RFC 4627中,根值只允许是Object或Array。而在上述例子中,根是一个Object。
assert(document.IsObject());
让我们查询一下根Object中有没有
"hello"成员。由于一个
Value可包含不同类型的值,我们可能需要验证它的类型,并使用合适的API去获取其值。在此例中,
"hello"成员关联到一个JSON
String。
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());
// 在此情况下,IsUint()/IsInt64()/IsUInt64()也会返回 true
assert(document["i"].IsInt());
printf("i = %d\n", document["i"].GetInt());
// 另一种用法: (int)document["i"]
assert(document["pi"].IsNumber());
assert(document["pi"].IsDouble());
printf("pi = %g\n", document["pi"].GetDouble());
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的typedef。在多数系统中,Array最多能存储2^32-1个元素。
你可以用整数字面量访问元素,如
a[0]、
a[1]、
a[2]。
Array与
std::vector相似,除了使用索引,也可使用迭代器来访问所有元素。
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
printf("%d ", itr->GetInt());
还有一些熟悉的查询函数:
SizeType Capacity() const
bool Empty() const
查询Object
和Array相似,我们可以用迭代器去访问所有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()]);
}
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
注意,当
operator[](const char*)找不到成员,它会断言失败。
若我们不确定一个成员是否存在,便需要在调用
operator[](const char*)前先调用
HasMember()。然而,这会导致两次查找。更好的做法是调用
FindMember(),它能同时检查成员是否存在并返回它的Value:
Value::ConstMemberIterator itr = document.FindMember("hello");
if (itr != document.MemberEnd())
printf("%s %s\n", itr->value.GetString());
查询Number
JSON只提供一种数值类型──Number。数字可以是整数或实数。RFC 4627规定数字的范围由解析器指定。由于C++提供多种整数及浮点数类型,DOM尝试尽量提供最广的范围及良好性能。
当解析一个Number时, 它会被存储在DOM之中,成为下列其中一个类型:
类型 | 描述 |
---|---|
unsigned | 32位无号整数 |
int | 32位有号整数 |
uint64_t | 64位无号整数 |
int64_t | 64位有号整数 |
double | 64位双精度浮点数 |
查检 | 提取 |
---|---|
bool IsNumber() | 不适用 |
bool IsUint() | unsigned GetUint() |
bool IsInt() | int GetInt() |
bool IsUint64() | uint64_t GetUint64() |
bool IsInt64() | int64_t GetInt64() |
bool IsDouble() | double GetDouble() |
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可能会丧失精度(因为
double的尾数只有52位)。
查询String
除了GetString(),
Value类也有一个
GetStringLength()。这里会解释个中原因。
根据RFC 4627,JSON String可包含Unicode字符
U+0000,在JSON中会表示为
"\\u0000"。问题是,C/C++通常使用空字符结尾字符串(null-terminated
string),这种字符串把`
\0'作为结束符号。
为了符合RFC 4627,RapidJSON支持包含
U+0000的String。若你需要处理这些String,便可使用
GetStringLength()去获得正确的字符串长度。
例如,当解析以下的JSON至
Document d之后:
{ "s" :
"a\u0000b" }
"a\\u0000b"值的正确长度应该是3。但
strlen()会返回1。
GetStringLength()也可以提高性能,因为用户可能需要调用
strlen()去分配缓冲。
此外,
std::string也支持这个构造函数:
string(const
char* s, size_t count);
此构造函数接受字符串长度作为参数。它支持在字符串中存储空字符,也应该会有更好的性能。
比较两个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类型
当使用默认构造函数创建一个Value或Document,它的类型便会是Null。要改变其类型,需调用SetXXX()或赋值操作,例如:
Document d;//
Null
d.SetObject();
Value v;// Null
v.SetInt(10);
v = 10; // 简写,和上面的相同
构造函数的各个重载
几个类型也有重载构造函数: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。
使用移动语意赋值。
为什么?此语意有何优点?
最简单的答案就是性能。对于固定大小的JSON类型(Number、True、False、Null),复制它们是简单快捷。然而,对于可变大小的JSON类型(String、Array、Object),复制它们会产生大量开销,而且这些开销常常不被察觉。尤其是当我们需要创建临时Object,把它复制至另一变量,然后再析构它。
例如,若使用正常*复制*语意:
Value o(kObjectType);
{
Value contacts(kArrayType);
// 把元素加进contacts数组。
// ...
o.AddMember("contacts", contacts, d.GetAllocator());// 深度复制contacts (可能有大量内存分配)
// 析构contacts。
}
复制语意产生大量的复制操作。
那个
oObject需要分配一个和contacts相同大小的缓冲区,对conacts做深度复制,并最终要析构contacts。这样会产生大量无必要的内存分配/释放,以及内存复制。
有一些方案可避免实质地复制这些数据,例如引用计数(reference counting)、垃圾回收(garbage collection, GC)。
为了使RapidJSON简单及快速,我们选择了对赋值采用*转移*语意。这方法与
std::auto_ptr相似,都是在赋值时转移拥有权。转移快得多简单得多,只需要析构原来的Value,把来源
memcpy()至目标,最后把来源设置为Null类型。
因此,使用转移语意后,上面的例子变成:
Value o(kObjectType);
{
Value contacts(kArrayType);
// adding elements to contacts array.
o.AddMember("contacts", contacts, d.GetAllocator());// 只需 memcpy() contacts本身至新成员的Value(16字节)
// contacts在这里变成Null。它的析构是平凡的。
}
转移语意不需复制。
在C++11中这称为转移赋值操作(move assignment operator)。由于RapidJSON 支持C++03,它在赋值操作采用转移语意,其它修改形函数如
AddMember(),
PushBack()也采用转移语意。
转移语意及临时值
有时候,我们想直接构造一个Value并传递给一个“转移”函数(如PushBack()、
AddMember())。由于临时对象是不能转换为正常的Value引用,我们加入了一个方便的
Move()函数:
Value a(kArrayType);
Document::AllocatorType& allocator = document.GetAllocator();
// a.PushBack(Value(42), allocator); // 不能通过编译
a.PushBack(Value().SetInt(42), allocator);//
fluent API
a.PushBack(Value(42).Move(), allocator);//
和上一行相同
创建String
RapidJSON提供两个String的存储策略。copy-string: 分配缓冲区,然后把来源数据复制至它。
const-string: 简单地储存字符串的指针。
Copy-string总是安全的,因为它拥有数据的克隆。Const-string可用于存储字符串字面量,以及用于在DOM一节中将会提到的in-situ解析中。
为了让用户自定义内存分配方式,当一个操作可能需要内存分配时,RapidJSON要求用户传递一个allocator实例作为API参数。此设计避免了在每个Value存储allocator(或document)的指针。
因此,当我们把一个copy-string赋值时, 调用含有allocator的
SetString()重载函数:
Document document;
Value author;
char buffer[10];
int len = sprintf(buffer,"%s %s",
"Milo",
"Yip");// 动态创建的字符串。
author.SetString(buffer, len, document.GetAllocator());
memset(buffer, 0, sizeof(buffer));
// 清空buffer后author.GetString() 仍然包含 "Milo Yip"
在此例子中,我们使用
Document实例的allocator。这是使用RapidJSON时常用的惯用法。但你也可以用其他allocator实例。
另外,上面的
SetString()需要长度参数。这个API能处理含有空字符的字符串。另一个
SetString()重载函数没有长度参数,它假设输入是空字符结尾的,并会调用类似
strlen()的函数去获取长度。
最后,对于字符串字面量或有安全生命周期的字符串,可以使用const-string版本的
SetString(),它没有allocator参数。对于字符串家面量(或字符数组常量),只需简单地传递字面量,又安全又高效:
Value s;
s.SetString("rapidjson");// 可包含空字符,长度在编译萁推导
s = "rapidjson";
// 上行的缩写
对于字符指针,RapidJSON需要作一个标记,代表它不复制也是安全的。可以使用
StringRef函数:
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); // 上行的缩写
修改Array
Array类型的Value提供与std::vector相似的API。
Clear()
Reserve(SizeType, Allocator&)
Value& PushBack(Value&, Allocator&)
template <typename T> GenericValue& PushBack(T, Allocator&)
Value& PopBack()
ValueIterator Erase(ConstValueIterator pos)
ValueIterator Erase(ConstValueIterator first, ConstValueIterator last)
注意,
Reserve(...)及
PushBack(...)可能会为数组元素分配内存,所以需要一个allocator。
以下是
PushBack()的例子:
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("Lua", allocator).PushBack("Mio", allocator);
与STL不一样的是,
PushBack()/
PopBack()返回Array本身的引用。这称为流畅接口(_fluent
interface_)。
如果你想在Array中加入一个非常量字符串,或是一个没有足够生命周期的字符串(见Create String),你需要使用copy-string API去创建一个String。为了避免加入中间变量,可以就地使用一个临时值:
// 就地Value参数
contact.PushBack(Value("copy",
document.GetAllocator()).Move(),
// copy string
document.GetAllocator());
// 显式Value参数
Value val("key",
document.GetAllocator()); // copy string
contact.PushBack(val, document.GetAllocator());
修改Object
Object是键值对的集合。每个键必须为String。要修改Object,方法是增加或移除成员。以下的API用来增加城员:Value& AddMember(Value&, Value&, Allocator& allocator)
Value& AddMember(StringRefType, Value&, Allocator&)
template <typename T> 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)使用了“转移最后”手法来达成常数时间复杂度。基本上就是析构迭代器位置的成员,然后把最后的成员转移至迭代器位置。因此,成员的次序会被改变。
深复制Value
若我们真的要复制一个DOM树,我们可使用两个APIs作深复制:含allocator的构造函数及CopyFrom()。
Document d;
Document::AllocatorType& a = d.GetAllocator();
Value v1("foo");
// Value v2(v1); // 不容许
Value v2(v1, a);//
制造一个克隆
assert(v1.IsString()); // v1不变
d.SetArray().PushBack(v1, a).PushBack(v2, a);
assert(v1.IsNull() && v2.IsNull());
// 两个都转移动d
v2.CopyFrom(d, a); // 把整个document复制至v2
assert(d.IsArray() && d.Size() == 2);
// d不变
v1.SetObject().AddMember("array", v2, a);
d.PushBack(v1, a);
交换Value
RapidJSON也提供Swap()。
Value a(123);
Value b("Hello");
a.Swap(b);
assert(a.IsString());
assert(b.IsInt());
无论两棵DOM树有多复杂,交换是很快的(常数时间)。
下一部分
本教程展示了如何询查及修改DOM树。RapidJSON还有一个重要概念:流 是读写JSON的通道。流可以是内存字符串、文件流等。用户也可以自定义流。
编码定义在流或内存中使用的字符编码。RapidJSON也在内部提供Unicode转换及校验功能。
DOM的基本功能已在本教程里介绍。还有更高级的功能,如原位(*in situ*)解析、其他解析选项及高级用法。
SAX 是RapidJSON解析/生成功能的基础。学习使用
Reader/
Writer去实现更高性能的应用程序。也可以使用
PrettyWriter去格式化JSON。
性能展示一些我们做的及第三方的性能测试。
技术内幕讲述一些RapidJSON内部的设计及技术。
你也可以参考常见问题、API文档、例子及单元测试。
相关文章推荐
- JS定义类的六种方式详解
- JSTL标准标签库
- RapidJSON简介
- For..In loops in javascript - key value pairs
- JS判断IP地址格式是否正确
- JavaScript中的数组遍历forEach()与map()方法以及兼容写法介绍
- 使用GSON解析JSON文件
- javascript深入理解js闭包
- Ext JS 6开发实例(一)
- Ext JS 6开发实例(一)
- Ext JS 6开发实例(一)
- Ext JS 6开发实例(一)
- js作用域
- 解析JSON文件
- Jsp的请求转发与重定向区别
- JS使用模板快速填充HTML控件数据
- ASP.NET 前台Javascript调用后台代码 / 后台调用前台Javascript
- js求出日期
- [Effective JavaScript 笔记] 第11条:熟练掌握闭包
- js中each用法及跳出each及if多层循环