💎
Zod Valibot に入門する
Zod Valibot
Zod
TypeScript向けのスキーマ宣言とデータ検証のためのライブラリ。
Valibot
Zodよりも、バンドルサイズが小さいライブラリ。
実際に使用する
test@example.comとtest_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>
おわりに
ZodやValibotの基本的な使い方についてまとめました。TypeScriptで、バリデーションを導入する際の参考になれば幸いです。
Discussion