]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | # Schema |
2 | ||
3 | (本功能于 v1.1.0 发布) | |
4 | ||
5 | JSON Schema 是描述 JSON 格式的一个标准草案。一个 schema 本身也是一个 JSON。使用 JSON Schema 去校验 JSON,可以让你的代码安全地访问 DOM,而无须检查类型或键值是否存在等。这也能确保输出的 JSON 是符合指定的 schema。 | |
6 | ||
7 | RapidJSON 实现了一个 [JSON Schema Draft v4](http://json-schema.org/documentation.html) 的校验器。若你不熟悉 JSON Schema,可以参考 [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/)。 | |
8 | ||
9 | [TOC] | |
10 | ||
11 | ## 基本用法 | |
12 | ||
13 | 首先,你要把 JSON Schema 解析成 `Document`,再把它编译成一个 `SchemaDocument`。 | |
14 | ||
15 | 然后,利用该 `SchemaDocument` 创建一个 `SchemaValidator`。它与 `Writer` 相似,都是能够处理 SAX 事件的。因此,你可以用 `document.Accept(validator)` 去校验一个 JSON,然后再获取校验结果。 | |
16 | ||
17 | ~~~cpp | |
18 | #include "rapidjson/schema.h" | |
19 | ||
20 | // ... | |
21 | ||
22 | Document sd; | |
23 | if (!sd.Parse(schemaJson).HasParseError()) { | |
24 | // 此 schema 不是合法的 JSON | |
25 | // ... | |
26 | } | |
27 | SchemaDocument schema(sd); // 把一个 Document 编译至 SchemaDocument | |
28 | // 之后不再需要 sd | |
29 | ||
30 | Document d; | |
31 | if (!d.Parse(inputJson).HasParseError()) { | |
32 | // 输入不是一个合法的 JSON | |
33 | // ... | |
34 | } | |
35 | ||
36 | SchemaValidator validator(schema); | |
37 | if (!d.Accept(validator)) { | |
38 | // 输入的 JSON 不合乎 schema | |
39 | // 打印诊断信息 | |
40 | StringBuffer sb; | |
41 | validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); | |
42 | printf("Invalid schema: %s\n", sb.GetString()); | |
43 | printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); | |
44 | sb.Clear(); | |
45 | validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); | |
46 | printf("Invalid document: %s\n", sb.GetString()); | |
47 | } | |
48 | ~~~ | |
49 | ||
50 | 一些注意点: | |
51 | ||
52 | * 一个 `SchemaDocment` 能被多个 `SchemaValidator` 引用。它不会被 `SchemaValidator` 修改。 | |
53 | * 可以重复使用一个 `SchemaValidator` 来校验多个文件。在校验其他文件前,须先调用 `validator.Reset()`。 | |
54 | ||
55 | ## 在解析/生成时进行校验 | |
56 | ||
57 | 与大部分 JSON Schema 校验器有所不同,RapidJSON 提供了一个基于 SAX 的 schema 校验器实现。因此,你可以在输入流解析 JSON 的同时进行校验。若校验器遇到一个与 schema 不符的值,就会立即终止解析。这设计对于解析大型 JSON 文件时特别有用。 | |
58 | ||
59 | ### DOM 解析 | |
60 | ||
61 | 在使用 DOM 进行解析时,`Document` 除了接收 SAX 事件外,还需做一些准备及结束工作,因此,为了连接 `Reader`、`SchemaValidator` 和 `Document` 要做多一点事情。`SchemaValidatingReader` 是一个辅助类去做那些工作。 | |
62 | ||
63 | ~~~cpp | |
64 | #include "rapidjson/filereadstream.h" | |
65 | ||
66 | // ... | |
67 | SchemaDocument schema(sd); // 把一个 Document 编译至 SchemaDocument | |
68 | ||
69 | // 使用 reader 解析 JSON | |
70 | FILE* fp = fopen("big.json", "r"); | |
71 | FileReadStream is(fp, buffer, sizeof(buffer)); | |
72 | ||
73 | // 用 reader 解析 JSON,校验它的 SAX 事件,并存储至 d | |
74 | Document d; | |
75 | SchemaValidatingReader<kParseDefaultFlags, FileReadStream, UTF8<> > reader(is, schema); | |
76 | d.Populate(reader); | |
77 | ||
78 | if (!reader.GetParseResult()) { | |
79 | // 不是一个合法的 JSON | |
80 | // 当 reader.GetParseResult().Code() == kParseErrorTermination, | |
81 | // 它可能是被以下原因中止: | |
82 | // (1) 校验器发现 JSON 不合乎 schema;或 | |
83 | // (2) 输入流有 I/O 错误。 | |
84 | ||
85 | // 检查校验结果 | |
86 | if (!reader.IsValid()) { | |
87 | // 输入的 JSON 不合乎 schema | |
88 | // 打印诊断信息 | |
89 | StringBuffer sb; | |
90 | reader.GetInvalidSchemaPointer().StringifyUriFragment(sb); | |
91 | printf("Invalid schema: %s\n", sb.GetString()); | |
92 | printf("Invalid keyword: %s\n", reader.GetInvalidSchemaKeyword()); | |
93 | sb.Clear(); | |
94 | reader.GetInvalidDocumentPointer().StringifyUriFragment(sb); | |
95 | printf("Invalid document: %s\n", sb.GetString()); | |
96 | } | |
97 | } | |
98 | ~~~ | |
99 | ||
100 | ### SAX 解析 | |
101 | ||
102 | 使用 SAX 解析时,情况就简单得多。若只需要校验 JSON 而无需进一步处理,那么仅需要: | |
103 | ||
104 | ~~~ | |
105 | SchemaValidator validator(schema); | |
106 | Reader reader; | |
107 | if (!reader.Parse(stream, validator)) { | |
108 | if (!validator.IsValid()) { | |
109 | // ... | |
110 | } | |
111 | } | |
112 | ~~~ | |
113 | ||
114 | 这种方式和 [schemavalidator](example/schemavalidator/schemavalidator.cpp) 例子完全相同。这带来的独特优势是,无论 JSON 多巨大,永远维持低内存用量(内存用量只与 Schema 的复杂度相关)。 | |
115 | ||
116 | 若你需要进一步处理 SAX 事件,便可使用模板类 `GenericSchemaValidator` 去设置校验器的输出 `Handler`: | |
117 | ||
118 | ~~~ | |
119 | MyHandler handler; | |
120 | GenericSchemaValidator<SchemaDocument, MyHandler> validator(schema, handler); | |
121 | Reader reader; | |
122 | if (!reader.Parse(ss, validator)) { | |
123 | if (!validator.IsValid()) { | |
124 | // ... | |
125 | } | |
126 | } | |
127 | ~~~ | |
128 | ||
129 | ### 生成 | |
130 | ||
131 | 我们也可以在生成(serialization)的时候进行校验。这能确保输出的 JSON 符合一个 JSON Schema。 | |
132 | ||
133 | ~~~ | |
134 | StringBuffer sb; | |
135 | Writer<StringBuffer> writer(sb); | |
136 | GenericSchemaValidator<SchemaDocument, Writer<StringBuffer> > validator(s, writer); | |
137 | if (!d.Accept(validator)) { | |
138 | // Some problem during Accept(), it may be validation or encoding issues. | |
139 | if (!validator.IsValid()) { | |
140 | // ... | |
141 | } | |
142 | } | |
143 | ~~~ | |
144 | ||
145 | 当然,如果你的应用仅需要 SAX 风格的生成,那么只需要把 SAX 事件由原来发送到 `Writer`,改为发送到 `SchemaValidator`。 | |
146 | ||
147 | ## 远程 Schema | |
148 | ||
149 | JSON Schema 支持 [`$ref` 关键字](http://spacetelescope.github.io/understanding-json-schema/structuring.html),它是一个 [JSON pointer](doc/pointer.zh-cn.md) 引用至一个本地(local)或远程(remote) schema。本地指针的首字符是 `#`,而远程指针是一个相对或绝对 URI。例如: | |
150 | ||
151 | ~~~js | |
152 | { "$ref": "definitions.json#/address" } | |
153 | ~~~ | |
154 | ||
155 | 由于 `SchemaDocument` 并不知道如何处理那些 URI,它需要使用者提供一个 `IRemoteSchemaDocumentProvider` 的实例去处理。 | |
156 | ||
157 | ~~~ | |
158 | class MyRemoteSchemaDocumentProvider : public IRemoteSchemaDocumentProvider { | |
159 | public: | |
160 | virtual const SchemaDocument* GetRemoteDocument(const char* uri, SizeTyp length) { | |
161 | // Resolve the uri and returns a pointer to that schema. | |
162 | } | |
163 | }; | |
164 | ||
165 | // ... | |
166 | ||
167 | MyRemoteSchemaDocumentProvider provider; | |
168 | SchemaDocument schema(sd, &provider); | |
169 | ~~~ | |
170 | ||
171 | ## 标准的符合程度 | |
172 | ||
173 | RapidJSON 通过了 [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) (Json Schema draft 4) 中 263 个测试的 262 个。 | |
174 | ||
175 | 没通过的测试是 `refRemote.json` 中的 "change resolution scope" - "changed scope ref invalid"。这是由于未实现 `id` schema 关键字及 URI 合并功能。 | |
176 | ||
177 | 除此以外,关于字符串类型的 `format` schema 关键字也会被忽略,因为标准中并没需求必须实现。 | |
178 | ||
179 | ### 正则表达式 | |
180 | ||
181 | `pattern` 及 `patternProperties` 这两个 schema 关键字使用了正则表达式去匹配所需的模式。 | |
182 | ||
183 | RapidJSON 实现了一个简单的 NFA 正则表达式引擎,并预设使用。它支持以下语法。 | |
184 | ||
185 | |语法|描述| | |
186 | |------|-----------| | |
187 | |`ab` | 串联 | | |
188 | |`a|b` | 交替 | | |
189 | |`a?` | 零或一次 | | |
190 | |`a*` | 零或多次 | | |
191 | |`a+` | 一或多次 | | |
192 | |`a{3}` | 刚好 3 次 | | |
193 | |`a{3,}` | 至少 3 次 | | |
194 | |`a{3,5}`| 3 至 5 次 | | |
195 | |`(ab)` | 分组 | | |
196 | |`^a` | 在开始处 | | |
197 | |`a$` | 在结束处 | | |
198 | |`.` | 任何字符 | | |
199 | |`[abc]` | 字符组 | | |
200 | |`[a-c]` | 字符组范围 | | |
201 | |`[a-z0-9_]` | 字符组组合 | | |
202 | |`[^abc]` | 字符组取反 | | |
203 | |`[^a-c]` | 字符组范围取反 | | |
204 | |`[\b]` | 退格符 (U+0008) | | |
205 | |`\|`, `\\`, ... | 转义字符 | | |
206 | |`\f` | 馈页 (U+000C) | | |
207 | |`\n` | 馈行 (U+000A) | | |
208 | |`\r` | 回车 (U+000D) | | |
209 | |`\t` | 制表 (U+0009) | | |
210 | |`\v` | 垂直制表 (U+000B) | | |
211 | ||
212 | 对于使用 C++11 编译器的使用者,也可使用 `std::regex`,只需定义 `RAPIDJSON_SCHEMA_USE_INTERNALREGEX=0` 及 `RAPIDJSON_SCHEMA_USE_STDREGEX=1`。若你的 schema 无需使用 `pattern` 或 `patternProperties`,可以把两个宏都设为零,以禁用此功能,这样做可节省一些代码体积。 | |
213 | ||
214 | ## 性能 | |
215 | ||
216 | 大部分 C++ JSON 库都未支持 JSON Schema。因此我们尝试按照 [json-schema-benchmark](https://github.com/ebdrup/json-schema-benchmark) 去评估 RapidJSON 的 JSON Schema 校验器。该评测测试了 11 个运行在 node.js 上的 JavaScript 库。 | |
217 | ||
218 | 该评测校验 [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) 中的测试,当中排除了一些测试套件及个别测试。我们在 [`schematest.cpp`](test/perftest/schematest.cpp) 实现了相同的评测。 | |
219 | ||
220 | 在 MacBook Pro (2.8 GHz Intel Core i7) 上收集到以下结果。 | |
221 | ||
222 | |校验器|相对速度|每秒执行的测试数目| | |
223 | |---------|:------------:|:----------------------------:| | |
224 | |RapidJSON|155%|30682| | |
225 | |[`ajv`](https://github.com/epoberezkin/ajv)|100%|19770 (± 1.31%)| | |
226 | |[`is-my-json-valid`](https://github.com/mafintosh/is-my-json-valid)|70%|13835 (± 2.84%)| | |
227 | |[`jsen`](https://github.com/bugventure/jsen)|57.7%|11411 (± 1.27%)| | |
228 | |[`schemasaurus`](https://github.com/AlexeyGrishin/schemasaurus)|26%|5145 (± 1.62%)| | |
229 | |[`themis`](https://github.com/playlyfe/themis)|19.9%|3935 (± 2.69%)| | |
230 | |[`z-schema`](https://github.com/zaggino/z-schema)|7%|1388 (± 0.84%)| | |
231 | |[`jsck`](https://github.com/pandastrike/jsck#readme)|3.1%|606 (± 2.84%)| | |
232 | |[`jsonschema`](https://github.com/tdegrunt/jsonschema#readme)|0.9%|185 (± 1.01%)| | |
233 | |[`skeemas`](https://github.com/Prestaul/skeemas#readme)|0.8%|154 (± 0.79%)| | |
234 | |tv4|0.5%|93 (± 0.94%)| | |
235 | |[`jayschema`](https://github.com/natesilva/jayschema)|0.1%|21 (± 1.14%)| | |
236 | ||
237 | 换言之,RapidJSON 比最快的 JavaScript 库(ajv)快约 1.5x。比最慢的快 1400x。 |