【zod】v4で使えるようになったオススメのスキーマ

はじめに

こんにちは、株式会社TOKOSのツキヤです!
2025年の7月に、zodのv4がリリースされましたね🚀
書き方が変わったり、便利なスキーマが追加されたりしました✨

そこで、既にあるが書き方等が変わったスキーマと、新規で便利そうなスキーマ達を紹介しようと思います💪

v3からのマイグレーションガイドも出ているので、一旦バージョンアップだけしたい方はそちらを参考にしてみてください!

オススメのスキーマ

まずは、v3以前から有ったスキーマ達です!

z.int()

皆さん、numberがほしいときにフワッとz.number()にしていませんか?
z.number()だと、少数等も含まれてしまいます😨
明確に「整数」が良い時はz.int()を使いましょう!

TypeScript
import { z } from "zod"

const schema = z.int()
schema.parse(1) // 成功!
schema.parse(0.1) // エラー!

こちらはv3から有りましたが、z.number().int()と書く必要がありました。
v4からはnumber()の宣言ナシにそのままz.int()と呼べるようになったので覚えておきましょう💪

また、「整数かつ 正の数」が良い場合は、z.int().positive()とも書けます!

TypeScript
import { z } from "zod"

const schema = z.int().positive()
schema.parse(1) // 成功!
schema.parse(0.1) // エラー!
schema.parse(-1) // エラー!

z.email()

メールアドレスのバリデーションを正規表現で頑張って設定してたりしてないですか??
そんな時に使えるのがz.email()です!

こちらは、一般的なメールアドレスの正規表現をカバーしてくれています✨

z.email()についても、v3からありました。ですが、こちらもz.string().email()string()が不要になっています。
また、カスタムもできるようになりました!!
細かく調整したい方はこちらも参照してください。

TypeScript
import { z } from "zod"

/*
 * Zodのデフォルトemailバリデーション(Gmail規則)
 * colinhacks.com/essays/reasonable-email-regex を参照
 */
z.email()

/*
 * ブラウザがinput[type=email]フィールドの検証に使用する正規表現
 * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email
 */
z.email({ pattern: z.regexes.html5Email })

// RFC 5322に準拠したクラシックなemailregex.comの正規表現
z.email({ pattern: z.regexes.rfc5322Email })

// Unicodeを許可する緩い正規表現(国際化メール対応)
z.email({ pattern: z.regexes.unicodeEmail })
ツキヤ
ツキヤ

他のz.string()系と併用していたスキーマも、同じくstring()が不要になっているので合わせて覚えておきたいです!

extend()

こちらも元からあるもので、スキーマ同士を合成させるような働きでした。
ですが、merge()との違いがよく分かってませんでした🤔

TypeScript
import { z } from "zod"

const humanSchema = z.object({
  name: z.string().min(1),
  age: z.number().int().positive(),
})

const extendProgramerSchema = humanSchema.extend({
  occupation: z.string().min(1),
})

const mergeProgramerSchema = humanSchema.merge(z.object({
  occupation: z.string().min(1),
}))

type Human = z.infer<typeof humanSchema>
// => { name: string; age: number }

type Programer = z.infer<typeof extendProgramerSchema>
// => { name: string; age: number; occupation: string } 🤔

type Programer = z.infer<typeof mergeProgramerSchema>
// => { name: string; age: number; occupation: string } 🤔

そういった背景もあってか、v4からはmerge()の方が非推奨になりました!
これで自信を持ってextend()を使えるので、皆さん覚えておきましょう🥳

…ただ、スキーマの合成の際のベストプラクティスは「shape + スプレッド構文」になったようです!
スプレッド構文を用いた方法が1番パフォーマンスが良いらしいので、僕は極力こちらを使うようにします!

TypeScript
import { z } from "zod"

const humanSchema = z.object({
  name: z.string().min(1),
  age: z.number().int().positive(),
})

const occupationSchema = z.object({
  occupation: z.string().min(1),
})

const programerSchema = z.object({
  ...humanSchema.shape,
  ...occupationSchema.shape
})

type Programer = z.infer<typeof extendProgramerSchema>
// => { name: string; age: number; occupation: string } 🥳

refine()

こちらもv3から存在します!
皆さん1回は使ったことがあると思っていて、カスタムのバリデーションを書けるやつですね!

TypeScript
import { z } from "zod"

z.string().refine(val => val.includes("@")) // 「@」が含まれていないとバリデーションエラー!

ただ、v3まではrefine()後にmin()等のスキーマを追加することができませんでした🥲

TypeScript
import { z } from "zod"

z.string().refine(val => val.includes("@")).min(10) // 型エラー🥲

ですが、v4からは可能になりました✨

TypeScript
import { z } from "zod"

z.string().refine(val => val.includes("@")).min(10) // 型エラーにならない🥳

次からは新しいスキーマの紹介です!!

z.templateRiteral

説明するよりコードを読んだ方が分かりやすいので記載します!

TypeScript
import { z } from "zod"

const email = z.templateLiteral([
  z.string().min(1),
  "@",
  z.string().max(64),
]);
type Email = z.infer<typeof email>
// => `${string}@${string}`というテンプレートリテラル型になる✨

配列の要素をまとめた上でテンプレートリテラル型になってくれる感じです!
パッといい感じの具体例は思い浮かびませんが、覚えておくとどこかしらでつかえそうです💪
(上記の例は、z.email()で代用できそうなので…)

z.stringbool()

文字列からいい感じにbooleanに変換したい時ありますよね?
例えば、文字列の"true"をbooleanのtrueにしたい時とかです!
そんな時に使えるのがこちらのstringbool()です!

TypeScript
import { z } from "zod"

const strbool = z.stringbool();
 
strbool.parse("true")         // => true
strbool.parse("1")            // => true
strbool.parse("yes")          // => true
strbool.parse("on")           // => true
strbool.parse("y")            // => true
strbool.parse("enabled")      // => true
 
strbool.parse("false");       // => false
strbool.parse("0");           // => false
strbool.parse("no");          // => false
strbool.parse("off");         // => false
strbool.parse("n");           // => false
strbool.parse("disabled");    // => false

こんな感じで、trueっぽい文字列はtrueに、falseっぽい文字列はfalseになります!

ただ、「自分で決めたいぜ!」って人もいると思います。
そんな人のためにも、自分で定義することが可能になっています。

TypeScript
import { z } from "zod"

const strbool = z.stringbool({
  truthy: ["yes", "true", "hoge"],
  falsy: ["no", "false", "huga"]
})

strbool.parse("yes")          // => true 
strbool.parse("true")         // => true
strbool.parse("hoge")         // => true

strbool.parse("no");          // => false 
strbool.parse("false");       // => false
strbool.parse("huga");        // => false

おわりに

今回は、v4になったzodの紹介をしました!
破壊的な変更も多く、この他にも便利になったり新しい機能等がいっぱい有るので、ぜひご自身でも確認してみてください!