モダンフロントエンド開発に必須級のライブラリZodを紹介する

環境

  • macOS Monterey 12.6
  • Windows 11
  • VSCode
  • node.js 18.6.0
  • npm 8.13.2
  • Typescript 4.6.4
  • Zod 3.19.1

Zodって何?

Zodは、TypeScript First なバリデーションライブラリです。

GitHub - colinhacks/zod: TypeScript-first schema validation with static type inference
TypeScript-first schema validation with static type inference - GitHub - colinhacks/zod: TypeScript-first schema validation with static type inference

基本的な使い方

  • zodでは、まずスキーマを作成します
    • スキーマには、型とバリデーションルールを設定します
  • スキーマをパースすることにより、バリデーションOKかNGかを判定します
    • パース時の挙動はparse()とsafeParseで異なります(※詳細は後述)

※githubより引用

import { z } from "zod";

// creating a schema for strings
const mySchema = z.string();

// parsing
mySchema.parse("tuna"); // => "tuna"
mySchema.parse(12); // => throws ZodError

// "safe" parsing (doesn't throw error if validation fails)
mySchema.safeParse("tuna"); // => { success: true; data: "tuna" }
mySchema.safeParse(12); // => { success: false; error: ZodError }

何がうれしいのか

上記のようなgithubの説明だと、単なるデータ型を指定しただけのスキーマなので、もしかするとzodのメリットがわかりにくいかもしれません。

zodを使用することで、データ型に加えてそのデータの文字列長や形式まで細かくチェックすることが可能になります。

例えば、Webフォームのメールアドレスや郵便番号の形式などについてのバリデーションをJsを用いて行うことがしばしばあると思います。

zodを使用すると、郵便番号の形式について以下のように簡潔にバリデーションを実装することが可能です。

import { z } from 'zod';

const regExp = /^[0-9]{3}-[0-9]{4}$/g;
const ZipCodeSchema = z.string().regex(regExp, { message: 'Invalid format zip code' });

const success = ZipCodeSchema.parse('500-0001'); //500-0001
const failed = ZipCodeSchema.parse('5000001'); //ZodError

また、以下のような特徴もあります

  • プロジェクトに導入しやすい
    • ライブラリ自体のサイズが小さい
    • 依存関係がない(JsでもTsでも可能)

  • スキーマからTypeScriptの型がシームレスに生成できる(←これ重要)
  • 結構カスタマイズがきく
  • デフォルトでnullを許容しない(shemaでnullableにしない限り)
    • なお、stringは空文字を許容するため、空文字を含めたくない場合は、name: z.string().min(1) とする必要がある

使用上のポイント

多種多様なオプション

スキーマ定義の際、多種多様なオプションを指定することが可能です。そのため、いかにこれらを組み合わせて使えるかが重要になってきます。

※以下はstringの例

import * as z from "zod";

z.string(); // 文字列
z.string().min(1); // 空文字不可
z.string().email(); // メールアドレス
z.string().url(); // URL
z.string().regex(regex); // 正規表現にマッチする文字列
...などなど

また、これらのオプションはStringやNumber等の各データ型につきそれぞれ用意されていますが、インターフェースが使いやすく設計されているので、データ型が違っても割と同じ感じでかけたりします

バリデーション結果

結果得られる値
バリデーションOK期待した型の値を返します
バリデーションNG検証エラーの内容を持ったZodError(Error クラスを継承)オブジェクトがthrowされます
ZodError: [
  {
    "validation": "email",
    "code": "invalid_string", // Zodのエラーコード(https://github.com/colinhacks/zod/blob/v3/ERROR_HANDLING.md#zodissuecode)
    "message": "Invalid email",
    "path": [
      "email" // エラーが発生したプロパティ名
    ]
  }
]

↑エラー内容が親切設計すぎる・・・

masayan
masayan

余談ですが、検証値がプリミティブ型ではない場合、検証結果は与えられた値とは異なる値になります。そのため、以下の厳密等価は常に偽となります 

const CustomerSchema = z.object({
  id: z.number(),
  name: z.string().max(25),
  email: z
    .string()
    .email()),
});

type Customer = z.infer<typeof CustomerSchema>;

const customer: Customer = { id: 1, name: '会員A', email: 'test@test.com' };
const success = CustomerSchema.parse(customer);

console.log(success === customer); //false

スキーマからTypeScriptの型を生成する

バリデーション用のスキーマを作ってから、その内容をもとにTypeScriptの型を生成できます。この機能は非常に強力です。
具体的には、例えば以下のようにCustomer型を作成したい場合、Customerスキーマを生成後、z.inferを使用して型を生成することが可能です
const CustomerSchema = z.object({
  id: z.number(),
  name: z.string().max(25),
  email: z
    .string()
    .email(),
});

type Customer = z.infer<typeof CustomerSchema>;

const customer: Customer = { id: 1, name: '会員A', email: 'test@test.com' };
CustomerSchema.parse(customer);

スキーマから型を生成する際のTipsとして、transformを使用すると、元のスキーマに特定のプロパティを追加した状態で型を生成することが可能です

例えば、フォームなどから姓名別々に入力内容が送信されてきた場合に、transformを使用すると、元のスキーマにフルネームのプロパティを追加して型生成が容易に行えます

const CustomerSchema = z
  .object({
    id: z.number(),
    lastName: z.string(),
    firstName: z.string(),
    email: z.string().email({ message: 'Invalid format email' }),
  })
  .transform((original) => {
    return {
      fullName: `${original.lastName} ${original.firstName}`,
      ...original,
    };
  });

type Customer = z.infer<typeof CustomerSchema>;

// type Customer = {
//     id: number;
//     lastName: string;
//     firstName: string;
//     email: string;
//     fullName: string;
// }

エラーハンドリング

例外が投げられた場合、zodErrorのissuesにアクセスすることでエラー内容を取得できます。

issuesの中身は、以下のようなZodIssue型のオブジェクトとなっており、issues[0].messageとすると、エラーメッセージを取得することが可能となる(実際にはissuesの中身をループして表示したりするかと)

code:"unrecognized_keys"
keys:['product_id']
message:"Unrecognized key(s) in object: 'product_id'"
path:[]

また、実運用時は、送出されたErrorがZodErrorかどうかinstanceofで確認して、ZodErrorなら、issuesを取り出してループして表示する等が想定されます

const CustomerSchema = z.object({
  id: z.number(),
  name: z.string().max(25),
  email: z.string().email(),
});

type Customer = z.infer<typeof CustomerSchema>;

try {
  const customer: Customer = { id: 1, name: '会員A', email: 'testtstest.com' }; //@がない
  const success = CustomerSchema.parse(customer);
} catch (error) {
  if (error instanceof ZodError) {
    //error.issues
  }
}

独自のバリデーションメッセージを設定する

一部のオプションでは、引数にカスタマイズしたエラー文を渡すことが出来ます

例えば以下のstringの regex オプションは、もともとは、”message”: “Invalid”,という簡素なメッセージなわけですが、第二引数にメッセージを指定することが可能です。

import { z } from 'zod';

const regExp = /^[0-9]{3}-[0-9]{4}$/g;
const ZipCodeSchema = z.string().regex(regExp, { message: 'Invalid format zip code' });

const failed = ZipCodeSchema.parse('5000001'); // "message": "Invalid format zip code"

regex以外にも、メッセージを指定可能なオプションは複数あります。(詳細は以下参照ください)

GitHub - colinhacks/zod: TypeScript-first schema validation with static type inference
TypeScript-first schema validation with static type inference - GitHub - colinhacks/zod: TypeScript-first schema validation with static type inference

バリデーション失敗時の挙動

parseメソッドを使用するとZodErrorを送出しますが、safeParseメソッドを使用してパースすると、エラーを送出することなく検証できます

この場合、戻り値として、{ success: false; error: ZodError } のようなオブジェクトが取得できます

const CustomerSchema = z.object({
  id: z.number(),
  name: z.string().min(25),
  email: z.string().email({ message: 'Invalid format email' }),
});

type Customer = z.infer<typeof CustomerSchema>;

try {
  const customer: Customer = { id: 1, name: '会員A', email: 'testtstest.com' };
  const result = CustomerSchema.parse(customer); // { success: false; error: ZodError }
} catch (error) {
  //・・・
}

無関係なプロパティを禁止する

デフォルトでは、スキーマは、解析中に認識されないキーを取り除いて検証成功とします。

具体的には、以下のようにCustomerSchemaに関係のないproduct_idというプロパティがある場合、デフォルトではこれを除いた値がバリデーションをクリアすれば検証成功とします。

.strict()オプションを使う事で、検証時に無関係なプロパティがあった場合に検証失敗とするように出来ます

const CustomerSchema = z.object({
  id: z.number(),
  name: z.string().max(25),
  email: z.string().email(),
});

try {
  const customer = { id: 1, name: '会員A', email: 'testt@test.com', product_id: 2 }; //here
  const success = CustomerSchema.strict().parse(customer);
} catch (error) {
  //・・・
}

以上です。

本記事で紹介したzodの機能は全体のほんの一部です。

データ型ごとに使用できるオプションやそのほかの機能(.refine()を使用したバリデーションのカスタマイズについてはGithubリポジトリのREADMEに詳細にまとまっていますので、そちらを参照ください

GitHub - colinhacks/zod: TypeScript-first schema validation with static type inference
TypeScript-first schema validation with static type inference - GitHub - colinhacks/zod: TypeScript-first schema validation with static type inference

TypeScript学習におすすめの書籍

 

タイトルとURLをコピーしました