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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章