TypeScriptでZod.mergeを使いこなす!複数スキーマを結合するデータバリデーション入門
生徒
「TypeScriptでウェブサイトの入力フォームのチェックを作っているのですが、共通の項目を何度も書くのが大変です。複数のチェックルールを一つにまとめる方法はありませんか?」
先生
「それなら、Zodというライブラリのmergeという機能を使うと便利ですよ。別々に作った入力チェックのルールを、合体させて一つの大きなルールにすることができます。」
生徒
「ルールを合体させることができるのですね!具体的にはどのようにプログラムを書けば良いのでしょうか?」
先生
「それでは、パソコンに触ったことがない方でも分かるように、基礎から順番に見ていきましょう!」
1. データバリデーションとZodの基本
プログラミングの世界では、ユーザーが画面から入力した文字や数字が正しい形式かどうかを確認する作業のことをデータバリデーションと呼びます。例えば、メールアドレスの入力欄に文字が何も入っていなかったり、年齢の入力欄に文字が入力されていたりしたときに、エラー画面を出してユーザーに教えてあげる仕組みのことです。この仕組みがないと、欠陥のあるおかしなデータがシステムの中に保存されてしまい、アプリが動かなくなる原因になります。
TypeScriptというプログラミング言語では、このデータバリデーションを簡単に実現するためにZod(ゾッド)という非常に人気のあるライブラリを利用します。ライブラリとは、便利な機能があらかじめ詰め込まれた道具箱のようなものです。Zodを使うことで、文字用、数字用といったチェックの規則を定義することができます。このチェックの規則や設計図のことを、専門用語でスキーマと呼びます。まずはこのスキーマという概念を理解することが、安全なデータ管理の第一歩となります。
2. スキーマを組み合わせる必要性とメリット
実際のウェブシステムを開発するとき、入力フォームは一つだけではありません。会員登録の画面、プロフィールの編集画面、商品の注文画面など、たくさんの画面が存在します。それぞれの画面で名前や住所、電話番号などを入力してもらいますが、画面ごとに毎回同じチェックのプログラムを書き直すのは非常に非効率的です。同じ内容を何度も書くと、後から規則を変更したくなったときに、すべての場所を修正しなければならず、修正漏れによる不具合の原因になります。
そこで重要になるのが、スキーマの組み合わせという考え方です。例えば、名前とメールアドレスをチェックする基本のスキーマを一つ作っておき、会員登録画面ではそれにパスワードのチェックを追加し、プロフィール画面では自己紹介のチェックを追加するというように、部品を組み合わせるようにして新しい規則を作ります。これにより、プログラムの記述量を大幅に減らすことができ、一箇所を直せば全体に修正が反映されるという大きなメリットが生まれます。
3. Zodにおけるmerge機能の使い方
Zodという道具箱の中には、二つのスキーマを合体させて一つの新しいスキーマを作るためのmerge(マージ)という機能が用意されています。マージとは、日本語で融合や結合、合体という意味を持つ言葉です。この機能を使うと、片方の設計図ともう片方の設計図を綺麗に重ね合わせて、両方の入力規則を同時に満たしているかをチェックする強力な設計図を新しく生み出すことができます。
具体的な記述方法としては、ベースとなるスキーマの記述の後にドットをつけてマージと書き、カッコの中に新しく合体させたいスキーマを指定します。この操作を行うことで、元のスキーマの性質を一切壊すことなく、新しい条件を追加した完璧なバリデーションルールを構築することが可能になります。パソコンの操作に慣れていない方でも、パズルを組み立てるような感覚で理解することができます。
4. 複数のスキーマを結合する実践コード
それでは、実際に二つの異なるスキーマを結合するシンプルなプログラムの例を見てみましょう。ここでは、ユーザーの基本情報である名前をチェックする規則と、連絡先であるメールアドレスをチェックする規則を別々に作成し、それを合体させて会員登録用の規則を作ります。
import { z } from "zod";
// ユーザーの基本情報をチェックする規則
const BaseUserSchema = z.object({
name: z.string()
});
// 連絡先の情報をチェックする規則
const ContactSchema = z.object({
email: z.string()
});
// 二つの規則を合体させて新しい規則を作る
const CompleteUserSchema = BaseUserSchema.merge(ContactSchema);
// 実際にデータが正しいかテストしてみる
const result = CompleteUserSchema.safeParse({
name: "山田太郎",
email: "yamada@example.com"
});
console.log(result.success);
上記のプログラムを実行すると、データが二つの規則を同時に満たしているため、検証が成功したことを示す結果が得られます。実行結果の出力は下記のようになります。
true
5. 型の自動抽出機能による安全性の向上
TypeScriptの最大の強みは、プログラムを実行する前に文字や数字の型に間違いがないかを厳密にチェックしてくれる点にあります。Zodを使ってスキーマを結合すると、その結合された規則から、TypeScriptで使えるデータの型を自動的に抽出することができます。この機能を型インファレンス(型推論)と呼びます。手動で型を定義する必要がないため、入力チェックの規則と型の定義がズレる心配がありません。
自動抽出された型を使用することで、開発中のパソコン画面上で、どの項目にどのデータを入れれば良いのかを案内してくれる支援機能が働きます。これにより、スペルミスやデータの入れ間違いを未然に防ぐことができ、プログラミング初心者であってもミスを極限まで減らしながら安全にシステムを構築していくことができます。
6. 型の自動抽出を行う具体的なプログラム
次に、マージしたスキーマから型を抽出して、その型を実際の変数に適用するプログラムの例を紹介します。これにより、データバリデーションだけでなく、プログラム全体の整合性が保たれる仕組みが完成します。
import { z } from "zod";
const ItemSchema = z.object({
id: z.string()
});
const PriceSchema = z.object({
amount: z.number()
});
// スキーマを合体
const ProductSchema = ItemSchema.merge(PriceSchema);
// 合体したスキーマから型を自動抽出する
type Product = z.infer<typeof ProductSchema>;
// 抽出した型を使って安全なオブジェクトを作成する
const newProduct: Product = {
id: "A123",
amount: 1500
};
console.log(newProduct.id);
console.log(newProduct.amount);
このプログラムでは、自動的に作られたプロダクトという型を利用して変数を作成しています。もし項目名が違っていたり、数字の場所に文字を入れたりすると、パソコンが即座にエラーを教えてくれます。実行結果は下記のようになります。
A123
1500
7. キーが重複した場合のマージの挙動
スキーマを結合する際に注意しなければならない重要なポイントがあります。それは、合体させる二つのスキーマの中で、まったく同じ名前の項目(キー)が定義されていた場合にどうなるかという問題です。例えば、一つ目のスキーマで名前の項目を必須として扱い、二つの目のスキーマでも同じ名前の項目を異なる規則で定義してしまったような場合です。
Zodのマージ機能のルールでは、後に指定したスキーマの規則が優先されるという仕組みになっています。つまり、上書き保存されるようなイメージです。前に定義されていた規則は、新しく合体させた後ろの規則によって完全に塗り替えられます。この挙動を正しく理解しておかないと、予期せぬ入力チェックのエラーや、思った通りに動かない原因になるため、同じ名前の項目を扱う際は細心の注意を払う必要があります。
8. キーが重複した際の上書き確認コード
項目名が重複した場合に、本当に後ろのスキーマの規則で上書きされるのかを実験するプログラムを作成してみましょう。ここでは、ステータスという項目を文字として定義したスキーマに、同じステータスという項目を数字として定義したスキーマをマージしてみます。
import { z } from "zod";
const FirstSchema = z.object({
status: z.string()
});
const SecondSchema = z.object({
status: z.number()
});
// 後ろの第二スキーマで上書きされる
const MergedSchema = FirstSchema.merge(SecondSchema);
// 文字を入れてテストしてみる
const testString = MergedSchema.safeParse({
status: "準備中"
});
// 数字を入れてテストしてみる
const testNumber = MergedSchema.safeParse({
status: 200
});
console.log("文字の結果:", testString.success);
console.log("数字の結果:", testNumber.success);
このプログラムを実行すると、ステータスという項目は完全に数字の規則へと上書きされているため、文字を入れた方は失敗し、数字を入れた方だけが成功します。実行結果は下記のようになります。
文字の結果: false
数字の結果: true
9. ネストされた複雑な構造におけるマージの注意点
最後に、より高度なデータ構造におけるマージの注意点について解説します。データの中には、住所情報のなかにさらに都道府県や市区町村という項目がぶら下がっているような、箱の中にさらに箱が入っているような二重の構造を持つものがあります。このような構造をプログラミングではネスト(入れ子)構造と呼びます。
Zodのマージ機能は、一番外側の箱の項目に対してのみ上書きの処理を行います。そのため、内側に入っている細かい箱の項目同士を、自動的に綺麗に混ぜ合わせてくれるわけではありません。内側の細かい項目まで丁寧に合体させたい場合は、外側の箱を単純にマージするのではなく、内側の箱のスキーマ同士をあらかじめマージしておいてから、外側の箱に組み込むという手順を踏む必要があります。この構造の違いを意識することで、どんなに複雑な入力フォームであっても、綺麗で不具合のない安全なチェックシステムを組み立てることができるようになります。