• Understanding JSON Schema


    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"] }
      

    jsonschema的五种基本类型

    string
    { "type": "string" }
    

    可以表示的字符串如:"This is a string""""Déjà vu"(unicode字符)

    length

    用于限制字符串的长度

    {
      "type": "string",
      "minLength": 2,
      "maxLength": 3
    }
    
    正则表达式

    使用pattern字段设置正则表达式,具体参见官方说明

    {
       "type": "string",
       "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"
    }
    
    数字类型
    integer
    { "type": "integer" }
    

    用于表示整数类型。需要注意的是,小数点的存在与否并不能判断它是一个整数还是浮点数,例如11.0都会被认为是整数,但3.1415926则是浮点数。

    number

    用于表示任意数字类型,即整数或浮点数

    { "type": "number" }
    
    multiples

    用于表示特定数字的倍数,如下可以是010、20,但23不是10的倍数,所以允许。

    {
        "type": "number",
        "multipleOf" : 10
    }
    
    range

    使用minimummaximum表示的数字范围(或使用exclusiveMinimumexclusiveMaximum表示独占范围)

    • xminimum
    • x > exclusiveMinimum
    • xmaximum
    • x < exclusiveMaximum

    如下可以表示01099,但-1100101是错误的:

    {
      "type": "number",
      "minimum": 0,
      "exclusiveMaximum": 100
    }
    

    注意在JSON Schema Draft 4中exclusiveMinimumexclusiveMaximum的工作方式并不相同,它们表示一个boolean值,用于判断是否排除minimummaximum

    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" }
      
    Pattern Properties

    有时候期望对于某一类属性名称,匹配一个特定的模式,此时可以使用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" }
      
    Additional Properties

    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" }
    }
    

    如下将additionalPropertiespropertiespatternProperties结合起来使用,例如{ "keyword": "value" }不匹配propertiespatternProperties,但它匹配了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关键字解决这种问题

    Unevaluated Properties

    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的数目可以使用minPropertiesmaxProperties进行限制,值为整数。

    {
      "type": "object",
      "minProperties": 2,
      "maxProperties": 3
    }
    
    array

    表示一组有序的元组,数组中可以包含不同类型的元素。

    { "type": "array" }
    

    允许:

    • [1, 2, 3, 4, 5]
      
    • [3, "different", { "types" : "of values" }]
      

    JSON使用了两种数组方式:

    • List validation: 任意长度的数组,每个元素都使用相同的模式
    • Tuple validation: 固定长度的数组,每个元素都有可能使用不同的模式
    item

    List validation下使用item关键字来校验数组中的元素

    {
      "type": "array",
      "items": {
        "type": "number"
      }
    }
    

    如上表达式允许数组[1, 2, 3, 4, 5][],但不允许[1, 2, "3", 4, 5]

    Tuple 校验

    假设为了表达一个地址:"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不是字符串
      
    Contains

    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]
      
    length

    使用minItemsmaxItems限制数组的长度

    {
      "type": "array",
      "minItems": 2,
      "maxItems": 3
    }
    
    Uniqueness

    uniqueItems设置为true,确保数组中元素的唯一性

    {
      "type": "array",
      "uniqueItems": true
    }
    

    将不允许:

    • [1, 2, 3, 3, 4]
      
    boolean
    { "type": "boolean" }
    

    需要注意truefalse要小写

    null
    { "type": "null" }
    

    需要注意的是,在JSON中null并不代表某些内容不存在

    通用关键字
    Annotations

    JSON Schema中有一些关键字,这些关键字不用于校验,仅用于描述模式,这类"注释"关键字并不是必须的,但建议在实践中使用,由此可以实现模式的"自文档"。

    titledescription关键字必须是字符串。

    default关键字指定了默认值,该值不会填充验证过程中缺失的值。一些非验证的工具,如文档生成器或格式生成器会使用该值来提示用户如何使用一个值。

    New in draft 6examples关键字提供了一组校验模式的例子,它并不用于校验,仅帮助读者解释模式的影响和目的。examples中不需要default,可以将default看作是另一个examples

    New in draft 7:通常会在API上下文中使用boolean类型的readOnlywriteOnly关键字,前者表示不可修改某个值,当使用PUT请求修改值时,会响应400 Bad RequestwriteOnly表示可以设置值,但将保持隐藏状态,即可以通过PUT请求设置一个值,但在无法通过GET请求检索到该值。

    New in draft 2019-09deprecated关键字用来表示未来将会移除该实例值。

    {
      "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关键字用于给模式添加注释,该值必须是字符串。

    Enumerated values

    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的应用来说非常有用。

    contentMediaType

    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

    contentEncoding关键字指定保存内容的编码类型,参见RFC 2054, part 6.1RFC 4648

    可接受的值为7bit, 8bit, binary, quoted-printable, base16, base32和 `base64,如果没有指定,则与JSON文档的编码相同。

    通常的用法如下:

    • 如果编码的内容和JSON文档相同,则无需指定contentEncoding,按原样将内容包含在字符串中即可。包含基于文本的类型,如text/htmlapplication/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) 不能通过给定模式的校验
    allOf
    {
      "anyOf": [
        { "type": "string", "maxLength": 5 },
        { "type": "number", "minimum": 0 }
      ]
    }
    

    可以允许:

    • "short"
      
    • 12
      

    不允许:

    • "too long"
      
    • -5
      
    oneOf
    {
      "oneOf": [
        { "type": "number", "multipleOf": 5 },
        { "type": "number", "multipleOf": 3 }
      ]
    }
    

    可以允许:

    • 10
      
    • 9
      

    不允许:

    • 2
      
    • 15 // 同时是3和5的倍数
      
    not
    { "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

    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
      }
      
    If-Then-Else

    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”关键字没有效果(只作提示作用)

    上述方式只能处理两个国家的情况,如果要处理多个国家,可以将多个ifthen成对包含到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
    
    implication

    可以使用模式组合关键字来表示"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
      }
      
    声明一个Dialect

    一个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.
    Guidelines

    可以使用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)

    虽然使用URL来标识模式,但但这些标识并不需要网络可达。

    基本URI

    使用非相对URI可能会很麻烦,因此JSON模式中使用的所有URI都可能是URI引用,它们会根据模式的基本URI进行解析,从而生成非相对URI。本节描述如何确定模式的基本URI。

    RFC-3986中定义了基本URI和相对引用解析。

    检索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/addresshttps://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"]
    }
    

    假设在匿名模式中使用$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"]
    }
    

    $def

    $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。

  • 相关阅读:
    JavaScript 深入了解对象中的属性
    JavaScript 开发规范
    vue 项目接口管理
    放大镜特效
    多用户ATM机(面向对象编程)
    浏览器检查块代码
    js中innerHTML与innerText的用法与区别
    symbol访问法及symbor注册表
    最常用的15个前端表单验证JS正则表达式
    数组的遍历
  • 原文地址:https://www.cnblogs.com/charlieroro/p/15939356.html
Copyright © 2020-2023  润新知