JSON Schema 验证器
弃用通知
该库目前处于维护模式,已被 erosb/json-sKema 取代。
这个仓库不会有任何新功能。它为 JSON Schema 规范的 draft-04、draft-06 和 draft-07 版本提供了稳固的支持。
最新的 draft 2020-12 版本仅由 erosb/json-sKema 支持。
[![Apache 2.0 许可证][ASL 2.0 badge]][ASL 2.0] [![构建状态][Travis badge master]][Travis] [![覆盖率状态][Coveralls.io badge master]][Coveralls.io]
- 何时使用这个库?
- Maven 安装
- 快速入门
- Draft 4、Draft 6 还是 Draft 7?
- 调查失败原因
- ValidationListeners - 跟踪验证过程
- 提前失败模式
- 宽松模式
- 默认值
- 正则表达式实现
- readOnly 和 writeOnly 上下文
- 格式验证器
- $ref 解析
- 排除依赖
- Javadoc
这个项目是 JSON Schema [Draft v4][draft-zyp-json-schema-04]、Draft v6 和 Draft v7 规范的实现。 它使用 org.json API(由 Douglas Crockford 创建)来表示 JSON 数据。
何时使用这个库?
假设你已经知道什么是 JSON Schema,并且想在 Java 应用程序中使用它来验证 JSON 数据。 但是 - 你可能已经发现 - 还有[另一个 Java 实现][java-json-tools/json-schema-validator]的 JSON Schema 规范。以下是一些关于选择哪一个的建议:
- 如果你使用 Jackson 在 Java 代码中处理 JSON,那么 [java-json-tools/json-schema-validator] 显然是更好的选择,因为它使用 Jackson
- 如果你想使用 org.json API,那么这个库是更好的选择
- 如果你需要 JSON Schema Draft 6 / 7 支持,那么你需要这个库
- 如果你想使用其他任何东西来处理 JSON(比如 GSON 或 javax.json),那么你会遇到一些麻烦,因为 目前没有基于这些库的 schema 验证库。这意味着你将不得不解析 JSON 两次:一次用于 schema 验证器,一次用于你自己的处理。在这种情况下,这个库可能仍然 是更好的选择,因为它似乎比基于 Jackson 的 [java-json-tools][java-json-tools/json-schema-validator] 库快两倍。
Maven 安装
在你的 pom.xml
中添加以下依赖:
<dependency>
<groupId>com.github.erosb</groupId>
<artifactId>everit-json-schema</artifactId>
<version>1.14.4</version>
</dependency>
关于旧版本的注意事项:版本 1.6.0
到 1.9.1
之间只能在 JitPack 上找到,坐标为 com.github.everit-org.json-schema:org.everit.json.schema
。版本 1.0.0
... 1.5.1
在 Maven Central 上可用,坐标为 org.everit.json:org.everit.json.schema
。
Java6/7 版本
有几次尝试使这个库在 Java 6/7 上工作。
@mindbender1 开发了 1.9.2 版本的 java6 移植版,可以通过 Maven Central 使用以下坐标访问:
<dependency>
<groupId>com.github.erosb</groupId>
<artifactId>everit-json-schema-jdk6</artifactId>
<version>1.9.2</version>
</dependency>
旧版本的向后移植:
- 1.4.1 版本由 Doctusoft 向后移植,坐标为
com.doctusoft:json-schema-java7:1.4.1
- 1.1.1 版本由 @rdruilhe 向后移植,可在 JitPack 上获得,坐标为
com.github.rdruilhe.json-schema:org.everit.json.schema:1.1.1
快速入门
import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.json.JSONTokener;
// ...
try (InputStream inputStream = getClass().getResourceAsStream("/path/to/your/schema.json")) {
JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream));
Schema schema = SchemaLoader.load(rawSchema);
schema.validate(new JSONObject("{\"hello\" : \"world\"}")); // 如果这个对象无效,则抛出 ValidationException
}
Draft 4、Draft 6 还是 Draft 7?
JSON Schema 目前有 4 个主要版本,Draft 3、Draft 4、Draft 6 和 Draft 7。这个库实现了其中的 3 个较新版本,你可以在这里和这里快速了解它们之间的差异。 由于两个版本有许多差异 - 而且 draft 6 不向后兼容 draft 4 - 知道你将使用哪个版本是很好的。 指定要使用的JSON Schema版本的最佳方法是在文档根节点使用"$schema"键包含其元模式URL。这是一种常见的表示法,便于库确定应使用哪个版本。
快速参考:
- 如果模式根节点有
"$schema": "http://json-schema.org/draft-04/schema"
,则使用Draft 4 - 如果模式根节点有
"$schema": "http://json-schema.org/draft-06/schema"
,则使用Draft 6 - 如果模式根节点有
"$schema": "http://json-schema.org/draft-07/schema"
,则使用Draft 7 - 如果没有找到以上任何一个,则默认使用Draft 4
如果你想明确指定元模式版本,可以通过如下方式配置加载器,将默认版本从Draft 4更改为Draft 6/7:
SchemaLoader loader = SchemaLoader.builder()
.schemaJson(yourSchemaJSON)
.draftV6Support() // 或 draftV7Support()
.build();
Schema schema = loader.load().build();
调查失败原因
从1.1.0版本开始,验证器会收集所有模式违规(而不是在第一个违规处立即失败)。每个失败都用JSON指针表示,从文档根指向违规部分。如果检测到多个模式违规,则会在违规的最近公共父元素处抛出ValidationException
,可以使用ValidationException#getCausingExceptions()
方法获取每个单独的违规。
为了演示上述概念,让我们看一个例子。考虑以下模式:
{
"type" : "object",
"properties" : {
"rectangle" : {"$ref" : "#/definitions/Rectangle" }
},
"definitions" : {
"size" : {
"type" : "number",
"minimum" : 0
},
"Rectangle" : {
"type" : "object",
"properties" : {
"a" : {"$ref" : "#/definitions/size"},
"b" : {"$ref" : "#/definitions/size"}
}
}
}
}
以下JSON文档对该模式只有一处违规(因为"a"不能为负):
{
"rectangle" : {
"a" : -5,
"b" : 5
}
}
在这种情况下,抛出的ValidationException
将指向#/rectangle/a
,并且不包含子异常:
try {
schema.validate(rectangleSingleFailure);
} catch (ValidationException e) {
// 打印 #/rectangle/a: -5.0 is not higher or equal to 0
System.out.println(e.getMessage());
}
现在,为了说明如何处理多个违规,让我们考虑以下JSON文档,其中"a"和"b"属性都违反了上述模式:
{
"rectangle" : {
"a" : -5,
"b" : "asd"
}
}
在这种情况下,抛出的ValidationException
将指向#/rectangle
,并且有2个子异常,分别指向#/rectangle/a
和#/rectangle/b
:
try {
schema.validate(rectangleMultipleFailures);
} catch (ValidationException e) {
System.out.println(e.getMessage());
e.getCausingExceptions().stream()
.map(ValidationException::getMessage)
.forEach(System.out::println);
}
这将打印以下输出:
#/rectangle: 2 schema violations found
#/rectangle/a: -5.0 is not higher or equal to 0
#/rectangle/b: expected type: Number, found: String
失败的JSON报告
从1.4.0版本开始,可以将ValidationException
实例打印为JSON格式的失败报告。ValidationException#toJSON()
方法返回一个JSONObject
实例,包含以下键:
"message"
: 对程序员友好的异常消息(验证失败的描述)"keyword"
: 违反的JSON Schema关键字"pointerToViolation"
: 一个JSON指针,表示从输入文档根到导致验证失败的片段的路径"schemaLocation"
: 一个JSON指针,表示从模式JSON根到违反的关键字的路径"causingExceptions"
: 一个(可能为空的)子异常数组。每个子异常都表示为一个JSON对象,结构与本列表中描述的相同。有关导致异常的更多信息,请参见上文。
请注意,完整的失败报告是一个层次树结构:可以使用#getCausingExceptions()
获取一个原因的子原因。
ValidationListeners - 跟踪验证过程
ValidationListener
可以用于解决实例JSON如何匹配(或不匹配)模式的歧义。你可以将ValidationListener
实现附加到验证器,以接收有关中间成功/失败结果的事件通知。
示例:
import org.everit.json.schema.Validator;
...
Validator validator = Validator.builder()
.withListener(new YourValidationListenerImplementation())
.build();
validator.performValidation(schema, input);
当前支持的事件:
- 解析
"$ref"
引用 "allOf"
/"anyOf"
/"oneOf"
模式下的子模式匹配"allOf"
/"anyOf"
/"oneOf"
模式下的子模式匹配失败"if"
模式匹配"if"
模式匹配失败"then"
模式匹配"then"
模式匹配失败"else"
模式匹配"else"
模式匹配失败
有关更多详细信息,请参阅org.everit.json.schema.event.ValidationListener
接口的javadoc。特定事件类还有适当的#toJSON()
和#toString()
实现,因此你可以以易于解析的格式打印它们。
快速失败模式
默认情况下,验证错误报告处于收集模式(参见"调查失败原因"章节)。这对于获得详细的错误报告很方便,但在某些情况下,更适合在发现失败时停止验证,而不检查JSON文档的其余部分。要切换到这种快速失败验证模式:
- 你必须显式为你的模式构建一个
Validator
实例,而不是调用Schema#validate(input)
- 你必须调用
ValidatorBuilder
的failEarly()
方法
示例:
import org.everit.json.schema.Validator;
...
Validator validator = Validator.builder()
.failEarly()
.build();
validator.performValidation(schema, input);
注意:Validator
类是不可变的且线程安全的,因此你不需要为每次验证创建一个新的实例,只需配置一次即可。
宽松模式
在某些情况下,当验证数字或布尔值时,接受可解析为这些基本类型的字符串值是有意义的,因为后续处理也会自动将这些字面值解析为适当的数值和逻辑值。此外,非字符串的基本类型值很容易转换为字符串,所以为什么不允许任何 JSON 基本类型作为字符串呢?
例如,我们看这个模式:
{
"properties": {
"booleanProp": {
"type": "boolean"
},
"integerProp": {
"type": "integer"
},
"nullProp": {
"type": "null"
},
"numberProp": {
"type": "number"
},
"stringProp": {
"type": "string"
}
}
}
以下 JSON 文档无法通过验证,尽管所有字符串都可以轻松转换为适当的值:
{
"numberProp": "12.34",
"integerProp": "12",
"booleanProp": "true",
"nullProp": "null",
"stringProp": 12.34
}
在这种情况下,如果你希望上述实例能够通过对模式的验证,你需要使用开启了宽松基本类型验证配置。例如:
import org.everit.json.schema.*;
...
Validator validator = Validator.builder()
.primitiveValidationStrategry(PrimitiveValidationStrategy.LENIENT)
.build();
validator.performValidation(schema, input);
注意:在宽松解析模式下,将接受所有 22 种可能的布尔字面值作为逻辑值。
默认值
JSON Schema 规范定义了 "default" 关键字来表示默认值,尽管它没有明确说明这应该如何影响验证过程。默认情况下,这个库不会设置默认值,但如果你需要这个功能,你可以在加载模式之前通过 SchemaLoaderBuilder#useDefaults(boolean)
方法开启它:
{
"properties": {
"prop": {
"type": "number",
"default": 1
}
}
}
JSONObject input = new JSONObject("{}");
System.out.println(input.get("prop")); // 打印 null
Schema schema = SchemaLoader.builder()
.useDefaults(true)
.schemaJson(rawSchema)
.build()
.load().build();
schema.validate(input);
System.out.println(input.get("prop")); // 打印 1
如果 input
中缺少一些在模式中有 "default"
值的属性,那么验证器在验证过程中会设置这些值。
正则表达式实现
为了支持 JSON Schema 的 "regex"
关键字,该库提供了两种可能的实现:
- 默认基于
java.util.regex
包 - 另一种基于 RE2J 库
虽然 RE2J 库提供了比 java.util.regex
显著更好的性能,但它与 java.util
或 ECMA 262 支持的语法不完全兼容。因此,如果你关心性能且其局限性可以接受,则推荐使用 RE2J。
可以通过 SchemaLoaderBuilder#regexpFactory()
调用激活 RE2J 实现:
SchemaLoader loader = SchemaLoader.builder()
.regexpFactory(new RE2JRegexpFactory())
// ...
.build();
注意:
- 如果你不需要 RE2J 实现,建议在你的
pom.xml
中排除它,以免不必要地增加你的构件大小 - 版本历史:在 1.0.0 ... 1.7.0 版本中使用了
java.util
实现,在 1.8.0 版本中使用了 RE2J 实现,在 1.9.0 版本中我们使其可配置,因为有一些回归问题的报告。
readOnly 和 writeOnly 上下文
该库支持 readOnly
和 writeOnly
关键字,这两个关键字首次出现在 Draft 7 中。如果你想使用这个功能,那么在验证之前你需要告诉验证器验证是在读取还是写入上下文中进行的。例如:
schema.json:
{
"properties": {
"id": {
"type": "number",
"readOnly": true
}
}
}
验证代码片段:
Validator validator = Validator.builder()
.readWriteContext(ReadWriteContext.WRITE)
.build();
validator.performValidation(schema, new JSONObject("{\"id\":42}"));
在这个例子中,我们告诉验证器验证发生在 WRITE
上下文中,而在输入的 JSON 对象中出现了 "id"
属性,该属性在模式中被标记为 "readOnly"
,因此这个调用将抛出 ValidationException
。
格式验证器
从 1.2.0
版本开始,该库支持 ["format"
关键字][draft-fge-json-schema-validation-00 format](这是规范的可选部分)。
支持的格式根据你使用的模式规范版本而有所不同(因为标准格式是在验证规范的不同版本中引入的)。
以下是支持的标准格式的兼容性表格:
Draft 4 | Draft 6 | Draft 7 | |
---|---|---|---|
date-time | :white_check_mark: | :white_check_mark: | :white_check_mark: |
:white_check_mark: | :white_check_mark: | :white_check_mark: | |
hostname | :white_check_mark: | :white_check_mark: | :white_check_mark: |
ipv4 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
ipv6 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
uri | :white_check_mark: | :white_check_mark: | :white_check_mark: |
uri-reference | :white_check_mark: | :white_check_mark: | |
uri-template | :white_check_mark: | :white_check_mark: | |
json-pointer | :white_check_mark: | :white_check_mark: | |
date | :white_check_mark: | ||
time | :white_check_mark: | ||
regex | :white_check_mark: | ||
relative-json-pointer | :white_check_mark: |
该库还支持添加自定义格式验证器。要使用自定义验证器,基本上你需要:
- 在实现
org.everit.json.schema.FormatValidator
接口的类中创建你自己的验证 - 在加载实际模式之前,在
org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder
实例中将你的验证器绑定到一个名称
示例
假设任务是创建一个自定义验证器,该验证器接受字符数为偶数的字符串。
自定义的 FormatValidator
将看起来像这样:
public class EvenCharNumValidator implements FormatValidator {
@Override
public Optional<String> validate(final String subject) {
if (subject.length() % 2 == 0) {
return Optional.empty();
} else {
return Optional.of(String.format("字符串 [%s] 的长度为奇数", subject));
}
}
}
要将 EvenCharNumValidator
绑定到 "format"
值(例如 "evenlength"
),你需要在模式加载器配置中将验证器实例绑定到关键字:
JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream));
SchemaLoader schemaLoader = SchemaLoader.builder()
.schemaJson(rawSchema) // rawSchema 是使用非标准格式 "evenlength" 的模式的 JSON 表示
.addFormatValidator("evenlength", new EvenCharNumValidator()) // EvenCharNumValidator 被绑定到 "evenlength" 关键字
.build();
Schema schema = schemaLoader.load().build(); // 使用上面创建的配置来创建模式
schema.validate(jsonDocument); // 在这里进行文档验证
$ref 解析
在 JSON Schema 文档中,可以使用相对 URI 来引用之前定义的类型。这些引用使用 "$ref"
和 "$id"
关键字表示。虽然规范详细描述了解析范围的改变和解引用,但它并没有解释当第一次出现的 "$ref"
或 "$id"
是相对 URI 时的预期行为。
在此实现中,可以使用适当的构建器方法显式定义一个作为基本 URI(解析范围)的绝对 URI:
SchemaLoader schemaLoader = SchemaLoader.builder()
.schemaJson(jsonSchema)
.resolutionScope("http://example.org/") // 设置默认解析范围
.build();
从类路径加载
随着模式的增长,你会希望将其拆分为多个源文件,并用 "$ref"
引用连接它们。
如果你想将模式存储在类路径上(而不是通过 HTTP 提供),那么推荐的方法是使用 classpath:
协议来让模式相互引用。要使 classpath:
协议工作:
- 如果你使用 Spring 框架,你不需要做任何事,spring 会自动安装必要的协议处理器
- 否则,你可以使用库的内置类路径感知
SchemaClient
,例如:
SchemaLoader schemaLoader = SchemaLoader.builder()
.schemaClient(SchemaClient.classPathAwareClient())
.schemaJson(jsonSchema)
.resolutionScope("classpath://my/schemas/directory/") // 设置默认解析范围
.build();
有了这个配置,jsonSchema
中的以下引用将被正确解析:
{
"properties": {
"sameDir": { "$ref": "sameDirSchema.json" },
"absPath": { "$ref": "classpath://somewhere/else/otherschema.json" },
"httpPath": { "$ref": "http://example.org/http-works-as-usual" },
}
}
sameDirSchema.json
将在类路径的 /my/schemas/directory/sameDirSchema.json
中查找。
通过 URI 注册模式
有时使用预加载的模式很有用,我们可以为这些模式分配一个任意的 URI(可能是 uuid),而不是通过 URL 加载模式。这可以通过模式加载器的 #registerSchemaByURI()
方法将模式分配给 URI 来实现。例如:
SchemaLoader schemaLoader = SchemaLoader.builder()
.registerSchemaByURI(new URI("urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63"), aJSONObject)
.registerSchemaByURI(new URI("http://example.org"), otherJSONObject)
.schemaJson(jsonSchema)
.resolutionScope("classpath://my/schemas/directory/")
.build();
注意:
- 传递的模式对象必须是
JSONObject
或Boolean
(形式参数类型是Object
只是因为这两者没有其他共同的超类)。 - 如果你愿意,你可以传递一个带有 HTTP 协议的 URL,它仍然是一个有效的 URI。在这种情况下,由于你预先将模式分配给了 URI,所以不会进行网络调用。这可以作为一种缓存策略(尽管定义你自己的
SchemaClient
实现也可以,或者你甚至可以利用java.net
包的可扩展协议处理)
排除依赖项
可以从库中排除一些依赖项,它仍然可用,但有一些限制:
- 如果你排除了
com.damnhandy:handy-uri-templates
依赖,那么你的模式不应使用"uri-template"
格式 - 如果你排除了
commons-validator:commons-validator
依赖,那么你的模式不应使用以下格式:"email"
,"ipv4"
,"ipv6"
,"hostname"
Javadoc
按库版本:
1.0.0 - 1.5.1 版本的生成 javadoc 可在 javadoc.io 上获取
中间版本(1.6.0 - 1.9.1)没有在任何地方发布。 [ASL 2.0 徽章]: https://img.shields.io/:license-Apache%202.0-blue.svg [ASL 2.0]: https://www.apache.org/licenses/LICENSE-2.0 [Travis master分支徽章]: https://travis-ci.org/everit-org/json-schema.svg?branch=master [Travis]: https://travis-ci.org/everit-org/json-schema [Coveralls.io master分支徽章]: https://coveralls.io/repos/github/everit-org/json-schema/badge.svg?branch=master [Coveralls.io]: https://coveralls.io/github/everit-org/json-schema?branch=master [java-json-tools/json-schema-validator]: https://github.com/java-json-tools/json-schema-validator [draft-zyp-json-schema-04]: https://tools.ietf.org/html/draft-zyp-json-schema-04 [draft-fge-json-schema-validation-00 格式]: https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-7