タタッと!ドラゴンさん🐉

タタッ!と書いてく記事まとめ

【Zodバリデーション】 文字列数値に最小値や最大値の制限をつけるには

🐉 グォォォオオ! Hello,World!! ドラゴンだ! 🐉ちなみに好きなスラムダンクのキャラクターは仙道くんだぜ! 営業👦...o(納期ギリギリの変更だけど...それでもエンジニア君なら、エンジニア君ならやってくれる)← 💢

🐉今回はZodバリデーションでちょっと詰まった文字列数値の制限について記載します!

Zodを使用した数値入力のバリデーション方法

ウェブアプリケーションの開発において、ユーザーからの入力値のバリデーションは非常に重要です。 特に、数値としての入力を期待するフィールドに対して、文字列として入力された数値に最小値や最大値を設定する場合、適切なバリデーションが求められます。本記事では、私の備忘録も兼ねて、JavaScriptの型安全なバリデーションライブラリである Zod を使用して、文字列として入力された数値に対して最小値および最大値を設定する方法について解説します。

問題の背景

例えば、果物の数量を入力するフォームフィールドがあり、その値が 1 以上であることを保証したいとします。以下のような Zod スキーマを定義した場合、意図したバリデーションが機能しないことがあります。

import { z } from 'zod'

export const fruitQuantitySchema = z.object({
  quantity: z
    .string()
    .min(1)
    .regex(/^[0-9]+$/, '半角数字で入力してください'),
})

上記のスキーマでは、z.string().min(1) を使用していますが、これは文字列の長さに対して最小値を設定しているため、数値としての最小値検証には適していません。その結果、ユーザーが 1 未満の値を入力してもエラーが表示されません。

解決方法

方法1: 数値として検証する

z.preprocess を使用して、入力値を数値に変換し、その後数値としてのバリデーションを行います。

import { z } from 'zod'

export const fruitQuantitySchema = z.preprocess((val) => {
  if (typeof val === 'string' && val.trim() !== '') {
    return parseInt(val, 10)
  }
  return val
},
z.number({
  required_error: '数量は必須です',
  invalid_type_error: '数値を入力してください',
})
.min(1, { message: '1以上の値を入力してください' })
)

ポイント:

  • z.preprocess は入力値を前処理するために使用します。ここでは、文字列を数値に変換しています。
  • z.number() で数値としてのバリデーションを行い、.min(1) で最小値を設定します。
  • ユーザーが 1 未満の数値を入力した場合、適切なエラーメッセージが表示されます。

方法2: 文字列として検証し、数値の範囲を確認する

文字列としての正規表現チェックに加えて、refine メソッドを使用して数値としての範囲を検証します。

import { z } from 'zod'

export const fruitQuantitySchema = z.string()
  .regex(/^[0-9]+$/, '半角数字で入力してください')
  .refine((val) => parseInt(val, 10) >= 1, '1以上の値を入力してください')

ポイント:

  • z.string() で文字列としての基本的なバリデーションを行います。
  • .regex で半角数字であることを検証します。
  • .refine を使用して、文字列を数値に変換し、1 以上であることを確認します。

フォームコンポーネントの修正

スキーマを修正した後、フォームコンポーネント側でも適切に設定を行う必要があります。以下は、React Hook Form と Ant Design を使用したフォームコンポーネントの例です。

import { Controller } from 'react-hook-form'
import { Input, Typography } from 'antd'
import { fruitQuantitySchema } from './schemas' // 上記で定義したスキーマ

const { Text } = Typography

<Controller
  control={control}
  name='quantity'
  rules={{ 
    required: '数量は必須です', 
    validate: (value) => fruitQuantitySchema.safeParse(value).success || '有効な数量を入力してください' 
  }}
  render={({ field, fieldState: { error } }) => (
    <div className="form-item">
      <label>数量</label>
      <Input
        {...field}
        placeholder="例: 10"
        disabled={!isEditable}
      />
      {error && <Text type="danger">{error.message}</Text>}
      <Text type="secondary"></Text>
    </div>
  )}
/>

ポイント:

  • Controller コンポーネントを使用して、フォームフィールドを管理します。
  • rules プロパティでバリデーションルールを設定し、fruitQuantitySchema を使用して入力値を検証します。
  • エラーメッセージを適切に表示するために、Ant Design の Typography コンポーネントを使用しています。

まとめ

数値として扱うべき入力フィールドに対して、文字列としてバリデーションを行う場合、単純な minmax メソッドでは期待通りの動作をしないことがあります。Zod を使用する場合、z.preprocessrefine メソッドを活用すれば、入力値を数値として検証できるはずです。

参考リンク