💎
Zodで条件分岐を含んだバリデーションを実装する
フォームの実装で「Aが〇〇のときだけBのバリデーションを変えたい」というケースはよくあります。
z.discriminatedUnion()
を使用する
フィールドの値で分岐する場合は
フィールドの値によって、バリデーションを実施するかどうか決定する場合はz.discriminatedUnion()
を使用することで実現できます。
v3.12.0
でリリースされたメソッドで第1引数に指定されたキーを評価することでz.union()
をよりもパース処理が高速になります。
const responseSchema = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.string() }),
z.object({ status: z.literal("failed"), error: z.instanceof(Error) }),
]);
export type Response = z.infer<typeof responseSchema>;
z.infer
から推論される型は下記のようなユニオン型になっています。
type Response = {
status: "success";
data: string;
} | {
status: "failed";
error: Error;
}
z.refine()
を使用する
特定の条件で分岐する場合はrefine()
メソッド内で条件分岐によりバリデーションを行うことができます。例えば「日付の範囲選択バリデーション」をするスキーマを定義したい場合は下記のように定義できます。
export const formSchema = z
.object({
startDate: z.string().refine(
(val) => {
return val.length > 0;
},
{ message: "日付を入力してください" },
),
endDate: z.string().refine(
(val) => {
return val.length > 0;
},
{ message: "日付を入力してください" },
),
})
.refine(
({ startDate, endDate }) => {
const start = dayjs(startDate);
const end = dayjs(endDate);
return end.isAfter(start); // 終了日が開始日より未来かどうか
},
{
message: "終了日は開始日より未来の日付にしてください",
path: ["endDate"],
},
);
refine()
はバリデーションを柔軟に行えますが、型推論が常にオプショナルになるのでdiscriminatedUnion()
と比べると型の恩恵が減ってしまいます。またバリデーションロジックも複雑になりがちです。
個人的にはまず最初にdiscriminatedUnion()
で実現できないか考えたのちにrefine()
の使用を検討することをおすすめします。
Discussion