Zod 上手筆記
這篇筆記的目的是記錄一些 zod 的常用操作,幫助開發者理解 zod 如何使用。
Zod 用來解決什麼問題?
把 raw input,轉換成符合驗證條件的 typed output
什麼是 raw input
JS 的變數,如 primitive 與 object
// Primitive
const food = "tuna";
const amount = 12; // number
const enable = true;
const bigAmount = 12n; // bigint
const field1 = null;
const field2 = undefined;
const date = Date();
const obj = { firstName: "", lastName: "" };
const arr = [];
什麼是 typed output
通過 zod 剖析後的結果,除了回傳的物件本身是驗證過的之外,還會自帶正確的 typescript 型別。
import { z } from "zod";
// 布林值
const enableSchema = z.boolean();
const enable = enableSchema.parse(true);
// 符合 email 格式的字串
const emailSchema = z.string().email();
const email = emailSchema.parse("abc@gmail.com");
// 符合 ethereum address 格式的字串
const addressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
const address = addressSchema.parse(
"0x1234567890123456789012345678901234567890",
);
// 日期物件
const dateSchema = z.date();
const date = dateSchema.parse(new Date());
// Big 物件(假設使用 big.js)
import { Big } from "big.js";
const bigSchema = z.instanceof(Big);
const big = bigSchema.parse(new Big(123.45));
什麼是驗證條件?
任何你想得到的驗證
- 例如檢查 number 在規定的範圍內
- 檢查字串符合 email 的格式
什麼是轉換(Transform)
舉些例子
- 將驗證後的字串變成小寫
- 將 string 變成 number
- 將 ISO8601 string 變成日期物件
學習要點
- 如何把 raw input 轉換成符合驗證條件的 typed output
- raw input 的 typing 是怎麼決定的?
- typed output 的 typing 是怎麼決定的?
- 怎麼定義驗證條件?
- 怎麼進行轉換?
Number 範例
從一個簡單的 number 範例開始。
const ageSchema = z.number().gte(18);
const age = ageSchema.parse(21);
const ageResult = ageSchema.safeParse(21);
ageSchema 規範輸入是 number,輸出也是 number,而且要求該 number 要 ≥ 18。
- parse
- 成功時會直接回傳 parse 成功的結果。
- 失敗時會 throw ZodError
.parse(data: unknown): T
- safeParse
- 失敗不會 throw ZodError,回傳一個 parse 結果的物件表示成功或失敗
.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }
添加更多驗證
z.number().gt(5).lt(10);
自訂錯誤訊息
z.number().lte(5, { message: "this👏is👏too👏big" });
接受 number 外的的 Input
z.number() 要求 input 是 number。但時常我們會遇到 input 是字串而非 number。
const ageSchema = z.number().gte(18);
ageSchema.parse("21"); // throw error, input type is not number
我們可以使用 z.coerce,會強制 input 都套用原生型別的 constructor,Number(input),再進行後續的處理。
const ageSchema2 = z.coerce.number().gte(18); // Number(input)
ageSchema.parse("21"); // return 21
⚠️ coerce 因為直接使用 built-in constructor 轉換,在遇到 null, undefined 等輸入時,並不會報錯。舉例來說,Number(null) 會回傳 0,而不是 throw error。
如果你想要將 null
, undefined
視為錯誤,你會需要 .pipe
String 範例
現在我們把學到的內容推廣到 string。
z.string() 要求 input 是 string,輸出也是 string,同時要滿足string length = 5 的驗證條件。
const labelSchema = z.string().length(5);
const label = labelSchema.parse("abcde"); // label = "abcde"
label.parse(12345); // throw ZodError, Expected string, received number
接受 string 外的 Input
coerce 就是讓 input 強制套用 built-in constructor,因此 input 會先被轉換成 String(input)
const labelSchema = z.coerce.string().length(5);
const label = label.parse(12345); // String(12345), then verify length
// label = "12345"
更進階的 string transform
將 string 轉換成小寫
一個常見的需求是,驗證完成後,改變輸入內容。像是改變大小寫、trim 掉空白,這個步驟稱為 transform。
const label = z.string().toLowerCase();
const str = label.parse("AbC"); // str = abc
將 string 轉換成 number
const toNumberSchema = z.coerce.number();
const n = toNumberSchema.parse("123"); // n = 123
將 string 轉換成 date 物件
// string 必須符合 ISO8601,預設不允許 timezone offset
const datetime = z.string().datetime();
datetime.parse("2020-01-01T00:00:00Z"); // pass
datetime.parse("2020-01-01T00:00:00.123Z"); // pass
datetime.parse("2020-01-01T00:00:00.123456Z"); // pass (arbitrary precision)
datetime.parse("2020-01-01T00:00:00+02:00"); // fail (no offsets allowed)
// Timezone offsets can be allowed by setting the offset option to true.
const datetime = z.string().datetime({ offset: true });
datetime.parse("2020-01-01T00:00:00+02:00"); // pass
datetime.parse("2020-01-01T00:00:00.123+02:00"); // pass (millis optional)
datetime.parse("2020-01-01T00:00:00.123+0200"); // pass (millis optional)
datetime.parse("2020-01-01T00:00:00.123+02"); // pass (only offset hours)
datetime.parse("2020-01-01T00:00:00Z"); // pass (Z still supported)