Skip to content

JSON Schema

本文介绍JSON Schema相关概念及实现。

1. 概述

1.1 JSON概述

要介绍JSON Schema,那就要先介绍什么是JSON。

JSON,全称JavaScript Object Notation,是一种数据交换格式,JSON定义了以下几种有效形式:

  • null:null值

    JSON
    null
  • boolean:布尔值

    json
    true
    false
  • 数字

    json
    42
    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对象,并且具有两个字段:nameage,其中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的正整数倍数。

image-20260127175121645

image-20260127175136861

2.2.2 字符串类型

当限制type为字符串类型时,还可以使用以下关键字,指定更严格的限制:

例如:

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "string",
  "minLength": 5,
  "maxLength": 8,
  "pattern": "^[A-Z]+$"
}

表示只接受5-8位的大写字母字符串。

image-20260127181425719

image-20260127181505987

再例如:

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "string",
  "format": "date"
}

使用了"format": "date",表示只接受格式为yyyy-MM-dd的日期字符串。

image-20260127182213958

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中定义的数量。

    itemsprefixItems结合时,表示prefixItems后添加的元素类型必须满足items的限制。

  • unevaluatedItems:用于控制是否允许在元组末尾添加多余的元素,默认值为true

    unevaluatedItemsitems同时存在时,unevaluatedItems被忽略不起作用。

  • minItems:用来限制数组中的元素个数最小值,需要大于等于minItems

  • maxItems:用来限制数组中的元素个数最大值,需要小于等于maxItems

  • uniqueItems:用来限制数组中的元素是否能相等,默认值为false表示可以相等;

  • contains:与items要求数组中的所有元素都为同一种类型不同,contains只要求数组中至少有一个元素满足contains指定的类型即可;

  • minContains :要求数组中满足contains限制的的元素最小数量(包含);

  • maxContains:要求数组中满足contains限制的的元素最大数量(包含);

image-20260128151036910

image-20260128151218158

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对象必须出现nameage字段,并且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_]*$"
      }

    表示字段名称只能是大小写字母和_

image-20260128152322477

image-20260128153610877

2.3 枚举和常量

在JSON Schema中,我们可以使用enum来限制值的范围只能是特定几个枚举值,使用const来限制值只能是某一个值。

enum中,各个值的数据类型可以不同。

image-20260128154219196

image-20260128154321432

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,必须是 integer

3.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
对应类GraalJSRegularExpressionFactoryJoniRegularExpressionFactory
合规性最高(完全符合 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/

[3] https://github.com/networknt/json-schema-validator