Appearance
JSON Schema
本文介绍JSON Schema相关概念及实现。
1. 概述
1.1 JSON概述
要介绍JSON Schema,那就要先介绍什么是JSON。
JSON,全称JavaScript Object Notation,是一种数据交换格式,JSON定义了以下几种有效形式:
null:null值
JSONnullboolean:布尔值
jsontrue false数字
json42 1.23字符串:用双引号括住的字符串
json"a string"数组:用中括号括住的多个元素,数组允许包含不同类型的元素
json[1, 2, 3, "a string", true]对象:键值对
json{ "name": "zs", "age": 18 }
1.2 JSON Schema概述
JSON Schema定义了 JSON 数据的结构、内容和约束规则。也就是说,JSON Schema规定了一个合格的JSON应该长什么样。并且,JSON Schema也是用JSON编写的。
例如,假设现在规定表达个人信息的JSON,需要一个JSON对象,并且具有两个字段:name和age,其中name值的数据类型为字符串,age值的数据类型为数字。那么,使用JSON Schema,可以描述如下:
json
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "number"
}
}
}以上只是一个简单案例,用于描述JSON Schema的作用,细节后续章节介绍。
目前,最新的JSON Schema版本是 Draft 2020-12。
2. 规范
2.1 基础
首先,JSON Schema是一个JSON对象:
json
{}如果JSON Schema是一个空对象,那么表示任何有效的JSON都是满足要求的。
当然,实际使用中需要限制JSON的类型,我们可以使用type关键字,例如:
json
{
"type": "string"
}这样,使用这个JSON Schema只会校验字符串JSON为符合要求的,其他类型的JSON都是错误的。
我们也可以为type指定一个数组,例如:
json
{
"type": ["string", "number"]
}这样,如果JSON为字符串或数字,都能通过这个JSON Schema的校验。
type的有效取值:array, boolean, integer, number, null, object, string。
由于JSON Schema有多个版本,所以我们可以使用$schema来限制这个JSON Schema使用哪个版本进行校验,例如:
json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string"
}2.2 type字段
本小节介绍type的不同取值。
2.2.1 数值类型
在JSON Schema中,数值类型有两种:
number:限制类型为数字;integer:限制类型为整数;
当限制type为数值类型时,还可以使用以下关键字,指定更严格的限制:
minimum:数字应大于等于minimum;maximum:数字应小于等于maximum;exclusiveMinimum:数字应大于exclusiveMinimum;exclusiveMaximum:数字应小于exclusiveMaximum;multipleOf:数字应是multipleOf的倍数;
例如:
json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "integer",
"exclusiveMinimum": 10,
"exclusiveMaximum": 100,
"multipleOf": 3
}表示只接受(10, 100)之间3的正整数倍数。


2.2.2 字符串类型
当限制type为字符串类型时,还可以使用以下关键字,指定更严格的限制:
minLength:字符串的最小长度(包含);maxLength:字符串的最大长度(包含);pattern:字符串需要符合的正则表达式;正则表达式规则:https://json-schema.org/understanding-json-schema/reference/regular_expressions;format:字符串需要符合的格式;内置格式:https://json-schema.org/understanding-json-schema/reference/type#format;
例如:
json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
"minLength": 5,
"maxLength": 8,
"pattern": "^[A-Z]+$"
}表示只接受5-8位的大写字母字符串。


再例如:
json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
"format": "date"
}使用了"format": "date",表示只接受格式为yyyy-MM-dd的日期字符串。

2.2.3 数组类型
当限制type为数组类型时,还可以使用以下关键字,指定更严格的限制:
items:如果要限制数组元素的类型,那么可以使用items,例如:json{ "type": "array", "items": { "type": "number" } }这样表示只接受数值数组。
prefixItems:在JSON中,数组元素可以是不同类型的,在Python中,这样的结构称为元组(tuple),JSON Schema可以使用prefixItems来限制数组中每个位置元素的类型,例如:json{ "type": "array", "prefixItems": [ { "type": "number" }, { "type": "string" } ] }表示数组第一个元素需要为数字,第二个元素需要为字符串,JSON Schema允许之后添加其他元素。
并且,JSON Schema允许元组中元素数量小于
prefixItems中定义的数量。当
items和prefixItems结合时,表示prefixItems后添加的元素类型必须满足items的限制。unevaluatedItems:用于控制是否允许在元组末尾添加多余的元素,默认值为true;当
unevaluatedItems和items同时存在时,unevaluatedItems被忽略不起作用。minItems:用来限制数组中的元素个数最小值,需要大于等于minItems;maxItems:用来限制数组中的元素个数最大值,需要小于等于maxItems;uniqueItems:用来限制数组中的元素是否能相等,默认值为false表示可以相等;contains:与items要求数组中的所有元素都为同一种类型不同,contains只要求数组中至少有一个元素满足contains指定的类型即可;minContains:要求数组中满足contains限制的的元素最小数量(包含);maxContains:要求数组中满足contains限制的的元素最大数量(包含);


2.2.4 对象类型
当限制type为对象类型时,还可以使用以下关键字,指定更严格的限制:
properties:用来定义对象中的字段及其限制,properties是一个对象,其字段是要校验的JSON的字段名称,字段值是JSON Schema,例如:json{ "type": "object", "properties":{ "name": {"type":"string"}, "age": {"type": "integer"} } }上面的例子表示,如果JSON对象中出现
name字段,那么name的值必须为字符串,但是,name字段也可以不出现,age字段同理。required:用来定义必须出现的字段,类型字符串数组,例如:json{ "type": "object", "properties":{ "name": {"type":"string"}, "age": {"type": "integer"} }, "required": ["name", "age"] }表示JSON对象必须出现
name和age字段,并且name字段的值为字符串,age字段的值为整数。additionalProperties:是否允许JSON字段中出现未在properties中定义的其他字段,默认值为false表示允许,例如:json"additionalProperties": false除了使用布尔值来限制是否允许出现多余字段,还可以使用对象,来限制多余字段值的类型,例如:
json"additionalProperties": { "type": "string" }minProperties:用来限制JSON对象中字段的最小数量(包含);maxProperties:用来限制JSON对象中字段的最大数量(包含);patternProperties:在properties中,字段名是精准匹配,在patternProperties中,可以使用正则表达式,来表示符合要求的字段名应该满足的要求,例如:json{ "type": "object", "patternProperties": { "^S_": { "type": "string" }, "^I_": { "type": "integer" } } }表示以
S_开头的字段,值应该是字符串;以I_开头的字段,值应该是整数。propertyNames:用来限制JSON对象中字段名称,例如:json"propertyNames": { "pattern": "^[A-Za-z_]*$" }表示字段名称只能是大小写字母和
_。


2.3 枚举和常量
在JSON Schema中,我们可以使用enum来限制值的范围只能是特定几个枚举值,使用const来限制值只能是某一个值。
在enum中,各个值的数据类型可以不同。


2.4 注解和注释
注解和注释对于JSON校验不起任何作用,只是添加一些说明字段,让JSON Schema可读性和维护性更强。
注解相关字段如下:
title:用来提供标题;description:用来提供说明;examples:用来提供一些案例数据;
在JSON中,是无法写注释的,因此JSON Schema定义了一个特殊的字段,用来写注释:$comment。
例如:
json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "enum test",
"description": "只接受特定枚举值",
"examples": [
"NORTH",
"SOURTH",
"WEST",
"EAST"
],
"$comment": "这是注释",
"enum": [
"NORTH",
"SOURTH",
"WEST",
"EAST"
]
}2.2.5 小结
以上简单介绍了一下JSON Schema的基本用法,还有一些其他高级用法,待以后有需要再查阅官方文档学习。
3. 实现
我们以Java为例,介绍如何在Java中引入相关依赖,用来校验JSON是否符合结构要求。
3.1 基本使用
首先引入依赖:
xml
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>3.0.0</version>
</dependency>然后就可以编写代码,用来校验JSON了:
java
@Test
void test(){
// 1. 在代码中定义 JSON Schema
String schemaData = "{\n" +
" \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"name\": {\n" +
" \"type\": \"string\"\n" +
" },\n" +
" \"age\": {\n" +
" \"type\": \"integer\"\n" +
" }\n" +
" },\n" +
" \"required\": [\n" +
" \"name\",\n" +
" \"age\"\n" +
" ]\n" +
"}";
// 2. 在代码中定义要校验的 JSON 数据,注意age不是数字
String inputJson = "{\n" +
" \"name\": \"zs\",\n" +
" \"age\": \"12\"\n" +
"}";
// 3. 初始化 Registry
SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12);
// 4. 获取 Schema
Schema schema = registry.getSchema(schemaData);
// 5. 执行校验,并开启格式断言
List<Error> errorList = schema.validate(inputJson, InputFormat.JSON, executionContext -> {
// 如果没有这一行,JSON Schema 可能会直接忽略 format 关键字,默认情况下,校验引擎默认不强制检查format
executionContext.executionConfig(config -> config.formatAssertionsEnabled(true));
});
// 6. 输出结果
if (errorList.isEmpty()) {
System.out.println("✅ 校验成功!JSON 格式符合规范。");
} else {
System.out.println("❌ 校验失败,发现以下错误:");
errorList.forEach(error -> System.out.println(" - " + error));
}
}结果如下:
txt
❌ 校验失败,发现以下错误:
- /age: 已找到 string,必须是 integer3.2 正则表达式库的选择
JSON Schema 标准规定正则表达式必须符合 JavaScript (ECMA 262) 规范。但 Java 自带的正则引擎(java.util.regex)与 JavaScript 的正则语法在某些高级特性(如:某些零宽断言、命名分组等)上是不完全兼容的。
为了解决这个“方言不同”的问题,networknt 校验器允许通过添加正则表达式解析依赖,把底层的正则处理逻辑换成更符合标准的引擎。
networknt提供了以下两种正则表达式解析依赖:
xml
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>${version.graaljs}</version>
</dependency>
<dependency>
<groupId>org.jruby.joni</groupId>
<artifactId>joni</artifactId>
<version>${version.joni}</version>
</dependency>两个依赖的对比:
| 特性 | GraalJS (推荐) | Joni |
|---|---|---|
| 对应类 | GraalJSRegularExpressionFactory | JoniRegularExpressionFactory |
| 合规性 | 最高(完全符合 JS 标准) | 较高(优于 Java 原生,但略逊于 Graal) |
| 包大小 | 非常大 (~50 MB) | 非常小 (~2 MB) |
| 性能 | 极快(经过高度优化) | 快(Ruby 社区移植版) |
| 适用场景 | 对正则兼容性有极端要求的复杂项目。 | 对包体积敏感,但又想比 Java 原生正则更兼容的项目。 |
使用场景:
如果只是做简单的字符串匹配(比如
^\d{4}$),其实这两个都不需要,直接用 Java 原生的就行,不用引入其他依赖;如果需要校验极其复杂的正则表达式,且包体积不是问题,选 GraalJS;
如果想要折中方案(既想兼容性好一点,又不想包太大),选 Joni;
例如,以使用joni为例。
首先引入依赖:
xml
<dependency>
<groupId>org.jruby.joni</groupId>
<artifactId>joni</artifactId>
<version>2.2.6</version>
</dependency>案例,使用Joni 正则引擎:
java
import com.networknt.schema.*;
import com.networknt.schema.Error;
import com.networknt.schema.regex.JoniRegularExpressionFactory;
import java.util.List;
public class JoniRegexExample {
public static void main(String[] args) {
// 1. 配置使用 Joni 正则引擎
SchemaRegistryConfig config = SchemaRegistryConfig.builder()
.regularExpressionFactory(JoniRegularExpressionFactory.getInstance())
.build();
// 2. 初始化 Registry
SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12,
builder -> builder.schemaRegistryConfig(config));
// 3. 定义 Schema
// 这个正则使用了负向先行断言 (?!...),确保字符串不以 "admin" 开头,
// 且必须包含至少一个数字,长度至少8位。
String schemaData = "{\n" +
" \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"password\": {\n" +
" \"type\": \"string\",\n" +
" \"pattern\": \"^(?!admin)(?=.*\\\\d).{8,}$\"\n" +
" }\n" +
" }\n" +
"}";
Schema schema = registry.getSchema(schemaData);
// 4. 测试数据
String inputJson = "{ \"password\": \"admin12345\" }"; // 会失败,因为以 admin 开头
// 5. 执行校验
List<Error> errors = schema.validate(inputJson, InputFormat.JSON);
if (errors.isEmpty()) {
System.out.println("✅ 校验通过");
} else {
System.out.println("❌ 校验失败:");
errors.forEach(e -> System.out.println(" " + e.getMessage()));
}
}
}参考资料
[1] JSON Schema:https://json-schema.org/
[2] JSON Schema Validator:https://www.jsonschemavalidator.net/