💎

Zod Valibot に入門する

に公開

Zod Valibot

Zod

 TypeScript向けのスキーマ宣言とデータ検証のためのライブラリ。

Valibot

Zodよりも、バンドルサイズが小さいライブラリ。

実際に使用する

test@example.comtest_example.comのデータを使用する。

import { z } from 'zod'
import * as v from 'valibot'

const valid_email = 'test@example.com'
const invalid_email = 'test_example.com'

// Zod
const zEmail = z.string().email()

// test@example.com
zEmail.parse(valid_email)

// { success: true, data: 'test@example.com' }
zEmail.safeParse(valid_email)

// ZodError: [
//   {
//     "validation": "email",
//     "code": "invalid_string",
//     "message": "Invalid email",
//     "path": []
//   }
// ]
// ...
zEmail.parse(invalid_email)

// { success: false, error: [Getter] }
zEmail.safeParse(invalid_email)

// Valibot
const vEmail = v.pipe(v.string(), v.email())

// test@example.com
v.parse(vEmail, valid_email)

// {
//   typed: true,
//   success: true,
//   output: 'test@example.com',
//   issues: undefined
// }
v.safeParse(vEmail, valid_email)

// ValiError: Invalid email: Received "test_example.com"
// ...
v.parse(vEmail, invalid_email)

// {
//   typed: true,
//   success: false,
//   output: 'test_example.com',
//   issues: [
//     {
//       kind: 'validation',
//       type: 'email',
//       input: 'test_example.com',
//       expected: null,
//       received: '"test_example.com"',
//       message: 'Invalid email: Received "test_example.com"',
//       requirement: /^[\w+-]+(?:\.[\w+-]+)*@[\da-z]+(?:[.-][\da-z]+)*\.[a-z]{2,}$/iu,
//       path: undefined,
//       issues: undefined,
//       lang: undefined,
//       abortEarly: undefined,
//       abortPipeEarly: undefined
//     }
//   ]
// }
v.safeParse(vEmail, invalid_email)

プロパティの絞り込み

userから、id,name,emailのみを取り出す。スキーマにはないプロパティは無視されます。

import { z } from 'zod'

const user = {
  id: 1,
  name: 'John Doe',
  username: 'john_doe',
  bio: 'I am a software engineer',
  email: 'john.doe@example.com',
  emailVerified: new Date(),
  image: 'https://example.com/image.png',
  coverImage: 'https://example.com/cover.png',
  profileImage: 'https://example.com/profile.png',
  hashedPassword: 'password',
  createdAt: new Date(),
  updatedAt: new Date(),
  hasNotifications: false,
  posts: ['post1', 'post2', 'post3'],
  comments: ['comment1', 'comment2', 'comment3'],
  notifications: ['notification1', 'notification2', 'notification3'],
  followers: ['follower1', 'follower2', 'follower3'],
  following: ['following1', 'following2', 'following3'],
}

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

const valid = UserSchema.safeParse(user)

// {
//   success: true,
//   data: { id: 1, name: 'John Doe', email: 'john.doe@example.com' }
// }
console.log(valid)

Zodスキーマの構造に一致しない場合、バリデーションエラーになります。(idをコメントアウト)

import { z } from 'zod'
import * as v from 'valibot'

const user = {
  // id: 1,
  name: 'John Doe',
  username: 'john_doe',
  bio: 'I am a software engineer',
  email: 'john.doe@example.com',
  emailVerified: new Date(),
  image: 'https://example.com/image.png',
  coverImage: 'https://example.com/cover.png',
  profileImage: 'https://example.com/profile.png',
  hashedPassword: 'password',
  createdAt: new Date(),
  updatedAt: new Date(),
  hasNotifications: false,
  posts: ['post1', 'post2', 'post3'],
  comments: ['comment1', 'comment2', 'comment3'],
  notifications: ['notification1', 'notification2', 'notification3'],
  followers: ['follower1', 'follower2', 'follower3'],
  following: ['following1', 'following2', 'following3'],
}

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

const valid = UserSchema.safeParse(user)

// { success: false, error: [Getter] }
console.log(valid)

Valibotも挙動は同様です。

import * as v from 'valibot'

const user = {
  id: 1,
  name: 'John Doe',
  username: 'john_doe',
  bio: 'I am a software engineer',
  email: 'john.doe@example.com',
  emailVerified: new Date(),
  image: 'https://example.com/image.png',
  coverImage: 'https://example.com/cover.png',
  profileImage: 'https://example.com/profile.png',
  hashedPassword: 'password',
  createdAt: new Date(),
  updatedAt: new Date(),
  hasNotifications: false,
  posts: ['post1', 'post2', 'post3'],
  comments: ['comment1', 'comment2', 'comment3'],
  notifications: ['notification1', 'notification2', 'notification3'],
  followers: ['follower1', 'follower2', 'follower3'],
  following: ['following1', 'following2', 'following3'],
}

const UserSchema = v.object({
  id: v.number(),
  name: v.string(),
  email: v.pipe(v.string(), v.email()),
})

const valid = v.safeParse(UserSchema, user)

// {
//   typed: true,
//   success: true,
//   output: { id: 1, name: 'John Doe', email: 'john.doe@example.com' },
//   issues: undefined
// }
console.log(valid)

idをコメントアウトし、Valibotスキーマと一致しないようにした場合。

import * as v from 'valibot'

const user = {
  // id: 1,
  name: 'John Doe',
  username: 'john_doe',
  bio: 'I am a software engineer',
  email: 'john.doe@example.com',
  emailVerified: new Date(),
  image: 'https://example.com/image.png',
  coverImage: 'https://example.com/cover.png',
  profileImage: 'https://example.com/profile.png',
  hashedPassword: 'password',
  createdAt: new Date(),
  updatedAt: new Date(),
  hasNotifications: false,
  posts: ['post1', 'post2', 'post3'],
  comments: ['comment1', 'comment2', 'comment3'],
  notifications: ['notification1', 'notification2', 'notification3'],
  followers: ['follower1', 'follower2', 'follower3'],
  following: ['following1', 'following2', 'following3'],
}

const UserSchema = v.object({
  id: v.number(),
  name: v.string(),
  email: v.pipe(v.string(), v.email()),
})

const valid = v.safeParse(UserSchema, user)

// {
//   typed: false,
//   success: false,
//   output: { name: 'John Doe', email: 'john.doe@example.com' },
//   issues: [
//     {
//       kind: 'schema',
//       type: 'object',
//       input: undefined,
//       expected: '"id"',
//       received: 'undefined',
//       message: 'Invalid key: Expected "id" but received undefined',
//       requirement: undefined,
//       path: [Array],
//       issues: undefined,
//       lang: undefined,
//       abortEarly: undefined,
//       abortPipeEarly: undefined
//     }
//   ]
// }
console.log(valid)

型を取得する

  • Zodでは、z.infer

  • Valibotでは、v.InferOutput

で、型を取得することが可能。

import { z } from 'zod'
import * as v from 'valibot'

const ZUserSchema = z.object({
  id: z.string(),
  name: z.string(),
  username: z.string(),
  bio: z.string().optional(),
  email: z.string(),
  emailVerified: z.date().optional(),
  image: z.string().optional(),
  coverImage: z.string().optional(),
  profileImage: z.string().optional(),
  hashedPassword: z.string().optional(),
  createdAt: z.date(),
  updatedAt: z.date(),
  hasNotifications: z.boolean().optional(),
  posts: z.array(
    z.object({
      id: z.string(),
      body: z.string().min(1).max(140),
      createdAt: z.date(),
      updatedAt: z.date(),
      userId: z.string(),
      comments: z.array(
        z.object({
          id: z.string(),
          body: z.string(),
          createdAt: z.date(),
          updatedAt: z.date(),
          userId: z.string(),
          postId: z.string(),
        }),
      ),
    }),
  ),
  comments: z.array(
    z.object({
      id: z.string(),
      body: z.string(),
      createdAt: z.date(),
      updatedAt: z.date(),
      userId: z.string(),
      postId: z.string(),
    }),
  ),
  followers: z.array(
    z.object({
      id: z.string(),
      userId: z.string(),
      followerId: z.string(),
    }),
  ),
  following: z.array(
    z.object({
      id: z.string(),
      userId: z.string(),
      followingId: z.string(),
    }),
  ),
})

type ZUserType = z.infer<typeof ZUserSchema>

const VUserSchema = v.object({
  id: v.string(),
  name: v.string(),
  username: v.string(),
  bio: v.optional(v.string()),
  email: v.string(),
  emailVerified: v.optional(v.date()),
  image: v.optional(v.string()),
  coverImage: v.optional(v.string()),
  profileImage: v.optional(v.string()),
  hashedPassword: v.optional(v.string()),
  createdAt: v.date(),
  updatedAt: v.date(),
  hasNotifications: v.optional(v.boolean()),
  posts: v.array(
    v.object({
      id: v.string(),
      body: v.pipe(v.string(), v.minLength(1), v.maxLength(140)),
      createdAt: v.date(),
      updatedAt: v.date(),
      userId: v.string(),
      comments: v.array(
        v.object({
          id: v.string(),
          body: v.string(),
          createdAt: v.date(),
          updatedAt: v.date(),
          userId: v.string(),
          postId: v.string(),
        }),
      ),
    }),
  ),
  comments: v.array(
    v.object({
      id: v.string(),
      body: v.string(),
      createdAt: v.date(),
      updatedAt: v.date(),
      userId: v.string(),
      postId: v.string(),
    }),
  ),
  followers: v.array(
    v.object({
      id: v.string(),
      userId: v.string(),
      followerId: v.string(),
    }),
  ),
  following: v.array(
    v.object({
      id: v.string(),
      userId: v.string(),
      followingId: v.string(),
    }),
  ),
})

type VUserType = v.InferOutput<typeof VUserSchema>

pick/omit

 特定のプロパティを抽出したり、除外したりすることが可能。

import { z } from 'zod'
import * as v from 'valibot'

const ZUserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  password: z.string().min(8),
  createdAt: z.date(),
  updatedAt: z.date(),
})

// Pick
// ZUserSchema から name プロパティだけを抽出
const ZPickUserNameSchema = ZUserSchema.pick({
  name: true,
})

type ZUserName = z.infer<typeof ZPickUserNameSchema>

// Omit
// ZUserSchema から password プロパティを除外
const ZOmitUserPasswordSchema = ZUserSchema.omit({
  password: true,
})

type ZOmitUserPassword = z.infer<typeof ZOmitUserPasswordSchema>

const VUserSchema = v.object({
  id: v.number(),
  name: v.string(),
  email: v.pipe(v.string(), v.email()),
  password: v.pipe(v.string(), v.minLength(8)),
  createdAt: v.date(),
  updatedAt: v.date(),
})

// Pick
// VUserSchema から name プロパティだけを抽出
const VPickUserNameSchema = v.pick(VUserSchema, ['name'])

type VUserName = v.InferOutput<typeof VPickUserNameSchema>

// Omit
// VUserSchema から password プロパティを除外
const VOmitUserPasswordSchema = v.omit(VUserSchema, ['password'])

type VOmitUserPassword = v.InferOutput<typeof VOmitUserPasswordSchema>

おわりに

ZodValibotの基本的な使い方についてまとめました。TypeScriptで、バリデーションを導入する際の参考になれば幸いです。

参考

Discussion