yup 小技メモ
特定の field をある条件下で取り除く
strip
を使うとobject からその field 自体を取り除くことができる。
const schema = object({
useThis: number(),
notThis: string().strip(),
});
schema.cast({ notThis: 'foo', useThis: 4 }); // => { useThis: 4 }
この機能を when
と組み合わせることによって、ある条件にそぐわない場合はその field を取り除く 挙動を実現できる。
以下の例では、"isJapanese
が true の場合のみ、favoriteSushi
を object に含ませたい" 状況を想定している。
const schema = object({
isJapanese: boolean(),
favoriteSushi: string().when('isJapanese', {
is: true,
then: string().required(),
// false の場合はこの field を削除
otherwise: string().strip(),
}),
});
schema.case({ isJapanese: true, favoriteSushi: 'マグロ' }) // => { isJapanese: true, favoriteSushi: 'マグロ' }
schema.cast({ isJapanese: false, favoriteSushi: 'マグロ' }); // => { isJapanese: false }
cast すると isJapanese
が false の場合は、favoriteSushi
が object から消えていることが確認できる。
(補足) transform
を使って undefned にセットするだけでは不十分なので注意。
object schema を merge
concat()
を使用することで object schema を merge できる
const userSchema = yup.object({
id: yup.string().required(),
name: yup.string().required(),
});
type User = yup.InferType<typeof userSchema>;
// {
// id: string;
// name: string;
// }
const extendedUserSchema = userSchema.concat(
yup.object({
imageUrl: yup.string(),
}),
);
type ExtendedUser = yup.InferType<typeof extendedUserSchema>;
// {
// id: string;
// name: string;
// imageUrl: string | undefined;
// }
nest した要素に required な項目を持つが、自身は optional な object
解決方法は2つ。
default(undefined)
を使用して、default 値を undefined にする。
1. const userSchema = yup.object({
id: yup.string().required(),
pet: yup
.object({
name: yup.string().required(),
})
.default(undefined),
});
こちらの方法は README に記載されたやり方なのでおすすめ。
lazy()
を使用して、schema を出し分ける。
2. const userSchema = yup.object({
id: yup.string().required(),
pet: yup.lazy((value) => {
if (!value) return yup.mixed();
return yup.object({
name: yup.string().required(),
});
}),
});
詳しく説明
nest した要素に required な項目を持つが、自身は optional な object
とは一体どういう状況なのか分かりにくいので、具体例と共に説明する。
以下の条件を満たす user object の schema を例にとって考える。
- id, pet の二つの field をもつ
- id は string で required
- pet は object で optional
- pet object は name を field にもつ
- name は string で required
つまり、pet
field が存在する場合は name が必須だが、pet
field 自体はなくてもOK な user object の schema を作っていく。
// OK
const validUser1 = {
id: '001',
};
// OK
const validUser2 = {
id: '001',
pet: {
name: 'John',
},
};
// NG!!!!
const invalidUser = {
id: '001',
pet: {}, // name 必須
};
この時、以下のような schema が思いつくが、これだと期待通りには動作してくれない。
const userSchema = yup.object({
id: yup.string().required(),
// pet object は optional
pet: yup.object({
// name は required
name: yup.string().required(),
}),
});
pet
field が存在する場合の validation は期待通りに動作してくれるが、
// pet に name が存在しないのでNG
userSchema.isValidSync({
id: '001',
pet: {},
});
// => false
// 正しいデータ
userSchema.isValidSync({
id: '001',
pet: {
name: 'a',
},
});
// => true
pet
field が存在しない場合、期待通りに動いてくれない。
// 正しいデータなので true を返してほしい
userSchema.isValidSync({
id: '001',
})
// => false
これは、yup.object()
な field が cast される際の挙動に起因する。
Object schema come with a default value already set, which "builds" out the object shape, a sets any defaults for fields:
yup は validation を実行する前に、"値が存在しない場合にdefault 値をセットする" といった入力値の変換 (cast) を行うのだが、object schema の field の場合、 nest した全ての field に、その field の default 値をセットした状態の object が default 値としてセットされる。
今回のケースで言うと、
userSchema.cast({
id: '001',
})
// => { id: 'id', pet: { name: undefined } }
pet
の default 値は { name: undefined }
になる。つまり、{ id: '001' }
は { id: 'id', pet: { name: undefined } }
に 変換された後、validation がかかり、結果、required な name が undefined なので、 validation に引っかかってしまう。
以上を踏まえると、対策方法が見えてくる。
"pet
object 自体が存在しない場合は許容したいが pet.name
は required にしたい" 、これを実現するためには、
-
- validatiion 実行前にセットされる default 値を object ではなく undefined にしてあげる
-
- validation 時に
pet
field の値の有無をチェックして schema を出し分けてあげる
- validation 時に
このどちらかの対応をすれば良い。
1. validatiion 実行前にセットされる default 値を object ではなく undefined にしてあげる
default()
を使用することで default 値を設定できるので、default(undefined)
として undefined が defaultt 値として使用されるよう設定してあげればOK。
const userSchema = yup.object({
id: yup.string().required(),
pet: yup
.object({
name: yup.string().required(),
})
.default(undefined),
});
pet
field の値の有無をチェックして schema を出し分けてあげる
2. validation 時に lazy()
を使えば、"validation 実行時に schema を変更させる" ことができる。
lazy()
を利用して userSchema を以下のように書き換えると期待した通りに動作してくれる。
validation 実行時に値が存在しない場合は yup.mixed()
を返すことで validation を通し、値が存在する場合は、name が required な object の schema を使用するよう設定している。
const userSchema = yup.object({
id: yup.string().required(),
pet: yup.lazy((value) => {
if (!value) return yup.mixed();
return yup.object({
name: yup.string().required(),
});
}),
});
// 正しいデータ
userSchema.isValidSync({
id: 'id',
});
// => true