学习链接
json-schema官网 - 英文
jsonschemavalidator 可在线校验网站
networknt的json-schema-validator github地址
networknt的json-schema-validator 个人gitee地址 - 里面有md文档说明和代码示例
JSON Schema 入门指南:如何定义和验证 JSON 数据结构
JSON Schema入门 - 比较详细
文章目录
- 学习链接
- 1、JSON Schema 入门指南
- 1、引言
- 2、什么是 JSON Schema?
- 3、JSON Schema 的基本结构
- 3.1 基本关键字
- 3.2 对象属性
- 3.3 数组元素
- 3.4 字符串约束
- 3.5 数值约束
- 4、示例:定义一个简单的 JSON Schema
- 2、java示例
- 源码结构图
- 1、普通属性校验示例
- pom.xml
- SchemaValidatorExample
- ValidationResult
- schema.json
- json-validator-message.properties
- json-validator-message_zh_CN.properties
- ResourceBundleTest
- 2、嵌套属性校验示例
- SchemaValidatorExample
- schema.json
- json-validator-message.properties
- json-validator-message_zh_CN.properties
- 3、$ref 引用
- 1、内部引用
- resources/user-schema.json
- JsonSchemaValidatorDemo
- 2、外部文件引用
- JsonSchemaValidatorDemo
- CommonSchemaLoader
- ==resources/user-schema.json==
- resources/common/address-schema.json
- resources/user-schema.json
- 4、format校验
- 1、内置format校验示例
- JsonSchemaValidatorDemo
- format-schema.json
- 2、覆盖内置的format校验逻辑
- JsonSchemaValidatorDemo
- resources/schemas/format-schema.json
- 3、自定义format校验
- JsonSchemaValidatorDemo
- DateTimeFormat
- json-validator-message.properties
- 5、自定义Validator校验
- 示例1
- CustomTypeCode
- ValidatorFactory
- AbsBaseJsonValidator
- json-validator-message_zh_CN.properties
- json-validator-message.properties
- MobilePhonePrefixValidator
- JsonSchemaValidatorDemo
- 示例2(推荐)
- JsonSchemaValidatorDemo2
- GroovyKeyword
- resources/schemas/format-schema.json
- json-validator-message.properties
- json-validator-message_zh_CN.properties
1、JSON Schema 入门指南
1、引言
在现代的 Web
开发和数据交换中,JSON
(JavaScript Object Notation)已经成为了一种非常流行的数据格式。它轻量、易读、易于解析,广泛应用于 API
通信、配置文件、数据存储等场景。然而,随着 JSON
数据结构的复杂性增加,如何确保 JSON
数据的有效性和一致性成为了一个挑战。这时,JSON Schema 就派上了用场。
本文将带你入门 JSON Schema,帮助你理解它的基本概念、语法结构,并通过实例演示如何使用 JSON Schema 来定义和验证 JSON
数据结构。
2、什么是 JSON Schema?
JSON Schema
是一种用于描述 JSON
数据结构的规范。它允许你定义 JSON
数据的格式、类型、约束条件等,从而确保 JSON
数据符合预期的结构。通过 JSON Schema
,你可以在数据交换、存储或处理之前,对 JSON
数据进行验证,确保其有效性和一致性。
简单来说,JSON Schema 就像是 JSON
数据的“蓝图”或“合同”,它规定了 JSON
数据应该长什么样子。
3、JSON Schema 的基本结构
一个 JSON Schema 本身也是一个 JSON
对象。它由一系列关键字(keywords)组成,这些关键字用于定义 JSON 数据的结构和约束条件。
3.1 基本关键字
$schema
: 指定使用的 JSON Schema 版本。例如,"$schema": "http://json-schema.org/draft-07/schema#"
表示使用Draft 7
版本的 JSON Schema。$id
: 为 Schema 定义一个唯一的标识符,通常是一个 URL。title
和description
: 分别为 Schema 提供标题和描述信息,便于理解和维护。- type: 定义 JSON 数据的类型。常见的类型有
object、array、string、number、integer、boolean
和null
。
3.2 对象属性
properties
: 定义对象中的各个属性及其对应的 Schema。required
: 指定哪些属性是必须的。additionalProperties
: 控制是否允许对象包含未在properties
中定义的额外属性。
3.3 数组元素
items
: 定义数组中每个元素的 Schema。minItems
和maxItems
: 分别指定数组的最小和最大长度。uniqueItems
: 指定数组中的元素是否必须唯一。
3.4 字符串约束
minLength
和 maxLength: 分别指定字符串的最小和最大长度。pattern
: 使用正则表达式约束字符串的格式。format
: 指定字符串的格式,如 email、date-time 等。
3.5 数值约束
minimum
和 maximum: 分别指定数值的最小和最大值。exclusiveMinimum
和exclusiveMaximum
: 指定数值是否排除最小值和最大值。multipleOf
: 指定数值必须是某个数的倍数。
4、示例:定义一个简单的 JSON Schema
假设我们要定义一个表示用户信息的 JSON 数据结构,要求如下:
- 用户对象必须包含
id
、name
和email
属性。 id
必须是整数。name
必须是字符串,且长度在 1 到 50 之间。email
必须是有效的电子邮件地址。- 用户对象可以包含可选的
age
属性,且必须是正整数。
对应的 JSON Schema 可以这样定义:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/user.schema.json",
"title": "User",
"description": "A user object",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 50
},
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true
}
},
"required": ["id", "name", "email"],
"additionalProperties": false
}
解释
$schema
和$id
分别指定了Schema
的版本和唯一标识符。type
指定了JSON
数据的类型为object
。properties
定义了对象的各个属性及其约束条件。required
指定了id
、name
和email
是必须的属性。additionalProperties
设置为false
,表示不允许对象包含未定义的属性。
2、java示例
源码结构图
1、普通属性校验示例
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.zzhua</groupId>
<artifactId>demo-metrics</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.networknt/json-schema-validator -->
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<!-- Used to validate ECMA 262 regular expressions -->
<!-- Approximately 50 MB in dependencies -->
<!-- GraalJSRegularExpressionFactory -->
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>21.3.10</version>
</dependency>
<dependency>
<!-- Used to validate ECMA 262 regular expressions -->
<!-- Approximately 2 MB in dependencies -->
<!-- JoniRegularExpressionFactory -->
<groupId>org.jruby.joni</groupId>
<artifactId>joni</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SchemaValidatorExample
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.i18n.ResourceBundleMessageSource;
import com.zzhua.util.ValidationResult;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class SchemaValidatorExample {
public static void main(String[] args) throws IOException {
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
JsonSchema schema = schemaFactory.getSchema(
new ClassPathResource("schema/test04/schema.json").getInputStream(),
SchemaValidatorsConfig
.builder()
// 可以去掉JsonNodePath instanceLocation前面的 / 斜线
.pathType(PathType.URI_REFERENCE)
// 快速失败
// .failFast(true)
// 自定义消息源(按顺序找指定的文件名。可支持i18n国际化。错误提示中的占位符的值可以看XxxValidator往arguments中设置了哪些值。)
.messageSource(new ResourceBundleMessageSource(/*"validate/json-validator-message",*/"jsv-messages"))
// 自定义消息错误提示属性
// (虽然SchemaValidatorsConfig#errorMessageKeyword默认值就是message,
// 但是builder中该属性默认是null,所以这里如果不主动设置的话,就会是null)
.errorMessageKeyword("message")
.build()
);
ObjectMapper mapper = new ObjectMapper();
String jsonStr = """
{
"age": 5,
"sex": true,
"email": "haloC",
"motto": "人心中的成见是一座大山,任你怎么努力使劲,也休想搬动",
"hobbies": ["java", 666, "spring" ]
}
""";
Set<ValidationMessage> validationMessages = schema.validate(mapper.readTree(jsonStr));
List<ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
ValidationResult
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ValidationResult {
private String type;
private String propertyPath;
private String errMsg;
}
schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "string",
"message": {
"type": "name的类型不对, 这里的优先级高"
}
},
"age": {
"type": "integer",
"minimum": 10,
"maximum": 120,
"message": {
"minimum": "年龄不能小于10岁哦",
"maximum": "年龄不能大于120岁哦"
}
},
"sex": {
"type": "string",
"enum": ["male", "female"],
"message": {
"enum": "这是个枚举哦, 它的取值有: {1}, 这里的优先级比外面优先级要高"
}
},
"email": {
"pattern": "^\\w{3,8}@\\w{3,8}\\.com$"
},
"motto": {
"type": "string",
"maxLength": 20,
"minLength": 5,
"message": {
"minLength": "这个字数太短啦, 最少5个字"
}
},
"hobbies": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["name","age"],
"message": {
"required": {
"name": "没有提供名字name",
"age": "没有提供age年龄"
},
"type": "属性 {0} 设置了 {1} 类型不正确,而应当为 {2} ",
"enum": "这是个枚举值, 取值有: {1}, 这里的优先级较低",
"pattern": "不符合这个正则表达式咯",
"maxLength": "这个字数太长啦, 最长20个字,看看哪里的生效~这里的不生效,上面如果要写就要写全了"
}
}
json-validator-message.properties
maxLength = ~~~{0}: zzhua~~~长度不得超过 {1} 个字符~~~
json-validator-message_zh_CN.properties
maxLength = ~~~{0}: zzhua-中文~~~长度不得超过 {1} 个字符~~~
ResourceBundleTest
用于加载属性配置文件,并支持i18n国际化
import java.util.Locale;
import java.util.ResourceBundle;
public class ResourceBundleTest {
public static void main(String[] args) {
// 1、ResourceBundle resourceBundle = ResourceBundle.getBundle(baseName, locale)
// 2、String val = resourceBundle.getString(key)
// 3、文件名:jsv-messages_zh_CN.properties
// 4、MessageFormat format = new MessageFormat("{0}: 未知属性{1}", locale)
// 5、String result = format.format(arguements, new StringBuffer(), null)
// ResourceBundle bundle = ResourceBundle.getBundle("validate/json-validator-message", Locale.ROOT);
ResourceBundle bundle = ResourceBundle.getBundle("validate/json-validator-message", Locale.CHINA);
String maxLength = bundle.getString("maxLength");
System.out.println(maxLength);
}
}
2、嵌套属性校验示例
SchemaValidatorExample
package com.zzhua.test05;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.i18n.ResourceBundleMessageSource;
import com.zzhua.util.ValidationResult;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;
import javax.xml.validation.SchemaFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class SchemaValidatorExample05 {
public static void main(String[] args) throws IOException {
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
JsonSchema schema = schemaFactory.getSchema(
new ClassPathResource("schema/test05/schema.json").getInputStream(),
SchemaValidatorsConfig
.builder()
// 可以去掉JsonNodePath instanceLocation前面的 / 斜线
.pathType(PathType.URI_REFERENCE)
// 快速失败
// .failFast(true)
// 自定义消息源(按顺序找指定的文件名。可支持i18n国际化。错误提示中的占位符的值可以看XxxValidator往arguments中设置了哪些值。)
.messageSource(new ResourceBundleMessageSource("validate/json-validator-message","jsv-messages"))
// 自定义消息错误提示属性
// (虽然SchemaValidatorsConfig#errorMessageKeyword默认值就是message,
// 但是builder中该属性默认是null,所以这里如果不主动设置的话,就会是null)
.errorMessageKeyword("message")
.build()
);
ObjectMapper mapper = new ObjectMapper();
String jsonStr = """
{
"user": {
"a": "b",
"name": 1,
"address": {
"street": "123 Main St",
"city": "Beijing",
"coordinates": {
"lat": 39.9042,
"lng": 116.4074
}
},
"friends": [
{
"name": "Bob",
"hobbies": ["Reading", "Sports"]
},
{
"name": "Charlie",
"hobbies": ["Music"]
}
]
}
}
""";
Set<ValidationMessage> validationMessages = schema.validate(mapper.readTree(jsonStr));
List<com.zzhua.util.ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(com.zzhua.util.ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["user"],
"properties": {
"user": {
"type": "object",
"required": ["name", "age", "address"],
"additionalProperties": false,
"message": {
"required": "{0}中缺少属性{1}",
"additionalProperties": "{0}中不允许存在额外属性{1}"
},
"properties": {
"name": { "type": "string" },
"age": { "type": "number", "minimum": 0 },
"address": {
"type": "object",
"required": ["street", "city"],
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"coordinates": {
"type": "object",
"required": ["lat", "lng"],
"properties": {
"lat": { "type": "number" },
"lng": { "type": "number" }
},
"additionalProperties": false
}
}
},
"friends": {
"type": "array",
"items": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" },
"hobbies": {
"type": "array",
"items": { "type": "string" },
"minItems": 1
}
},
"additionalProperties": false
}
}
}
}
}
}
json-validator-message.properties
type = 类型不太对哦{1}-{2}
json-validator-message_zh_CN.properties
type = 中文-类型不太对哦{1}-{2}
3、$ref 引用
1、内部引用
resources/user-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/user-schema.json",
"definitions": {
"address": {
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
}
},
"type": "object",
"properties": {
"billing_address": {
"$ref": "#/definitions/address"
},
"shipping_address": {
"$ref": "#/definitions/address"
}
}
}
JsonSchemaValidatorDemo
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.zzhua.util.ValidationResult;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class JsonSchemaValidatorDemo {
public static void main(String[] args) throws JsonProcessingException {
// 1. 加载主 Schema
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
InputStream schemaStream = JsonSchemaValidatorDemo.class.getResourceAsStream("/schemas/user-schema.json");
JsonSchema schema = schemaFactory.getSchema(
schemaStream,
SchemaValidatorsConfig
.builder()
.pathType(PathType.URI_REFERENCE)
.build()
);
// 2. 准备待校验的 JSON 数据
String jsonData = """
{
"billing_address": {
"city":"shenzhen",
"state": "gd",
"street_address": "lsydxq48d"
}
}
""";
// 3. 执行校验
Set<ValidationMessage> validationMessages = schema.validate(new ObjectMapper().readTree(jsonData));
List<com.zzhua.util.ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(com.zzhua.util.ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
2、外部文件引用
JsonSchemaValidatorDemo
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.resource.PrefixSchemaMapper;
import com.zzhua.util.ValidationResult;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class JsonSchemaValidatorDemo {
public static void main(String[] args) throws JsonProcessingException {
SchemaValidatorsConfig config = SchemaValidatorsConfig
.builder()
.pathType(PathType.URI_REFERENCE)
.errorMessageKeyword("message")
.build();
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(
SpecVersion.VersionFlag.V7,
builder -> {
builder.schemaMappers(schemaMapperBuilder -> {
schemaMapperBuilder.add(new PrefixSchemaMapper("http://www.example.com", ""));
});
builder.schemaLoaders(schemaLoaderBuilder -> {
schemaLoaderBuilder.add(new CommonSchemaLoader());
});
}
);
// 1. 加载主 Schema
InputStream schemaStream = JsonSchemaValidatorDemo.class.getResourceAsStream("/schemas/user-schema.json");
JsonSchema schema = schemaFactory.getSchema(schemaStream, config);
// 2. 准备待校验的 JSON 数据
String jsonData = """
{
"billing_address":{
"state": 1
},
"customer_name": "ab",
"enum_no_use_name": "zzhua",
"all_can_use_name": "zz"
}
""";
// 3. 执行校验
Set<ValidationMessage> validationMessages = schema.validate(new ObjectMapper().readTree(jsonData));
List<com.zzhua.util.ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(com.zzhua.util.ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
CommonSchemaLoader
参照 ClasspathSchemaLoader
import com.networknt.schema.AbsoluteIri;
import com.networknt.schema.resource.InputStreamSource;
import com.networknt.schema.resource.SchemaLoader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.function.Supplier;
public class CommonSchemaLoader implements SchemaLoader {
private final Supplier<ClassLoader> classLoaderSource;
/**
* Constructor.
*/
public CommonSchemaLoader() {
this(CommonSchemaLoader::getClassLoader);
}
public CommonSchemaLoader(Supplier<ClassLoader> classLoaderSource) {
this.classLoaderSource = classLoaderSource;
}
@Override
public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
String iri = absoluteIri != null ? absoluteIri.toString() : "";
String name = null;
if (iri.startsWith("/schemas/common")) {
name = iri.substring(1);
}
if (name != null) {
ClassLoader classLoader = this.classLoaderSource.get();
String resource = name;
return () -> {
InputStream result = classLoader.getResourceAsStream(resource);
if (result == null) {
result = classLoader.getResourceAsStream(resource.substring(1));
}
if (result == null) {
throw new FileNotFoundException(iri);
}
return result;
};
}
return null;
}
protected static ClassLoader getClassLoader() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = SchemaLoader.class.getClassLoader();
}
return classLoader;
}
}
resources/user-schema.json
-
当在一个模式对象中使用$ref时,其他属性会被忽略,因为$ref会完全引用外部模式,取代当前对象。这意味着如果用户在同一个对象里同时使用$ref和其他属性,比如description或type,这些额外的属性可能会被验证器忽略,导致不符合预期的结果。
- 更常用的解决方案是使用allOf来组合引用和本地属性。例如,使用allOf数组,其中一个是$ref引用外部模式,另一个是包含本地属性的对象。这样可以将外部模式和本地属性合并,确保所有约束都被应用。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "classpath:/schemas/address-schema.json",
"type": "object",
"definitions": {
"address": {
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
}
},
"properties": {
"billing_address": {
"$ref": "http://www.example.com/schemas/common/address-schema.json#/properties/address"
},
"customer_name": {
"$ref": "http://www.example.com/schemas/common/nickname-schema.json"
},
"enum_no_use_name": {
"$ref": "http://www.example.com/schemas/common/nickname-schema.json",
"enum": ["mike","jerry"]
},
"all_can_use_name": {
"allOf": [
{"$ref": "http://www.example.com/schemas/common/nickname-schema.json"},
{
"enum": ["mike","jerryyyyyyyyyyyyyyyy"],
"message": {"enum": "all_can_use_name枚举值会和$ref引入的内容合并吗?会合并!"}
}
]
},
"shipping_address": {
"$ref": "#/definitions/address"
}
},
"required": ["customer_name"]
}
resources/common/address-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/nickname-schema.json",
"type": "string",
"minLength": 3,
"maxLength": 8,
"message": {
"minLength": "~minLength~昵称长度必须在3-8个字符之间",
"maxLength": "~maxLength~昵称长度必须在3-8个字符之间"
}
}
resources/user-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "classpath:/schemas/address-schema.json",
"type": "object",
"definitions": {
"address": {
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
}
},
"properties": {
"billing_address": {
"$ref": "http://www.example.com/schemas/common/address-schema.json#/properties/address"
},
"customer_name": {
"$ref": "http://www.example.com/schemas/common/nickname-schema.json"
},
"shipping_address": {
"$ref": "#/definitions/address"
}
},
"required": ["customer_name"]
}
4、format校验
1、内置format校验示例
JsonSchemaValidatorDemo
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.resource.PrefixSchemaMapper;
import com.zzhua.util.ValidationResult;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class JsonSchemaValidatorDemo {
public static void main(String[] args) throws JsonProcessingException {
SchemaValidatorsConfig config = SchemaValidatorsConfig
.builder()
.pathType(PathType.URI_REFERENCE)
.errorMessageKeyword("message")
.failFast(false)
// 如果不开启format校验,那么format会只被认为是个注释,而不会去作校验
.formatAssertionsEnabled(true)
.build();
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(
SpecVersion.VersionFlag.V7
);
// 1. 加载主 Schema
InputStream schemaStream = JsonSchemaValidatorDemo.class.getResourceAsStream("/schemas/format-schema.json");
JsonSchema schema = schemaFactory.getSchema(schemaStream, config);
// 2. 准备待校验的 JSON 数据
String jsonData = """
{
"mailbox": "111.com"
}
""";
// 3. 执行校验
Set<ValidationMessage> validationMessages = schema.validate(new ObjectMapper().readTree(jsonData));
List<com.zzhua.util.ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(com.zzhua.util.ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
format-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "classpath:/schemas/address-schema.json",
"type": "object",
"properties": {
"mailbox": {
"type": "string",
"format": "email",
"message": {
"format": "邮箱格式不正确的哦"
}
}
},
"required": ["mailbox"]
}
2、覆盖内置的format校验逻辑
如果内置的format校验逻辑不能满足需求,可以使用自己的覆盖掉内置的逻辑
JsonSchemaValidatorDemo
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.format.PatternFormat;
import com.networknt.schema.i18n.ResourceBundleMessageSource;
import com.networknt.schema.resource.PrefixSchemaMapper;
import com.zzhua.util.ValidationResult;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class JsonSchemaValidatorDemo {
public static void main(String[] args) throws JsonProcessingException {
// 这段代码参照 Version7 这个类的代码
JsonMetaSchema jsonMetaSchemaV7 = JsonMetaSchema.builder(SchemaId.V7)
.specification(SpecVersion.VersionFlag.V7)
.idKeyword("$id")
.formats(Formats.DEFAULT)
// 直接就覆盖了内置的email (的format校验)
.format(PatternFormat.of("email", "^@@.com$", "format.email"))
.keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V7))
.keywords(Arrays.asList(
new NonValidationKeyword("$schema"),
new NonValidationKeyword("$id"),
new AnnotationKeyword("title"),
new AnnotationKeyword("description"),
new AnnotationKeyword("default"),
new NonValidationKeyword("definitions"),
new NonValidationKeyword("$comment"),
new AnnotationKeyword("examples"),
new NonValidationKeyword("then"),
new NonValidationKeyword("else"),
new NonValidationKeyword("additionalItems")))
.build();
JsonSchemaFactory jsonSchemaFactory = new JsonSchemaFactory.Builder()
.defaultMetaSchemaIri(jsonMetaSchemaV7.getIri())
.metaSchema(jsonMetaSchemaV7)
.build();
InputStream schemaStream = JsonSchemaValidatorDemo.class.getResourceAsStream("/schemas/format-schema.json");
SchemaValidatorsConfig config = SchemaValidatorsConfig
.builder()
.failFast(false)
.pathType(PathType.URI_REFERENCE)
.messageSource(new ResourceBundleMessageSource("json-validator-message", "jsv-messages"))
.build();
JsonSchema schema = jsonSchemaFactory.getSchema(schemaStream, config);
// 2. 准备待校验的 JSON 数据
String jsonData = """
{
"mailbox": "@@Acom"
}
""";
// 3. 执行校验
Set<ValidationMessage> validationMessages = schema.validate(new ObjectMapper().readTree(jsonData));
List<com.zzhua.util.ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(com.zzhua.util.ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
resources/schemas/format-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "classpath:/schemas/address-schema.json",
"type": "object",
"properties": {
"mailbox": {
"type": "string",
"format": "email"
}
}
}
3、自定义format校验
JsonSchemaValidatorDemo
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.i18n.ResourceBundleMessageSource;
import com.networknt.schema.resource.PrefixSchemaMapper;
import com.zzhua.util.ValidationResult;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class JsonSchemaValidatorDemo {
public static void main(String[] args) throws JsonProcessingException {
SchemaValidatorsConfig config = SchemaValidatorsConfig
.builder()
.pathType(PathType.URI_REFERENCE)
.errorMessageKeyword("message")
.failFast(false)
// 如果不开启format校验,那么format会被认为只是个注释,而不会去作校验
.formatAssertionsEnabled(true)
.messageSource(new ResourceBundleMessageSource("validate/json-validator-message","jsv-messages"))
.build();
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(
SpecVersion.VersionFlag.V7,
builder -> {
builder.schemaMappers(schemaMapperBuilder -> {
schemaMapperBuilder.add(new PrefixSchemaMapper("http://www.example.com", ""));
});
builder.schemaLoaders(schemaLoaderBuilder -> {
schemaLoaderBuilder.add(new CommonSchemaLoader());
});
builder.metaSchemas(jsonMetaSchemaMap -> {
// 1、JsonSchemaFactory$Builder 构建 JsonSchemaFactory(用于维护了一堆的JsonMetaSchema)
// 2、JsonSchemaVersion 接口 用于提供 JsonMetaSchema 实例,
// 该接口下有 Version7、Version4 等版本,
// 比如 Version7 提供了 属于当前版本的 JsonMetaSchema 对 draft07 的json-schema校验支持
// 3、此处理解的关键代码在 JsonMetaSchema#createKeywordsMap(Map<String, Keyword> kwords, Map<String, Format> formats)
// 4、format规则实际运行参照 FormatKeyword
JsonMetaSchema jsonMetaSchemaV7 = jsonMetaSchemaMap.get(JsonMetaSchema.getV7().getIri());
if (jsonMetaSchemaV7 != null) {
JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(jsonMetaSchemaV7)
// 自定义format校验
.format(new DateTimeFormat())
.build();
jsonMetaSchemaMap.put(JsonMetaSchema.getV7().getIri(), jsonMetaSchema);
}
});
}
);
// 1. 加载主 Schema
InputStream schemaStream = JsonSchemaValidatorDemo.class.getResourceAsStream("/schemas/format-schema.json");
JsonSchema schema = schemaFactory.getSchema(schemaStream, config);
// 2. 准备待校验的 JSON 数据
String jsonData = """
{
"mailbox": "12551",
"expiryTime": "2023-12:25"
}
""";
// 3. 执行校验
Set<ValidationMessage> validationMessages = schema.validate(new ObjectMapper().readTree(jsonData));
List<com.zzhua.util.ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(com.zzhua.util.ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
DateTimeFormat
import com.networknt.schema.ExecutionContext;
import com.networknt.schema.Format;
import org.springframework.util.StringUtils;
import java.time.format.DateTimeFormatter;
public class DateTimeFormat implements Format {
/* thread safe */
public static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public boolean matches(ExecutionContext executionContext, String value) {
try {
if (!StringUtils.hasText(value)) {
return false;
}
dateTimeFormatter.parse(value);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public String getName() {
// schema.json中填写format对应的值,如:format: "datetime"
return "datetime";
}
@Override
public String getMessageKey() {
// 取错误信息的key,如:"format.datetime"
return "format.datetime";
}
}
json-validator-message.properties
format.datetime = {0}: {1} 时间格式需要符合yyyy-MM-dd HH:mm:ss格式哦
5、自定义Validator校验
示例1
(或者,交给自定义Format校验就行了,用不着自定义Validator校验)
这里模仿 ValidatorTypeCode
,EnumsValidator
,MaxLengthValidator
,BaseJsonValidator
,Keyword
,ErrorMessageType
这些原类,代码基本复制这些类,但支持实现自定义Validator校验。
下面检查手机号码是否以指定的前缀开头。
CustomTypeCode
import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.*;
public class CustomTypeCode implements Keyword, ErrorMessageType {
public static final CustomTypeCode CUSTOM_PHONE = new CustomTypeCode("mobilePhone", "202501", MobilePhonePrefixValidator::new);
private final String value;
private final String errorCode;
private final ValidatorFactory validatorFactory;
public CustomTypeCode(String value, String errorCode, ValidatorFactory validatorFactory) {
this.value = value;
this.errorCode = errorCode;
this.validatorFactory = validatorFactory;
}
@Override
public String getValue() {
return value;
}
@Override
public String getErrorCode() {
return this.errorCode;
}
@Override
public String getErrorCodeValue() {
return getValue();
}
@Override
public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
if (this.validatorFactory == null) {
throw new UnsupportedOperationException("No suitable validator for " + getValue());
}
return validatorFactory.newInstance(
schemaLocation, evaluationPath,
schemaNode, parentSchema,
validationContext);
}
}
ValidatorFactory
@FunctionalInterface
interface ValidatorFactory {
JsonValidator newInstance(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext);
}
AbsBaseJsonValidator
仿照BaseJsonValidator实现
import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.*;
import com.networknt.schema.i18n.DefaultMessageSource;
public abstract class AbsBaseJsonValidator extends ValidationMessageHandler implements JsonValidator {
protected final boolean suppressSubSchemaRetrieval;
protected final JsonNode schemaNode;
protected final ValidationContext validationContext;
public AbsBaseJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
JsonSchema parentSchema, ErrorMessageType errorMessageType, Keyword keyword,
ValidationContext validationContext, boolean suppressSubSchemaRetrieval) {
super(errorMessageType,
(validationContext != null && validationContext.getConfig() != null)
? validationContext.getConfig().getErrorMessageKeyword()
: null,
(validationContext != null && validationContext.getConfig() != null)
? validationContext.getConfig().getMessageSource()
: DefaultMessageSource.getInstance(),
keyword,
parentSchema, schemaLocation, evaluationPath);
this.validationContext = validationContext;
this.schemaNode = schemaNode;
this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval;
}
@Override
public SchemaLocation getSchemaLocation() {
return this.schemaLocation;
}
@Override
public JsonNodePath getEvaluationPath() {
return this.evaluationPath;
}
@Override
public String getKeyword() {
return this.keyword.getValue();
}
}
json-validator-message_zh_CN.properties
mobilePhone = 属性: {0}, 手机号码不是以前缀 {1} 开头, 实际的值为 {2}
json-validator-message.properties
mobilePhone = 属性: {0}, 手机号码不是以前缀 {1} 开头, 实际的值为 {2}
MobilePhonePrefixValidator
import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.*;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.Set;
public class MobilePhonePrefixValidator extends AbsBaseJsonValidator {
private static final String KEYWORD = "mobilePhone";
private String prefix = "";
public MobilePhonePrefixValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath,
JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext) {
super(schemaLocation,
evaluationPath,
schemaNode,
parentSchema,
CustomTypeCode.CUSTOM_PHONE,
CustomTypeCode.CUSTOM_PHONE,
validationContext,
false);
System.out.println("进入MobilePhoneValidatorPrefix构造方法");
if (schemaNode != null && schemaNode.isTextual()) {
this.prefix = schemaNode.asText();
}
}
@Override
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
System.out.println("进入mobilePhone校验器");
JsonType nodeType = TypeFactory.getValueNodeType(node, this.validationContext.getConfig());
if (nodeType != JsonType.STRING) {
// ignore no-string typs
return Collections.emptySet();
}
String textVal = node.textValue();
if (!StringUtils.hasText(textVal) || !textVal.startsWith(prefix)) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.failFast(executionContext.isFailFast()).arguments(prefix, textVal).build());
}
return Collections.emptySet();
}
}
JsonSchemaValidatorDemo
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.i18n.ResourceBundleMessageSource;
import com.networknt.schema.resource.PrefixSchemaMapper;
import com.zzhua.util.ValidationResult;
import com.zzhua.validator.CustomTypeCode;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class JsonSchemaValidatorDemo {
public static void main(String[] args) throws JsonProcessingException {
SchemaValidatorsConfig config = SchemaValidatorsConfig
.builder()
.pathType(PathType.URI_REFERENCE)
.errorMessageKeyword("message")
.failFast(false)
// 如果不开启format校验,那么format会被认为只是个注释,而不会去作校验
.formatAssertionsEnabled(true)
.messageSource(new ResourceBundleMessageSource("validate/json-validator-message","jsv-messages"))
.build();
JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(
SpecVersion.VersionFlag.V7,
builder -> {
builder.schemaMappers(schemaMapperBuilder -> {
schemaMapperBuilder.add(new PrefixSchemaMapper("http://www.example.com", ""));
});
builder.schemaLoaders(schemaLoaderBuilder -> {
schemaLoaderBuilder.add(new CommonSchemaLoader());
});
builder.metaSchemas(jsonMetaSchemaMap -> {
// 1、JsonSchemaFactory$Builder 构建 JsonSchemaFactory(用于维护了一堆的JsonMetaSchema)
// 2、JsonSchemaVersion 接口 用于提供 JsonMetaSchema 实例,
// 该接口下有 Version7、Version4 等版本,
// 比如 Version7 提供了 属于当前版本的 JsonMetaSchema 对 draft07 的json-schema校验支持
// 3、此处理解的关键代码在 JsonMetaSchema#createKeywordsMap(Map<String, Keyword> kwords, Map<String, Format> formats)
// 4、format规则实际运行参照 FormatKeyword
JsonMetaSchema jsonMetaSchemaV7 = jsonMetaSchemaMap.get(JsonMetaSchema.getV7().getIri());
if (jsonMetaSchemaV7 != null) {
Set<String> keywordsSet = jsonMetaSchemaV7.getKeywords().keySet();
System.out.println("keywordsSet#size: " + keywordsSet.size() + ", keywordsSet: " + keywordsSet);
JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(jsonMetaSchemaV7)
// 自定义format校验
.format(new DateTimeFormat())
.keywords(List.of(CustomTypeCode.CUSTOM_PHONE))
.build();
jsonMetaSchemaMap.put(JsonMetaSchema.getV7().getIri(), jsonMetaSchema);
}
});
}
);
// 1. 加载主 Schema
InputStream schemaStream = JsonSchemaValidatorDemo.class.getResourceAsStream("/schemas/format-schema.json");
JsonSchema schema = schemaFactory.getSchema(schemaStream, config);
// 2. 准备待校验的 JSON 数据
String jsonData = """
{
"phone": "5662273"
}
""";
// 3. 执行校验
Set<ValidationMessage> validationMessages = schema.validate(new ObjectMapper().readTree(jsonData));
List<com.zzhua.util.ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(com.zzhua.util.ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
示例2(推荐)
JsonSchemaValidatorDemo2
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.*;
import com.networknt.schema.i18n.ResourceBundleMessageSource;
import com.zzhua.util.ValidationResult;
import org.springframework.util.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class JsonSchemaValidatorDemo2 {
public static final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource("validate/json-validator-message", "jsv-messages");
public static void main(String[] args) throws JsonProcessingException {
JsonMetaSchema jsonMetaSchemaV7 = JsonMetaSchema.builder(SchemaId.V7)
.specification(SpecVersion.VersionFlag.V7)
.idKeyword("$id")
.formats(Formats.DEFAULT)
.keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V7))
.keywords(Arrays.asList(
new NonValidationKeyword("$schema"),
new NonValidationKeyword("$id"),
new AnnotationKeyword("title"),
new AnnotationKeyword("description"),
new AnnotationKeyword("default"),
new NonValidationKeyword("definitions"),
new NonValidationKeyword("$comment"),
new AnnotationKeyword("examples"),
new NonValidationKeyword("then"),
new NonValidationKeyword("else"),
new NonValidationKeyword("additionalItems")))
.keyword(new GroovyKeyword())
.build();
JsonSchemaFactory jsonSchemaFactory = new JsonSchemaFactory.Builder()
.defaultMetaSchemaIri(jsonMetaSchemaV7.getIri())
.metaSchema(jsonMetaSchemaV7)
.build();
InputStream schemaStream = JsonSchemaValidatorDemo2.class.getResourceAsStream("/schemas/format-schema.json");
SchemaValidatorsConfig config = SchemaValidatorsConfig
.builder()
.failFast(false)
.pathType(PathType.URI_REFERENCE)
.messageSource(messageSource)
.build();
JsonSchema schema = jsonSchemaFactory.getSchema(schemaStream, config);
String jsonData = """
{
"lang": {
"script": "222"
}
}
""";
Set<ValidationMessage> validationMessages = schema.validate(new ObjectMapper().readTree(jsonData));
List<ValidationResult> results = new ArrayList<>();
for (ValidationMessage validationMessage : validationMessages) {
results.add(ValidationResult.builder()
.type(validationMessage.getType())
.errMsg(validationMessage.getMessage())
.propertyPath(deducePropertyPath(validationMessage))
.build());
}
for (ValidationResult validationResult : results) {
System.out.println("-------------------");
System.out.println("校验的关键字: " + validationResult.getType());
System.out.println("校验的属性: " + validationResult.getPropertyPath());
System.out.println("校验的错误提示: " + validationResult.getErrMsg());
}
}
private static String deducePropertyPath(ValidationMessage validationMessage) {
// 推断属性名
String property = validationMessage.getProperty();
if (StringUtils.hasText(property)) {
return property;
}
JsonNodePath instanceLocation = validationMessage.getInstanceLocation();
if (instanceLocation != null) {
return instanceLocation.toString();
}
JsonNodePath evaluationPath = validationMessage.getEvaluationPath();
if (evaluationPath != null) {
return evaluationPath.toString();
}
return "unknown";
}
}
GroovyKeyword
import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import java.util.*;
@Slf4j
public class GroovyKeyword extends AbstractKeyword {
public GroovyKeyword() {
super("groovy");
}
@Override
public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
String configVal = schemaNode.asText();
return new AbstractJsonValidator(schemaLocation, evaluationPath, this, schemaNode) {
@Override
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
log.info("GroovyKeyword Validator校验");
log.info("executionContext: {}", executionContext);
log.info("node: {}", node);
log.info("rootNode: {}", rootNode);
log.info("instanceLocation: {}", instanceLocation);
log.info("获取到 SchemaValidatorsConfig: {}, 从这可以拿到各种配置", validationContext.getConfig());
log.info("configVal: {}", configVal);
if (!node.isTextual()) {
// ignore no-string typs
return Collections.emptySet();
}
String text = node.asText();
if (StringUtils.hasText(text)) {
if (!Objects.equals(configVal, text)) {
// 获取提示消息
Locale locale = Optional.ofNullable(executionContext.getExecutionConfig().getLocale()).orElse(Locale.ROOT);
String tipMessage = JsonSchemaValidatorDemo2.messageSource.getMessage("groovy", locale, configVal, text);
return Collections.singleton(
ValidationMessage.builder()
.message(tipMessage)
.evaluationPath(instanceLocation)
.type("groovy")
.build()
);
}
}
return Collections.emptySet();
}
};
}
}
resources/schemas/format-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "classpath:/schemas/address-schema.json",
"type": "object",
"properties": {
"lang": {
"properties": {
"script": {
"groovy": "1+1"
}
}
}
}
}
json-validator-message.properties
groovy = 与预设的脚本不一致, 您设置的是:{1}, 应当为:{0}
json-validator-message_zh_CN.properties
groovy = 中文 - 与预设的脚本不一致, 您设置的是:{1}, 应当为:{0}
import com.networknt.schema.PathType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Arrays;
import java.util.List;
@Data
@ConfigurationProperties(prefix = "schema.validation")
public class SchemaValidationProperties {
private Json json;
@Data
public static class Json {
private String prefixSchema = "http://www.example.com";
private String replacement = "";
private String schemaLocation = "classpath*:schemas/json/**/*.json";
private boolean failFast = false;
private List<String> baseNames = Arrays.asList("validate/json-validator-message","jsv-messages");
private String errorMessageKeyword = "message";
private PathType pathType = PathType.URI_REFERENCE;
private boolean preloadJsonSchema = true;
}
}
import com.networknt.schema.*;
import com.networknt.schema.i18n.ResourceBundleMessageSource;
import com.zzhua.core.JsonSchemaFactoryBean;
import com.zzhua.core.JsonSchemaFactoryCustomizer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableConfigurationProperties(SchemaValidationProperties.class)
public class JsonSchemaConfiguration {
private SchemaValidationProperties.Json json;
private List<JsonSchemaFactoryCustomizer> factoryCustomizers;
private List<Format> formats;
private List<Keyword> keywords;
public JsonSchemaConfiguration(SchemaValidationProperties schemaValidationProperties,
ObjectProvider<List<JsonSchemaFactoryCustomizer>> factoryCustomizers) {
this.json = schemaValidationProperties.getJson();
this.factoryCustomizers = factoryCustomizers.getIfAvailable();
}
@Autowired
public void setFormats(List<Format> formats) {
this.formats = formats;
}
@Autowired
public void setKeywords(List<Keyword> keywords) {
this.keywords = keywords;
}
@Bean
@ConditionalOnMissingBean(ResourceBundleMessageSource.class)
public ResourceBundleMessageSource defaultResourceBundleMessageSource() {
List<String> baseNameList = new ArrayList<>();
if (CollectionUtils.isEmpty(json.getBaseNames())) {
// 添加默认的语言包
baseNameList.add("jsv-message");
}
String[] baseNames = baseNameList.toArray(new String[0]);
return new ResourceBundleMessageSource(baseNames);
}
@Bean
@ConditionalOnMissingBean(SchemaValidatorsConfig.class)
public SchemaValidatorsConfig defaultSchemaValidatorsConfig(ResourceBundleMessageSource resourceBundleMessageSource) {
return SchemaValidatorsConfig
.builder()
.pathType(json.getPathType())
.errorMessageKeyword(json.getErrorMessageKeyword())
.formatAssertionsEnabled(true)
.failFast(json.isFailFast())
.preloadJsonSchema(json.isPreloadJsonSchema())
.messageSource(resourceBundleMessageSource)
.cacheRefs(true)
.build();
}
@Bean
@ConditionalOnMissingBean(JsonSchemaFactory.class)
public JsonSchemaFactoryBean jsonSchemaFactoryV7(SchemaValidatorsConfig schemaValidatorsConfig) {
JsonSchemaFactoryBean jsonSchemaFactoryBean = new JsonSchemaFactoryBean();
jsonSchemaFactoryBean.setSchemaValidatorsConfig(schemaValidatorsConfig);
jsonSchemaFactoryBean.setJson(json);
jsonSchemaFactoryBean.setFormats(formats);
jsonSchemaFactoryBean.setKeywords(keywords);
jsonSchemaFactoryBean.setCustomizers(factoryCustomizers);
return jsonSchemaFactoryBean;
}
}
public interface JsonSchemaFactoryCustomizer {
void customize(JsonSchemaFactory jsonSchemaFactory);
}
import com.networknt.schema.*;
import com.networknt.schema.resource.PrefixSchemaMapper;
import com.zzhua.SchemaValidationProperties;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JsonSchemaFactoryBean implements FactoryBean<JsonSchemaFactory>, InitializingBean {
private boolean initialized = false;
private JsonSchemaFactory jsonSchemaFactory;
private SchemaValidatorsConfig schemaValidatorsConfig;
private SchemaValidationProperties.Json json;
private List<Format> formats;
private List<Keyword> keywords;
private List<JsonSchemaFactoryCustomizer> factoryCustomizers;
public void setSchemaValidatorsConfig(SchemaValidatorsConfig schemaValidatorsConfig) {
this.schemaValidatorsConfig = schemaValidatorsConfig;
}
public void setFormats(List<Format> formats) {
this.formats = formats;
}
public void setKeywords(List<Keyword> keywords) {
this.keywords = keywords;
}
public void setCustomizers(List<JsonSchemaFactoryCustomizer> customizers) {
this.factoryCustomizers = customizers;
}
public void setJson(SchemaValidationProperties.Json json) {
this.json = json;
}
@Override
public JsonSchemaFactory getObject() {
return this.jsonSchemaFactory;
}
@Override
public Class<?> getObjectType() {
return JsonSchemaFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
// 避免可能存在的多次初始化调用(比如编码错误)
if (!initialized) {
this.jsonSchemaFactory = buildJsonSchemaFactory();
initialized = true;
}
}
private JsonSchemaFactory buildJsonSchemaFactory() {
JsonMetaSchema jsonMetaSchemaV7 = JsonMetaSchema.builder(SchemaId.V7)
.specification(SpecVersion.VersionFlag.V7)
.idKeyword("$id")
// 添加内置的format校验
.formats(Formats.DEFAULT)
// 添加自定义format
.formats(formats)
// 添加v7的关键字
.keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V7))
.keywords(Arrays.asList(
new NonValidationKeyword("$schema"),
new NonValidationKeyword("$id"),
new AnnotationKeyword("title"),
new AnnotationKeyword("description"),
new AnnotationKeyword("default"),
new NonValidationKeyword("definitions"),
new NonValidationKeyword("$comment"),
new AnnotationKeyword("examples"),
new NonValidationKeyword("then"),
new NonValidationKeyword("else"),
new NonValidationKeyword("additionalItems"))
)
.keywords(keywords)
.build();
JsonSchemaFactory jsonSchemaFactory = new JsonSchemaFactory.Builder()
.defaultMetaSchemaIri(jsonMetaSchemaV7.getIri())
.metaSchema(jsonMetaSchemaV7)
.enableSchemaCache(true)
.schemaMappers(builder ->
builder.add(new PrefixSchemaMapper(json.getPrefixSchema(), json.getReplacement()))
)
.schemaLoaders(builder ->
builder.add(new CommonSchemaLoader(json.getSchemaLocation()))
)
.build();
if (!ObjectUtils.isEmpty(factoryCustomizers)) {
factoryCustomizers.forEach(customizer -> customizer.customize(jsonSchemaFactory));
}
// 初始化schema
// 匹配 schemas/json 目录及其子目录下的所有文件
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = new Resource[0];
try {
resources = resolver.getResources("classpath*:schemas/json/**/*.*");
// 存储文件相对路径与资源的映射
Map<String, Resource> fileMap = new HashMap<>();
for (Resource resource : resources) {
if (!resource.exists() || !resource.isReadable()) continue;
// 获取完整的类路径URI
String uri = resource.getURI().toString();
// 提取 schemas/json/ 之后的部分作为相对路径
String pattern = "schemas/json/";
int index = uri.indexOf(pattern);
if (index != -1) {
// 截取相对路径并标准化
String relativePath = uri.substring(index + pattern.length())
.replaceAll("/+", "/"); // 处理多余斜杠
// 排除目录(Spring 默认不会返回目录,但二次验证)
if (!relativePath.endsWith("/")) {
fileMap.put(relativePath, resource);
}
}
}
if (!CollectionUtils.isEmpty(fileMap)) {
JsonSchemaCache schemaCache = new JsonSchemaCache();
for (Map.Entry<String, Resource> entry : fileMap.entrySet()) {
JsonSchema schema = jsonSchemaFactory.getSchema(entry.getValue().getInputStream(), schemaValidatorsConfig);
schemaCache.add(entry.getKey(), schema);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return jsonSchemaFactory;
}
public static void main(String[] args) throws IOException {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 匹配 schemas/json 目录及其子目录下的所有文件
Resource[] resources = resolver.getResources("classpath*:schemas/json/**/*.*");
// 存储文件相对路径与资源的映射
Map<String, Resource> fileMap = new HashMap<>();
for (Resource resource : resources) {
if (!resource.exists() || !resource.isReadable()) continue;
// 获取完整的类路径URI
String uri = resource.getURI().toString();
// 提取 schemas/json/ 之后的部分作为相对路径
String pattern = "schemas/json/";
int index = uri.indexOf(pattern);
if (index != -1) {
// 截取相对路径并标准化
String relativePath = uri.substring(index + pattern.length())
.replaceAll("/+", "/"); // 处理多余斜杠
// 排除目录(Spring 默认不会返回目录,但二次验证)
if (!relativePath.endsWith("/")) {
fileMap.put(relativePath, resource);
}
}
}
// 打印结果
fileMap.forEach((path, res) -> {
System.out.println("相对路径: " + path);
System.out.println("完整路径: " + res);
System.out.println("-----");
});
}
}
public class JsonSchemaCache {
public ConcurrentHashMap<String, JsonSchema> schemaMap = new ConcurrentHashMap<>();
public void add(String pathKey, JsonSchema jsonSchema) {
schemaMap.put(pathKey, jsonSchema);
}
public JsonSchema get(String pathKey) {
return schemaMap.get(pathKey);
}
}
import com.networknt.schema.AbsoluteIri;
import com.networknt.schema.resource.InputStreamSource;
import com.networknt.schema.resource.SchemaLoader;
import com.zzhua.SchemaValidationProperties;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.function.Supplier;
public class CommonSchemaLoader implements SchemaLoader {
private final Supplier<ClassLoader> classLoaderSource;
private final String schemaLocation;
/**
* Constructor.
*/
public CommonSchemaLoader(String schemaLocation) {
this(CommonSchemaLoader::getClassLoader, schemaLocation);
}
public CommonSchemaLoader(Supplier<ClassLoader> classLoaderSource, String schemaLocation) {
this.classLoaderSource = classLoaderSource;
this.schemaLocation = schemaLocation;
}
@Override
public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
String iri = absoluteIri != null ? absoluteIri.toString() : "";
String name = null;
if (iri.startsWith("/schemas")) {
name = iri.substring(1);
}
if (name != null) {
ClassLoader classLoader = this.classLoaderSource.get();
String resource = name;
return () -> {
InputStream result = classLoader.getResourceAsStream(resource);
if (result == null) {
result = classLoader.getResourceAsStream(resource.substring(1));
}
if (result == null) {
throw new FileNotFoundException(iri);
}
return result;
};
}
return null;
}
protected static ClassLoader getClassLoader() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = SchemaLoader.class.getClassLoader();
}
return classLoader;
}
}