跳到主要内容

Schema(模式)

Merak 从 v1.1.0 开始支持 JSON Schema,用于描述 JSON 数据格式。JSON Schema 本身也是 JSON 文档。通过 JSON Schema 验证 JSON 可以让代码安全地访问 DOM,而无需手动检查类型或键是否存在,并确保输出的 JSON 符合指定的模式。

Merak 实现了 JSON Schema Draft v4 验证器。若不熟悉 JSON Schema,可参考 Understanding JSON Schema


基本用法

  1. 先将 JSON Schema 解析为 Document,再编译为 SchemaDocument
  2. 使用 SchemaDocument 创建 SchemaValidatorSchemaValidator 可处理 SAX 事件,因此可以通过 document.Accept(validator) 验证 JSON,并获取验证结果。
#include "merak/json/schema.h"

Document sd;
if (sd.Parse(schemaJson).HasParseError()) {
// Schema 不是合法 JSON
}

SchemaDocument schema(sd); // 编译 Document 为 SchemaDocument
// 之后不再需要 sd

Document d;
if (d.Parse(inputJson).HasParseError()) {
// 输入不是合法 JSON
}

SchemaValidator validator(schema);
if (!d.Accept(validator)) {
// JSON 不符合 Schema
StringBuffer sb;
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
printf("Invalid schema: %s\n", sb.GetString());
printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());
sb.Clear();
validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
printf("Invalid document: %s\n", sb.GetString());
}

注意事项

  • 一个 SchemaDocument 可被多个 SchemaValidator 引用,不会被修改。
  • SchemaValidator 可复用验证多个 JSON 文档,调用 validator.Reset() 重置状态。

解析/序列化时验证

Merak 提供基于 SAX 的验证器,可在解析 JSON 流时即时验证,一旦发现不符合 Schema 的值,解析会立即终止。适合大 JSON 文件。

DOM 解析

DOM 解析需要 Document 做额外的准备和清理工作,因此需要 SchemaValidatingReader 连接 ReaderSchemaValidatorDocument

SchemaDocument schema(sd);

FILE* fp = fopen("big.json", "r");
FileReadStream is(fp, buffer, sizeof(buffer));

Document d;
SchemaValidatingReader<kParseDefaultFlags, FileReadStream, UTF8<> > reader(is, schema);
d.Populate(reader);

if (!reader.GetParseResult()) {
if (!reader.IsValid()) {
StringBuffer sb;
reader.GetInvalidSchemaPointer().StringifyUriFragment(sb);
printf("Invalid schema: %s\n", sb.GetString());
printf("Invalid keyword: %s\n", reader.GetInvalidSchemaKeyword());
sb.Clear();
reader.GetInvalidDocumentPointer().StringifyUriFragment(sb);
printf("Invalid document: %s\n", sb.GetString());
}
}

SAX 解析

如果只需验证 JSON,可直接使用 SAX:

SchemaValidator validator(schema);
Reader reader;
if (!reader.Parse(stream, validator)) {
if (!validator.IsValid()) {
// JSON 不符合 Schema
}
}
  • SAX 验证的内存使用仅与 Schema 复杂度相关,无论 JSON 多大,内存保持低。

可进一步处理 SAX 事件,使用 GenericSchemaValidator 指定输出 Handler:

MyHandler handler;
GenericSchemaValidator<SchemaDocument, MyHandler> validator(schema, handler);
Reader reader;
if (!reader.Parse(ss, validator)) {
if (!validator.IsValid()) {
// ...
}
}

序列化验证

可在序列化时验证 JSON:

StringBuffer sb;
Writer<StringBuffer> writer(sb);
GenericSchemaValidator<SchemaDocument, Writer<StringBuffer> > validator(s, writer);
if (!d.Accept(validator)) {
if (!validator.IsValid()) {
// 验证失败或编码问题
}
}

SAX 风格生成同样可通过将事件重定向到 SchemaValidator 实现验证。


远程 Schema

JSON Schema 支持 $ref 指向本地或远程 Schema:

{ "$ref": "definitions.json#/address" }
  • 本地指针以 # 开头。
  • 远程指针为 URI,需要用户实现 IRemoteSchemaDocumentProvider
class MyRemoteSchemaDocumentProvider : public IRemoteSchemaDocumentProvider {
public:
const SchemaDocument* GetRemoteDocument(const char* uri, SizeType length) {
// 解析 uri 并返回 SchemaDocument
}
};

MyRemoteSchemaDocumentProvider provider;
SchemaDocument schema(sd, &provider);

标准符合性

  • Merak 在 JSON Schema Test Suite 中通过 262/263 测试(Draft v4)。
  • 唯一失败测试:refRemote.jsonid 和 URI 解析未实现。
  • format 关键字被忽略(标准不强制实现)。

正则表达式

patternpatternProperties 使用正则匹配。Merak 默认实现简单 NFA:

语法含义
ab连接
`ab`
a?0 或 1 次
a*0 或多次
a+1 或多次
a{3}恰好 3 次
a{3,}至少 3 次
a{3,5}3~5 次
(ab)分组
^a字符串开始
a$字符串结束
.任意字符
[abc]字符类
[a-c]字符范围
[^abc]非字符类
[\b]退格 U+0008
\n \r \t控制字符

C++11 可用 std::regex 替代 NFA,通过定义:

#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0
#define RAPIDJSON_SCHEMA_USE_STDREGEX 1

未使用 pattern 时可禁用以减少代码大小。


性能

  • 大多数 C++ JSON 库不支持 JSON Schema。
  • Merak 在 MacBook Pro 测试结果:
验证器相对速度每秒测试数
Merak155%30682
ajv100%19770
is-my-json-valid70%13835
jsen57.7%11411
schemasaurus26%5145
themis19.9%3935
z-schema7%1388
jsck3.1%606
jsonschema0.9%185
skeemas0.8%154
tv40.5%93
jayschema0.1%21
  • Merak 比最快的 JavaScript 库(ajv)快约 1.5 倍,比最慢的快 1400 倍。