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字段可以是一个字符串或一个数组
如果是一个字符串则表示基本数据类型,如:42、42.0
{ "type": "number" }如果是一个字符串,则表示数据可以是其中的任一基本类型,如:42、"Life, the universe, and everything",但不能是结构化的数据类型,如:["Life", "the universe", "and everything"]
{ "type": ["number", "string"] }{ "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}$"}{ "type": "integer" }用于表示整数类型。需要注意的是,小数点的存在与否并不能判断它是一个整数还是浮点数,例如1和1.0都会被认为是整数,但3.1415926则是浮点数。
用于表示任意数字类型,即整数或浮点数
{ "type": "number" }用于表示特定数字的倍数,如下可以是0、10、20,但23不是10的倍数,所以允许。
{ "type": "number", "multipleOf" : 10}使用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.objects是JSON中的mapping类型,即将"keys"映射到"values","keys"必须是字符串,通常将每一对映射称为"属性"。
{ "type": "object" }可以表达如下值:
{ "key": "value", "another_key": "another_value"}属性是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"}默认情况下,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" }properties的数目可以使用minProperties和maxProperties进行限制,值为整数。
{ "type": "object", "minProperties": 2, "maxProperties": 3}表示一组有序的元组,数组中可以包含不同类型的元素。
{ "type": "array" }允许:
[1, 2, 3, 4, 5][3, "different", { "types" : "of values" }]JSON使用了两种数组方式:
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" }}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}将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}New in draft 7 $comment
$comment关键字用于给模式添加注释,该值必须是字符串。
enum关键字用于指定一组固定的值。它必须是一个数组,且最少包含一个元素,每个元素都是唯一的。
{ "enum": ["red", "amber", "green", null, 42]}New in draft 6
const关键字用于指定单个值。如下例,将country限制为"United States of America",不允许出现其他值:
{ "properties": { "country": { "const": "United States of America" } }}JSON Schema中有一组关键字用于描述和选择性校验保存在JSON字符串中的非JSON数据。由于很难为所有媒体类型编写校验器,因此JSON 模式校验器不需要基于这些关键字验证JSON字符串的内容。但对于那些需要消费经过校验的JSON的应用来说非常有用。
contentMediaType关键字用于指定字符串内容的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文档的编码相同。
通常的用法如下:
contentEncoding,按原样将内容包含在字符串中即可。包含基于文本的类型,如text/html 或application/xmlcontentEncoding设置为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关键字无法表达的复杂限制。
这些关键字为:
{ "anyOf": [ { "type": "string", "maxLength": 5 }, { "type": "number", "minimum": 0 } ]}可以允许:
"short"12不允许:
"too long"-5{ "oneOf": [ { "type": "number", "multipleOf": 5 }, { "type": "number", "multipleOf": 3 } ]}可以允许:
109不允许:
215 // 同时是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关键字的值是一个对象,对象中的每个条目会映射到属性的名称。
如下,当提供了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类似。
| if | then | else | whole schema |
|---|---|---|---|
| T | T | n/a | T |
| T | F | n/a | F |
| F | n/a | T | T |
| F | n/a | F | F |
| n/a | n/a | n/a | T |
例如,如果你想编写一个模式来处理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"}上述"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关键字用于声明JSON Schema的dialect。$schema关键字的值也是模式的标识符,可用于根据$schema标识的dialect 验证模式是否有效。描述另一个模式的模式称为"meta-schema"。
$schema位于整个文档的根,它不适用于外部引用的($ref,$dynamicRef)文档。
http://json-schema.org/draft-04/schema#http://json-schema.org/draft-06/schema#.http://json-schema.org/draft-07/schema#.https://json-schema.org/draft/2019-09/schema.可以使用Meta-data关键字提供帮助信息,因为这类字段并不会影响校验过程。
{ "type": "object", "requiredProperties": { "foo": { "type": "string" } }}允许:
{ "foo": "bar" }{ "foo": 42 } //无法识别requiredProperties字段本章介绍如何使用工具来重用和构造模式。
与其他编程语言类似,如果将模式分为多个逻辑单元,那么就可以互相引用。为了引用一个模式,需要一种方式来标识一个模式,称为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)
虽然使用URL来标识模式,但但这些标识并不需要网络可达。
使用非相对URI可能会很麻烦,因此JSON模式中使用的所有URI都可能是URI引用,它们会根据模式的基本URI进行解析,从而生成非相对URI。本节描述如何确定模式的基本URI。
RFC-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关键字定义基本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"]}除了表示一个模式文档,还可以标识子模式。最常见的方式是在指向该子模式的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关键字在模式中创建一个命名锚点,并在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 是一个根据基本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"]}假设在匿名模式中使用
$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" } }}在子模式中使用$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。
本文来自博客园,作者:charlieroro,转载请注明原文链接:https://www.cnblogs.com/charlieroro/p/15939356.html