參考

結構化複雜的模式

當編寫即使是中等複雜度的電腦程式時,普遍認為將程式「結構化」成可重複使用的函式,比在任何使用它們的地方複製貼上重複的程式碼片段更好。同樣地,在 JSON Schema 中,對於除了最簡單的模式以外的任何模式,將模式結構化成可以在多個地方重複使用的部分真的很有用。本章將介紹可用於重複使用和結構化模式的工具,以及一些使用這些工具的實際範例。

模式識別

與任何其他程式碼一樣,如果模式可以分解成邏輯單元並在必要時相互參照,則模式更容易維護。為了參照模式,我們需要一種方法來識別模式。模式文件由非相對 URI 識別。

模式文件不一定需要有識別符,但如果您想從另一個模式參照一個模式,則需要一個識別符。在本文件中,我們將沒有識別符的模式稱為「匿名模式」。

在以下章節中,我們將看到如何確定模式的「識別符」。

URI 術語有時可能不直觀。在本文件中,使用以下定義。

  • URI [1]非相對 URI:包含方案(https)的完整 URI。它可能包含 URI 片段(#foo)。有時本文會使用「非相對 URI」來明確指出不允許使用相對 URI。
  • 相對參照 [2]:不包含方案(https)的部分 URI。它可能包含片段(#foo)。
  • URI 參照 [3]:相對參照或非相對 URI。它可能包含 URI 片段(#foo)。
  • 絕對 URI [4]:包含方案(https)但不包含 URI 片段(#foo)的完整 URI。

即使 schema 是透過 URI 來識別,這些識別符號不一定是可以網路定址的。它們僅僅是識別符號。一般而言,實作不會發出 HTTP 請求(https://)或從檔案系統讀取(file://)來取得 schema。相反地,它們提供一種將 schema 載入內部 schema 資料庫的方法。當 schema 被其 URI 識別符號引用時,會從內部 schema 資料庫中檢索該 schema。

基礎 URI

使用非相對 URI 可能會很麻煩,因此 JSON Schema 中使用的任何 URI 都可以是 URI 參考,這些參考會根據 schema 的基礎 URI 解析為非相對 URI。本節說明如何確定 schema 的基礎 URI。

基礎 URI 的確定和相對參考解析由 RFC-3986 定義。如果您熟悉 HTML 中的運作方式,本節應該會讓您感到非常熟悉。

檢索 URI

用於提取 schema 的 URI 稱為「檢索 URI」。通常可以將匿名 schema 傳遞給實作,在這種情況下,該 schema 將沒有檢索 URI。

假設使用 URI https://example.com/schemas/address 引用了一個 schema,並且檢索到以下 schema。

schema
{ "type": "object", "properties": { "street_address": { "type": "string" }, "city": { "type": "string" }, "state": { "type": "string" } }, "required": ["street_address", "city", "state"]}

此 schema 的基礎 URI 與檢索 URI 相同,為 https://example.com/schemas/address

$id

您可以使用 schema 根部的 $id 關鍵字來設定基礎 URI。$id 的值是一個沒有片段的 URI 參考,會根據 檢索 URI 進行解析。結果的 URI 是該 schema 的基礎 URI。

特定草案資訊:
草案 4
草案 4-7
在草案 4 中,$id 只是 id(沒有美元符號)。

這類似於 HTML 中的 <base> 標籤。

$id 關鍵字出現在子 schema 中時,其含義略有不同。請參閱 捆綁章節以了解更多。

假設 URI https://example.com/schema/addresshttps://example.com/schema/billing-address 都識別以下 schema。

schema
{ "$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 時使用相對參考可能會出現問題。例如,我們無法將此綱要作為匿名綱要使用,因為不會有檢索 URI,而且無法針對空值解析相對參考。基於此原因和其他原因,建議您在使用 $id 宣告基準 URI 時,務必使用絕對 URI。

無論擷取 URI 為何,或者是否作為匿名綱要使用,以下綱要的基礎 URI 將永遠是 https://example.com/schemas/address

schema
{ "$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 的意思是

  • 1) 找到鍵 properties 的值
  • 2) 在該物件中,找到鍵 street_address 的值

URI https://example.com/schemas/address#/properties/street_address 識別了以下綱要中突出顯示的子綱要。

schema
{ "$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 片段中使用該名稱。錨點必須以字母開頭,後接任意數量的字母、數字、-_:.

特定草案資訊:
草案 4
Draft 6-7

在 Draft 4 中,宣告錨點的方式與 Draft 6-7 相同,只是 $id 僅為 id(不含美元符號)。

如果定義的具名錨點不符合這些命名規則,則行為未定義。您的錨點可能在某些實作中有效,但在其他實作中無效。

URI https://example.com/schemas/address#street_address 可識別以下模式中反白部分的子模式。

schema
{ "$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 參考,會根據模式的 Base URI 解析。當評估 $ref 時,實作會使用解析後的識別符號來檢索被參考的模式,並將該模式應用於實例

特定草案資訊
在 Draft 4-7 中,$ref 的行為略有不同。當一個物件包含 $ref 屬性時,該物件會被視為參考,而不是模式。因此,您放入該物件中的任何其他屬性都不會被視為 JSON Schema 關鍵字,並且驗證器會忽略它們。$ref 只能在預期會有模式的地方使用。

對於這個範例,假設我們想要定義一個客戶記錄,其中每個客戶可能會有一個寄送地址和一個帳單地址。地址的內容都相同 — 它們都有街道地址、城市和州 — 所以我們不希望在我們想要儲存地址的每個地方都重複模式的這部分。這不僅會使模式更加冗長,而且會使未來更新更加困難。如果我們想像中的公司未來開始進行國際業務,並且我們想要在所有地址中新增一個國家/地區欄位,最好在單一位置執行此操作,而不是在所有使用地址的地方執行。

schema
{ "$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 中的 URI 參考會根據綱要的基礎 URIhttps://example.com/schemas/customer)進行解析,結果會是 https://example.com/schemas/address。實作會擷取該綱要,並使用它來評估「shipping_address」和「billing_address」屬性。

當在匿名綱要中使用 $ref 時,相對參考可能無法解析。假設此範例被用作匿名綱要

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

/properties/shipping_address$ref 能夠順利解析,即使沒有非相對的基本 URI 可以進行解析。然而,在 /properties/billing_address$ref 無法解析成非相對的 URI,因此不能用來檢索地址綱要。

$defs

有時候,我們會有僅限於在目前綱要中使用的子綱要,將它們定義為獨立的綱要沒有意義。雖然我們可以使用 JSON 指標或具名錨點來識別任何子綱要,但 $defs 關鍵字提供了一個標準化的位置,讓我們可以保存用於在目前綱要文件中重複使用的子綱要。

讓我們擴展先前的客戶綱要範例,為姓名屬性使用一個共通的綱要。為此定義一個新的綱要沒有意義,而且它只會在這個綱要中使用,所以使用 $defs 是個不錯的選擇。

schema
{ "$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" } }}

$defs 不僅僅適用於避免重複。它也可以用於編寫更容易閱讀和維護的綱要。綱要的複雜部分可以在 $defs 中使用描述性名稱定義,並在需要的地方進行參照。這讓綱要的讀者能夠更快且更容易地在高層次理解綱要,然後再深入研究更複雜的部分。

可以參照外部的子綱要,但一般來說,你會希望限制 $ref 僅參照外部綱要或在 $defs 中定義的內部子綱要。

遞迴

可以使用 $ref 關鍵字來建立遞迴的 schema,讓 schema 可以參考自身。舉例來說,你可能會建立一個 person schema,其中包含一個 children 陣列,而每個 children 也都是 person 的實例。

schema
{ "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 可能會在解析器中導致無限迴圈,因此被明確禁止。

schema
{ "$defs": { "alice": { "$ref": "#/$defs/bob" }, "bob": { "$ref": "#/$defs/alice" } }}

擴展遞迴模式

2019-09 草案中的新功能

文件即將推出

捆綁

使用多個綱要文件對於開發來說很方便,但對於發佈來說,通常更方便將所有綱要捆綁到單個綱要文件中。這可以使用子綱要中的 $id 關鍵字來完成。當 $id 在子綱要中使用時,它表示一個嵌入式綱要。嵌入式綱要的識別符號是 $id 的值,針對它出現的綱要的 Base URI 解析而得。包含嵌入式綱要的綱要文件稱為複合綱要文件。複合綱要文件中每個具有 $id 的綱要都稱為綱要資源。

特定草案資訊:
草案 4
草案 4-7
在草案 4 中,$id 只是 id(沒有美元符號)。

這類似於 HTML 中的 <iframe> 標籤

在開發綱要時,使用嵌入式綱要是很不尋常的。一般來說,最好不要明確地使用此功能,而是使用綱要捆綁工具來建構捆綁的綱要(如果需要)。::

此範例顯示了捆綁到複合綱要文件中的客戶綱要範例和地址綱要範例。

schema
{ "$id": "https://example.com/schemas/customer", "$schema": "https://json-schema.dev.org.tw/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": "https://example.com/schemas/address", "$schema": "https://json-schema.dev.org.tw/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 來參照地址綱要,因為如果您解除綱要的捆綁,該參照將不再指向地址綱要。

特定草案資訊
在 Draft 4-7 中,這兩個 URI 都是有效的,因為子綱要 $id 僅表示 Base URI 變更,而不是嵌入式綱要。但是,即使允許,仍然強烈建議 JSON 指標不要跨越具有 Base URI 變更的綱要。

您也應該看到 "$ref": "#/definitions/state" 解析為地址綱要中的 definitions 關鍵字,而不是像未使用嵌入式綱要時一樣解析為最上層綱要中的關鍵字。

每個綱要資源都是獨立評估的,並且可以使用不同的 JSON 綱要方言。上面的範例中,地址綱要資源使用 Draft 7,而客戶綱要資源使用 Draft 2020-12。如果沒有在嵌入式綱要中聲明 $schema,則預設為使用父綱要的方言。

特定草案資訊
在 Draft 4-7 中,子綱要 $id 僅僅是 Base URI 變更,而不被視為獨立的綱要資源。由於 $schema 僅允許在綱要資源的根目錄使用,因此使用子綱要 $id 捆綁的所有綱要都必須使用相同的方言。
特定草案資訊
在 Draft 2020-12 中,新增了在嵌入式綱要中更改方言(使用 $schema 並使用與父綱要不同的值)的支援。

需要協助嗎?

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

幫助我們改進文件!

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

仍然需要協助嗎?

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