How can i create union schema ?
Like this
type A = B | C
If i use concat, i get
type A = B & C
too pity that yup does not have something like https://github.com/joanllenas/ts.data.json#jsondecoderoneof
but typescript inferred a to be of type string | number in the following code.
declare const condition: boolean;
let schema =
condition ?
yup.string()
: yup.number();
let a = schema.cast('whatever');
@RichardYan314
const SchemaA = object({ a: yup.string() })
const SchemaB = object({ b: yup.number() })
How can i concat two schemes, and get type = B | C ?
No you cannot concat two schemes and get a union type. It is not possible.
const schemaA = yup.object({
a: yup.string()
});
const schemaB = yup.object({
b: yup.string()
});
const AconcatB = schemaA.concat(schemaB);
type a = yup.InferType<typeof AconcatB>;
a will have type {a: string, b: string}, because of:
interface ObjectSchema<T extends object | null | undefined = object> {
concat<U extends object>(schema: ObjectSchema<U>): ObjectSchema<T & U>;
}
on a second thought, if you use concat and get A & B, you could do as A | B because A & B subtypes to A | B. is this what you are looking for?
I can get either key A or key B, not both
from server i get {a: '1'}or {b: 9}, not {a: '1', b: 9}
No you cannot concat two schemes and get a union type. It is not possible.
It is possible, but apparently not in this library...
If there are no more hacks or solutions, you can close the topic
Then I guess the best way is
const schemaA = yup.object({
a: yup.string()
});
const schemaB = yup.object({
b: yup.string()
});
let value: {a: string} | {b: string};
value = {a: '1'};
let rst1 = schemaA.isValidSync(value) ?
schemaA.cast(value) :
schemaB.validateSync(value);
// >> { a: '1' }
value = {b: '2'};
let rst2 = schemaA.isValidSync(value) ?
schemaA.cast(value) :
schemaB.validateSync(value);
// >> { b: '2' }
Both rst1 and rst2 have type {a: string} | {b: string}.
If you do not like this, you could try build something like
yup.oneOf(schema: Schema[]): Schema
which returns the result of the first successful schema or raise exception if all schema fail.
I cannot close this issue. I am not related to this project and am just a passer-by.
Both
rst1andrst2have type{a: string} | {b: string}.
If i'll use https://github.com/jquense/yup#typescript-support, can i get a valid typescript interface ?
concat does not create a discriminated union, which is why the type doesn't reflect that. It combines the two schema, where the input value must satisfy both schema, not one or the other.
at the moment there is no direct support for "one of type" sort of things because it's unclear how to implement it. @RichardYan314 solution is good if you are fine with only sync schema's and illustrates the abiguity with implementing.
what should the result this be for instance?:
yup.oneOf([schemaA, schemaB]).cast({ a: 4 })
in both schema, there is no "failed" or "successful" cast to determin which schema to use here. You can't check by validating, because validation is async and cast is not, you can't assume all schema support validateSync either
Can I add a note here. I have been playing around and I am not sure there is a yup.oneOf() method is seems you need to use something like yup.mixed().oneOf([valueA, valueB]) however then it appears you cannot pass schemas in but actual values. Maybe I am missing something.
@jdolle @ryardley @polRk @RichardYan314 @joefiorini @schubidu @henriquehbr
I built a library (Zod https://github.com/vriad/zod) that addresses this limitation (any many other issues) with Yup's type inference system. The main idea was to provide a chainable, Yup-like interface with a focus on rigorous typings and good dev experience.
It has built-in support for union types too — here's how the example above looks:
const A = z.object({
a: z.string()
});
const B = z.object({
b: z.string()
});
const AorB = z.union([ A, B ])
type AorB = z.TypeOf<typeof AorB>
// => { a: string } | { b: string }
Here's an initial implementation I'm using
type SchemaUnionOptions = {
nonIntersectingSchemas?: yup.ObjectSchema[] | boolean;
};
type InferSchemaUnion<T> = T extends yup.Schema<infer R>[] ? R : never
export const schemaUnion = (schemas: yup.ObjectSchema[], options: SchemaUnionOptions = {}) => {
const { nonIntersectingSchemas = false } = options;
const allNonIntersectingKeys: string[] = [];
if (nonIntersectingSchemas) {
if (Array.isArray(nonIntersectingSchemas)) {
allNonIntersectingKeys.push(...nonIntersectingSchemas.map(schema => Object.keys(schema.fields)).flat());
} else {
allNonIntersectingKeys.push(...schemas.map(schema => Object.keys(schema.fields)).flat());
}
}
return yup.object<InferSchemaUnion<T>>.test({
name: 'schemaUnion',
message: 'value did not match any schema: ${value}',
test: async value => {
const errors: ObjectOf<Error> = {};
for (const [idx, schema] of Object.entries(schemas)) {
if (allNonIntersectingKeys.length) {
const schemaKeys = Object.keys(schema.fields);
const valueKeys = Object.keys(value);
const invalidKeys = allNonIntersectingKeys.filter(key => !schemaKeys.includes(key));
// invalidKeys are the difference between the current schema's keys and the remaining keys which cannot intersect
// so if there is an intersection between the invalidKeys array that are also in our object's properties and
// the current schema's keys match any of the object's keys, our object is invalid because we have an intersection
if (invalidKeys.some(key => valueKeys.includes(key)) && schemaKeys.some(key => valueKeys.includes(key))) {
const prettyObjectKeys = (arr: string[]) => `{"${arr.join('", "')}"}`;
throw Error(
`Object keys ${prettyObjectKeys(valueKeys)} cannot intersect with ${prettyObjectKeys(
schemaKeys
)} and ${prettyObjectKeys(invalidKeys)}`
);
}
}
try {
await schema.validate(value);
return true;
} catch (error) {
errors[schema.describe().label || `${error.name}${idx}`] = error.message;
}
}
throw { error: 'Object failed all schema validations', validationErrors: errors, value };
},
});
};
It allows you to validate an object that can only be one of a union of schemas. It also can error if there are keys that intersect between all of the schemas or a subset of schemas.
const A = yup.object({ a: yup.object() });
const B = yup.object({ b: yup.object() });
const CD = yup.object({ c: yup.string(), d: yup.string()})
const AB = schemaUnion([A, B]);
const AB: yup.ObjectSchema<{
a: object;
} | {
b: object;
}>
const ABCD = schemaUnion([A, B, CD])
const ABCD: yup.ObjectSchema<{
a: object;
} | {
b: object;
} | {
c: string;
d: string;
}>
Most helpful comment
@jdolle @ryardley @polRk @RichardYan314 @joefiorini @schubidu @henriquehbr
I built a library (Zod https://github.com/vriad/zod) that addresses this limitation (any many other issues) with Yup's type inference system. The main idea was to provide a chainable, Yup-like interface with a focus on rigorous typings and good dev experience.
It has built-in support for union types too — here's how the example above looks: