一個 JSON Schema 到 Zod 的轉換器能從單一 JSON 值產生可審閱的 Zod 原始碼,無需上傳且無執行階段依賴,但它刻意不進一步驗證你的真實 API 合約,因為單一範例無法確立格式、範圍、列舉或商業規則。「JSON Schema to Zod」這個詞彙其實被用於兩種不同的工作,而這個區別決定了你能在輸出中信任什麼。有些讀者指的是 JSON Schema 規範——Draft 7、2019-09 或 2020-12——一個帶有 type、properties、required、format、minimum 和 pattern 等關鍵字的詞彙表。其他讀者其實手邊只有一個 JSON 值,並希望取得能貼進 TypeScript 檔案的 Zod 原始碼。JSON to Zod Schema Converter 採取的是第二種路徑:它解析一個嚴格的 JSON 值,以遞迴方式走訪結構,並產生一個 Zod 宣告,同時附帶一個推論出來的 TypeScript 型別。了解你手邊是哪一種工作,會徹底改變在該架構可安全用於驗證正式流量之前,還需要進行哪些檢查。

json schema to zod converter
json schema to zod converter

JSON Schema 規範與 JSON 範例:選擇正確的起點

如果你的輸入是一份 JSON Schema 文件——也就是像 {"type":"object","properties":{"id":{"type":"string"}},"required":["id"]} 這樣的字面物件——把它送進以範例為基礎的轉換器,所得到的 Zod 架構描述的是該架構物件本身的形狀,而不是該架構原本要驗證的資料。輸出會把 type 與 properties 當成普通物件上的一般屬性名稱來處理,這並沒有用。要翻譯真正的 JSON Schema 文件(Draft 7+),需要使用不同類型的程式庫,它會走訪關鍵字樹,並產生對應的 Zod 呼叫,例如 z.object({ id: z.string() }),並在 minLength 上加上 .min(1) 之類的細化處理。這些程式庫以 npm 套件形式存在;當你把一個值貼進以範例為先的轉換器時,在瀏覽器中執行的並不是它們。

以範例為先的方法真正擅長的是更常見的真實工作情境:你從隊友、API 回應或儲存的測試資料中拿到單一 JSON 承載,並希望取得一份可以編輯的 Zod 架構起點。轉換器把那一筆觀察轉成可匯入的原始碼,讓你能審閱、與文件比對差異,並加以延伸。「規範翻譯」與「範例推論」的區別,是你按下「產生」之前應做的第一個決定。

產生器自動處理的型別對應

對基本型別而言,轉換是確定性的。每個觀察到的 JSON 值都會落入下列其中一個有文件記載的 Zod 建構子,並且因為相同的架構會被去重、混合值會以確定性順序排列,對同一份 JSON 重複轉換會產生穩定的原始碼。

觀察到的 JSON 值產生的 Zod 運算式備註
字串,例如 "hello"z.string()不推論格式;請依文件手動加上 .email()、.url() 或 .uuid()。
有限數字,例如 42 或 3.14z.number()已經過 JSON.parse;請以 JavaScript 數字精度視之。
布林值 true 或 falsez.boolean()不會對 truthy 字串或 0/1 數字進行強制轉型。
nullz.null()若與其他觀察到的型別並存,會變成 .nullable()。
物件 {...}z.object({...})屬性依來源出現順序排列,而非依字母順序。
陣列 [...]z.array(...)空陣列會產生 z.array(z.unknown()),而不是猜測元素型別。

在輸出中,屬於合法 JavaScript 識別字的鍵會保持不加引號。含有空格、連字號、以數字開頭或引號的鍵,則會以 JSON 字串跳脫方式輸出,使外層的 z.object 呼叫仍維持合法原始碼。這樣既能讓產生的文字保持可讀性,又能處理屬性命名不拘常規的 API 回應。

三個步驟把 JSON 值轉成 Zod 原始碼

以範例為先的工作流程刻意設計得很短。每個步驟都有審閱關卡,讓你在推論結果進入程式碼庫前及時攔下問題。

  1. 貼上一個有效且具代表性的 JSON 值,並輸入一個有效的架構識別字,例如 UserSchema。僅接受嚴格 JSON——JavaScript 註解、尾端逗號、單引號、NaN、Infinity、undefined 與 BigInt 語法會被拒絕,而不會默默修復。請使用一個能代表你真正想驗證之形狀的單一承載,而不是會隱藏可選欄位的精簡範例。
  2. 產生 Zod 原始碼,然後檢視輸出中關於可選、可空、聯合、數字與空陣列的選擇。檢查在樣本中至少有一個物件缺少的屬性是否加上 .optional()、曾經出現過 null 的鍵是否加上 .nullable(),並確認混合型別陣列是否以穩定順序產生了 z.union([...]) 項目。
  3. 把原始碼複製到已安裝 Zod 的專案中,並以權威的資料合約進行測試。請同時以通過的正式環境案例與被拒絕的邊界案例來跑這個架構,而不只是用原本的範例,以確認必填欄位、格式與商業規則都運作正確。

處理上限為輸入 500,000 個字元、走訪 50,000 個值、40 層巢狀,以及產生 1,000,000 個字元。達到任何一項上限都會回傳明確的錯誤,絕不會輸出半成品架構。即使在這些限制之內,過深或過寬的範例仍可能是不良的合約文件,因此建議使用小而具代表性的測試資料,並把獨立的承載拆成多個架構。

在提交前應審閱的推論決策

範例推論編碼了少數刻意為之的選擇,與人類撰寫架構的方式不同。事先理解它們能避免程式碼審閱時的意外。

範例中的情境產生器產生的結果原因
空陣列 []z.array(z.unknown())沒有未來元素型別的證據;以明確的不確定訊號呈現,而非默默猜測為 z.string() 或 z.never()。
含重疊鍵的物件陣列合併鍵,每個值以遞迴方式合併在指定預算內,擷取每個元素觀察到之形狀的聯集。
在每個樣本物件中都出現的物件屬性必填(不加 .optional())在樣本中持續出現會被視為必填。
在至少一個樣本物件中缺少的物件屬性.optional()在任何合併樣本中缺席,即把該屬性翻為可選。
同一個鍵在樣本中持有不同型別以確定性順序的遞迴聯合保留每個觀察到的值型別,且各次執行之間不重新排序。
null 與其他觀察到的型別並存類似 z.string().nullable() 的形式null 被視為「缺席或未設定」的哨兵值,而不是獨立的型別。

合併規則是確定性的,這代表兩個人在不同機器上貼上同一個 JSON 值,所看到的 Zod 原始碼會位元完全相同。這個特性對程式碼審閱、差異比對與共用測試資料都很重要——重複執行不會引入雜訊,且在同一次輸出中,相同的架構會被去重。

轉換器拒絕猜測之處——以及為什麼

產生器不會推論字串格式、最小值、最大值、整數限制、列舉、字面值、可辨別聯合、日期、UUID、URL、電子郵件地址、品牌、強制轉型、預設值、轉換、細化、record、tuple、嚴格物件行為、遞迴參考或商業規則。其理由非常具體:單一樣本無法區分「2024-03-15」與一般字串,無法證明數字欄位永遠是整數,也無法從單一觀察還原出封閉的列舉。拼字變體與小樣本不足以確立意圖,因此轉換器把這些決定留給你。

一旦有了穩定的起始架構,手動補上這些規則並不困難。例如,在貼上使用者物件後,如果你的合約保證識別字不可為空,可以為 id: z.string() 加上 .min(1);如果 API 文件記載使用 UUID,則可以加上 .uuid()。官方的 Zod API 參考文件 記載了你可以串接在基本建構子上的每個方法,在執行測試前於 package.json 中釘住特定的 Zod 版本,能讓產生的原始碼與你實際部署的執行階段保持相容。

數字有一項特別值得提出的注意事項:因為解析是透過 JSON.parse 進行,樣本中的每個數字都已經遵守 JavaScript 的數字精度。識別字、貨幣金額,或任何需要精確十進位語義的值,通常應該保留為字串——一旦產生器產生了 z.number(),該架構就會接受任何數字,而不會檢查你合約所要求的精度。

把輸出接入真正的專案

產生架構的該頁面不會載入 Zod、不會執行產生的程式碼,也不會上傳你的樣本。安裝發生在你自己的專案中,並搭配釘住特定版本的 Zod 與所選的模組格式。複製原始碼後,請執行 Prettier 等格式化工具、執行 TypeScript 編譯器以確認推論出來的型別與你的領域模型一致,並針對真實測試資料(而非原始樣本)中每個通過的案例與每個被拒絕的案例,各寫至少一個測試。

請把產生的架構視為需要審閱的起點,而非完成的合約。在你單一範例中出現的欄位,在正式環境中仍可能是可選的;在樣本中缺席的值仍可能有效;而 API 文件中記載的任何約束——範圍、列舉、格式、建立時必填但更新時可選的語意——都必須透過閱讀權威文件來補上,而不是靠推論。這個審閱步,正是「能編譯的架構」與「能保護應用程式免於壞資料的架構」之間的差別。

如果你正在權衡選項,Convert .properties Files to JSON Online 有詳細說明。