本文へスキップ
ウェブエンジニア問題集
第6章

z.objectでオブジェクトを検証する — 未知のキーの扱い

約7分
この章の目次開く

実務で検証する対象は、単体の文字列よりもオブジェクトであることがほとんどです。APIのリクエストボディ、フォームの送信値、設定ファイル——どれもキーと値の集まりです。

z.object の基本

構文: z.object(shape)

引数渡せるもの説明
shapeキーとスキーマのオブジェクトどのキーにどのスキーマを適用するか

戻り値: オブジェクトスキーマ

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.email(),
});
ts

値にスキーマを書くだけなので、そのまま入れ子にできます。

const postSchema = z.object({
  title: z.string(),
  author: z.object({
    name: z.string(),
  }),
  tags: z.array(z.string()),
});
ts

オブジェクト以外を渡すと弾かれます。null は expected object, received null、配列は expected object, received array というメッセージになります。JavaScriptでは typeof null === 'object' ですが、Zodは区別してくれます。


定義していないキーは黙って捨てられる

ここが最初につまずくところです。スキーマに書いていないキーが入力に含まれていても、エラーにはなりません。結果から静かに取り除かれます。

const schema = z.object({ id: z.number(), name: z.string() });
 
schema.parse({ id: 1, name: 'A', extra: 'x' });
// { id: 1, name: 'A' } — extra は消えている
ts

「送ったはずのフィールドが保存されていない」という不具合の犯人は、たいていこの挙動です。スキーマにキーを足し忘れると、リクエストには乗っているのに検証後のデータからは消えてしまいます。

学習者学習者

エラーにしてくれた方が気づけるのに、どうして黙って捨てるの?

先生先生

安全側に倒しているんだ。知らないキーをそのまま通すと、想定外の値がDBやログに流れ込む。捨てておけば「スキーマに書いたものだけ」が下流に届く、と保証できる。エラーにしたいなら、次の表のように書き方を変えればいい。

4つの書き方

書き方余分なキーがあると
z.object({...})黙って取り除く(デフォルト)
z.strictObject({...})エラーにする(Unrecognized key: "x")
z.looseObject({...})そのまま通す
z.object({...}).catchall(スキーマ)余分なキーの値を指定スキーマで検証する
z.strictObject({ id: z.number() }).safeParse({ id: 1, x: 2 });
// エラー: Unrecognized key: "x"
 
z.looseObject({ id: z.number() }).parse({ id: 1, x: 2 });
// { id: 1, x: 2 } — x も残る
 
z.object({ id: z.number() }).catchall(z.string()).parse({ id: 1, x: 'ok' });
// { id: 1, x: 'ok' } — 追加キーは文字列であることを要求する
ts

使い分けの目安です。

  • 設定ファイルやCLIの引数 — strictObject。書き間違えたキーを黙って無視されると、設定が効かない理由が分からなくなる
  • APIレスポンス — デフォルトの object。サーバー側がフィールドを追加しても壊れない
  • フォームやリクエストボディ — デフォルトの object。余計な値を下流に流さない
画面を見て驚いている人のイラスト

エラーはどの項目で起きたか分かる

オブジェクトを検証すると、issues の各要素に path が入ります。どのキーで失敗したかを示す配列です。

const schema = z.object({
  user: z.object({ name: z.string() }),
  tags: z.array(z.string()),
});
 
schema.safeParse({ user: { name: 1 }, tags: ['a'] }).error.issues[0].path;
// ['user', 'name'] — ネストした位置も分かる
 
schema.safeParse({ user: { name: 'a' }, tags: ['a', 2] }).error.issues[0].path;
// ['tags', 1] — 配列の場合はインデックスが入る
ts

そして重要なのが、最初のエラーで止まらないことです。

schema.safeParse({ user: { name: 1 }, tags: [2] }).error.issues.length;
// 2 — 両方のエラーが集まる
ts

全項目を検証してから、失敗した項目をまとめて返します。だからフォームで「5個の入力ミスを一度に表示する」ことができます。1件ずつ指摘して5回送信させる、という体験にならずに済むのはこの仕組みのおかげです。


スキーマを組み立てる

一度書いたオブジェクトスキーマは、部品として使い回せます。

メソッド引数何をするか
.extend(shape)追加するキーとスキーマキーを足した新しいスキーマ
.pick({ キー: true })残すキー指定したキーだけのスキーマ
.omit({ キー: true })除くキー指定したキーを外したスキーマ
.partial()なし全キーを optional にする
.required()なし全キーを必須に戻す
.keyof()なしキー名のenumスキーマ
.shape(プロパティ)個々のキーのスキーマを取り出す

戻り値: .shape 以外はすべて新しいスキーマ。元のスキーマは変わらない

実際に効くのは、同じリソースに対して用途別のスキーマを作る場面です。

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.email(),
});
 
// 新規作成: idはサーバーが採番するので受け取らない
const createUserSchema = userSchema.omit({ id: true });
 
// 部分更新: 送られてきた項目だけ更新する
const updateUserSchema = createUserSchema.partial();
ts

userSchema を1か所直せば、作成用も更新用も追従します。項目が増えるたびに3つのスキーマを手で直す、という状態を避けられます。

別のスキーマと合成したいときは、.shape を渡します。

const timestamps = z.object({ createdAt: z.iso.datetime() });
const userWithTime = userSchema.extend(timestamps.shape);
ts

よくあるハマりどころ

送ったはずの項目が消える

前述のデフォルト挙動です。検証後のデータに項目が無いときは、まずスキーマにそのキーを書いたかを確認してください。

.partial() を使うと空オブジェクトが通る

.partial() は全キーを任意にするため、{} が検証を通ります。部分更新のAPIでは「1項目以上必須」を別途チェックする必要があります。

updateUserSchema.safeParse({}).success; // true — 何も更新しない更新リクエスト
ts

配列を渡したのにobjectエラーが出る

z.object() は配列を受け付けません。配列を検証したいなら z.array() を使います。z.array(userSchema) のように、オブジェクトスキーマを中に入れる形になります。

ちゃんと使うためのポイント

  • z.object() は定義していないキーをエラーにせず、黙って取り除く
  • 厳しくしたいなら strictObject、通したいなら looseObject
  • strictObject を外部APIのレスポンスに使うと、相手の項目追加で壊れる
  • エラーは path でどのキーかが分かり、全項目分がまとめて返る
  • .omit() や .partial() で、1つのスキーマから作成用・更新用を派生させる

ここまでで、単体の値からオブジェクトまで検証できるようになりました。値が「通るか通らないか」の基準に迷ったときは、デフォルト挙動の早見表に戻ってきてください。

参考リンク

  • Objects - Zod — オブジェクトスキーマの公式リファレンス(英語)
TypeScriptクイズに挑戦するこの章で学んだ型と検証の知識を、4択クイズでアウトプットして定着させよう