2020-12 版本發行說明

先前的草案 (2019-09) 引入了許多新概念,包括 $recursiveRef/$recursiveAnchorunevaluatedProperties/unevaluatedItems、詞彙表等等。從那時起,這些新功能已在實際 schema 中看到多種實作和使用。這個草案主要致力於與應用我們在野外實作和使用這些新功能所學到的經驗相關的變更。

本文嘗試將對 schema 作者最有用的資訊放在最上方,將對實作者有用的資訊放在最下方。

items 和 additionalItems 的變更

用於定義陣列和元組的關鍵字已重新設計,以幫助降低 JSON Schema 的學習曲線。由於 items 關鍵字同時用於這兩種類型,我們經常看到人們在打算定義陣列時錯誤地定義了元組,並且不明白為什麼只驗證陣列中的第一個項目。

itemsadditionalItems 關鍵字已替換為 prefixItemsitems,其中 prefixItems 具有與舊 items 的 schema 陣列相同的功能,而新的 items 關鍵字具有與舊 additionalItems 關鍵字相同的功能。

儘管 items 的含義已更改,但定義陣列的語法保持不變。只有定義元組的語法已更改。其想法是陣列具有項目 (items),並且可選擇性地具有一些位置定義的項目,這些項目位於普通項目之前 (prefixItems)。

以下是一些範例,說明這些變更。

開放元組

草案 2019-09草案 2020-12
資料
{ "items": [ { "$ref": "#/$defs/foo" }, { "$ref": "#/$defs/bar" } ]}
資料
{ "prefixItems": [ { "$ref": "#/$defs/foo" }, { "$ref": "#/$defs/bar" } ]}

封閉式元組

草案 2019-09草案 2020-12
資料
{ "items": [ { "$ref": "#/$defs/foo" }, { "$ref": "#/$defs/bar" } ], "additionalItems": false}
資料
{ "prefixItems": [ { "$ref": "#/$defs/foo" }, { "$ref": "#/$defs/bar" } ], "items": false}

具有約束的額外項目的元組

草案 2019-09草案 2020-12
資料
{ "items": [ { "$ref": "#/$defs/foo" }, { "$ref": "#/$defs/bar" } ], "additionalItems": { "$ref": "#/$defs/baz" }}
資料
{ "prefixItems": [ { "$ref": "#/$defs/foo" }, { "$ref": "#/$defs/bar" } ], "items": { "$ref": "#/$defs/baz" }}

$dynamicRef 和 $dynamicAnchor

$recursiveRef$recursiveAnchor 關鍵字被更強大的 $dynamicRef$dynamicAnchor 關鍵字所取代。$recursiveRef$recursiveAnchor 在先前的草案中被引入,以解決擴展遞迴模式的問題。隨著「遞迴」關鍵字得到一些使用,並且我們對它們有了更深入的了解,我們發現了如何將它們概括化以解決更多類型的問題。名稱的更改反映了這些關鍵字不僅僅適用於擴展遞迴模式。

$dynamicAnchor 可以被認為類似於普通的 $anchor,只是它可以跨模式引用,而不僅限於定義它的模式中。您可以將舊的 $recursiveAnchor 視為以相同的方式運作,只是它只允許您在每個模式中建立一個錨點,它必須位於模式的根部,並且錨點名稱始終為空。

$dynamicRef 的運作方式與舊的 $recursiveRef 相同,只是片段不再為空 ( "$dynamicRef": "#my-anchor" 而不是 "$recursiveRef": "#"),並且允許使用非僅片段 URI。當 $dynamicRef 包含非僅片段的 URI 參考時,URI 參考解析到的模式會被用作動態解析的起點。

以下是如何將使用 $recursiveRef 的模式轉換為使用 $dynamicRef

草案 2019-09草案 2020-12
1// tree schema, extensible
2{
3"$schema": "https://json-schema.dev.org.tw/draft/2019-09/schema",
4"$id": "https://example.com/tree",
5"$recursiveAnchor": true,
6"type": "object",
7"properties": {
8"data": true,
9"children": {
10  "type": "array",
11  "items": { "$recursiveRef": "#" }
12}
13}
14}
15// strict-tree schema, guards against misspelled properties
16{
17"$schema": "https://json-schema.dev.org.tw/draft/2019-09/schema",
18"$id": "https://example.com/strict-tree",
19"$recursiveAnchor": true,
20"$ref": "tree",
21"unevaluatedProperties": false
22}
1// tree schema, extensible
2{
3"$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema",
4"$id": "https://example.com/tree",
5"$dynamicAnchor": "node",
6"type": "object",
7"properties": {
8"data": true,
9"children": {
10  "type": "array",
11  "items": { "$dynamicRef": "#node"}
12}
13}
14}
15// strict-tree schema, guards against misspelled properties
16{
17"$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema",
18"$id": "https://example.com/strict-tree",
19"$dynamicAnchor": "node",
20"$ref": "tree",
21"unevaluatedProperties": false
22}

contains 和 unevaluatedItems

在先前的草案中,沒有明確指定 contains 關鍵字如何或是否會影響 unevaluatedItems 關鍵字。本草案明確指出,陣列中任何通過 contains 模式驗證的項目都被視為「已評估」。

這讓您可以使用 contains 來表達比先前草案中更簡潔的一些約束。這個範例展示了如何表達一個陣列,其中某些項目符合一個模式,而其他所有項目符合另一個模式。

草案 2019-09草案 2020-12
資料
{ "type": "array", "contains": { "type": "string" }, "items": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } }
資料
{ "type": "array", "contains": { "type": "string" }, "unevaluatedItems": { "type": "number" } }

不幸的是,此變更意味著您可能無法在某些過去可以使用的情況下使用 contains。請考慮這個 2019-09 草案綱要,它描述了一個包含兩個字串的元組,其中兩個字串之一必須包含三個或更多字元,且不允許任何額外的項目。

綱要
{ "$schema": "https://json-schema.dev.org.tw/draft/2019-09/schema", "type": "array", "items": [{ "type": "string" }, { "type": "string" }], "contains": { "type": "string", "minLength": 3 }, "unevaluatedItems": false}

給定這個 schema,實例 ["a", "b", "ccc"] 將會失敗,因為 "ccc" 被視為未評估,並且不符合 unevaluatedItems 關鍵字。現在讓我們天真地將這個範例轉換為 draft 2020-12 的 schema。

綱要
{ "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "type": "array", "prefixItems": [{ "type": "string" }, { "type": "string" }], "contains": { "type": "string", "minLength": 3 }, "unevaluatedItems": false}

給定這個綱要,實例 ["a", "b", "ccc"] 會通過驗證,因為 "ccc" 被視為已評估,且不適用於 unevaluatedItems 關鍵字。為了修正這個問題,我們可以採用之前在有 contains 關鍵字之前使用的相同布林代數轉換。

綱要
{ "$schema": "https://json-schema.dev.org.tw/draft/2020-12/schema", "type": "array", "prefixItems": [{ "type": "string" }, { "type": "string" }], "not": { "items": { "not": { "type": "string", "minLength": 3 } } }, "unevaluatedItems": false}

給定此綱要,實例 ["a", "b", "ccc"] 將會失敗,因為 "ccc" 被視為未評估,且像先前的草案一樣,不符合 unevaluatedItems 關鍵字的要求。

正規表示式

現在預期(但非嚴格要求)正規表示式要支援 Unicode 字元。先前,這部分未明確規範,實作可能支援也可能不支援正規表示式中的 Unicode。

媒體類型變更

JSON Schema 定義了兩種媒體類型:application/schema+jsonapplication/schema-instance+json。此草案取消了對 schema 媒體類型參數的支援。它造成了許多混亂和歧見。由於我們沒有看到任何證據顯示有人實際使用它,因此決定暫時將其移除。

嵌入式綱要與綑綁

在 2019-09 草案中,子綱要中 $id 的意義從指示目前綱要內的基礎 URI 變更,改為指示獨立於父綱要的嵌入式綱要。包含一或多個嵌入式綱要的綱要稱為「複合綱要文件」。此草案介紹了關於綑綁器應如何嵌入綱要以建立複合綱要文件的指導方針。

如果您參考外部綱要,該綱要可以宣告自己的 $schema,且可能與參考綱要的 $schema 不同。實作需要準備好切換處理模式,或是在不支援參考綱要的 $schema 時擲回錯誤。嵌入式綱要的運作方式完全相同。它們可能會宣告一個與父綱要不同的 $schema,實作需要準備好適當地處理 $schema 的變更。

嵌入式綱要的 $schema 與其父綱要不同的一個顯著後果是,實作無法直接針對 meta-schema 驗證複合綱要文件。複合綱要文件需要被分解,且每個綱要資源都需要根據該綱要的適當 meta-schema 進行個別驗證。

此草案介紹了關於如何使用嵌入式綱要將綱要綑綁到複合綱要文件中的官方指導方針。此方法旨在不必修改綱要(除了新增到 $defs 之外),使輸出結果在驗證綑綁的綱要或遵循外部參考時保持盡可能相似。以下是一個具有我們想要綑綁的外部參考的客戶綱要範例。

綱要
{ "$schema": "https://json-schema.dev.org.tw/draft/2020-12", "$id": "https://example.com/schema/customer",
"type": "object", "properties": { "name": { "type": "string" }, "phone": { "$ref": "/schema/common#/$defs/phone" }, "address": { "$ref": "/schema/address" }
資料
{ "$schema": "https://json-schema.dev.org.tw/draft/2020-12", "$id": "https://example.com/schema/address",
"type": "object", "properties": { "address": { "type": "string" }, "city": { "type": "string" }, "postalCode": { "$ref": "/schema/common#/$defs/usaPostalCode" }, "state": { "$ref": "/$defs/states" } },
"$defs": { "states": { "enum": [...] } }}
資料
{ "$schema": "https://json-schema.dev.org.tw/draft/2019-09", "$id": "https://example.com/schema/common",
"$defs": { "phone": { "type": "string", "pattern": "^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$" }, "usaPostalCode": { "type": "string", "pattern": "^[0-9]{5}(?:-[0-9]{4})?$" }, "unsignedInt": { "type": "integer", "minimum": 0 } }}

為了將這些綱要打包,我們只需使用 $defs 將每個引用的綱要添加為嵌入式綱要。以下是打包後的綱要外觀。

資料
{ "$schema": "https://json-schema.dev.org.tw/draft/2020-12", "$id": "https://example.com/schema/customer",
"type": "object", "properties": { "name": { "type": "string" }, "phone": { "$ref": "/schema/common#/$defs/phone" }, "address": { "$ref": "/schema/address" } },
"$defs": { "https://example.com/schema/address": { "$id": "https://example.com/schema/address",
"type": "object", "properties": { "address": { "type": "string" }, "city": { "type": "string" }, "postalCode": { "$ref": "/schema/common#/$defs/usaPostalCode" }, "state": { "$ref": "#/$defs/states" } },
"$defs": { "states": { "enum": [...] } } }, "https://example.com/schema/common": { "$schema": "https://json-schema.dev.org.tw/draft/2019-09", "$id": "https://example.com/schema/common",
"$defs": { "phone": { "type": "string", "pattern": "^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$" }, "usaPostalCode": { "type": "string", "pattern": "^[0-9]{5}(?:-[0-9]{4})?$" }, "unsignedInt": { "type": "integer", "minimum": 0 } } } }}

以下是您可能從此範例中注意到的一些事項。

  1. 沒有任何 $ref 被修改。即使是本地參考也沒有變更。
  2. https://example.com/schema/common#/ 中的 $defs/unsignedInt 即使沒有被使用,也隨著通用 schema 一起被引入。允許刪除多餘的定義,但並非必要。
  3. https://example.com/schema/address 沒有宣告 $schema。因為它使用與 https://example.com/schema/customer 相同的 $schema,它可以跳過該宣告,並使用它所嵌入的 schema 的 $schema
  4. https://example.com/schema/common 使用與它所嵌入的文件不同的 $schema。這是允許的。
  5. 來自 https://example.com/schema/common 的定義在其他兩個 schema 中都有使用,因此只需要包含一次。打包工具不需要將一個 schema 嵌入到另一個嵌入的 schema 中。

註解

收集註解的實作現在應該在「詳細」輸出格式中包含未知關鍵字的註解。未知關鍵字的註解值為該關鍵字的值。

詞彙變更

unevaluatedPropertiesunevaluatedItems 關鍵字已從應用程式詞彙移至它們自己的指定詞彙,該詞彙在預設的中繼 schema 中是必需的。在 Draft 2019-09 中,如果沒有實作這些關鍵字,則會預期抛出錯誤。這是應用程式詞彙的特殊行為。將「未評估」關鍵字移至它們自己的詞彙,允許我們移除該特殊情況,並允許建構不需要這些關鍵字的方言。

格式詞彙被分成兩個獨立的詞彙。「格式註解」詞彙將 format 關鍵字視為註解,「格式斷言」詞彙將 format 關鍵字視為斷言。「格式註解」詞彙用於預設的中繼 schema 中,並且是必需的。在 Draft 2019-09 中,format 預設應作為註解評估,並且實作可以提供配置來更改行為,將 format 作為斷言評估。獨立的詞彙允許移除特殊的配置需求,並僅使用詞彙系統來表達應使用哪種行為。

需要協助嗎?

您覺得這些文件有幫助嗎?

幫助我們改善文件!

在 JSON Schema 中,我們重視文件的貢獻,就像其他任何類型的貢獻一樣!

仍然需要協助嗎?

學習 JSON Schema 通常令人困惑,但別擔心,我們在這裡提供幫助!