Valibot 逆引きチートシート
概要
「 Valibot であの処理書くにはどうすればいいんだっけ?」みたいなヤツをまとめたスクラップです。
目次
- 作成したスキーマから型を生成する
- エラー情報からメッセージのみを取り出す
- 検証に失敗した時のフォールバック値を設定する
- オブジェクトスキーマからキーを取り出してスキーマを作成する
- Internationalization ( 国際化対応 ) について
- 非同期な検証を実装する
- 入力値を別の値に変換する
- スキーマ内のスキーマを取得する
- 検証処理をカスタマイズする
- 生成される型に情報を付加したい( Branded Types )
- 検証してない値を検証結果に含める
- スキーマからデフォルト値を取得する
- スキーマを使って型ガード(Type Guards)する
- 複数のオブジェクトのスキーマをマージする
- オブジェクトのスキーマから特定のプロパティを除外する
- 省略可能(Optional)なスキーマを必須(Required)にする
- ラップされているスキーマを取り出す
- スキーマの入力値の型を生成する
- デフォルトのエラーメッセージを設定する
- 入力値や期待値を使ってエラーメッセージをカスタマイズする
作成したスキーマから型を生成する
検証した結果の型を生成したい時は v.InferOutput<T>
を使う。
import * as v from 'valibot';
const Schema = v.object({
email: v.string(),
password: v.string(),
});
type SchemaType = v.InferOutput<typeof Schema>;
/*
{
email: string;
password: string;
}
*/
エラー情報からメッセージのみを取り出す
検証に失敗した時に得られる issues
からエラーメッセージのみを取り出すには v.flatten()
を使用します。
import * as v from 'valibot';
const Schema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
const result = v.safeParse(Schema, {
email: 'hoge',
password: 12345,
});
if (!result.success) {
const issues = v.flatten(result.issues);
console.log(issues);
/*
{
nested: {
email: ["Invalid email: ..."],
password: ["Invalid type: ..."],
}
}
*/
}
ちなみに v.flatten()
を実行しない場合、result.issues
の中身は以下のようになります。
コードを見る
[
{
kind: 'validation',
type: 'email',
input: 'hoge',
expected: null,
received: '"hoge"',
message: 'Invalid email: Received "hoge"',
requirement: RegExp,
path: [
{
type: 'object',
origin: 'value',
input: {
email: 'hoge',
password: 1111,
},
key: 'email',
value: 'hoge',
},
],
issues: undefined,
lang: undefined,
abortEarly: undefined,
abortPipeEarly: undefined,
},
{
kind: 'schema',
type: 'string',
input: 1111,
expected: 'string',
received: '1111',
message: 'Invalid type: Expected string but received 1111',
requirement: undefined,
path: [
{
type: 'object',
origin: 'value',
input: {
email: 'hoge',
password: 1111,
},
key: 'password',
value: 1111,
},
],
issues: undefined,
lang: undefined,
abortEarly: undefined,
abortPipeEarly: undefined,
},
]
検証に失敗した時のフォールバック値を設定する
検証に失敗した時、デフォルトではエラーオブジェクトが返されますが、エラーオブジェクトではなく任意の値を返して欲しい場合は v.fallback()
を使用します。
import * as v from 'valibot';
const Schema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
const FallbackSchema = v.fallback(Schema, {
email: 'hoge@example.com',
password: 'fallback_password',
});
// フォールバックされるので検証に失敗しても throw されない
const result = v.parse(FallbackSchema, null)
console.log({ result });
/*
{
result: {
email: "hoge@example.com",
password: "test_password"
}
}
*/
オブジェクトスキーマからキーを取り出してスキーマを作成する
オブジェクトスキーマからキー文字列を取り出してスキーマを作成するには v.keyof()
を使用します。
import * as v from 'valibot';
const Schema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
const SchemaKeys = v.keyof(Schema)
// → v.PicklistSchema<["email", "password"], undefined>
type SchemaKeysType = v.InferOutput<typeof SchemaKeys>
// → "email" | "password"
Internationalization ( 国際化対応 ) について
エラーメッセージを i18n (Internationalization) させたい時があります。
基本的なメッセージであれば公式が用意してくれている @valibot/i18n を使用するだけで対応できます。
// 全ての言語を適用します (オススメしません)
import '@valibot/i18n';
// 特定の言語のみ適用します
import '@valibot/i18n/ja';
// スキーマのみ適用します
import '@valibot/i18n/ja/schema';
// 特定の関数のみに適用します
import '@valibot/i18n/ja/minLength';
言語の切り替え
表示する言語を指定する場合は v.setGlobalConfig()
を使用するか v.parse()
などで検証を実行する時に直接指定します。
import * as v from 'valibot';
// 実行後、全ての検証で日本語のエラーメッセージが適用されます
v.setGlobalConfig({ lang: 'ja' });
// この検証のみで日本語のエラーメッセージが適用されます
v.parse(Schema, input, { lang: 'ja' });
@valibot/i18n
では対応できない時
v.setGlobalMessage()
, v.setSchemaMessage()
, v.setSpecificMessage()
を使用することで、個別にエラーメッセージを対応させることができます。
import * as v from 'valibot';
// デフォルトのエラーメッセージを設定します (これがフォールバックとして使用されます)
v.setGlobalMessage((issue) => `検証に失敗しました`, 'ja');
// スキーマのデフォルトのエラーメッセージを設定します
v.setSchemaMessage((issue) => `スキーマの検証に失敗しました`, 'ja');
// 特定の関数のデフォルトのエラーメッセージを設定します
v.setSpecificMessage(v.minLength, (issue) => `長さが不正です`, 'ja');
Paraglide JS と連携もできる
また、Paraglide JS と連携することも可能なようです。
import * as v from 'valibot';
import * as m from './paraglide/messages.js';
const LoginSchema = v.object({
email: v.pipe(
v.string(),
v.nonEmpty(m.emailRequired),
v.email(m.emailInvalid)
),
password: v.pipe(
v.string(),
v.nonEmpty(m.passwordRequired),
v.minLength(8, m.passwordInvalid)
),
});
非同期な検証を実装する
非同期な検証をするには v.pipeAsync()
, v.objectAsync()
などのような非同期に対応するスキーマを作成し、v.parseAsync()
などのような専用の関数で検証します。
import * as v from 'valibot';
const isUsernameAvailable = async (input: string): boolean => {
const result = await fetch(`/api/check_username?username=${input}`)
return result.isAvailable;
}
// 非同期なスキーマを定義
const Schema = v.objectAsync({
username: v.pipeAsync(v.string(), v.checkAsync(isUsernameAvailable)),
avatarUrl: v.pipe(v.string(), v.url()),
});
const input = { username: 'hoge', avatarUrl: 'https://example.com/avatar.png' }
const result = await v.parseAsync(Schema, input);
console.log({ result });
/*
{
result: {
username: string;
avatarUrl: string;
}
}
*/
throw される値について
通常の検証処理では、検証に失敗するとエラーメッセージなどの情報を取得できますが、自前で用意した非同期な検証では throw した値がそのまま検証結果として返される点に注意してください。
import * as v from 'valibot';
const isCheckName = async (input: string): Promise<boolean> => {
throw new Error("hoge"); // わざと throw する
}
const Schema = v.objectAsync({
name: v.pipeAsync(v.string(), v.checkAsync(isCheckName))
})
v.parseAsync(Schema, { name: "" }).catch(err => {
console.error(err); // `Error: hoge` ← isCheckName() で throw した値になってしまう
})
そのため、自前で非同期な検証処理を行う時はなるべく throw させないようにしましょう。
入力値を別の値に変換する
入力値を別の値へ変換するには v.transform()
を使用します。
import * as v from 'valibot';
const Schema = v.pipe(
v.string(),
v.transform((input) => input.length), // string → number へ変換する
// 以降は number 型として検証できる
v.minValue(5),
v.maxValue(10)
);
// number 型になる
type SchemaType = v.InferOutput<typeof Schema>;
const result: number = v.parse(Schema, 'hello,world!')
console.log({ result }) // { result: 12 }
スキーマ内のスキーマを取得する
v.object()
や v.array()
などスキーマを内包するスキーマがありますが、そのスキーマから内包しているスキーマを取り出すことができます。
v.object() の場合
.entries
プロパティから内包しているスキーマを取得できます。
import * as v from 'valibot';
const Schema = v.object({
email: v.pipe(v.string(), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
// Schema から`email`のスキーマを取り出す
const EmailSchema = Schema.entries.email
v.array() の場合
.item
プロパティから内包しているスキーマを取得できます。
import * as v from 'valibot';
const Schema = v.array( v.pipe(v.string(), v.email()))
// Schema から`email`のスキーマを取り出す
const EmailSchema = Schema.item
v.record() の場合
.key
プロパティでキーのスキーマを、.value
で値のスキーマを取得できます。
import * as v from 'valibot';
const Schema = v.record(v.string(), v.pipe(v.string(), v.email()))
const UsernameSchema = Schema.key; // キーのスキーマを取り出す
const EmailSchema = Schema.value; // 値のスキーマを取り出す
検証処理をカスタマイズする
検証処理に独自の検証を追加したい時があります。その場合は、v.check()
または v.custom()
を使用することで目的を達成できます。
v.custom() を使う場合
v.custom()
は独自の検証処理を持つスキーマを定義するときに使用します。
import * as v from 'valibot';
const Schema = v.custom<`${number}px`>((input) => {
return typeof input === 'string' ? /^\d+px$/.test(input) : false
});
type SchemaType = v.InferOutput<typeof Schema> // `${number}px`
v.check() を使う場合
v.check()
は既にあるスキーマに対して個別の検証処理を使いする時に使用します。
import * as v from 'valibot';
const Schema = v.pipe(
v.string(),
v.check<`${number}px`>((input) => /^\d+px$/.test(input))
);
type SchemaType = v.InferOutput<typeof Schema> // `${number}px`
v.custom() と v.check() の違いについて
valibot では スキーマ ・メソッド・アクション という役割を持つ関数の組み合わせで成り立っています。
https://valibot.dev/guides/mental-model/ より引用
今回の場合、v.custom()
は スキーマ、v.check()
は アクション というそれぞれ違う役割を担っています。
スキーマは、文字列、オブジェクト、日付などの特定のデータ型を独立して検証します。そのため再利用性が高く、他のオブジェクトにネストさせたり、他のスキーマやアクションと合わせて別のスキーマを作るなど柔軟な検証ができます。
一方アクションの方は、スキーマの検証をさらに強化したり、検証の前処理などを行う役割があります。スキーマほど再利用性は高くありませんが、データ型に左右されない柔軟な処理を行うことが可能です。(例: v.transform()
など)
違いまとめ表
名前 | メリット | デメリット |
---|---|---|
v.custom() |
再利用性の高い検証を定義できる | 検証するデータ型が必要 |
v.check() |
データ型に左右されない処理ができる | 再利用性が高くない |
生成される型に情報を付加したい( Branded Types )
TypeScript では構造が同じ値は同じ型として扱われます。スキーマから生成した型ももちろん構造が同じであれば v.parse()
などの戻り値じゃなくても代入することができます。
import * as v from 'valibot';
const Schema = v.pipe(v.string(), v.maxLength(5));
type SchemaType = v.InferOutput<typeof Schema>; // string 型になる
// 問題なく代入できる
const validStr: SchemaType = v.parse(Schema, 'valid');
// v.parse() してないけど string なので問題なく代入できる
const invalidStr: SchemaType = 'invalid';
しかし、これでは SchemaType
を使っているにもかかわらず検証していない値が入る可能性があります。
そこで、v.brand()
を使うことで SchemaType
を使っている変数には v.parse()
などで検証した値しか入らないようにできます。
import * as v from 'valibot';
// v.brand() を使って幽霊型を付与する
const Schema = v.pipe(v.string(), v.maxLength(5), v.brand('any'));
// string & v.Brand<"any">型になる
type SchemaType = v.InferOutput<typeof Schema>;
// 問題なく代入できる
const validStr: SchemaType = v.parse(Schema, "valid");
// 型エラーになり代入できなくなる
const invalidStr: SchemaType = "invalid";
// ~~~~~~~~~~
// └─ Type 'string' is not assignable to type 'string & Brand<"any">'.
検証してない値を検証結果に含める
一番簡単な方法は v.is()
を使って Type Guard してしまうことです。
import * as v from 'valibot';
const Schema = v.object({ key: v.string() })
const value = { key: "hello", key2: "world" }
if(v.is(Schema, value)) {
value; // 型では `{ key: string }` だが実際は `{ key: "hello", key2: "world" }` となる
}
もしくは、v.looseObject()
や v.looseTuple()
などでスキーマ定義時に含めるように指定します。
import * as v from "valibot";
const Schema = v.looseObject({ key: v.string() });
const value = { key: "hello", key2: "world" }
const result = v.parse(Schema, input)
console.log({ result }) // { result: { key: "hello", key2: "world" } }
スキーマからデフォルト値を取得する
v.optional()
や v.nullable()
と言ったスキーマはデフォルト値を設定できますが、それらのスキーマからデフォルト値を取得するには v.getDefault()
を使用します。
import * as v from 'valibot';
const Schema = v.optional(v.string(), "default value");
const defaultValue = v.getDefault(Schema);
console.log({ deafultvalue }); // { defaultValue: "default value" }
また、オブジェクトスキーマなどからデフォルト値を取得するには v.getDefaults()
を使用します。
import * as v from 'valibot';
const Schema = v.object({
key: v.optional(v.string(), "default value"),
key2: v.string()
});
const defaultValue = v.getDefaults(Schema);
console.log({ defaultValue });
/*
{
defaultValue: {
key: "default value",
key2: undefeind, // デフォルト値が無いスキーマ undefined になることに注意!
}
}
*/
スキーマを使って型ガード(Type Guards)する
検証を Type Guard として実行するには v.is()
を使用します。
import * as v from 'valibot';
const Schema = v.pipe(v.string(), v.email());
const data: unknown = 'jane@example.com';
if (v.is(Schema, data)) {
const email = data; // string
} else {
const email = data; // unknown
}
複数のオブジェクトのスキーマをマージする
TypeScript で言うところの extends
みたいなことをしたい時、.entries
から各プロパティのスキーマを参照できるので、それを使ってマージする。
import * as v from 'valibot';
const Schema1 = v.object({ foo: v.string(), baz: v.number() });
const Schema2 = v.object({ bar: v.string(), baz: v.boolean() });
const MergedSchema = v.object({
...Schema1.entries,
...Schema2.entries,
});
// → { foo: string; bar: string; baz: boolean }
オブジェクトのスキーマから特定のプロパティを除外する
v.omit()
を使う。
import * as v from 'valibot';
const Schema = v.omit(
v.object({
key1: v.string(),
key2: v.number(),
key3: v.boolean(),
}),
['key1', 'key3']
);
type SchemaType = v.InferOutput<typeof Schema>
// { key2: number }
省略可能(Optional)なスキーマを必須(Required)にする
v.required()
を使う。
import * as v from 'valibot';
const Schema = v.required(
v.object({
key1: v.optional(v.string()),
key2: v.optional(v.number()),
})
);
type SchemaType = v.InferOutput<typeof Schema>
// { key1: string; key2: number }
ラップされているスキーマを取り出す
v.nullable()
や v.optional()
と言ったスキーマをラップするようなスキーマから内部のスキーマを取り出すには v.unwrap()
または .wrapped
プロパティを使用します。
import * as v from 'valibot';
const Schema = v.optional(v.string());
// `string | undefined` から `string` のスキーマを取り出す
const StringSchema = v.unwrap(Schema);
// または以下のようにも書けます
const WrappedSchema = Schema.wrapped;
スキーマの入力値の型を生成する
v.transform()
など入力値と出力値の型が違う時に、入力値の型を生成するには v.InferInput<T>
を使用する。
import * as v from 'valibot';
const Schema = v.object({
date: v.pipe(v.string(), v.transform(str => new Date(str)))
});
type SchemaType = v.InferInput<typeof Schema>
// { date: string; }
デフォルトのエラーメッセージを設定する
v.parse()
や v.safeParse()
の第三引数で設定できます。
import * as v from 'valibot';
const Schema = v.object({
key: v.string('個別に設定しているエラーメッセージ'),
key2: v.string(),
});
const input = {}
const result = v.safeParse(
Schema,
input,
{
message: 'これはデフォルトメッセージです', // ここでデフォルト値を設定できる
},
);
if (!result.success) {
console.log(v.flatten(result.issues));
/*
{
nested: {
key: [
"個別に設定しているエラーメッセージ"
],
key2: [
"これはデフォルトメッセージです"
]
}
}
*/
}
入力値や期待値を使ってエラーメッセージをカスタマイズする
例えば、入力された文字から残りの文字数を計算してエラーメッセージとして出すには以下のようにします。
import * as v from 'valibot';
const Schema = v.pipe(
v.string(),
v.maxLength(10, (issue) => {
return `あと${issue.input.length - issue.requirement}文字短くして下さい`;
}),
);
const result = v.safeParse(Schema, '1234567890ABC');
if (!result.success) {
console.log(result.issues[0].message); // → "あと3文字短くして下さい"
}