JSONスキーマでZodの.transform()と.default()を使用する

JSONを検証済みの型付きデータに変換する際にZodの.transform()によるデータ変換と.default()によるフォールバック値の使い方を学びます。

Advanced

詳細な説明

Zodのtransformとdefault

Zodは単なるバリデーターではなく、パース中にデータを変換することもできます。これにより、乱雑なAPIレスポンスをクリーンな型付きデータに正規化するのに最適です。

.default() — フォールバック値

import { z } from "zod";

const configSchema = z.object({
  theme: z.enum(["light", "dark"]).default("dark"),
  pageSize: z.number().int().default(20),
  showSidebar: z.boolean().default(true),
  locale: z.string().default("en"),
});

type Config = z.infer<typeof configSchema>;

// 欠落フィールドにデフォルトが適用される
configSchema.parse({});
// { theme: "dark", pageSize: 20, showSidebar: true, locale: "en" }

configSchema.parse({ theme: "light" });
// { theme: "light", pageSize: 20, showSidebar: true, locale: "en" }

.transform() — データ変換

const userSchema = z.object({
  name: z.string().transform((s) => s.trim()),
  email: z.string().email().transform((s) => s.toLowerCase()),
  createdAt: z.string().datetime().transform((s) => new Date(s)),
  tags: z.string().transform((s) => s.split(",").map((t) => t.trim())),
});

userSchema.parse({
  name: "  Alice  ",
  email: "ALICE@Example.COM",
  createdAt: "2024-03-15T10:30:00Z",
  tags: "dev, design, pm",
});
// {
//   name: "Alice",
//   email: "alice@example.com",
//   createdAt: Dateオブジェクト,
//   tags: ["dev", "design", "pm"]
// }

入力型 vs 出力型

transformを使用すると、入力型と出力型が異なります:

const schema = z.string().transform((s) => parseInt(s, 10));

type Input = z.input<typeof schema>;   // string
type Output = z.output<typeof schema>; // number(z.inferと同じ)

フォーム/API型にはz.inputを、内部型にはz.output(またはz.infer)を使用します。

.preprocess() — バリデーション前の変換

const numberFromString = z.preprocess(
  (val) => (typeof val === "string" ? Number(val) : val),
  z.number().min(0).max(100)
);

numberFromString.parse("42");   // 42
numberFromString.parse(42);     // 42
numberFromString.parse("abc");  // ZodError(NaNはz.number()に失敗)

z.coerce — 組み込み型強制

// .preprocess()のよりシンプルな代替
z.coerce.number().parse("42");     // 42
z.coerce.boolean().parse("true");  // true
z.coerce.date().parse("2024-03-15T10:30:00Z"); // Dateオブジェクト
z.coerce.string().parse(42);      // "42"

transformのチェーン

const slugSchema = z.string()
  .min(1)
  .transform((s) => s.toLowerCase())
  .transform((s) => s.replace(/\s+/g, "-"))
  .transform((s) => s.replace(/[^a-z0-9-]/g, ""));

slugSchema.parse("Hello World!"); // "hello-world"

transformにより、Zodは受動的なバリデーターから、バリデーションと正規化を1ステップで行うアクティブなデータ処理パイプラインに変わります。

ユースケース

フォーマットが一貫しない複数のサードパーティAPI(大文字小文字が混在するメール、数値の代わりの文字列、欠落するオプショナルフィールド)からデータを受け取り、すべてを一貫した内部フォーマットに正規化するZodスキーマが必要な場合に使用します。

試してみる — JSON to Zod Schema

フルツールを開く