json schema 在线校验器
译自:Understanding JSON Schema
{
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"birthday": { "type": "string", "format": "date" },
"address": {
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" },
"country": { "type" : "string" }
}
}
}
}
type字段
type字段可以是一个字符串或一个数组
-
如果是一个字符串则表示基本数据类型,如:42、42.0
{ "type": "number" }
-
如果是一个字符串,则表示数据可以是其中的任一基本类型,如:
42
、"Life, the universe, and everything"
,但不能是结构化的数据类型,如:["Life", "the universe", "and everything"]
{ "type": ["number", "string"] }
- string
- number
- integer
- object
- array
- boolean
- null
{ "type": "string" }
可以表示的字符串如:"This is a string"
、""
、"Déjà vu"
(unicode字符)
用于限制字符串的长度
{
"type": "string",
"minLength": 2,
"maxLength": 3
}
正则表达式
使用pattern
字段设置正则表达式,具体参见官方说明
{
"type": "string",
"pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"
}
数字类型
integer
{ "type": "integer" }
用于表示整数类型。需要注意的是,小数点的存在与否并不能判断它是一个整数还是浮点数,例如1
和1.0
都会被认为是整数,但3.1415926
则是浮点数。
用于表示任意数字类型,即整数或浮点数
{ "type": "number" }
multiples
用于表示特定数字的倍数,如下可以是0
、10
、20,但23
不是10的倍数,所以允许。
{
"type": "number",
"multipleOf" : 10
}
range
使用minimum
和maximum
表示的数字范围(或使用exclusiveMinimum
和exclusiveMaximum
表示独占范围)
- x ≥
minimum
- x >
exclusiveMinimum
- x ≤
maximum
- x <
exclusiveMaximum
如下可以表示0
,10
,99
,但-1
和100
、101
是错误的:
{
"type": "number",
"minimum": 0,
"exclusiveMaximum": 100
}
注意在JSON Schema Draft 4中exclusiveMinimum
和exclusiveMaximum
的工作方式并不相同,它们表示一个boolean值,用于判断是否排除minimum
和maximum
值
if exclusiveMinimum is false, x ≥ minimum.
if exclusiveMinimum is true, x > minimum.
object
objects是JSON中的mapping类型,即将"keys"映射到"values","keys"必须是字符串,通常将每一对映射称为"属性"。
{ "type": "object" }
可以表达如下值:
{
"key": "value",
"another_key": "another_value"
}
properties
属性是object中使用properties
关键字定义的key-value对。properties
的值是一个对象,每个key的值作为一个property的名称,且每个值都用来校验该属性。任何与properties
的属性名不匹配的属性都将被忽略。
{
"type": "object",
"properties": {
"number": { "type": "number" },
"street_name": { "type": "string" },
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
}
}
上述表达式可以匹配
-
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" }
-
{ "number": 1600, "street_name": "Pennsylvania" }
-
{ }
-
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" }
但不能匹配
-
{ "number": "1600", "street_name": "Pennsylvania", "street_type": "Avenue" }
有时候期望对于某一类属性名称,匹配一个特定的模式,此时可以使用patternProperties
:它使用正则表达式来进行模式匹配。如果一个属性的名称匹配到特定的正则表达式,则使用对于的模式来校验该属性的值。
如下表示使用S_
开头的属性必须是字符串类型,而使用 I_
开头的则必须是整数类型,并忽略不匹配正则表达式的属性。
{
"type": "object",
"patternProperties": {
"^S_": { "type": "string" },
"^I_": { "type": "integer" }
}
}
上述表达式可以匹配
-
{ "S_25": "This is a string" }
-
{ "I_0": 42 }
-
{ "keyword": "value" }
但不能匹配:
-
{ "S_0": 42 }
-
{ "I_42": "This is a string" }
additionalProperties
关键字用于控制不在properties
关键字或不在patternProperties
正则表达式列表中的属性。默认情况下允许这类properties。将additionalProperties
设置为false
表示不允许额外的属性。
如下表达式不允许{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" }
{
"type": "object",
"properties": {
"number": { "type": "number" },
"street_name": { "type": "string" },
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
},
"additionalProperties": false
}
还可以使用非boolean对额外的属性增加更加复杂的限制。如下表示进允许类型为字符串的额外属性,此时可以允许{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" }
,但不允许{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "office_number": 201 }
{
"type": "object",
"properties": {
"number": { "type": "number" },
"street_name": { "type": "string" },
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
},
"additionalProperties": { "type": "string" }
}
如下将additionalProperties
和properties
、patternProperties
结合起来使用,例如{ "keyword": "value" }
不匹配properties
和patternProperties
,但它匹配了additionalProperties
,因此允许该对象。
{
"type": "object",
"properties": {
"builtin": { "type": "number" }
},
"patternProperties": {
"^S_": { "type": "string" },
"^I_": { "type": "integer" }
},
"additionalProperties": { "type": "string" }
}
扩展封闭模式
需要注意由于additionalProperties
只能识别相同子模式的属性,因此可能会限制使用Schema Composition关键字进行扩展。例如下述表达式本意是要求对象中包含"street_address", "city", "state"和"type"这几个字段:
{
"allOf": [
{
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"],
"additionalProperties": false
}
],
"properties": {
"type": { "enum": [ "residential", "business" ] }
},
"required": ["type"]
}
但对于下述对象,会因为将"type"认为是额外的属性,而无法通过additionalProperties
的校验
{
"street_address": "1600 Pennsylvania Avenue NW",
"city": "Washington",
"state": "DC",
"type": "business"
}
但下述对象又由于缺少"type"而无法通过required
的校验
{
"street_address": "1600 Pennsylvania Avenue NW",
"city": "Washington",
"state": "DC"
}
由于additionalProperties
只能识别相同子模式中的properties,它会将非"street_address", "city"和"state"的属性认为是额外的属性,一种解决方案是将additionalProperties
转移到扩展的模式中,并在扩展的模式中重新定义属性
{
"allOf": [
{
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
],
"properties": {
"street_address": true,//使用boolean表示必须出现该属性
"city": true,
"state": true,
"type": { "enum": [ "residential", "business" ] }
},
"required": ["type"],
"additionalProperties": false
}
在 draft 2019-09可以使用unevaluatedProperties
关键字解决这种问题
unevaluatedProperties
关键字与additionalProperties
类似的,但它可以识别子模式的属性。因此上述例子可以写为:
{
"allOf": [
{
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
],
"properties": {
"type": { "enum": ["residential", "business"] }
},
"required": ["type"],
"unevaluatedProperties": false
}
这样就可以允许:
{
"street_address": "1600 Pennsylvania Avenue NW",
"city": "Washington",
"state": "DC",
"type": "business"
}
不允许:
{
"street_address": "1600 Pennsylvania Avenue NW",
"city": "Washington",
"state": "DC",
"type": "business",
"something that doesn't belong": "hi!"
}
unevaluatedProperties的工作原理是收集所有在处理模式时成功验证的属性,并将其作为允许的属性列表使用。下面例子中仅在"type"为"business"时允许"department"属性。
{
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" },
"type": { "enum": ["residential", "business"] }
},
"required": ["street_address", "city", "state", "type"],
"if": {
"type": "object",
"properties": {
"type": { "const": "business" }
},
"required": ["type"]
},
"then": {
"properties": {
"department": { "type": "string" }
}
},
"unevaluatedProperties": false
}
上述表达式允许:
{
"street_address": "1600 Pennsylvania Avenue NW",
"city": "Washington",
"state": "DC",
"type": "business",
"department": "HR"
}
不允许:
{
"street_address": "1600 Pennsylvania Avenue NW",
"city": "Washington",
"state": "DC",
"type": "residential",
"department": "HR"
}
Required Properties
默认情况下,properties
关键字中的属性不是必须的,但可以通过required
关键字指定需要的属性。
required
关键字可以指定0或多个字符串数组,每个字符串都必须唯一。如下表达式要求对象中有"name"和"email"属性。
{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"address": { "type": "string" },
"telephone": { "type": "string" }
},
"required": ["name", "email"]
}
注意上述表达式不允许如下对象,这是因为null的类型不是"string",而是"null":
{
"name": "William Shakespeare",
"address": "Henley Street, Stratford-upon-Avon, Warwickshire, England",
"email": null
}
属性名称
New in draft 6
属性的名称可以根据模式进行验证,而不考虑它们的值。如下强制所有的名称必须是有效的ASCII 字符
{
"type": "object",
"propertyNames": {
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
}
}
由于对象的keys必须是字符串,这也意味着propertyNames
的模式至少是:
{ "type": "string" }
size
properties的数目可以使用minProperties
和maxProperties
进行限制,值为整数。
{
"type": "object",
"minProperties": 2,
"maxProperties": 3
}
array
表示一组有序的元组,数组中可以包含不同类型的元素。
{ "type": "array" }
允许:
-
[1, 2, 3, 4, 5]
-
[3, "different", { "types" : "of values" }]
JSON使用了两种数组方式:
- List validation: 任意长度的数组,每个元素都使用相同的模式
- Tuple validation: 固定长度的数组,每个元素都有可能使用不同的模式
List validation下使用item
关键字来校验数组中的元素
{
"type": "array",
"items": {
"type": "number"
}
}
如上表达式允许数组[1, 2, 3, 4, 5]
、[]
,但不允许[1, 2, "3", 4, 5]
假设为了表达一个地址:"1600 Pennsylvania Avenue NW"。该地址是一个4元组[number, street_name, street_type, direction]
其中:
number
: 地址号码,必须是数字street_name
: 街区名称,必须是字符串street_type
: 街区类型,必须来自一组固定的字符串值direction
: 城市象限,必须来自一组固定的字符串值
为了实现上述目的,需要使用prefixItems
关键字,prefixItems
表示一个数组,每个元素即一个模式,对应文档数组的相应索引,即第一个元素校验输入数组的第一个元素,第二个元素校验输入数组的第二个元素。
在 Draft 4 - 2019-09中,使用
items
关键字的另一种形式来进行元组验证。当items
是一个多模式数组是,它的行为和prefixItems
相同。
实现上述目的的表达式如下:
{
"type": "array",
"prefixItems": [
{ "type": "number" },
{ "type": "string" },
{ "enum": ["Street", "Avenue", "Boulevard"] },
{ "enum": ["NW", "NE", "SW", "SE"] }
]
}
允许:
-
[1600, "Pennsylvania", "Avenue", "NW"]
-
[10, "Downing", "Street"] //前三个元素
-
[1600, "Pennsylvania", "Avenue", "NW", "Washington"]
不允许:
-
["Palais de l'Élysée"] //第一个元素不是数字
-
[24, "Sussex", "Drive"] //第三个元素不匹配
可以使用items
关键字控制是否允许出现prefixItems
中定义的元组之外的元素。
如果将items
设置为false
{
"type": "array",
"prefixItems": [
{ "type": "number" },
{ "type": "string" },
{ "enum": ["Street", "Avenue", "Boulevard"] },
{ "enum": ["NW", "NE", "SW", "SE"] }
],
"items": false
}
将不允许:
-
[1600, "Pennsylvania", "Avenue", "NW", "Washington"] //包含额外元素
允许:
-
[1600, "Pennsylvania", "Avenue"]
可以使用非boolean的模式表示更复杂的限制,表示可以添加那些额外的元素:
{
"type": "array",
"prefixItems": [
{ "type": "number" },
{ "type": "string" },
{ "enum": ["Street", "Avenue", "Boulevard"] },
{ "enum": ["NW", "NE", "SW", "SE"] }
],
"items": { "type": "string" }
}
此时允许:
-
[1600, "Pennsylvania", "Avenue", "NW", "Washington"]
但不允许:
-
[1600, "Pennsylvania", "Avenue", "NW", 20500] //20500不是字符串
New in draft 6
contains
关键字要求数组中至少出现一个特定模式的元素
如下不允许出现["life", "universe", "everything", "forty-two"]
这种不带任何数字的数组:
{
"type": "array",
"contains": {
"type": "number"
}
}
minContains / maxContains
New in draft 2019-09
和contains
关键字配合使用,限制contains
的模式次数。
{
"type": "array",
"contains": {
"type": "number"
},
"minContains": 2,
"maxContains": 3
}
不允许:
-
["apple", "orange", 2]
-
["apple", "orange", 2, 4, 8, 16]
使用minItems
和maxItems
限制数组的长度
{
"type": "array",
"minItems": 2,
"maxItems": 3
}
Uniqueness
将uniqueItems
设置为true
,确保数组中元素的唯一性
{
"type": "array",
"uniqueItems": true
}
将不允许:
-
[1, 2, 3, 3, 4]
{ "type": "boolean" }
需要注意true
和false
要小写
{ "type": "null" }
需要注意的是,在JSON中null
并不代表某些内容不存在
JSON Schema中有一些关键字,这些关键字不用于校验,仅用于描述模式,这类"注释"关键字并不是必须的,但建议在实践中使用,由此可以实现模式的"自文档"。
title
和description
关键字必须是字符串。
default
关键字指定了默认值,该值不会填充验证过程中缺失的值。一些非验证的工具,如文档生成器或格式生成器会使用该值来提示用户如何使用一个值。
New in draft 6:examples
关键字提供了一组校验模式的例子,它并不用于校验,仅帮助读者解释模式的影响和目的。examples
中不需要default
,可以将default
看作是另一个examples
New in draft 7:通常会在API上下文中使用boolean类型的readOnly
和writeOnly
关键字,前者表示不可修改某个值,当使用PUT
请求修改值时,会响应400 Bad Request
。writeOnly
表示可以设置值,但将保持隐藏状态,即可以通过PUT
请求设置一个值,但在无法通过GET
请求检索到该值。
New in draft 2019-09 :deprecated
关键字用来表示未来将会移除该实例值。
{
"title": "Match anything",
"description": "This is a schema that matches anything.",
"default": "Default value",
"examples": [
"Anything",
4035
],
"deprecated": true,
"readOnly": true,
"writeOnly": false
}
Comments
New in draft 7 $comment
$comment
关键字用于给模式添加注释,该值必须是字符串。
enum
关键字用于指定一组固定的值。它必须是一个数组,且最少包含一个元素,每个元素都是唯一的。
{
"enum": ["red", "amber", "green", null, 42]
}
Constant values
New in draft 6
const
关键字用于指定单个值。如下例,将country
限制为"United States of America"
,不允许出现其他值:
{
"properties": {
"country": {
"const": "United States of America"
}
}
}
Media: 字符串编码的非JSON数据
JSON Schema中有一组关键字用于描述和选择性校验保存在JSON字符串中的非JSON数据。由于很难为所有媒体类型编写校验器,因此JSON 模式校验器不需要基于这些关键字验证JSON字符串的内容。但对于那些需要消费经过校验的JSON的应用来说非常有用。
contentMediaTypecontentMediaType
关键字用于指定字符串内容的MIME类型,参见 RFC 2046。IANA正式注册了一系列MIME类型,但具体支持的类型将取决于应用程序和操作系统。
{
"type": "string",
"contentMediaType": "text/html"
}
可以允许"<!DOCTYPE html><html xmlns=\"http://www.w3.org/1999/xhtml\"><head></head></html>"
contentEncoding
关键字指定保存内容的编码类型,参见RFC 2054, part 6.1和RFC 4648。
可接受的值为7bit
, 8bit
, binary
, quoted-printable
, base16
, base32
和 `base64,如果没有指定,则与JSON文档的编码相同。
通常的用法如下:
- 如果编码的内容和JSON文档相同,则无需指定
contentEncoding
,按原样将内容包含在字符串中即可。包含基于文本的类型,如text/html
或application/xml
- 如果内容是二进制,将
contentEncoding
设置为base64
,并使用Base64进行编码,这类包含很多媒体类型,如image/png
或音频类型,如audio/mpeg
.
{
"type": "string",
"contentEncoding": "base64",
"contentMediaType": "image/png"
}
可以允许"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA..."
JSON Schema中有一些关键字可以用于将模式组合到一起。注意,这并意味着它们会组合来自多个文件或JSON树的模式(尽管这些功能有助于实现这一点),更多参见构建复杂模式。组合模式可能很简单,比如允许同时根据多个标准校验一个值。
这些关键字对应于众所周知的布尔代数概念,如AND、OR、XOR和NOT。你可以使用这些关键字来表达标准JSON Schema关键字无法表达的复杂限制。
这些关键字为:
- allOf: (AND) 必须通过所有子模式的校验
- anyOf: (OR) 必须通过任一个子模式的校验
- oneOf: (XOR) 必须只能通过某一个子模式的校验
- not: (NOT) 不能通过给定模式的校验
{
"anyOf": [
{ "type": "string", "maxLength": 5 },
{ "type": "number", "minimum": 0 }
]
}
可以允许:
-
"short"
-
12
不允许:
-
"too long"
-
-5
{
"oneOf": [
{ "type": "number", "multipleOf": 5 },
{ "type": "number", "multipleOf": 3 }
]
}
可以允许:
-
10
-
9
不允许:
-
2
-
15 // 同时是3和5的倍数
{ "not": { "type": "string" } }
允许:
-
42
-
{ "key": "value" }
不允许:
-
"I am a string"
如下组合是不符合逻辑的,因为数据不可能既是字符串又是数字:
{
"allOf": [
{ "type": "string" },
{ "type": "number" }
]
}
分解模式
可以将"因子"放到子模式的公共部分之外,如下两种模式是等价的:
{
"oneOf": [
{ "type": "number", "multipleOf": 5 },
{ "type": "number", "multipleOf": 3 }
]
}
{
"type": "number",
"oneOf": [
{ "multipleOf": 5 },
{ "multipleOf": 3 }
]
}
子模式条件
dependentRequired
dependentRequired
关键字要求当对象中出现给定的属性时,要求出现特定的属性。例如,如果你有信用卡号,则必须保证还有一个账单地址,发明之如果没有信用卡号,那么也不需要账单地址了。使用dependentRequired
关键字可以表示一个属性对其他属性的依赖关系。dependentRequired
关键字的值是一个对象,对象中的每个条目会映射到属性的名称。
如下,当提供了credit_card
属性时,也必须出现billing_address
属性:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" },
"billing_address": { "type": "string" }
},
"required": ["name"],
"dependentRequired": {
"credit_card": ["billing_address"]
}
}
允许:
-
{ "name": "John Doe", "credit_card": 5555555555555555, "billing_address": "555 Debtor's Lane" }
-
{ "name": "John Doe" //没有提供credit_card,不产生依赖 }
-
{ "name": "John Doe", "billing_address": "555 Debtor's Lane" //billing_address并没有任何依赖 }
不允许:
-
{ "name": "John Doe", "credit_card": 5555555555555555 }
dependentSchemas
关键字要求当出现给定的属性时,应用特定的子模式。下面表示当出现credit_card
时,要求出现billing_address
,且billing_address
必须是字符串
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" }
},
"required": ["name"],
"dependentSchemas": {
"credit_card": {
"properties": {
"billing_address": { "type": "string" }
},
"required": ["billing_address"]
}
}
}
允许:
-
{ "name": "John Doe", "credit_card": 5555555555555555, "billing_address": "555 Debtor's Lane" }
-
{ "name": "John Doe", "billing_address": "555 Debtor's Lane" //不存在credit_card }
New in draft 7,与编程语言中的if
/then
/else
类似。
例如,如果你想编写一个模式来处理United States 和Canada的地址,这两个国家的邮政编码格式不同,我们需要根据不同的国家来进行校验。如果地址在United States,则postal_code
字段为zipcode:5位数字,后面跟4位可选的数字后缀。如果地址在Canada,则postal_code
字段为6位字母数字串。
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"default": "United States of America",
"enum": ["United States of America", "Canada"]
}
},
"if": {
"properties": { "country": { "const": "United States of America" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
},
"else": {
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
}
}
允许:
-
{ "street_address": "1600 Pennsylvania Avenue NW", "country": "United States of America", "postal_code": "20500" }
-
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "20500" }
-
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "K1M 1M4" }
不允许:
-
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "10000" }
-
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "K1M 1M4" }
上例中并没有要求出现"country"属性,因此如果未定义"country"属性,默认行为会将"postal_code"验证为美国邮政编码。“default”关键字没有效果(只作提示作用)
上述方式只能处理两个国家的情况,如果要处理多个国家,可以将多个if
和then
成对包含到allOf
中。
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"default": "United States of America",
"enum": ["United States of America", "Canada", "Netherlands"]
}
},
"allOf": [
{
"if": {
"properties": { "country": { "const": "United States of America" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
}
},
{
"if": {
"properties": { "country": { "const": "Canada" } },
"required": ["country"]
},
"then": {
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
}
},
{
"if": {
"properties": { "country": { "const": "Netherlands" } },
"required": ["country"]
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{4} [A-Z]{2}" } }
}
}
]
}
允许:
-
{ "street_address": "1600 Pennsylvania Avenue NW", "country": "United States of America", "postal_code": "20500" }
-
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "20500" }
-
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "K1M 1M4" }
-
{ "street_address": "Adriaan Goekooplaan", "country": "Netherlands", "postal_code": "2517 JX" }
不允许:
-
{ "street_address": "24 Sussex Drive", "country": "Canada", "postal_code": "10000" }
-
{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "K1M 1M4" }
implication上述"if"模式中的
required
字段是必须的,如果没有该字段,则会将该模式作为默认模式执行。例如,对于如下语句:{ "street_address": "1600 Pennsylvania Avenue NW", "postal_code": "K1M 1M4" }
如果按照上述表达式执行,结果为:
Message:JSON does not match all schemas from 'allOf'. Invalid schema indexes: 0. Schema path: #/allOf Message:JSON does not match schema from 'then'. Schema path:#/allOf/0/then/then Message:String 'K1M 1M4' does not match regex pattern '[0-9]{5}(-[0-9]{4})?'. Schema path:#/allOf/0/then/properties/postal_code/pattern
如果去掉所有的
required
字段,则会将所有if
模式作为默认模式进行匹配校验,结果如下:Message:JSON does not match all schemas from 'allOf'. Invalid schema indexes: 0, 2. Schema path:#/allOf Message:JSON does not match schema from 'then'. Schema path:#/allOf/2/then/then Message:String 'K1M 1M4' does not match regex pattern '[0-9]{4} [A-Z]{2}'. Schema path:#/allOf/2/then/properties/postal_code/pattern Message:JSON does not match schema from 'then'. Schema path:#/allOf/0/then/then Message:String 'K1M 1M4' does not match regex pattern '[0-9]{5}(-[0-9]{4})?'. Schema path:#/allOf/0/then/properties/postal_code/pattern
可以使用模式组合关键字来表示"if-then"条件,
{
"type": "object",
"properties": {
"restaurantType": { "enum": ["fast-food", "sit-down"] },
"total": { "type": "number" },
"tip": { "type": "number" }
},
"anyOf": [
{
"not": {
"properties": { "restaurantType": { "const": "sit-down" } },
"required": ["restaurantType"]
}
},
{ "required": ["tip"] }
]
}
允许:
-
{ "restaurantType": "sit-down", "total": 16.99, "tip": 3.4 }
-
{ "restaurantType": "fast-food", "total": 6.99 }
-
{ "total": 5.25 }
不允许:
-
{ "restaurantType": "sit-down", //不满足anyOf "total": 16.99 }
一个JSON Schema版本称为一个Dialect,Dialect表示用于评估模式的一组关键字和语义。每个发布的JSON Schama都是一个新的Dialect。
$schema$schema
关键字用于声明JSON Schema的dialect。$schema
关键字的值也是模式的标识符,可用于根据$schema
标识的dialect 验证模式是否有效。描述另一个模式的模式称为"meta-schema"。
$schema
位于整个文档的根,它不适用于外部引用的($ref
,$dynamicRef)文档。
- Draft 4:
http://json-schema.org/draft-04/schema#
- Draft 6:
http://json-schema.org/draft-06/schema#
. - Draft 7:
http://json-schema.org/draft-07/schema#
. - Draft 2019-09:
https://json-schema.org/draft/2019-09/schema
.
可以使用Meta-data关键字提供帮助信息,因为这类字段并不会影响校验过程。
{
"type": "object",
"requiredProperties": {
"foo": { "type": "string" }
}
}
允许:
-
{ "foo": "bar" }
-
{ "foo": 42 } //无法识别requiredProperties字段
本章介绍如何使用工具来重用和构造模式。
Schema Identification与其他编程语言类似,如果将模式分为多个逻辑单元,那么就可以互相引用。为了引用一个模式,需要一种方式来标识一个模式,称为non-relative URIs。
标识并不是必须的,只有在需要引用时才会用到标识。无标识的模式称为"匿名模式"。
URI术语有时可能不直观。在本文件中,使用了以下定义。
- URI [1] 或非相对 URI: 包含一个 scheme (
https
)的完整URL,可能包含一个URL片段 (#foo
)。有时,本文档会使用"非相对URI"来明确说明不允许使用相对URI- relative reference [2]: 不包含 scheme (
https
)的部分URL,可能包含一个片段(#foo
).- URI-reference [3]: 相对引用或非相对URI,可能包含一个URL片段 (
#foo
)- absolute URI [4] 包含一个 scheme (
https
)的完整URL,但不包含URL片段 (#foo
)
基本URI虽然使用URL来标识模式,但但这些标识并不需要网络可达。
使用非相对URI可能会很麻烦,因此JSON模式中使用的所有URI都可能是URI引用,它们会根据模式的基本URI进行解析,从而生成非相对URI。本节描述如何确定模式的基本URI。
检索URIRFC-3986中定义了基本URI和相对引用解析。
用于获取模式的URI称为“检索URI”。
假设使用URI引用了一个模式https://example.com/schemas/address
,然后检索到以下模式。
{
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
此时该模式的基本URI与检索URI相同
$id可以在模式的根使用$id
关键字定义基本URI,$id
的值是一个URI引用,没有根据检索URI解析的片段。
假设URIs https://example.com/schema/address
和 https://example.com/schema/billing-address
都使用了如下模式:
{
"$id": "/schemas/address",
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
无论使用两个URI中的哪一个来检索此模式,基本URI都是https://example.com/schemas/address
,这是$id
根据检索URI解析出的结果。
然而,在设置基本URI时使用相对引用可能会有问题。例如,不能将此模式用作匿名模式,由于没有检索URI,且无法对任何内容解析相对引用。出于这种原因,建议在使用$id
声明基本URI时,使用完整的URI。
{
"$id": "https://example.com/schemas/address",
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
JSON指针
除了表示一个模式文档,还可以标识子模式。最常见的方式是在指向该子模式的URI片段中使用JSON 指针 。
JSON指针描述了一个斜杠分隔的路径,用于遍历文档中对象中的键。/properties/street_address
意味着:
- 找到第一个键
properties
的值 - 在该对象中找到键
street_address
的值
URI https://example.com/schemas/address#/properties/street_address
标识了下述模式的含注释的子模式
{
"$id": "https://example.com/schemas/address",
"type": "object",
"properties": {
"street_address":
{ "type": "string" }, //标识该子模式
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
$anchor
一种不太常见的识别子模式的方法是使用$anchor
关键字在模式中创建一个命名锚点,并在URI片段中使用该名称。锚点必须以字母开头,后跟任意数量的字母、数字-
, _
, :
或.
。
URI https://example.com/schemas/address#street_address
标识了下述模式的含注释的子模式
{
"$id": "https://example.com/schemas/address",
"type": "object",
"properties": {
"street_address":
{
"$anchor": "street_address",//标识该子模式
"type": "string" //标识该子模式
},
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"]
}
$ref
一个模式可以使用$ref
关键字引用另一个模式。 $ref
是一个根据基本URI解析的URI引用。
假设需要定义一个客户记录,每个客户都可能有一个送货地址和账单地址。地址格式是相同的,都有一个街区地址、城市和国家。
$ref
中的URL引用根据基本URI (https://example.com/schemas/customer
)解析为 https://example.com/schemas/address
.
{
"$id": "https://example.com/schemas/customer",
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"shipping_address": { "$ref": "/schemas/address" },
"billing_address": { "$ref": "/schemas/address" }
},
"required": ["first_name", "last_name", "shipping_address", "billing_address"]
}
$def假设在匿名模式中使用
$ref
,则无法解析相对引用。如下例中,/properties/shipping_address
的$ref
可以正常解析,但/properties/billing_address
的$ref
则无法正常解析{ "type": "object", "properties": { "first_name": { "type": "string" }, "last_name": { "type": "string" }, "shipping_address": { "$ref": "https://example.com/schemas/address" }, "billing_address": { "$ref": "/schemas/address" } }, "required": ["first_name", "last_name", "shipping_address", "billing_address"] }
$defs
关键字提供了一个标准化的位置来保存子模式,以便在当前模式文档中重用。
扩展前面的客户模式示例,为name
属性使用公共模式。为此定义一个新的模式是没有意义的,它只会在该模式中使用,因此可以选择使用$defs
。
{
"$id": "https://example.com/schemas/customer",
"type": "object",
"properties": {
"first_name": { "$ref": "#/$defs/name" },
"last_name": { "$ref": "#/$defs/name" },
"shipping_address": { "$ref": "/schemas/address" },
"billing_address": { "$ref": "/schemas/address" }
},
"required": ["first_name", "last_name", "shipping_address", "billing_address"],
"$defs": {
"name": { "type": "string" }
}
}
$ref
不仅仅有助于避免重复。它还可以用于编写更易于阅读和维护的模式。可以使用带有描述性名称的$defs
来定义模式的复杂部分,并在需要的地方引用。
递归可以引用外部子模式,但通常将
$ref
限制为引用外部模式或$defs
中定义的内部子模式。
$ref
关键字可以为指向的模式创建递归模式。例如,person
模式中有一个children
数组,而每个数组元素又是一个person
实例:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#" }
}
}
}
允许:
-
{ "name": "Elizabeth", "children": [ { "name": "Charles", "children": [ { "name": "William", "children": [ { "name": "George" }, { "name": "Charlotte" } ] }, { "name": "Harry" } ] } ] }
上面创建了一个指向自身的模式,有效地在校验器中创建了一个“循环”。但需要注意,如下,在$ref
引用另一个$ref
可能会在解析器中导致无限循环。
{
"$defs": {
"alice": { "$ref": "#/$defs/bob" },
"bob": { "$ref": "#/$defs/alice" }
}
}
Bundling
在子模式中使用$id
时,它表示一个嵌入的模式,它的标识符是$id
的值,该值根据它出现在其中的模式的基本URI进行解析。包含嵌入模式的模式文档称为复合模式文档。复合模式文档中每个带有$id
的模式称为模式资源。
本例显示了捆绑到复合模式文档中的客户模式示例和地址模式示例:
{
"$id": "https://example.com/schemas/customer",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"shipping_address": { "$ref": "/schemas/address" },
"billing_address": { "$ref": "/schemas/address" }
},
"required": ["first_name", "last_name", "shipping_address", "billing_address"],
"$defs": {
"address": {
"$id": "/schemas/address",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "$ref": "#/definitions/state" }
},
"required": ["street_address", "city", "state"],
"definitions": {
"state": { "enum": ["CA", "NY", "... etc ..."] }
}
}
}
}
无论模式资源是否捆绑,复合模式文档中的所有引用都必须相同。注意,客户模式中的$ref
关键字没有变更。唯一的区别是,地址模式现在定义为/$defs/address
,而不是单独的模式文档。你无法使用#/$defs/address
引用地址模式,因为如果将模式拆分,该引用将不再指向地址模式。
此外还可以看到“$ref”:“#/definitions/state”
解析为地址模式中的definitions
关键字,而不是顶层模式中的definitions
关键字,就像不使用嵌入模式时一样。
每个模式资源都是独立评估的,可以使用不同的JSON模式dialects。上面的示例中,地址模式资源使用Draft 7,而客户模式资源使用Draft 2020-12。如果嵌入式模式中没有声明$schema
,则默认使用父模式的dialects。