T-CREATOR

フォーム入力情報からZodを利用してDTO作成しへ変換処理を実施するやり方を紹介

フォーム入力情報からZodを利用してDTO作成しへ変換処理を実施するやり方を紹介

Zodはバリデーションと型定義だけでなく、入力データを任意の出力形式に変換する機能(DTO)も備えています。
これにより、フォームのUI層では扱いやすいデータ型で管理し、バックエンドに送信するDTO(Data Transfer Object)を
シームレスに橋渡し
することができます。

このドキュメントでは、フォーム入力 → DTOへの変換パターンを豊富な例とともに解説します。


基本:.transform()を使った変換

z.string().transform(fn)などの記法で、値を別の形式に変換できます。

例:文字列の数値入力 → numberへ変換

tsconst formSchema = z.object({
  price: z.string().transform((val) => parseFloat(val)),
});

type Dto = z.infer<typeof formSchema>;

const result = formSchema.parse({ price: "123.45" });
// result.price は number型(123.45)

複数フィールド → 単一の構造に変換(例:名前統合)

tsconst formSchema = z
  .object({
    firstName: z.string(),
    lastName: z.string(),
  })
  .transform((val) => ({
    fullName: `${val.lastName} ${val.firstName}`,
  }));

type NameDto = z.infer<typeof formSchema>;
// NameDto = { fullName: string }

入力補助形式 → サーバー形式(例:日付)

tsconst formSchema = z.object({
  birthday: z
    .string()
    .refine((val) => /^\d{4}-\d{2}-\d{2}$/.test(val), {
      message: "日付はYYYY-MM-DD形式で入力してください",
    })
    .transform((val) => new Date(val)),
});

type BirthdayDto = z.infer<typeof formSchema>;
// BirthdayDto = { birthday: Date }

nested .transform().refine() の組み合わせ

tsconst formSchema = z
  .object({
    height: z.string().transform((val) => parseInt(val, 10)),
    weight: z.string().transform((val) => parseInt(val, 10)),
  })
  .refine(({ height, weight }) => height > 0 && weight > 0, {
    message: "身長と体重は正の数である必要があります",
  });

type BodyDto = z.infer<typeof formSchema>;
// BodyDto = { height: number; weight: number }

状態(チェックボックスやセレクト)からDTO形式へ変換

例:チェックボックス → フラグ配列へ変換

tsconst formSchema = z
  .object({
    apple: z.boolean(),
    banana: z.boolean(),
    orange: z.boolean(),
  })
  .transform((val) => {
    const selected: string[] = [];
    if (val.apple) selected.push("apple");
    if (val.banana) selected.push("banana");
    if (val.orange) selected.push("orange");
    return { selectedFruits: selected };
  });

type FruitDto = z.infer<typeof formSchema>;
// FruitDto = { selectedFruits: string[] }

日付・時間を分割入力 → 1つのDate型へ変換

tsconst formSchema = z
  .object({
    year: z.string(),
    month: z.string(),
    day: z.string(),
  })
  .transform((val) => {
    const { year, month, day } = val;
    return {
      birthdate: new Date(`${year}-${month}-${day}`),
    };
  });

type DateDto = z.infer<typeof formSchema>;
// DateDto = { birthdate: Date }

入力型(フォーム用)とDTO型を明確に分けて使うパターン

Zodでは、入力型と出力型(変換後のDTO)を明示的に分けることも可能です。

入力スキーマ(string型)

tsconst rawFormSchema = z.object({
  age: z.string(),
});

DTOスキーマ(number型に変換)

tsconst dtoSchema = rawFormSchema.transform((val) => ({
  age: parseInt(val.age, 10),
}));

type FormInput = z.input<typeof dtoSchema>; // { age: string }
type FormDto = z.output<typeof dtoSchema>;  // { age: number }

この方法により、UIと送信データの責任を分離しながら、型整合性を維持できます。


.transform() をネストオブジェクトで使う例

tsconst formSchema = z
  .object({
    profile: z.object({
      age: z.string(),
      nickname: z.string(),
    }),
  })
  .transform((val) => ({
    profile: {
      age: Number(val.profile.age),
      nickname: val.profile.nickname,
    },
  }));

type ProfileDto = z.infer<typeof formSchema>;
// ProfileDto = { profile: { age: number; nickname: string } }

.pipe() を使って変換とバリデーションを分離

pipe().transform()と違い、変換後の型に対してさらにバリデーションを適用できます。

tsconst schema = z
  .string()
  .transform((val) => parseFloat(val))
  .pipe(z.number().positive());

schema.parse("100");  // OK → 100
schema.parse("-5");   // ❌ → バリデーションエラー

まとめ

Zodを使えば、フォーム入力データをDTO形式に変換しながら、同時に型安全性・妥当性も確保できます。

技法目的
.transform()値の直接変換(文字列→数値など)
.refine() / .superRefine()バリデーションロジックの追加
.pipe()変換後にさらに制約を加えたいとき
z.input<> / z.output<>入力型と出力型を明確に分離

こうしたパターンを活用することで、UIは柔軟に・サーバーには堅牢にという理想的なデータ設計が実現できます。

記事Article

もっと見る