什么是 JSON Schema
JSON Schema 本身就是一个 JSON 文档,它用一种基于 JSON 的格式来定义其他 JSON 数据的结构和内容。它可以用来:
- 验证:确保 JSON 数据符合预期的格式(必填字段、数据类型、范围等)。
- 文档化:作为数据模型的清晰、机器可读的文档。
- 交互式验证:为前端表单提供动态校验规则。
- 自动化测试:验证 API 请求和响应的结构。
如何开始使用?
- 在线验证工具:访问 https://jsonschema.dev 或 https://www.jsonschemavalidator.net,可以直观地编写 Schema 和测试数据。
- 选择版本:在 Schema 根节点使用
"$schema"关键字指定版本,如"https://json-schema.org/draft/2020-12/schema",这有助于编辑器和验证器提供正确的功能。 - 编程使用:几乎所有主流语言都有 JSON Schema 验证库(如 Python 的
jsonschema, Java 的everit-org/json-schema, JavaScript 的ajv)。
核心概念与关键字
| 类别 | 关键字 | 用途 |
|---|---|---|
| 核心 | $schema, $id, title, description |
标识 Schema 版本、提供元信息和文档 |
| 类型 | type |
定义基本数据类型 |
| 对象 | properties, required, additionalProperties |
定义对象结构、必填字段、允许额外字段 |
| 数组 | items, minItems, maxItems, uniqueItems |
定义数组元素、长度、唯一性 |
| 字符串 | minLength, maxLength, pattern, format |
定义字符串长度、格式、预定义格式 |
| 数值 | minimum, maximum, exclusiveMinimum, multipleOf |
定义数值范围、倍数 |
| 逻辑 | enum, const |
限制值为固定选项 |
| 组合 | allOf, anyOf, oneOf, not |
组合多个验证条件 |
| 条件 | if, then, else |
根据条件应用不同验证规则 |
| 复用 | $defs, $ref |
定义和引用可重用的子模式 |
类型约束:type
这是最核心的关键字,用于定义 JSON 数据的类型。基本类型有:string, number, integer, boolean, null, object, array。
验证一个字符串
// Schema |
验证一个整数
// Schema |
对象属性约束:properties, required, additionalProperties
用于定义 JSON 对象的结构。
properties: 定义对象中各个属性的 schema。required: 定义一个字符串数组,列出必须存在的属性。additionalProperties: 布尔值,定义是否允许出现properties中未定义的额外属性。false表示不允许。
定义一个用户对象
// Schema |
数组约束:items, minItems, maxItems, uniqueItems
用于定义 JSON 数组的结构。
items: 定义数组内每个元素的 schema。minItems/maxItems: 定义数组的最小/最大长度。uniqueItems: 布尔值,定义数组元素是否必须全部唯一。
定义一个数字列表
// Schema |
字符串约束:minLength, maxLength, pattern, format
基本使用
用于对字符串进行更细致的校验。
minLength/maxLength: 字符串的最小/最大长度。pattern: 使用正则表达式验证字符串格式。format: 使用预定义格式验证(如email,date-time,uri,ipv4等)。
定义用户名和邮箱
// Schema |
内置格式
具体参见 https://json-schema.fullstack.org.cn/understanding-json-schema/reference/string
以下是 JSON 模式规范中指定的格式列表。
日期和时间
日期和时间以 RFC 3339,第 5.6 节 的形式表示。这是日期格式的子集,也通常称为 ISO8601 格式。
"date-time":日期和时间组合在一起,例如,2018-11-13T20:20:39+00:00。"time":草案 7 中新增时间,例如,20:20:39+00:00"date":草案 7 中新增日期,例如,2018-11-13"duration":2019-09 草案中新增由ISO 8601 ABNF for “duration”定义的持续时间。例如,P3D表示持续时间为 3 天。
电子邮件地址
"email":互联网电子邮件地址,请参阅 RFC 5321,第 4.1.2 节。"idn-email":草案 7 中新增互联网电子邮件地址的国际化形式,请参阅RFC 6531。
主机名
"hostname":互联网主机名,请参阅 RFC 1123,第 2.1 节。"idn-hostname":草案 7 中新增国际化的互联网主机名,参见RFC5890,第 2.3.2.3 节。
IP 地址
"ipv4": IPv4 地址,根据 RFC 2673,第 3.2 节 中定义的点分十进制 ABNF 语法。"ipv6": IPv6 地址,根据 RFC 2373,第 2.2 节 定义。
资源标识符
"uuid":2019-09 草案中新增通用唯一标识符 (UUID),根据RFC 4122定义。例如:3e4666bf-d5e5-4aa7-b8ce-cefe41c7568a"uri": 通用资源标识符 (URI),根据 RFC3986。"uri-reference":草案 6 中的新增内容URI 引用(URI 或相对引用),根据RFC3986,第 4.1 节。"iri":草案 7 中新增”uri” 的国际化等效项,根据RFC3987。"iri-reference":草案 7 中新增”uri-reference” 的国际化等效项,根据RFC3987如果模式中的值可以相对于特定的源路径(例如,来自网页的链接),通常最好使用"uri-reference"(或"iri-reference")而不是"uri"(或"iri")。"uri"应仅在路径必须为绝对路径时使用。
URI 模板
"uri-template":草案 6 中的新增内容根据RFC6570的 URI 模板(任何级别)。如果您还不知道什么是 URI 模板,您可能不需要此值。
JSON 指针
"json-pointer":草案 6 中的新增内容JSON 指针,根据RFC6901。在构建复杂模式中,对 JSON 模式中使用 JSON 指针进行了更多讨论。请注意,这应仅在整个字符串仅包含 JSON 指针内容时使用,例如/foo/bar。JSON 指针 URI 片段,例如#/foo/bar/应使用"uri-reference"。"relative-json-pointer":草案 7 中新增相对 JSON 指针。
正则表达式
"regex":草案 7 中新增正则表达式,应根据ECMA 262方言有效。
数字约束:minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
用于对数字进行范围和小数精度校验。
minimum/maximum: 定义数值的下限/上限(包含等于)。exclusiveMinimum/exclusiveMaximum: 定义数值的下限/上限(不包含等于)。multipleOf: 定义数值必须是某个数字的倍数。
定义商品价格和数量
// Schema |
枚举与常量:enum, const
用于将值限制在一个固定的集合中。
enum: 值必须是列表中的某一个。const: 值必须严格等于这个常量。
定义订单状态
// Schema |
条件逻辑:if, then, else
允许你根据数据的某些条件来应用不同的验证规则。
条件验证 - 如果用户在国内,则邮编必填且格式为6位数字;如果在国外,则邮编可选
// Schema |
组合模式:allOf, anyOf, oneOf, not
用于组合多个模式规则。
allOf: 数据必须满足所有给定的模式。anyOf: 数据必须满足至少一个给定的模式。oneOf: 数据必须满足恰好一个给定的模式。not: 数据必须不满足给定的模式。
组合模式
// Schema: 定义一个既是正数又是偶数的整数 |
结构组织:$defs / definitions 和 $ref
为了复用和模块化 Schema,可以使用引用。
$defs(旧版草案中叫definitions): 一个容器,用于在你的 Schema 中存放可重用的子模式。$ref: 一个引用,指向$defs中的某个子模式或外部 URL。
使用 $defs 和 $ref 复用模式
// Schema |
注释
JSON Schema 包含一些关键字,这些关键字不严格用于验证,而是用于描述模式的各个部分。这些“注释”关键字都不需要,但鼓励为了良好的实践使用它们,并可以使您的模式“自文档化”。
注释关键字可以在任何模式或子模式中使用。与其他关键字一样,它们只能使用一次。
The title 和 description 关键字必须是字符串。一个“标题”最好简短,而一个“描述”将提供关于模式所描述的数据目的的更详细的解释。
The default 关键字指定一个默认值。此值不用于在验证过程中填充缺失的值。非验证工具(例如文档生成器或表单生成器)可以使用此值来提示用户如何使用值。但是,default 通常用于表示如果一个值丢失,那么该值的语义与该值存在且为默认值时相同。The default 的值应该通过它所处的模式进行验证,但这不是必需的。
draft 6 中的新功能
The examples 关键字是一个提供验证模式的示例数组的地方。这不用于验证,但可能有助于向读者解释模式的效果和目的。每个条目都应该通过它所处的模式进行验证,但这并不是严格要求的。没有必要在 examples 数组中复制 default 值,因为 default 将被视为另一个示例。
draft 7 中的新功能
布尔关键字 readOnly 和 writeOnly 通常用于 API 上下文中。 readOnly 指示不应修改值。它可以用于指示更改值的 PUT 请求会导致 400 Bad Request 响应。 writeOnly 指示可以设置值,但它将保持隐藏状态。它可以用于指示您可以使用 PUT 请求设置值,但它不会包含在使用 GET 请求检索该记录时。
draft 2019-09 中的新功能
The deprecated 关键字是一个布尔值,表示该关键字适用的实例值不应使用,并且将来可能会被删除
{ |
组合模式:allOf, anyOf, oneOf, not详解
这些关键字都是基于布尔逻辑的:
- AND (
allOf): 必须满足所有条件。 - OR (
anyOf): 必须满足至少一个条件。 - XOR (
oneOf): 必须满足恰好一个条件。 - NOT (
not): 必须不满足这个条件。
它们接受一个数组作为值,数组中的每个元素都是一个独立的 schema 对象。
| 关键字 | 逻辑 | 描述 | 相当于 |
|---|---|---|---|
allOf |
AND (∧) |
必须满足所有子模式 | schema1 ∧ schema2 ∧ ... |
anyOf |
OR (∨) |
必须满足至少一个子模式 | schema1 ∨ schema2 ∨ ... |
oneOf |
XOR (⊕) |
必须满足恰好一个子模式 | (schema1 ∧ ¬schema2) ∨ (¬schema1 ∧ schema2) |
not |
NOT (¬) |
必须不满足该子模式 | ¬schema |
重要提示:
oneOf的陷阱:在使用oneOf时,要特别注意确保子模式之间是互斥的,否则很容易出现一个数据同时匹配多个模式的情况导致验证失败。通常需要通过添加额外的约束(如const或pattern)来明确区分它们。- 性能:
anyOf和oneOf会按顺序尝试验证每个子模式,直到找到匹配项(anyOf)或确定只有一个匹配项(oneOf)。如果子模式很多或很复杂,可能会影响性能。 - 错误信息:当组合模式验证失败时,产生的错误信息可能比较难以理解,因为它需要解释复杂的逻辑关系。在设计 schema 时需要权衡复杂性和可维护性。
allOf - 逻辑与(AND)
数据必须满足 所有 在 allOf 数组中提供的模式。这常用于组合多个约束或从多个来源继承属性。
用法: "allOf": [ {schema1}, {schema2}, ... ]
组合验证
创建一个 schema,要求数据是一个字符串,并且长度在 1 到 10 之间,同时必须全部大写。
// Schema |
组合对象(类似继承)
组合一个基础地址 schema 和一个包含额外字段的详细地址 schema。
// Schema |
anyOf - 逻辑或(OR)
数据必须满足 anyOf 数组中提供的 至少一个 模式。这常用于创建联合类型或定义多种可接受的数据格式。
用法: "anyOf": [ {schema1}, {schema2}, ... ]
示例:支持多种类型或格式
验证一个字段可以是字符串形式的电话号码,也可以是数字形式的 ID。// Schema
{
"anyOf": [
{
"type": "string",
"pattern": "^\\d{3}-\\d{3}-\\d{4}$" // 匹配 123-456-7890
},
{
"type": "integer",
"minimum": 1000
}
]
}
// 有效数据 (满足第一个schema)
"555-123-4567"
// 有效数据 (满足第二个schema)
2000
// 无效数据 (两个schema都不满足)
"hello" // 不是正确的电话格式,也不是数字
500 // 是数字,但小于 minimum: 1000
oneOf - 逻辑异或(XOR)
数据必须满足 oneOf 数组中提供的 恰好一个 模式。不能同时满足多个。这常用于枚举或确保数据只有一种明确的类型。
用法: "oneOf": [ {schema1}, {schema2}, ... ]
示例:精确的类型选择
验证一个值要么是字符串,要么是数字,但不能同时满足两者的验证规则(注意:一个数字字符串 "5" 同时是字符串且可以被解释为数字)。// Schema
{
"oneOf": [
{ "type": "string" },
{ "type": "number" }
]
}
// 有效数据 (只满足 string)
"Hello"
// 有效数据 (只满足 number)
42
// 无效数据 (同时满足两个!)
"5"
// 为什么?因为它既是 string (满足第一个schema),又可以被转换为数字 (满足第二个schema的 `type: number` 验证)。
// 要解决这个问题,需要让模式互斥,例如:
/*
{
"oneOf": [
{
"type": "string",
"pattern": "\\D" // 确保字符串包含非数字字符
},
{
"type": "number"
}
]
}
*/
// 这样 "5" 会因为不满足 pattern 而只匹配 number schema。
更实用的例子:支付方式
订单只能使用一种支付方式。// Schema
{
"type": "object",
"oneOf": [
{ // 信用卡支付
"properties": {
"paymentType": { "const": "credit_card" },
"cardNumber": { "type": "string" }
},
"required": ["paymentType", "cardNumber"]
},
{ // PayPal支付
"properties": {
"paymentType": { "const": "paypal" },
"email": { "type": "string", "format": "email" }
},
"required": ["paymentType", "email"]
}
]
}
// 有效数据 (只匹配信用卡模式)
{
"paymentType": "credit_card",
"cardNumber": "4111..."
}
// 有效数据 (只匹配PayPal模式)
{
"paymentType": "paypal",
"email": "user@example.com"
}
// 无效数据 (匹配了0个模式,缺少required字段)
{
"paymentType": "credit_card"
// 缺少 cardNumber
}
// 无效数据 (匹配了多个模式,这是不允许的)
{
"paymentType": "credit_card",
"cardNumber": "4111...",
"email": "user@example.com"
// 这个对象同时满足了两个模式定义的properties,违反了 oneOf
}
not - 逻辑非(NOT)
数据必须 不满足 not 关键字后面提供的模式。这常用于排除某些特定的值或格式。
用法: "not": { schema }
示例:排除特定值
确保一个值不是空字符串。// Schema
{
"not": {
"type": "string",
"maxLength": 0
}
}
// 有效数据
"Hello"
42
["a"]
{"a": "b"}
// 无效数据
"" // 满足了 not 后面的schema(是字符串且长度<=0),所以被排除。
示例:确保不是某个枚举值
状态不能是 "deprecated"。// Schema
{
"type": "string",
"not": {
"const": "deprecated"
}
}
// 有效数据
"active"
"inactive"
// 无效数据
"deprecated"
条件逻辑:if, then, else详解
这三个关键字总是组合使用,其逻辑类似于编程语言中的 if-then-else 语句:
if:- 这是一个条件判断。它的值是一个 schema 对象。
- 验证器会检查当前数据实例是否满足
if这个 schema 中定义的规则。 - 注意:
if条件本身的验证结果只用于决定执行路径,它不会导致整个验证失败。即使数据不满足if条件,也只是导致then被跳过,验证会继续检查else(如果存在)。
then:- 如果数据实例满足了
if条件,那么验证器就会检查它是否也满足then这个 schema 的规则。 - 如果此时数据不满足
then的规则,验证就会失败。
- 如果数据实例满足了
else:- (可选关键字)如果数据实例不满足
if条件,那么验证器就会检查它是否满足else这个 schema 的规则。 - 如果此时数据不满足
else的规则,验证就会失败。详细示例说明
- (可选关键字)如果数据实例不满足
特别注意:
if本身不贡献错误:数据即使不满足if条件,也不会因此导致验证失败。失败只发生在数据满足了if却不满足then,或者不满足if却不满足else的情况下。else是可选的:如果你只想在条件满足时施加额外约束,而在条件不满足时什么都不做,可以省略else。- 条件可以是任何 Schema:
if条件不仅可以检查简单的相等性(const),还可以使用type,pattern,enum, 甚至嵌套的allOf/anyOf等来构建复杂条件。 - 避免过度嵌套:虽然可以嵌套
if-then-else(如示例1),但过度嵌套会使 Schema 难以理解和维护。有时使用oneOf可能是更清晰的选择。 - 与组合模式结合:条件逻辑可以和你之前学到的
allOf,anyOf,not等组合使用,构建出极其强大的验证逻辑(如示例2中then里使用了not)。
基本的字段依赖验证(经典用例)
场景:一个用户对象。如果 country 是 "USA",那么 zipCode 字段是必须的,并且必须匹配 5 位数字或 “ ZIP+4 ” 的格式。如果 country 是 "Canada",那么 postalCode 字段是必须的,并且必须匹配加拿大的邮政编码格式。对于其他国家,这两个字段都是可选的。
{ |
验证结果分析:
| 数据 | if 条件 | 应用规则 | 验证结果 | 原因 |
|---|---|---|---|---|
{"country": "USA"} |
满足 | then |
无效 | 缺少必需的 zipCode 字段。 |
{"country": "USA", "zipCode": "12345"} |
满足 | then |
有效 | 满足 then 的所有规则。 |
{"country": "USA", "zipCode": "123"} |
满足 | then |
无效 | zipCode 格式不匹配 pattern。 |
{"country": "Canada", "postalCode": "K1A 0B1"} |
不满足 | 外层的 else -> 内层的 then |
有效 | 满足内层 then 的规则。 |
{"country": "Germany"} |
不满足 | 外层的 else -> 内层的 else |
有效 | 内层 else 为空,无额外约束。 |
{"country": "Germany", "zipCode": "abc"} |
不满足 | 外层的 else -> 内层的 else |
有效 | 内层 else 为空,zipCode 存在与否和格式都不受约束。 |
验证互斥的字段组合
场景:一个支付对象。支付方式可以是信用卡(需要 cardNumber)也可以是 PayPal(需要 email)。但不能同时提供两者。
{ |
验证结果分析:
| 数据 | 验证结果 | 原因 |
|---|---|---|
{"paymentMethod": "credit_card", "cardNumber": "4111..."} |
有效 | 满足 if,应用 then:有 cardNumber 且无 email。 |
{"paymentMethod": "paypal", "email": "a@b.c"} |
有效 | 不满足 if,应用 else:有 email 且无 cardNumber。 |
{"paymentMethod": "credit_card"} |
无效 | 满足 if,应用 then:缺少必需的 cardNumber。 |
{"paymentMethod": "credit_card", "cardNumber": "4111...", "email": "a@b.c"} |
无效 | 满足 if,应用 then:但 then 中的 not 规则禁止了 email 字段的存在。 |
{"paymentMethod": "paypal"} |
无效 | 不满足 if,应用 else:缺少必需的 email。 |
验证数值范围依赖
场景:一个产品折扣对象。如果 discountType 是 "percentage"(百分比),那么 value 必须在 0 到 100 之间。如果是 "fixed"(固定金额),那么 value 必须大于 0。
{ |
验证结果分析:
| 数据 | 验证结果 | 原因 |
|---|---|---|
{"discountType": "percentage", "value": 50} |
有效 | 满足 if,应用 then:50 在 0-100 之间。 |
{"discountType": "percentage", "value": 150} |
无效 | 满足 if,应用 then:150 > 100。 |
{"discountType": "fixed", "value": 30} |
有效 | 不满足 if,应用 else:30 > 0。 |
{"discountType": "fixed", "value": 0} |
无效 | 不满足 if,应用 else:0 不大于 0 (exclusiveMinimum)。 |
使用条件逻辑与组合模式验证多层嵌套对象
使用条件逻辑与组合模式验证多层嵌套对象
场景描述
假设我们正在构建一个电子商务平台的订单系统,需要验证订单数据的结构。订单对象具有以下复杂要求:
- 订单必须包含基本信息和至少一个商品项
- 根据支付方式不同,需要不同的验证规则:
- 信用卡支付需要卡号和安全码
- PayPal支付需要邮箱地址
- 银行转账需要银行账户信息
- 根据配送地址的国家不同,需要不同的邮编验证规则
- 某些商品类型有特殊要求:
- 数字商品需要电子邮件地址用于发送
- 危险品需要特殊处理标志和免责声明同意
- 订单总额必须等于所有商品价格加上运费和税费的总和
完整Schema示例
{ |
详细解释
基本结构验证
Schema 首先定义了订单对象必须包含的基本字段:
orderId: 必须符合特定格式的字符串customer: 包含姓名和邮箱的对象items: 至少包含一个商品的数组shippingAddress: 配送地址对象payment: 支付信息对象totalAmount: 订单总金额
商品项验证 (使用 oneOf)
商品项使用 oneOf 来确保每种商品类型都有特定的必需字段:
- 实物商品: 需要
type,productId,name,price,quantity - 数字商品: 需要
type,productId,name,price,licenseType - 危险品: 需要
type,productId,name,price,quantity,hazardLevel
配送地址验证 (使用嵌套 if-then-else)
根据不同的国家,应用不同的邮编格式验证:
- 美国(US): 5位或5-4位数字格式
- 加拿大(CA): 字母数字交替格式
- 英国(UK): 英国特有的邮编格式
- 其他国家: 没有特定格式要求
支付方式验证 (使用 oneOf)
支付方式使用 oneOf 确保每种支付方式都有正确的字段:
- 信用卡: 需要卡号、有效期和安全码
- PayPal: 需要邮箱地址
- 银行转账: 需要账户号、路由号和银行名称
条件验证 (使用 allOf 和 if-then)
使用 allOf 组合多个条件验证规则:
规则1: 如果订单包含数字商品,则客户必须提供有效的电子邮件地址{
"if": {
"properties": {
"items": {
"contains": {
"properties": {
"type": { "const": "digital" }
}
}
}
}
},
"then": {
"properties": {
"customer": {
"properties": {
"email": {
"type": "string",
"format": "email"
}
},
"required": ["email"]
}
}
}
}
规则2: 如果订单包含危险品,则必须接受免责声明{
"if": {
"properties": {
"items": {
"contains": {
"properties": {
"type": { "const": "hazardous" }
}
}
}
}
},
"then": {
"properties": {
"disclaimerAccepted": { "const": true }
},
"required": ["disclaimerAccepted"]
}
}
有效数据示例
包含数字商品的信用卡订单
{ |
包含危险品的银行转账订单
{ |
无效数据示例
缺少危险品免责声明
{ |
数字商品缺少客户邮箱
{ |
这个复杂的示例展示了如何结合使用 JSON Schema 的条件逻辑 (if-then-else) 和组合模式 (allOf, oneOf) 来验证多层嵌套对象。关键点包括:
- 使用
oneOf确保多种选项中的恰好一种被满足(如支付方式、商品类型) - 使用嵌套的
if-then-else根据特定条件应用不同的验证规则(如根据国家验证邮编格式) - 使用
allOf组合多个条件验证 确保所有相关规则都被应用(如数字商品需要邮箱、危险品需要免责声明) - 使用
contains关键字 检查数组中是否存在特定类型的元素
这种组合使用使得 JSON Schema 能够表达极其复杂和精细的验证逻辑,几乎可以满足任何现实世界中的数据验证需求。
$schema和$id关键字
$schema 和 $id 是 JSON Schema 的元数据关键字,它们不是固定不变的字符串,但其作用和写法有明确的约定。
$schema 关键字
含义:$schema 关键字用于声明这个 JSON 文档本身遵循的是哪个版本的 JSON Schema 规范草案(Draft)。它就像是 XML 中的 DOCTYPE 声明或 HTML 中的 `` 声明。
为什么需要它?
- 版本控制:JSON Schema 规范本身在不断发展,有不同的草案版本(如 Draft-07, Draft-2019-09, Draft-2020-12)。每个版本都可能引入新的关键字或修改现有关键字的行为。使用
$schema明确告知验证器应该使用哪一套规则来解析这个 schema。 - 工具链支持:许多编辑器(如 VS Code、IntelliJ IDEA)和验证器依赖这个值来提供自动完成、语法高亮和实时验证。如果没有它,这些工具可能不知道如何正确处理你的 schema 文件。
常用值(不是固定写法,但必须从以下中选一个):
| 值 | 对应的草案版本 | 说明 |
|---|---|---|
"http://json-schema.org/draft-07/schema#" |
Draft-07 | 较旧的版本,但仍被广泛使用。 |
"http://json-schema.org/draft/2019-09/schema" |
Draft-2019-09 | 引入了 unevaluatedProperties 等新关键字。 |
"https://json-schema.org/draft/2020-12/schema" |
Draft-2020-12 | 当前推荐的最新稳定版本。 |
结论:虽然值不是固定的,但强烈建议总是在 schema 的根节点包含 $schema 关键字,并指定为你所使用的草案版本URL。这被认为是最佳实践。
$id 关键字
含义:$id 为 schema 定义一个统一资源标识符(URI)。这个 URI 是 schema 的唯一标识符,可以理解为它的“命名空间”或“基础地址”。
它有两个主要作用:
唯一标识(作为引用名称)
这是它的核心作用。当其他 schema 想要引用(使用 $ref) 当前这个 schema 或其内部定义时,就会使用这个 $id 作为基址。
例如,你在一个 schema 中定义了:{
"$id": "https://example.com/schemas/address.json",
"$defs": {
"street": { "type": "string" }
}
}
在另一个 schema 中,你可以这样引用它:{
"type": "object",
"properties": {
"homeAddress": {
"$ref": "https://example.com/schemas/address.json#/$defs/street"
},
"workAddress": {
"$ref": "https://example.com/schemas/address.json"
}
}
}
这里的 $ref 值就是基于 $id 提供的基址进行构建的。
解析相对引用(作为基础URI)
在 schema 内部,如果使用相对路径进行引用(例如 "$ref": "#/$defs/myDefinition"),这个相对路径是相对于 $id 所定义的基址来解析的。
关于它的值:
- 可以是任何 URI:它不一定需要是一个真实可访问的网址。它只是一个标识符。常用的格式是
https://你的域名/schemas/文件名.json。 - 推荐使用绝对 URI:为了确保唯一性和可移植性,最好使用完整的绝对 URI(如
https://...),而不是相对路径(如"/schemas/address.json")。 - 在根节点定义:
$id通常在 schema 的根节点定义,为整个文档设置基础 URI。但它也可以在其他位置出现,用于修改局部的基础 URI(高级用法)。
总结与对比
| 关键字 | 作用 | 必要性 | 值示例 |
|---|---|---|---|
$schema |
定义本schema遵循的规范版本 | 强烈推荐 | "https://json-schema.org/draft/2020-12/schema" |
$id |
为本schema定义一个唯一标识符(URI),供外部或内部引用 | 推荐(尤其在schema需要被复用时) | "https://example.com/schemas/address.json" |
所以,你给出的例子:
{ |
含义是:
- “本 Schema 文档遵循 JSON Schema Draft-2020-12 版本的规范。”
- “本 Schema 的唯一标识符是
https://example.com/complex-order-schema.json。其他 Schema 可以通过这个 URI 来引用我,我内部的相对引用路径也基于这个 URI 进行解析。”
特别注意:
- 始终包含
$schema。 - 如果你的 Schema 会被其他 Schema 通过
$ref引用,或者你想清晰地命名它,那么就应该包含$id。 - 如果你只是写一个简单的、一次性的、不被复用的 Schema,
$id可以省略。
JSON Schema 中的唯一性约束
在 JSON Schema 中,唯一性约束主要通过 uniqueItems 关键字来实现,但它有一个重要的限制:它只适用于数组(array)类型,用于确保数组中的所有元素都是唯一的。
JSON Schema 没有提供类似于数据库中的 UNIQUE 约束的关键字来直接保证整个文档中某个字段的唯一性(例如,确保所有用户对象的 email 字段值都是唯一的)。这种全局唯一性需要在应用层或数据库层实现。
数组元素的唯一性约束 (uniqueItems)
作用:当 uniqueItems 设置为 true 时,要求数组中的每个元素都是唯一的。
工作原理:验证器会使用深度比较(deep comparison)来检查数组元素是否重复。对于对象,会比较所有属性及其值;对于数组,会按顺序比较每个元素。
针对不同数据类型的示例:
基本数据类型数组
// Schema |
字符串数组
// Schema |
布尔值数组
// Schema |
对象数组 - 基于整个对象的唯一性
// Schema |
嵌套结构数组
// Schema |
针对对象特定字段的唯一性约束
这是更复杂但更常见的需求:确保数组中每个对象的某个特定字段(或一组字段)的值是唯一的。
JSON Schema 没有直接的关键字来实现这一点,但可以通过组合使用 uniqueItems 和其他技术来模拟这种效果。
使用枚举和大小限制
这种方法适用于已知所有可能值的情况。// Schema: 确保角色名称唯一
{
"type": "array",
"items": {
"type": "object",
"properties": {
"roleName": {
"type": "string",
"enum": ["admin", "editor", "viewer", "guest"] // 预定义唯一值
}
},
"required": ["roleName"]
},
"maxItems": 4 // 不能超过枚举值的数量
}
// 有效数据
[
{ "roleName": "admin" },
{ "roleName": "editor" }
]
// 无效数据(通过 maxItems 间接防止重复)
[
{ "roleName": "admin" },
{ "roleName": "editor" },
{ "roleName": "viewer" },
{ "roleName": "guest" },
{ "roleName": "admin" } // 虽然能通过enum检查,但会被maxItems阻止
]
使用组合模式实现真正基于字段的唯一性
这是一种更强大的方法,使用 allOf 和 not 的组合来验证唯一性。
{ |
更精确的实现方式:
实际上,更准确的方法是使用应用逻辑或自定义验证,因为标准的 JSON Schema 无法直接实现基于字段的唯一性。但在某些验证器中,你可以这样模拟:
{ |
针对多个字段组合的唯一性约束
如果需要确保多个字段的组合是唯一的,可以使用类似的方法:
{ |
实际应用建议
- 对于简单唯一性(整个数组元素唯一):使用
uniqueItems: true 对于字段级唯一性:
- 如果可能值已知且有限:使用
enum+maxItems - 对于一般情况:在应用层实现验证逻辑
- 对于需要严格验证的场景:考虑使用数据库的唯一约束
- 如果可能值已知且有限:使用
性能考虑:
uniqueItems需要对数组进行 O(n²) 的比较,对于大型数组可能影响性能。
总结
| 约束类型 | JSON Schema 支持 | 实现方法 |
|---|---|---|
| 数组元素整体唯一 | ✅ 直接支持 | uniqueItems: true |
| 对象特定字段唯一 | ❌ 不直接支持 | 应用层逻辑,或使用 enum + maxItems 间接实现 |
| 多字段组合唯一 | ❌ 不直接支持 | 应用层逻辑 |
JSON Schema 与 Go 校验实现
增强后的 JSON Schema
{ |
增强的 Go 语言校验实现
package main |
主要增强内容
clientID 唯一性约束:
- 在 JSON Schema 中添加了
minimum: 1约束,确保 clientID 为正整数 - 添加了
validateClientIDUnique自定义验证函数,检查所有 shapes 和 tags 中的 clientID 是否唯一 - 该函数会记录重复的 clientID 及其位置信息
- 在 JSON Schema 中添加了
增强的错误报告:
- 改进了错误消息,明确指出哪个 clientID 重复以及在哪个位置
- 区分了 shapes 和 tags 中的重复情况
测试用例:
- 添加了
testDuplicateClientID函数,用于测试 clientID 重复的情况 - 这有助于验证自定义验证函数的正确性
- 添加了
结构化验证流程:
- 将验证流程分为三个部分:
- JSON Schema 验证
- Points 相关验证(偶数个元素、非负值)
- ClientID 唯一性验证
- 将验证流程分为三个部分:
使用说明
首先安装所需的Go依赖:
go get github.com/xeipuuv/gojsonschema
将上述代码保存为
validate.go文件运行程序:
go run validate.go
程序将输出验证结果,包括:
- JSON Schema 验证结果
- Points 验证结果(偶数个元素、非负值)
- ClientID 唯一性验证结果
- ClientID 重复测试结果
注意事项
- 示例JSON数据中的 clientID 已经确保唯一(1-7)
- 测试函数中特意创建了一个重复的 clientID 用于验证
- 所有验证错误都会以清晰的方式报告,包括重复的 clientID 值和位置信息
- clientID 现在必须是正整数(最小值1)
使用 Java 实现 JSON Schema 校验
下面我将展示如何使用 Java 实现与之前 Go 代码相同的 JSON Schema 校验功能,包括对 points 属性的约束和 clientID 唯一性检查。
添加 Maven 依赖
首先,在 pom.xml 中添加必要的依赖:
<dependencies> |
JSON Schema 文件
创建 schema.json 文件放在资源目录下 (src/main/resources/schema.json):
{ |
Java 实现代码
创建 JsonValidator.java 类:
package com.example.validator; |
单元测试
创建 JsonValidatorTest.java 测试类:
package com.example.validator; |
使用说明
项目设置:
- 创建一个 Maven 项目
- 将上述代码添加到相应的包中
- 将 JSON Schema 文件放在
src/main/resources/schema.json
运行验证:
- 可以直接运行
JsonValidator类的main方法进行测试 - 也可以运行单元测试来验证各种情况
- 可以直接运行
API 使用:
JsonValidator validator = new JsonValidator();
String jsonString = "..."; // 你的 JSON 字符串
JsonValidator.ValidationResult result = validator.validate(jsonString);
if (result.isValid()) {
System.out.println("验证通过");
} else {
result.printResults(); // 打印所有错误信息
}
功能说明
这个 Java 实现提供了与之前 Go 代码相同的功能:
- JSON Schema 验证:使用 NetworkNT 的 JSON Schema 验证器验证基本结构和类型
- Points 验证:
- 检查 points 数组元素个数是否为偶数
- 检查 points 数组元素值是否都大于等于 0
- ClientID 唯一性验证:检查所有 shapes 和 tags 中的 clientID 是否唯一
- 综合验证:提供统一的验证接口,返回详细的验证结果
这个实现能够有效验证您的 JSON 数据是否符合所有预期的约束条件,确保数据的一致性和完整性。
参考文档
- https://json-schema.fullstack.org.cn/learn/getting-started-step-by-step
- https://json-schema.org/
- https://json-schema.fullstack.org.cn/specification
- https://json-schema.fullstack.org.cn/draft/2020-12/json-schema-core