mirror of
https://github.com/Tencent/rapidjson.git
synced 2025-03-04 07:27:28 +01:00
Localizes readme, features and tutorial
This commit is contained in:
parent
617c61a381
commit
35c726b1ff
2366
build/Doxyfile.zh-cn
Normal file
2366
build/Doxyfile.zh-cn
Normal file
File diff suppressed because it is too large
Load Diff
95
doc/features.zh-cn.md
Normal file
95
doc/features.zh-cn.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 特点
|
||||
|
||||
## 总体
|
||||
|
||||
* 跨平台
|
||||
* 编译器:Visual Studio、gcc、clang等
|
||||
* 架构:x86、x64、ARM等
|
||||
* 操作系统:Windows、Mac OS X、Linux、iOS、Android等
|
||||
* 容易安装
|
||||
* 只有头文件的库。只需把头文件复制至你的项目中。
|
||||
* 独立、最小依赖
|
||||
* 不需依赖STL、BOOST等。
|
||||
* 只包含`<cstdio>`, `<cstdlib>`, `<cstring>`, `<inttypes.h>`, `<new>`, `<stdint.h>`。
|
||||
* 没使用C++异常、RTTI
|
||||
* 高性能
|
||||
* 使用模版及内联函数去降低函数调用开销。
|
||||
* 内部经优化的Grisu2及浮点数解析实现。
|
||||
* 可选的SSE2/SSE4.1支持。
|
||||
|
||||
## 符合标准
|
||||
|
||||
* RapidJSON应完全符合RFC4627/ECMA-404标准。
|
||||
* 支持Unicod代理对(surrogate pair)。
|
||||
* 支持空字符(`"\u0000"`)。
|
||||
* 例如,可以优雅地解析及处理`["Hello\u0000World"]`。含读写字符串长度的API。
|
||||
|
||||
## Unicode
|
||||
|
||||
* 支持UTF-8、UTF-16、UTF-32编码,包括小端序和大端序。
|
||||
* 这些编码用于输入输出流,以及内存中的表示。
|
||||
* 支持从输入流自动检测编码。
|
||||
* 内部支持编码的转换。
|
||||
* 例如,你可以读取一个UTF-8文件,让RapidJSON把JSON字符串转换至UTF-16的DOM。
|
||||
* 内部支持编码校验。
|
||||
* 例如,你可以读取一个UTF-8文件,让RapidJSON检查是否所有JSON字符串是合法的UTF-8字节序列。
|
||||
* 支持自定义的字符类型。
|
||||
* 预设的字符类型是:UTF-8为`char`,UTF-16为`wchar_t`,UTF32为`uint32_t`。
|
||||
* 支持自定义的编码。
|
||||
|
||||
## API风格
|
||||
|
||||
* SAX(Simple API for XML)风格API
|
||||
* 类似于[SAX](http://en.wikipedia.org/wiki/Simple_API_for_XML), RapidJSON提供一个事件循序访问的解析器API(`rapidjson::GenericReader`)。RapidJSON也提供一个生成器API(`rapidjson::Writer`),可以处理相同的事件集合。
|
||||
* DOM(Document Object Model)风格API
|
||||
* 类似于HTML/XML的[DOM](http://en.wikipedia.org/wiki/Document_Object_Model),RapidJSON可把JSON解析至一个DOM表示方式(`rapidjson::GenericDocument`),以方便操作。如有需要,可把DOM转换(stringify)回JSON。
|
||||
* DOM风格API(`rapidjson::GenericDocument`)实际上是由SAX风格API(`rapidjson::GenericReader`)实现的。SAX更快,但有时DOM更易用。用户可根据情况作出选择。
|
||||
|
||||
## 解析
|
||||
|
||||
* 递归式(预设)及迭代式解析器
|
||||
* 递归式解析器较快,但在极端情况下可出现堆栈溢出。
|
||||
* 迭代式解析器使用自定义的堆栈去维持解析状态。
|
||||
* 支持原位(*in situ*)解析。
|
||||
* 把JSON字符串的值解析至原JSON之中,然后让DOM指向那些字符串。
|
||||
* 比常规分析更快:不需字符串的内存分配、不需复制(如字符串不含转义符)、缓存友好。
|
||||
* 对于JSON数字类型,支持32-bit/64-bit的有号/无号整数,以及`double`。
|
||||
* 错误处理
|
||||
* 支持详尽的解析错误代号。
|
||||
* 支持本地化错误信息。
|
||||
|
||||
## DOM (Document)
|
||||
|
||||
* RapidJSON在类型转换时会检查数值的范围。
|
||||
* 字符串字面量的优化
|
||||
* 只储存指针,不作复制
|
||||
* 优化“短”字符串
|
||||
* 在`Value`内储存短字符串,无需额外分配。
|
||||
* 对UTF-8字符串来说,32位架构下可存储最多11字符,64位下15字符。
|
||||
* 可选地支持`std::string`(定义`RAPIDJSON_HAS_STDSTRING=1`)
|
||||
|
||||
## 生成
|
||||
|
||||
* 支持`rapidjson::PrettyWriter`去加入换行及缩进。
|
||||
|
||||
## 输入输出流
|
||||
|
||||
* 支持`rapidjson::GenericStringBuffer`,把输出的JSON储存于字符串内。
|
||||
* 支持`rapidjson::FileReadStream`及`rapidjson::FileWriteStream`,使用`FILE`对象作输入输出。
|
||||
* 支持自定义输入输出流。
|
||||
|
||||
## 内存
|
||||
|
||||
* 最小化DOM的内存开销。
|
||||
* 对大部分32/64位机器而言,每个JSON值只占16或20字节(不包含字符串)。
|
||||
* 支持快速的预设分配器。
|
||||
* 它是一个堆栈形式的分配器(顺序分配,不容许单独释放,适合解析过程之用)。
|
||||
* 使用者也可提供一个预分配的缓冲区。(有可能达至无需CRT分配就能解析多个JSON)
|
||||
* 支持标准CRT(C-runtime)分配器。
|
||||
* 支持自定义分配器。
|
||||
|
||||
## 其他
|
||||
|
||||
* 一些C++11的支持(可选)
|
||||
* 右值引用(rvalue reference)
|
||||
* `noexcept`修饰符
|
515
doc/tutorial.zh-cn.md
Normal file
515
doc/tutorial.zh-cn.md
Normal file
@ -0,0 +1,515 @@
|
||||
# 教程
|
||||
|
||||
本教程简介文件对象模型(Document Object Model, DOM)API。
|
||||
|
||||
如[用法一览](readme.zh-cn.md)中所示,可以解析一个JSON至DOM,然后就可以轻松查询及修改DOM,并最终转换回JSON。
|
||||
|
||||
[TOC]
|
||||
|
||||
# Value 及 Document {#ValueDocument}
|
||||
|
||||
每个JSON值都储存为`Value`类,而`Document`类则表示整个DOM,它存储了一个DOM树的根`Value`。RapidJSON的所有公开类型及函数都在`rapidjson`命名空间中。
|
||||
|
||||
# 查询Value {#QueryValue}
|
||||
|
||||
在本节中,我们会使用到`example/tutorial/tutorial.cpp`中的代码片段。
|
||||
|
||||
假设我们用C语言的字符串储存一个JSON(`const char* json`):
|
||||
~~~~~~~~~~js
|
||||
{
|
||||
"hello": "world",
|
||||
"t": true ,
|
||||
"f": false,
|
||||
"n": null,
|
||||
"i": 123,
|
||||
"pi": 3.1416,
|
||||
"a": [1, 2, 3, 4]
|
||||
}
|
||||
~~~~~~~~~~
|
||||
|
||||
把它解析至一个`Document`:
|
||||
~~~~~~~~~~cpp
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
// ...
|
||||
Document document;
|
||||
document.Parse(json);
|
||||
~~~~~~~~~~
|
||||
|
||||
那么现在该JSON就会被解析至`document`中,成为一棵*DOM树*:
|
||||
|
||||

|
||||
|
||||
自从RFC 7159作出更新,合法JSON文件的根可以是任何类型的JSON值。而在较早的RFC 4627中,根值只允许是Object或Array。而在上述例子中,根是一个Object。
|
||||
~~~~~~~~~~cpp
|
||||
assert(document.IsObject());
|
||||
~~~~~~~~~~
|
||||
|
||||
让我们查询一下根Object中有没有`"hello"`成员。由于一个`Value`可包含不同类型的值,我们可能需要验证它的类型,并使用合适的API去获取其值。在此例中,`"hello"`成员关联到一个JSON String。
|
||||
~~~~~~~~~~cpp
|
||||
assert(document.HasMember("hello"));
|
||||
assert(document["hello"].IsString());
|
||||
printf("hello = %s\n", document["hello"].GetString());
|
||||
~~~~~~~~~~
|
||||
|
||||
~~~~~~~~~~
|
||||
world
|
||||
~~~~~~~~~~
|
||||
|
||||
JSON True/False值是以`bool`表示的。
|
||||
~~~~~~~~~~cpp
|
||||
assert(document["t"].IsBool());
|
||||
printf("t = %s\n", document["t"].GetBool() ? "true" : "false");
|
||||
~~~~~~~~~~
|
||||
|
||||
~~~~~~~~~~
|
||||
true
|
||||
~~~~~~~~~~
|
||||
|
||||
JSON Null值可用`IsNull()`查询。
|
||||
~~~~~~~~~~cpp
|
||||
printf("n = %s\n", document["n"].IsNull() ? "null" : "?");
|
||||
~~~~~~~~~~
|
||||
|
||||
~~~~~~~~~~
|
||||
null
|
||||
~~~~~~~~~~
|
||||
|
||||
JSON Number类型表示所有数值。然而,C++需要使用更专门的类型。
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
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包含一些元素。
|
||||
~~~~~~~~~~cpp
|
||||
// 使用引用来连续访问,方便之余还更高效。
|
||||
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 {#QueryArray}
|
||||
|
||||
缺省情况下,`SizeType`是`unsigned`的typedef。在多数系统中,Array最多能存储2^32-1个元素。
|
||||
|
||||
你可以用整数字面量访问元素,如`a[0]`、`a[1]`、`a[2]`。
|
||||
|
||||
Array与`std::vector`相似,除了使用索引,也可使用迭待器来访问所有元素。
|
||||
~~~~~~~~~~cpp
|
||||
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
|
||||
printf("%d ", itr->GetInt());
|
||||
~~~~~~~~~~
|
||||
|
||||
还有一些熟悉的查询函数:
|
||||
* `SizeType Capacity() const`
|
||||
* `bool Empty() const`
|
||||
|
||||
## 查询Object {#QueryObject}
|
||||
|
||||
和Array相似,我们可以用迭代器去访问所有Object成员:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
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:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value::ConstMemberIterator itr = document.FindMember("hello");
|
||||
if (itr != document.MemberEnd())
|
||||
printf("%s %s\n", itr->value.GetString());
|
||||
~~~~~~~~~~
|
||||
|
||||
## 查询Number {#QueryNumber}
|
||||
|
||||
JSON只提供一种数值类型──Number。数字可以是整数或实数。RFC 4627规定数字的范围由解析器指定。
|
||||
|
||||
由于C++提供多种整数及浮点数类型,DOM尝试尽量提供最广的范围及良好性能。
|
||||
|
||||
当解析一个Number时, 它会被存储在DOM之中,成为下列其中一个类型:
|
||||
|
||||
类型 | 描述
|
||||
-----------|---------------------------------------
|
||||
`unsigned` | 32位无号整数
|
||||
`int` | 32位有号整数
|
||||
`uint64_t` | 64位无号整数
|
||||
`int64_t` | 64位有号整数
|
||||
`double` | 64位双精度浮点数
|
||||
|
||||
当查询一个Number时, 你可以检查该数字是否能以目标类型来提取:
|
||||
|
||||
查检 | 提取
|
||||
------------------|---------------------
|
||||
`bool IsNumber()` | 不适用
|
||||
`bool IsUint()` | `unsigned GetUint()`
|
||||
`bool IsInt()` | `int GetInt()`
|
||||
`bool IsUint64()` | `uint64_t GetUint()`
|
||||
`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 {#QueryString}
|
||||
|
||||
除了`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`之后:
|
||||
|
||||
~~~~~~~~~~js
|
||||
{ "s" : "a\u0000b" }
|
||||
~~~~~~~~~~
|
||||
`"a\u0000b"`值的正确长度应该是3。但`strlen()`会返回1。
|
||||
|
||||
`GetStringLength()`也可以提高性能,因为用户可能需要调用`strlen()`去分配缓冲。
|
||||
|
||||
此外,`std::string`也支持这个构造函数:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
string(const char* s, size_t count);
|
||||
~~~~~~~~~~
|
||||
|
||||
此构造函数接受字符串长度作为参数。它支持在字符串中存储空字符,也应该会有更好的性能。
|
||||
|
||||
## 比较两个Value
|
||||
|
||||
你可使用`==`及`!=`去比较两个Value。当且仅当两个Value的类型及内容相同,它们才当作相等。你也可以比较Value和它的原生类型值。以下是一个例子。
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
if (document["hello"] == document["n"]) /*...*/; // 比较两个值
|
||||
if (document["hello"] == "world") /*...*/; // 与字符串家面量作比较
|
||||
if (document["i"] != 123) /*...*/; // 与整数作比较
|
||||
if (document["pi"] != 3.14) /*...*/; // 与double作比较
|
||||
~~~~~~~~~~
|
||||
|
||||
Array/Object顺序以它们的元素/成员作比较。当且仅当它们的整个子树相等,它们才当作相等。
|
||||
|
||||
注意,现时若一个Object含有重复命名的成员,它与任何Object作比较都总会返回`false`。
|
||||
|
||||
# 创建/修改值 {#CreateModifyValues}
|
||||
|
||||
有多种方法去创建值。 当一个DOM树被创建或修改后,可使用`Writer`再次存储为JSON。
|
||||
|
||||
## 改变Value类型 {#ChangeValueType}
|
||||
当使用默认构造函数创建一个Value或Document,它的类型便会是Null。要改变其类型,需调用`SetXXX()`或赋值操作,例如:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Document d; // Null
|
||||
d.SetObject();
|
||||
|
||||
Value v; // Null
|
||||
v.SetInt(10);
|
||||
v = 10; // 简写,和上面的相同
|
||||
~~~~~~~~~~
|
||||
|
||||
### 构造函数的各个重载
|
||||
几个类型也有重载构造函数:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
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)`:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value o(kObjectType);
|
||||
Value a(kArrayType);
|
||||
~~~~~~~~~~
|
||||
|
||||
## 转移语意(Move Semantics) {#MoveSemantics}
|
||||
|
||||
在设计RapidJSON时有一个非常特别的决定,就是Value赋值并不是把来源Value复制至目的Value,而是把把来源Value转移(move)至目的Value。例如:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value a(123);
|
||||
Value b(456);
|
||||
b = a; // a变成Null,b变成数字123。
|
||||
~~~~~~~~~~
|
||||
|
||||

|
||||
|
||||
为什么?此语意有何优点?
|
||||
|
||||
最简单的答案就是性能。对于固定大小的JSON类型(Number、True、False、Null),复制它们是简单快捷。然而,对于可变大小的JSON类型(String、Array、Object),复制它们会产生大量开销,而且这些开销常常不被察觉。尤其是当我们需要创建临时Object,把它复制至另一变量,然后再析构它。
|
||||
|
||||
例如,若使用正常*复制*语意:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value o(kObjectType);
|
||||
{
|
||||
Value contacts(kArrayType);
|
||||
// 把元素加进contacts数组。
|
||||
// ...
|
||||
o.AddMember("contacts", contacts); // 深度复制contacts (可能有大量内存分配)
|
||||
// 析构contacts。
|
||||
}
|
||||
~~~~~~~~~~
|
||||
|
||||

|
||||
|
||||
那个`o` Object需要分配一个和contacts相同大小的缓冲区,对conacts做深度复制,并最终要析构contacts。这样会产生大量无必要的内存分配/释放,以及内存复制。
|
||||
|
||||
有一些方案可避免实质地复制这些数据,例如引用计数(reference counting)、垃圾回收(garbage collection, GC)。
|
||||
|
||||
为了使RapidJSON简单及快速,我们选择了对赋值采用*转移*语意。这方法与`std::auto_ptr`相似,都是在赋值时转移拥有权。转移快得多简单得多,只需要析构原来的Value,把来源`memcpy()`至目标,最后把来源设置为Null类型。
|
||||
|
||||
因此,使用转移语意后,上面的例子变成:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value o(kObjectType);
|
||||
{
|
||||
Value contacts(kArrayType);
|
||||
// adding elements to contacts array.
|
||||
o.AddMember("contacts", contacts); // 只需 memcpy() contacts本身至新成员的Value(16字节)
|
||||
// contacts在这里变成Null。它的析构是平凡的。
|
||||
}
|
||||
~~~~~~~~~~
|
||||
|
||||

|
||||
|
||||
在C++11中这称为转移赋值操作(move assignment operator)。由于RapidJSON 支持C++03,它在赋值操作采用转移语意,其它修改形函数如`AddMember()`, `PushBack()`也采用转移语意。
|
||||
|
||||
### 转移语意及临时值 {#TemporaryValues}
|
||||
|
||||
有时候,我们想直接构造一个Value并传递给一个“转移”函数(如`PushBack()`、`AddMember()`)。由于临时对象是不能转换为正常的Value引用,我们加入了一个方便的`Move()`函数:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
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 {#CreateString}
|
||||
RapidJSON提供两个String的存储策略。
|
||||
|
||||
1. copy-string: 分配缓冲区,然后把来源数据复制至它。
|
||||
2. const-string: 简单地储存字符串的指针。
|
||||
|
||||
Copy-string总是安全的,因为它拥有数据的克隆。Const-string可用于存储字符串字面量,以及用于在DOM一节中将会提到的in-situ解析中。
|
||||
|
||||
为了让用户自定义内存分配方式,当一个操作可能需要内存分配时,RapidJSON要求用户传递一个allocator实例作为API参数。此设计避免了在每个Value存储allocator(或document)的指针。
|
||||
|
||||
因此,当我们把一个copy-string赋值时, 调用含有allocator的`SetString()`重载函数:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
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参数。对于字符串家面量(或字符数组常量),只需简单地传递字面量,又安全又高效:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value s;
|
||||
s.SetString("rapidjson"); // 可包含空字符,长度在编译萁推导
|
||||
s = "rapidjson"; // 上行的缩写
|
||||
~~~~~~~~~~
|
||||
|
||||
对于字符指针,RapidJSON需要作一个标记,代表它不复制也是安全的。可以使用`StringRef`函数:
|
||||
|
||||
~~~~~~~~~cpp
|
||||
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 {#ModifyArray}
|
||||
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()`的例子:
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
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](#CreateString)),你需要使用copy-string API去创建一个String。为了避免加入中间变量,可以就地使用一个[临时值](#TemporaryValues):
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
// 就地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 {#ModifyObject}
|
||||
Object是键值对的集合。每个键必须为String。要修改Object,方法是增加或移除成员。以下的API用来增加城员:
|
||||
|
||||
* `Value& AddMember(Value&, Value&, Allocator& allocator)`
|
||||
* `Value& AddMember(StringRefType, Value&, Allocator&)`
|
||||
* `template <typename T> Value& AddMember(StringRefType, T value, Allocator&)`
|
||||
|
||||
以下是一个例子。
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value contact(kObject);
|
||||
contact.AddMember("name", "Milo", document.GetAllocator());
|
||||
contact.AddMember("married", true, document.GetAllocator());
|
||||
~~~~~~~~~~
|
||||
|
||||
使用`StringRefType`作为name参数的重载版本与字符串的`SetString`的接口相似。 这些重载是为了避免复制`name`字符串,因为JSON object中经常会使用常数键名。
|
||||
|
||||
如果你需要从非常数字符串或生命周期不足的字符串创建键名(见[创建String](#CreateString)),你需要使用copy-string API。为了避免中间变量,可以就地使用[临时值](#TemporaryValues):
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
// 就地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 {#DeepCopyValue}
|
||||
若我们真的要复制一个DOM树,我们可使用两个APIs作深复制:含allocator的构造函数及`CopyFrom()`。
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
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 {#SwapValues}
|
||||
|
||||
RapidJSON也提供`Swap()`。
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
Value a(123);
|
||||
Value b("Hello");
|
||||
a.Swap(b);
|
||||
assert(a.IsString());
|
||||
assert(b.IsInt());
|
||||
~~~~~~~~~~
|
||||
|
||||
无论两棵DOM树有多复杂,交换是很快的(常数时间)。
|
||||
|
||||
# 下一部分 {#WhatsNext}
|
||||
|
||||
本教程展示了如何询查及修改DOM树。RapidJSON还有一个重要概念:
|
||||
|
||||
1. [流](doc/stream.md) 是读写JSON的通道。流可以是内存字符串、文件流等。用户也可以自定义流。
|
||||
2. [编码](doc/encoding.md)定义在流或内存中使用的字符编码。RapidJSON也在内部提供Unicode转换及校验功能。
|
||||
3. [DOM](doc/dom.md)的基本功能已在本教程里介绍。还有更高级的功能,如原位(*in situ*)解析、其他解析选项及高级用法。
|
||||
4. [SAX](doc/sax.md) 是RapidJSON解析/生成功能的基础。学习使用`Reader`/`Writer`去实现更高性能的应用程序。也可以使用`PrettyWriter`去格式化JSON。
|
||||
5. [性能](doc/performance.md)展示一些我们做的及第三方的性能测试。
|
||||
6. [技术内幕](doc/internals.md)讲述一些RapidJSON内部的设计及技术。
|
||||
|
||||
你也可以参考[常见问题](faq.md)、API文档、例子及单元测试。
|
100
readme.zh-cn.md
Normal file
100
readme.zh-cn.md
Normal file
@ -0,0 +1,100 @@
|
||||

|
||||
|
||||
Copyright (c) 2011-2014 Milo Yip (miloyip@gmail.com)
|
||||
|
||||
[RapidJSON GitHub](https://github.com/miloyip/rapidjson/)
|
||||
|
||||
[RapidJSON 文档](http://miloyip.github.io/rapidjson/)
|
||||
|
||||
## 简介
|
||||
|
||||
RapidJSON是一个C++的JSON解析器及生成器。它的灵感来自[RapidXml](http://rapidxml.sourceforge.net/)。
|
||||
|
||||
* RapidJSON小而全。它同时支持SAX和DOM风格的API。SAX解析器只有约500行代码。
|
||||
|
||||
* RapidJSON快。它的性能可与`strlen()`相比。可支持SSE2/SSE4.1加速。
|
||||
|
||||
* RapidJSON独立。它不依赖于BOOST等外部库。它甚至不依赖于STL。
|
||||
|
||||
* RapidJSON对内存友好。在大部分32/64位机器上,每个JSON值只占16或20字节(除字符串外)。它预设使用一个快速的内存分配器,令分析器可以紧凑地分配内存。
|
||||
|
||||
* RapidJSON对Unicode友好。它支持UTF-8、UTF-16、UTF-32 (大端序/小端序),并内部支持这些编码的检测、校验及转码。例如,RapidJSON可以在分析一个UTF-8文件至DOM时,把当中的JSON字符串转码至UTF-16。它也支持代理对(surrogate pair)及`"\u0000"`(空字符)。
|
||||
|
||||
在[这里](doc/features.md)可读取更多特点。
|
||||
|
||||
JSON(JavaScript Object Notation)是一个轻量的数据交换格式。RapidJSON应该完全遵从RFC7159/ECMA-404。 关于JSON的更多信息可参考:
|
||||
* [Introducing JSON](http://json.org/)
|
||||
* [RFC7159: The JavaScript Object Notation (JSON) Data Interchange Format](http://www.ietf.org/rfc/rfc7159.txt)
|
||||
* [Standard ECMA-404: The JSON Data Interchange Format](http://www.ecma-international.org/publications/standards/Ecma-404.htm)
|
||||
|
||||
## 兼容性
|
||||
|
||||
RapidJSON是跨平台的。以下是一些曾测试的平台/编译器组合:
|
||||
* Visual C++ 2008/2010/2013 在 Windows (32/64-bit)
|
||||
* GNU C++ 3.8.x 在 Cygwin
|
||||
* Clang 3.4 在 Mac OS X (32/64-bit) 及 iOS
|
||||
* Clang 3.4 在 Android NDK
|
||||
|
||||
用户也可以在他们的平台上生成及执行单元测试。
|
||||
|
||||
## 安装
|
||||
|
||||
RapidJSON是只有头文件的C++库。只需把`include/rapidjson`目录复制至系统或项目的include目录中。
|
||||
|
||||
生成测试及例子的步骤:
|
||||
|
||||
1. 执行 `git submodule update --init` 去获取 thirdparty submodules (google test)。
|
||||
2. 下载 [premake4](http://industriousone.com/premake/download)。
|
||||
3. 复制 premake4 可执行文件至 `rapidjson/build` (或系统路径)。
|
||||
4. 进入`rapidjson/build/`目录,在Windows下执行`premake.bat`,在Linux或其他平台下执行`premake.sh`。
|
||||
5. 在Windows上,生成位于`rapidjson/build/vs2008/`或`/vs2010/`内的项目方案.
|
||||
6. 在其他平台上,在`rapidjson/build/gmake/`目录执行GNU `make`(如 `make -f test.make config=release32`、`make -f example.make config=debug32`)。
|
||||
7. 若成功,可执行文件会生成在`rapidjson/bin`目录。
|
||||
|
||||
生成[Doxygen](http://doxygen.org)文档的步骤:
|
||||
|
||||
1. 下载及安装[Doxygen](http://doxygen.org/download.html)。
|
||||
2. 在顶层目录执行`doxygen build/Doxyfile`。
|
||||
3. 在`doc/html`浏览文档。
|
||||
|
||||
## 用法一览
|
||||
|
||||
此简单例子解析一个JSON字符串至一个document (DOM),对DOM作出简单修改,最终把DOM转换(stringify)至JSON字符串。
|
||||
|
||||
~~~~~~~~~~cpp
|
||||
// rapidjson/example/simpledom/simpledom.cpp`
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/writer.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
int main() {
|
||||
// 1. 把JSON解析至DOM。
|
||||
const char* json = "{\"project\":\"rapidjson\",\"stars\":10}";
|
||||
Document d;
|
||||
d.Parse(json);
|
||||
|
||||
// 2. 利用DOM作出修改。
|
||||
Value& s = d["stars"];
|
||||
s.SetInt(s.GetInt() + 1);
|
||||
|
||||
// 3. 把DOM转换(stringify)成JSON。
|
||||
StringBuffer buffer;
|
||||
Writer<StringBuffer> writer(buffer);
|
||||
d.Accept(writer);
|
||||
|
||||
// Output {"project":"rapidjson","stars":11}
|
||||
std::cout << buffer.GetString() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
~~~~~~~~~~
|
||||
|
||||
注意此例子并没有处理潜在错误。
|
||||
|
||||
下图展示执行过程。
|
||||
|
||||

|
||||
|
||||
还有许多[例子](https://github.com/miloyip/rapidjson/tree/master/example)可供参考。
|
Loading…
x
Reference in New Issue
Block a user