该库目前处于维护模式,并由 erosb/json-sKema 取代。
这个存储库不会看到任何新功能。它为 JSON Schema 规范的草案 04、草案 06 和草案 07 版本提供了可靠的支持。
最新草案 2020-12 仅由 erosb/json-sKema 支持。
该项目是 JSON Schema Draft v4、Draft v6 和 Draft v7 规范的实现。它使用 org.json API(由 Douglas Crockford 创建)来表示 JSON 数据。
假设您已经知道 JSON Schema 是什么,并且希望在 Java 应用程序中使用它来验证 JSON 数据。但是 - 正如您可能已经发现的 - JSON Schema 规范还有其他 Java 实现。因此,这里有一些关于使用哪一种的建议:
将以下依赖项添加到您的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
之间的版本只能在具有com.github.everit-org.json-schema:org.everit.json.schema
坐标的 JitPack 上找到。版本1.0.0
... 1.5.1
可在 Maven Central 上的org.everit.json:org.everit.json.schema
坐标下找到。
为了让该库在 Java 6/7 上运行,我们进行了几次尝试。
版本 1.9.2 的 java6 端口由 @mindbender1 开发,可通过 Maven Central 访问,坐标如下:
< dependency >
< groupId >com.github.erosb</ groupId >
< artifactId >everit-json-schema-jdk6</ artifactId >
< version >1.9.2</ version >
</ dependency >
旧版本的向后移植:
com.doctusoft:json-schema-java7:1.4.1
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 " }" )); // throws a ValidationException if this object is invalid
}
JSON Schema 目前有 4 个主要版本,Draft 3、Draft 4、Draft 6 和 Draft 7。该库实现了 3 个较新的版本,您可以快速查看此处和此处的差异。由于这两个版本有许多差异 - 并且草案 6 不向后兼容草案 4 - 最好知道您将使用哪个版本。
表示要使用的 JSON 架构版本的最佳方法是使用"$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 6 / 7:
SchemaLoader loader = SchemaLoader . builder ()
. schemaJson ( yourSchemaJSON )
. draftV6Support () // or 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 ) {
// prints #/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
从版本1.4.0
开始,可以将ValidationException
实例打印为 JSON 格式的失败报告。 ValidationException#toJSON()
方法返回一个带有以下键的JSONObject
实例:
"message"
:程序员友好的异常消息(验证失败的描述)"keyword"
: 被违反的 JSON Schema 关键字"pointerToViolation"
:一个 JSON 指针,表示从输入文档根到导致验证失败的片段的路径"schemaLocation"
:一个 JSON 指针,表示从架构 JSON 根到违规关键字的路径"causingExceptions"
:子异常的数组(可能为空)。每个子异常都表示为一个 JSON 对象,其结构与此清单中描述的相同。请参阅上面有关引发异常的更多信息。请考虑到完整的故障报告是分层树结构:可以使用#getCausingExceptions()
获取原因的子原因。
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" )); // prints null
Schema schema = SchemaLoader . builder ()
. useDefaults ( true )
. schemaJson ( rawSchema )
. build ()
. load (). build ();
schema . validate ( input );
System . out . println ( input . get ( "prop" )); // prints 1
如果input
中缺少一些在架构中具有"default"
值的属性,则验证器将在验证期间设置它们。
为了支持 JSON Schema 的"regex"
关键字,该库提供了两种可能的实现:
java.util.regex
包虽然 RE2J 库提供了比java.util.regex
明显更好的性能,但它与java.util
或 ECMA 262 支持的语法并不完全兼容。因此,如果您担心性能并且其限制可以接受,建议使用 RE2J。
可以通过SchemaLoaderBuilder#regexpFactory()
调用激活 RE2J 实现:
SchemaLoader loader = SchemaLoader . builder ()
. regexpFactory ( new RE2JRegexpFactory ())
// ...
. build ();
笔记:
pom.xml
中,这样它就不会不必要地增加工件的大小java.util
实现,在 1.8.0 中,使用了 RE2J 实现,而在 1.9.0 中,由于报告了一些回归,我们使其可配置。 该库支持首次出现在 Draft 7 中的readOnly
和writeOnly
关键字。如果您想利用此功能,那么在验证之前您需要告诉验证器验证是否发生在读或写上下文中。例子:
架构.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"
关键字(这是规范的可选部分)。
支持的格式根据您使用的模式规范版本而有所不同(因为标准格式是在验证规范的不同版本中引入的)。
以下是支持的标准格式的兼容性表:
草案4 | 草案 6 | 草案 7 | |
---|---|---|---|
日期时间 | ✅ | ✅ | ✅ |
电子邮件 | ✅ | ✅ | ✅ |
主机名 | ✅ | ✅ | ✅ |
ipv4 | ✅ | ✅ | ✅ |
ipv6 | ✅ | ✅ | ✅ |
乌里 | ✅ | ✅ | ✅ |
uri 引用 | ✅ | ✅ | |
uri 模板 | ✅ | ✅ | |
json 指针 | ✅ | ✅ | |
日期 | ✅ | ||
时间 | ✅ | ||
正则表达式 | ✅ | ||
相对 json 指针 | ✅ |
该库还支持添加自定义格式验证器。要使用自定义验证器基本上你必须
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 ( "the length of string [%s] is odd" , subject ));
}
}
}
要将EvenCharNumValidator
绑定到"format"
值(例如"evenlength"
),您必须将验证器实例绑定到模式加载器配置中的关键字:
JSONObject rawSchema = new JSONObject ( new JSONTokener ( inputStream ));
SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaJson ( rawSchema ) // rawSchema is the JSON representation of the schema utilizing the "evenlength" non-standard format
. addFormatValidator ( "evenlength" , new EvenCharNumValidator ()) // the EvenCharNumValidator gets bound to the "evenlength" keyword
. build ();
Schema schema = schemaLoader . load (). build (); // the schema is created using the above created configuration
schema . validate ( jsonDocument ); // the document validation happens here
在 JSON 架构文档中,可以使用相对 URI 来引用先前定义的类型。此类引用使用"$ref"
和"$id"
关键字来表达。虽然该规范详细描述了解析范围更改和取消引用,但它没有解释当第一次出现的"$ref"
或"$id"
是相对 URI 时的预期行为。
在此实现的情况下,可以使用适当的构建器方法显式定义用作基本 URI(解析范围)的绝对 URI:
SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaJson ( jsonSchema )
. resolutionScope ( "http://example.org/" ) // setting the default resolution scope
. build ();
随着模式的增长,您将需要将其拆分为多个源文件,并将它们与"$ref"
引用连接起来。如果您想将模式存储在类路径上(而不是通过 HTTP 提供服务),那么推荐的方法是使用classpath:
协议使模式相互引用。要使classpath:
协议起作用:
SchemaClient
,例如: SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaClient ( SchemaClient . classPathAwareClient ())
. schemaJson ( jsonSchema )
. resolutionScope ( "classpath://my/schemas/directory/" ) // setting the default resolution scope
. build ();
鉴于此配置,以下引用将在jsonSchema
中正确解析:
{
"properties" : {
"sameDir" : { "$ref" : " sameDirSchema.json " },
"absPath" : { "$ref" : " classpath://somewhere/else/otherschema.json " },
"httpPath" : { "$ref" : " http://example.org/http-works-as-usual " },
}
}
并将在类路径上的/my/schemas/directory/sameDirSchema.json
中查找sameDirSchema.json
。
有时,使用预加载的模式很有用,我们可以为其分配任意 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
,因为这两个没有任何其他公共超类)。SchemaClient
实现也可以,或者您甚至可以利用java.net
包的可扩展协议处理) 一些依赖项可以从库中排除,但它仍然可用,但有一些限制:
com.damnhandy:handy-uri-templates
依赖项,那么您的架构不应使用"uri-template"
格式commons-validator:commons-validator
依赖项,那么您的架构不应使用以下格式: "email"
、 "ipv4"
、 "ipv6"
、 "hostname"
按库版本:
生成的版本 1.0.0 - 1.5.1 的 javadoc 可在 javadoc.io 上获取
对于介于 (1.6.0 - 1.9.1) 之间的版本,它不会在任何地方发布。