Java REST API中JSON Schema校验及自定义函数调用技术咨询
我来帮你把这些问题理清楚,都是Java REST API里做JSON Schema校验时的常见需求,一个个说:
标准的JSON Schema(比如Draft 7/2019-09/2020-12这些官方版本)本身并没有内置“直接调用自定义函数”的语法——毕竟它是跨语言的通用规范,得保证不同语言的校验器都能兼容。但几乎所有Java的JSON Schema校验库都支持自定义关键字扩展:你可以定义一个专属的关键字(比如customPerilCheck),然后在校验库中给这个关键字绑定你的自定义校验逻辑(也就是你说的自定义函数),最终在Schema里通过这个关键字触发校验。
JSON Schema有官方的演进版本,目前主流且支持度最高的是:
- Draft 7:兼容性最广,大部分库都完美支持
- Draft 2019-09:新增了一些模块化、元数据相关的特性
- Draft 2020-12:最新稳定版,优化了部分语法和功能
编写Schema时必须通过$schema字段声明遵循的版本,比如:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://example.com/example.json", "type": "object" }
基础的类型约束、格式校验、枚举、必填项等都要遵循对应版本的官方规则,自定义逻辑只能通过扩展关键字实现,不能直接在Schema里写函数代码。
Jackson提供了jackson-module-jsonSchema模块,但它的核心能力是从Java对象生成JSON Schema,原生并不支持“用Schema校验JSON数据”,也没有内置的自定义校验扩展。
如果想用Jackson做校验,通常需要结合专门的JSON Schema校验库,或者自己扩展Jackson的校验逻辑。更推荐直接用专门的校验库,比如:
com.networknt/json-schema-validator:轻量高性能,扩展自定义关键字很方便org.everit.json.schema:老牌稳定库,支持丰富的扩展场景
下面用com.networknt/json-schema-validator库做演示,它的扩展流程比较清晰:
1. 添加Maven依赖
<dependency> <groupId>com.networknt</groupId> <artifactId>json-schema-validator</artifactId> <version>1.0.80</version> <!-- 建议用最新稳定版 --> </dependency>
2. 编写带自定义关键字的Schema
给peril字段添加自定义关键字customPerilValidator,用来触发你的自定义校验逻辑:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "http://example.com/example.json", "type": "object", "properties": { "peril": { "type": "string", "customPerilValidator": true // 这里的值可以是配置参数,比如允许的风险列表 } }, "required": ["peril"] }
3. 实现自定义校验器(你的自定义函数逻辑)
import com.networknt.schema.JsonValidator; import com.networknt.schema.ValidationMessage; import java.util.Set; public class CustomPerilValidator implements JsonValidator { // 自定义校验逻辑:比如校验peril是否属于允许的风险类型 private static final Set<String> ALLOWED_PERILS = Set.of("fire", "flood", "earthquake"); @Override public Set<ValidationMessage> validate(Object node, com.networknt.schema.JsonSchema parentSchema, String instanceLocation, com.networknt.schema.ExecutionContext executionContext) { String perilValue = node.toString(); if (!ALLOWED_PERILS.contains(perilValue)) { return Set.of(ValidationMessage.builder() .instanceLocation(instanceLocation) .keyword("customPerilValidator") .message("无效的风险类型:" + perilValue + ",允许的值为:" + String.join(", ", ALLOWED_PERILS)) .build()); } return Set.of(); // 校验通过返回空集合 } }
4. 注册关键字并执行校验
import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; import java.util.Set; public class SchemaValidationDemo { public static void main(String[] args) throws Exception { // 加载Schema文件 ObjectMapper mapper = new ObjectMapper(); InputStream schemaStream = SchemaValidationDemo.class.getResourceAsStream("/example-schema.json"); JsonNode schemaNode = mapper.readTree(schemaStream); // 创建Schema工厂并注册自定义关键字 JsonSchemaFactory factory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012)) .addKeyword(new com.networknt.schema.Keyword("customPerilValidator") { @Override public JsonValidator newValidator(String keyword, JsonNode schemaNode, com.networknt.schema.JsonSchema parentSchema, com.networknt.schema.ValidationContext validationContext) { return new CustomPerilValidator(); } }) .build(); // 编译Schema并校验输入JSON JsonSchema schema = factory.getSchema(schemaNode); String inputJson = "{\"peril\": \"fire\"}"; // 合法输入 // String inputJson = "{\"peril\": \"hurricane\"}"; // 非法输入,会触发校验错误 JsonNode inputNode = mapper.readTree(inputJson); Set<ValidationMessage> errors = schema.validate(inputNode); // 输出校验结果 if (errors.isEmpty()) { System.out.println("JSON校验通过!"); } else { errors.forEach(error -> System.out.println("校验错误:" + error.getMessage())); } } }
- 不同校验库的扩展方式略有差异,比如
org.everit.json.schema是通过SchemaValidatorBuilder注册自定义关键字 - 自定义关键字名称要避免和JSON Schema标准关键字冲突
- 如果需要给自定义函数传递参数,可以把
customPerilValidator的值设为对象(比如{"allowedValues": ["fire", "flood"]}),然后在校验器里读取这个参数
内容的提问来源于stack exchange,提问作者user3897533

