Yaml校验
1. 核心目的和研究方向
项目中用到了yaml,例如:
# 字符串
name: jack
# 0-200岁,正数
age: 18
yaml需要客户填写,但是客户可能不会按照规则填写,所以需要定义一个schema文件来校验,例如:
name:
type: string
age:
type: integer
min: 0
max: 200
本文涉及
- 是否存在这样的schema?以及schema如何编写?
- 如何使用Java项目进行yaml文件的验证
2. 是否存在这样的schema?以及schema如何编写?
2.1 是否存在这样的Schema
存在,以下2种
名字 | 说明 | 优点 | 缺点 | 参考文档 | Java库 |
---|---|---|---|---|---|
JsonSchema | 用于校验标准Json数据格式的一种Schema,简单的yaml可以使用JsonSchema来进行校验。 | 标准丰富、支持的库多、支持的语言多 | 有些Yaml的扩展语法、JsonSchema可能无法验证 | https://json-schema.org/learn/getting-started-step-by-step | |
YamlSchema | 专门用于校验Yaml数据格式的Schema | 原生支持Yaml、支持扩展语法 | 支持的编程语言少、库少 | https://asdf-standard.readthedocs.io/en/1.0.3/schemas/yaml_schema.html |
2.2 Schema如何编写
以 com.networknt.json-schema-validator
库为例子,
以如下文件为例子:
# 数字类型,范围为0-50
number_field: 25
# 字符串类型
string_field: "some string"
# 数组类型
array_field: ["item1", "item2"]
# 对象类型
people:
# name必须填
name: jack
# 规定为数字字母下划线
alphanumeric: "abc123_"
# 枚举-单选
single_choice: "option1"
# 枚举-多选
multi_choice: ["option1", "option2"]
2.2.1 JsonSchema
{
"type": "object",
"properties": {
"number_field": {
"type": "integer",
"minimum": 0,
"maximum": 50
},
"string_field": {
"type": "string"
},
"array_field": {
"type": "array",
"items": {
"type": "string"
}
},
"people": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
},
"alphanumeric": {
"type": "string",
"pattern": "^[a-zA-Z0-9_]+$"
},
"single_choice": {
"type": "string",
"enum": ["option1", "option2", "option3"]
},
"multi_choice": {
"type": "array",
"items": {
"type": "string",
"enum": ["option1", "option2", "option3"]
}
}
},
"required": ["alphanumeric", "single_choice", "multi_choice", "array_field", "number_field", "string_field", "people"],
"errorMessage": {
"required": "缺少必填字段:''{0}/{1}''",
"enum": "''{0}''的值必须是:''{1}''中的一个",
"type": "''{0}''的类型必须是: ''{2}'',但你提供的是''{1}''"
}
}
2.2.2 YamlSchema
type: object
properties:
alphanumeric:
type: string
pattern: "^[a-zA-Z0-9_]+$"
errorMessage:
pattern: "alphanumeric 字段只允许包含字母、数字和下划线"
single_choice:
type: string
enum: ["option1", "option2", "option3"]
multi_choice:
type: array
items:
type: string
enum: ["option1", "option2", "option3"]
errorMessage:
enum: "multi_choice 数组的每一项必须是 option1, option2, option3 其中之一"
errorMessage:
type: "multi_choice 字段必须是一个数组"
array_field:
type: array
items:
type: string
errorMessage: "array_field 字段必须是一个字符串数组"
number_field:
type: integer
minimum: 0
maximum: 50
errorMessage: "number_field 字段的值必须是 0 到 50 之间的整数"
string_field:
type: string
errorMessage: "string_field 字段必须是一个字符串"
people:
type: object
properties:
name:
type: string
required:
- name
errorMessage:
required:
name: "people.name 字段是必填项"
required:
- alphanumeric
- single_choice
- multi_choice
- array_field
- number_field
- string_field
- people
errorMessage:
"required": "缺少必填字段:''{0}/{1}''"
"enum": "''{0}''的值必须是:''{1}''中的一个"
"type": "''{0}''的类型必须是: ''{2}'',但你提供的是''{1}''"
3. 如何使用Java项目进行yaml文件的验证
3.1 导入Maven包
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<!--注意替换成你的版本/最新版本-->
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<!--注意替换成你的版本/最新版本-->
<version>2.12.5</version>
</dependency>
3.2 准备测试数据
data.yml
valid_data:
alphanumeric: "abc123_"
single_choice: "option1"
multi_choice: ["option1", "option2"]
array_field: ["item1", "item2"]
number_field: 25
string_field: "some string"
invalid_data:
alphanumeric: "abc 123!" # 包含无效字符
single_choice: "invalid_option" # 不在选项列表中
multi_choice: ["option1", 123] # 包含无效类型
array_field: "not an array" # 不是数组
number_field: 55 # 超出范围
string_field: 123 # 不是字符串
people:
age: 25 # 不是对象
address:
city: "London"
3.3 编写代码
3.3.1 使用JsonSchema
3.3.1.2 编写schema
schema.json
{
"type": "object",
"properties": {
"alphanumeric": {
"type": "string",
"pattern": "^[a-zA-Z0-9_]+$"
},
"single_choice": {
"type": "string",
"enum": ["option1", "option2", "option3"]
},
"people": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"address": {
"type": "object",
"province": {
"type": "string"
},
"required": ["province"]
}
},
"required": ["name", "address"]
},
"multi_choice": {
"type": "array",
"items": {
"type": "string",
"enum": ["option1", "option2", "option3"]
}
},
"array_field": {
"type": "array",
"items": {
"type": "string"
}
},
"number_field": {
"type": "integer",
"minimum": 0,
"maximum": 50
},
"string_field": {
"type": "string"
}
},
"required": ["alphanumeric", "single_choice", "multi_choice", "array_field", "number_field", "string_field", "people"],
"errorMessage": {
"required": "缺少必填字段:''{0}/{1}''",
"enum": "''{0}''的值必须是:''{1}''中的一个",
"type": "''{0}''的类型必须是: ''{2}'',但你提供的是''{1}''"
}
}
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.util.Set;
public class YamlValidator {
public static void main(String[] args) throws IOException {
// 读取和解析 YAML数据 文件
JsonNode yamlNode = new ObjectMapper(new YAMLFactory())
.readTree(new ClassPathResource("data.yml").getInputStream());
// 读取和解析 JSON Schema 文件
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012)
.getSchema(new ClassPathResource("schema.json").getInputStream(), SchemaValidatorsConfig.builder()
.errorMessageKeyword("errorMessage")
.build());
// 校验其中一个数据,你也可以校验valid_data
// 另外,如果你的data文件,是一整个yaml,不区分分数据块,那么你无需yamlNode.get("..."),直接使用yamlNode自身即可
Set<ValidationMessage> validDataErrors = schema.validate(yamlNode.get("invalid_data"));
if (validDataErrors.isEmpty()) {
System.out.println(dataName + " 通过校验");
} else {
System.out.println(dataName + " 校验错误:");
validDataErrors.forEach(error -> System.out.println(error.getMessage()));
}
}
}
3.3.1 使用YamlSchema
3.3.1.2 编写schema
schema.yaml
type: object
properties:
alphanumeric:
type: string
pattern: "^[a-zA-Z0-9_]+$"
errorMessage:
pattern: "alphanumeric 字段只允许包含字母、数字和下划线"
single_choice:
type: string
enum: ["option1", "option2", "option3"]
multi_choice:
type: array
items:
type: string
enum: ["option1", "option2", "option3"]
errorMessage:
enum: "multi_choice 数组的每一项必须是 option1, option2, option3 其中之一"
errorMessage:
type: "multi_choice 字段必须是一个数组"
array_field:
type: array
items:
type: string
errorMessage: "array_field 字段必须是一个字符串数组"
number_field:
type: integer
minimum: 0
maximum: 50
errorMessage: "number_field 字段的值必须是 0 到 50 之间的整数"
string_field:
type: string
errorMessage: "string_field 字段必须是一个字符串"
people:
type: object
properties:
name:
type: string
required:
- name
errorMessage:
required:
name: "people.name 字段是必填项"
required:
- alphanumeric
- single_choice
- multi_choice
- array_field
- number_field
- string_field
- people
errorMessage:
"required": "缺少必填字段:''{0}/{1}''"
"enum": "''{0}''的值必须是:''{1}''中的一个"
"type": "''{0}''的类型必须是: ''{2}'',但你提供的是''{1}''"
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.networknt.schema.InputFormat;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import com.networknt.schema.serialization.JsonNodeReader;
import java.util.Set;
public class YamlTest2 {
public static void main(String[] args) {
// schema文件内容
String schemaData2 = "...上述的schema文件内容";
// 数据内容,Java17的多行字符串写法
String inputData = """
alphanumeric: '1)'
single_choice: option5
multi_choice: 1
people:
age: 10
""";
// 构建yamlSchema数据读取器
JsonNodeReader jsonNodeReader = JsonNodeReader.builder().yamlMapper(new ObjectMapper(new YAMLFactory())).build();
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012,
builder -> builder.jsonNodeReader(jsonNodeReader).build());
SchemaValidatorsConfig config = SchemaValidatorsConfig.builder()
.errorMessageKeyword("errorMessage")
.build();
// 读取schema
JsonSchema schema = factory.getSchema(schemaData2, InputFormat.YAML, config);
// 校验数据
Set<ValidationMessage> validDataErrors = schema.validate(inputData, InputFormat.YAML);
// 打印校验失败的信息
validDataErrors.forEach(error -> System.out.println(error.getMessage()));
}
}