SAX
“SAX” 一词源自 Simple API for XML。我们借用此术语应用于 JSON 的解析与生成。
在 Merak 中,Reader(GenericReader<...> 的 typedef)是 JSON 的 SAX 风格解析器,而 Writer(GenericWriter<...> 的 typedef)是 JSON 的 SAX 风格生成器。
Reader
Reader 用于从输入流解析 JSON。在读取流中的字符时,它根据 JSON 语法分析这些字符,并将事件发送给 handler。
例如,考虑如下 JSON:
{
"hello": "world",
"t": true ,
"f": false,
"n": null,
"i": 123,
"pi": 3.1416,
"a": [1, 2, 3, 4]
}
当 Reader 解析此 JSON 时,会依次发送以下事件到 handler:
StartObject()
Key("hello", 5, true)
String("world", 5, true)
Key("t", 1, true)
Bool(true)
Key("f", 1, true)
Bool(false)
Key("n", 1, true)
Null()
Key("i")
Uint(123)
Key("pi")
Double(3.1416)
Key("a")
StartArray()
Uint(1)
Uint(2)
Uint(3)
Uint(4)
EndArray(4)
EndObject(7)
这些事件可以很容易映射到 JSON 结构,除了一些事件参数需要额外说明。可以参考 simplereader 示例,它的输出与上述完全一致:
#include "merak/json/reader.h"
#include <iostream>
using namespace merak::json;
using namespace std;
struct MyHandler : public BaseReaderHandler<UTF8<>, MyHandler> {
bool Null() { cout << "Null()" << endl; return true; }
bool Bool(bool b) { cout << "Bool(" << boolalpha << b << ")" << endl; return true; }
bool Int(int i) { cout << "Int(" << i << ")" << endl; return true; }
bool Uint(unsigned u) { cout << "Uint(" << u << ")" << endl; return true; }
bool Int64(int64_t i) { cout << "Int64(" << i << ")" << endl; return true; }
bool Uint64(uint64_t u) { cout << "Uint64(" << u << ")" << endl; return true; }
bool Double(double d) { cout << "Double(" << d << ")" << endl; return true; }
bool String(const char* str, SizeType length, bool copy) {
cout << "String(" << str << ", " << length << ", " << boolalpha << copy << ")" << endl;
return true;
}
bool StartObject() { cout << "StartObject()" << endl; return true; }
bool Key(const char* str, SizeType length, bool copy) {
cout << "Key(" << str << ", " << length << ", " << boolalpha << copy << ")" << endl;
return true;
}
bool EndObject(SizeType memberCount) { cout << "EndObject(" << memberCount << ")" << endl; return true; }
bool StartArray() { cout << "StartArray()" << endl; return true; }
bool EndArray(SizeType elementCount) { cout << "EndArray(" << elementCount << ")" << endl; return true; }
};
void main() {
const char json[] = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } ";
MyHandler handler;
Reader reader;
StringStream ss(json);
reader.Parse(ss, handler);
}
注意,Merak 使用模板静态绑定 Reader 类型和 handler 类型,而不是使用虚函数类。此方式可以通过内联函数提高性能。
Handler
如上例所示,用户需要实现 handler 来处理 Reader 的事件(函数调用)。handler 必须包含以下成员函数:
class Handler {
bool Null();
bool Bool(bool b);
bool Int(int i);
bool Uint(unsigned i);
bool Int64(int64_t i);
bool Uint64(uint64_t i);
bool Double(double d);
bool RawNumber(const Ch* str, SizeType length, bool copy);
bool String(const Ch* str, SizeType length, bool copy);
bool StartObject();
bool Key(const Ch* str, SizeType length, bool copy);
bool EndObject(SizeType memberCount);
bool StartArray();
bool EndArray(SizeType elementCount);
};
-
Null():当Reader遇到 JSON null 值时调用。 -
Bool(bool):当Reader遇到 JSON true 或 false 值时调用。 -
遇到 JSON 数字时,
Reader会选择合适的 C++ 类型并调用 唯一一个Int(int)、Uint(unsigned)、Int64(int64_t)、Uint64(uint64_t)或Double(double)。如果启用kParseNumbersAsStrings选项,则调用RawNumber()。 -
遇到 JSON 字符串时,调用
String(const char* str, SizeType length, bool copy): -
第一个参数指向字符串。
-
第二个参数为字符串长度(不含结尾
\0)。Merak 支持字符串中包含 null 字符\0,此时strlen(str) < length。 -
copy表示 handler 是否需要复制字符串。正常解析时copy = true;仅在原地解析(in situ)时copy = false。字符类型与目标编码相关,稍后说明。 -
遇到 JSON 对象开始时调用
StartObject()。对象是键值对集合(成员)。如果对象包含成员,先调用Key()获取成员名,再根据值类型调用对应函数,直到调用EndObject(SizeType memberCount)。memberCount仅供参考,用户可忽略。 -
JSON 数组类似对象,但更简单。开始时调用
StartArray(),每个元素调用对应函数,结束时调用EndArray(SizeType elementCount),elementCount仅供参考。
每个 handler 函数返回 bool,通常返回 true。如果 handler 遇到错误,可返回 false 通知事件发送者停止处理。
例如,当使用 Reader 解析 JSON 时,若 handler 检测到 JSON 不符合要求,可以返回 false,使 Reader 停止解析,并进入错误状态,错误码为 kParseErrorTermination。
GenericReader
如前所述,Reader 是 GenericReader 模板类的 typedef:
namespace merak::json {
template <typename SourceEncoding, typename TargetEncoding, typename Allocator = MemoryPoolAllocator<> >
class GenericReader {
// ...
};
typedef GenericReader<UTF8<>, UTF8<> > Reader;
} // namespace merak::json
Reader 默认使用 UTF-8 作为源和目标编码。源编码指 JSON 流的编码;目标编码用于 String() 参数输出。
例如,要解析 UTF-8 流并输出 UTF-16 字符串事件:
GenericReader<UTF8<>, UTF16<> > reader;
注意 UTF16 默认类型为 wchar_t,此 reader 会调用 handler 的 String(const wchar_t*, SizeType, bool)。
第三个模板参数 Allocator 是内部数据结构的分配器类型(实际上是栈)。
Parsing
Reader 唯一功能是解析 JSON:
template <unsigned parseFlags, typename InputStream, typename Handler>
bool Parse(InputStream& is, Handler& handler);
template <typename InputStream, typename Handler>
bool Parse(InputStream& is, Handler& handler); // 使用 parseFlags = kDefaultParseFlags
解析出错时返回 false。用户可调用 HasParseError()、GetParseErrorCode() 和 GetErrorOffset() 获取错误状态。实际上,Document 使用这些 Reader 函数获取解析错误。详见 DOM。
Writer
Reader 是将 JSON 解析成事件,而 Writer 则正好相反,它将事件生成 JSON。
Writer 使用非常简单。如果应用程序只需要把一些数据转换成 JSON,直接使用 Writer 往往比先构建 Document 再用 Writer 转换更方便。
在 simplewriter 示例中,我们做了 simplereader 的逆操作:
#include "merak/json/writer.h"
#include "merak/json/stringbuffer.h"
#include <iostream>
using namespace merak::json;
using namespace std;
void main() {
StringBuffer s;
Writer<StringBuffer> writer(s);
writer.StartObject();
writer.Key("hello");
writer.String("world");
writer.Key("t");
writer.Bool(true);
writer.Key("f");
writer.Bool(false);
writer.Key("n");
writer.Null();
writer.Key("i");
writer.Uint(123);
writer.Key("pi");
writer.Double(3.1416);
writer.Key("a");
writer.StartArray();
for (unsigned i = 0; i < 4; i++)
writer.Uint(i);
writer.EndArray();
writer.EndObject();
cout << s.GetString() << endl;
}
输出:
{"hello":"world","t":true,"f":false,"n":null,"i":123,"pi":3.1416,"a":[0,1,2,3]}
String()和Key()各有两个重载版本。一个有 3 个参数,遵循 handler 概念,可处理包含 null 字符的字符串;另一个是更简单的版本,如上例所示。- 示例中
EndArray()和EndObject()没有参数。虽然可以传SizeType参数,但Writer会忽略它。
为什么不用 sprintf() 或 std::stringstream?
原因有几条:
Writer保证输出合法 JSON,如果事件顺序错误(如Int()紧跟StartObject()),调试模式下会断言失败。Writer::String()可处理字符串转义(如将U+000A转换为\n)并支持 Unicode 转码。Writer一致地处理数字输出。Writer实现了事件 handler 概念,可处理Reader、Document或其他事件生成器的事件。Writer针对不同平台进行了优化。
总之,使用 Writer API 生成 JSON 比 ad-hoc 方法更可靠、更简单。
Template
Writer 与 Reader 有一些设计差异。它是模板类,而非 typedef,没有 GenericWriter。声明如下:
namespace merak::json {
template<typename OutputStream, typename SourceEncoding = UTF8<>, typename TargetEncoding = UTF8<>, typename Allocator = CrtAllocator<> >
class Writer {
public:
Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth)
// ...
};
} // namespace merak::json
OutputStream模板参数:输出流类型,必须显式指定。SourceEncoding:String(const Ch*, …)输入字符串编码。TargetEncoding:输出流编码。Allocator:内部数据结构(栈)分配器类型。
writeFlags 是以下标志的组合:
| 写入标志 | 含义 |
|---|---|
kWriteNoFlags | 无标志 |
kWriteDefaultFlags | 默认选项,等价于宏 RAPIDJSON_WRITE_DEFAULT_FLAGS(定义为 kWriteNoFlags) |
kWriteValidateEncodingFlag | 验证 JSON 字符串编码 |
kWriteNanAndInfFlag | 允许写入 Infinity、-Infinity 和 NaN |
构造函数的 levelDepth 参数影响初始内存分配,用于存储层级信息。
PrettyWriter
Writer 输出的 JSON 是最紧凑的格式,不带空格或换行,适合网络传输或存储,但不利于人工阅读。
因此,Merak 提供了 PrettyWriter,可在输出中添加缩进和换行。
使用方法与 Writer 几乎相同,PrettyWriter 提供了 SetIndent(Ch indentChar, unsigned indentCharCount) 函数,默认缩进为 4 个空格。
Completeness and Reset
Writer每次只能输出一个 JSON,根节点可为任意类型。当处理完单个根事件(如String())或匹配的最终EndObject()/EndArray()时,输出 JSON 为完整格式。可通过Writer::IsComplete()检测。- JSON 完成后,
Writer不能再接受新事件,否则输出将无效(如出现多个根节点)。若要复用Writer对象,可调用Writer::Reset(OutputStream& os)重置内部状态并设置新输出流。
Techniques
Parsing JSON to Custom Data Structures
Document 的解析完全依赖 Reader。实际上,Document 是一个 handler,接收事件并在解析 JSON 时构建 DOM。
用户可直接使用 Reader 构建其他数据结构,省去构建 DOM 的步骤,减少内存开销并提高性能。
在 messagereader 示例中,ParseMessages() 解析 JSON,应为对象形式的键值对:
#include "merak/json/reader.h"
#include "merak/json/error/en.h"
#include <iostream>
#include <string>
#include <map>
using namespace std;
using namespace merak::json;
typedef map<string, string> MessageMap;
struct MessageHandler : public BaseReaderHandler<UTF8<>, MessageHandler> {
MessageHandler() : state_(kExpectObjectStart) {}
bool StartObject() {
switch (state_) {
case kExpectObjectStart:
state_ = kExpectNameOrObjectEnd;
return true;
default:
return false;
}
}
bool String(const char* str, SizeType length, bool) {
switch (state_) {
case kExpectNameOrObjectEnd:
name_ = string(str, length);
state_ = kExpectValue;
return true;
case kExpectValue:
messages_.insert(MessageMap::value_type(name_, string(str, length)));
state_ = kExpectNameOrObjectEnd;
return true;
default:
return false;
}
}
bool EndObject(SizeType) { return state_ == kExpectNameOrObjectEnd; }
bool Default() { return false; } // 其他事件无效
MessageMap messages_;
enum State { kExpectObjectStart, kExpectNameOrObjectEnd, kExpectValue } state_;
std::string name_;
};
void ParseMessages(const char* json, MessageMap& messages) {
Reader reader;
MessageHandler handler;
StringStream ss(json);
if (reader.Parse(ss, handler))
messages.swap(handler.messages_);
else {
ParseErrorCode e = reader.GetParseErrorCode();
size_t o = reader.GetErrorOffset();
cout << "Error: " << GetParseError_En(e) << endl;
cout << " at offset " << o << " near '" << string(json).substr(o, 10) << "...'" << endl;
}
}
示例输出:
{ "greeting" : "Hello!", "farewell" : "bye-bye!" }
farewell: bye-bye!
greeting: Hello!
Parse a JSON with invalid schema.
{ "greeting" : "Hello!", "farewell" : "bye-bye!", "foo" : {} }
Error: Terminate parsing due to Handler error.
at offset 59 near '} }...'
json1成功解析为MessageMap,由于std::map的特性,打印顺序按 key 排序,与 JSON 顺序不同。json2中foo的值是空对象,StartObject()会在state_ = kExpectValue下返回false,导致解析中断,错误码为kParseErrorTermination。
Filtering JSON
Writer 可处理 Reader 发出的事件。例如,example/condense/condense.cpp 将 Writer 设置为 Reader 的 handler,移除 JSON 中的所有空白字符。example/pretty/pretty.cpp 将其替换为 PrettyWriter,可为 JSON 添加缩进与换行。
SAX 风格 API 可加入中间层以过滤 JSON 内容。例如 capitalize 示例可将 JSON 中所有字符串转换为大写:
#include "merak/json/reader.h"
#include "merak/json/writer.h"
#include "merak/json/filereadstream.h"
#include "merak/json/filewritestream.h"
#include "merak/json/error/en.h"
#include <vector>
#include <cctype>
using namespace merak::json;
template<typename OutputHandler>
struct CapitalizeFilter {
CapitalizeFilter(OutputHandler& out) : out_(out) {}
bool Null() { return out_.Null(); }
bool Bool(bool b) { return out_.Bool(b); }
bool Int(int i) { return out_.Int(i); }
bool Uint(unsigned u) { return out_.Uint(u); }
bool Int64(int64_t i) { return out_.Int64(i); }
bool Uint64(uint64_t u) { return out_.Uint64(u); }
bool Double(double d) { return out_.Double(d); }
bool RawNumber(const char* str, SizeType length, bool copy) { return out_.RawNumber(str, length, copy); }
bool String(const char* str, SizeType length, bool) {
buffer_.clear();
for (SizeType i = 0; i < length; i++)
buffer_.push_back(std::toupper(str[i]));
return out_.String(&buffer_.front(), length, true);
}
bool StartObject() { return out_.StartObject(); }
bool Key(const char* str, SizeType length, bool copy) { return String(str, length, copy); }
bool EndObject(SizeType memberCount) { return out_.EndObject(memberCount); }
bool StartArray() { return out_.StartArray(); }
bool EndArray(SizeType elementCount) { return out_.EndArray(elementCount); }
OutputHandler& out_;
std::vector<char> buffer_;
};
int main(int, char*[]) {
Reader reader;
char readBuffer[65536];
FileReadStream is(stdin, readBuffer, sizeof(readBuffer));
char writeBuffer[65536];
FileWriteStream os(stdout, writeBuffer, sizeof(writeBuffer));
Writer<FileWriteStream> writer(os);
CapitalizeFilter<Writer<FileWriteStream> > filter(writer);
if (!reader.Parse(is, filter)) {
fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode()));
return 1;
}
return 0;
}
注意,不能简单地将 JSON 当作字符串全部转换为大写。比如:
["Hello\nWorld"]
直接转换为大写会破坏转义字符:
["HELLO\NWORLD"]
而 capitalize 生成的结果正确:
["HELLO\nWORLD"]
更复杂的过滤器也可实现,但 SAX API 每次仅提供单个事件信息,用户需要自行记录上下文信息(如根节点路径、相关值)。对于某些场景,使用 DOM 实现比 SAX 更简单。