-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Could Zod provide type guards for narrowing union types? #430
Comments
No type guards in Zod@3 due to how they interact with the function f(x: AB) {
const maybeA = ASchema.safeParse(x);
if (maybeA.success) {
maybeA.data.a // maybeA.data is A
}
const maybeB = BSchema.safeParse(x);
if (maybeB.success) {
maybeB.data.b // maybeB.data is B
}
throw new Error("Could not parse with ASchema or BSchema");
} I've found that most unions want (or already have!) some kind of discriminant though, so in that case TypeScript is already smart enough to narrow the type if you check the discriminant. const ASchema = z.object({ type: z.literal("A"), a: z.string() });
const BSchema = z.object({ type: z.literal("B"), b: z.string() });
const ABSchema = z.union([ASchema, BSchema]);
type AB = z.infer<typeof ABSchema>;
function f(x: AB) {
switch (x.type) {
case "A": {
x.a // x is A
break;
}
case "B": {
x.b // x is B
break;
}
}
} |
Thanks Scott 👍 @redbaron I propose using a discriminated union for this. It's the best way to handle unions like this: const ASchema = z.object({ kind: z.literal("a"), a: z.string() });
const BSchema = z.object({ kind: z.literal("b"), b: z.string() });
const ABSchema = z.union([ASchema, BSchema]);
const value = ABSchema.parse("asdf");
if (value.kind === "a") {
value.a;
}else{
value.b;
} |
I ended up writing my own type guard :( Couldn't use the discriminated union, because the schema is not set by me. Why type guards are interacting badly with transform? |
I actually had that same question in #293 and it took a second to wrap my head around it, but since transformers have an input and output type, the original data is only validated for the import { z } from "zod";
const transformed = z.object({ foo: z.string().transform((maybeNumber) => Number(maybeNumber) });
type InputType = z.input<typeof transformed>; // { foo: string; }
type OutputType = z.output<typeof transformed>; // { foo: number; }
const validInput = { foo: "42" };
const validOutput = transformed.parse(validInput);
// validInput is of type { foo: string; }
// validOutput is of type { foo: number; } |
It's a bit of a heavy approach, but sometimes I'll tag the data myself by either wrapping it like |
I'm fairly new to Zod, but it appears this can safely be done now with the third generic parameter of import { z } from 'zod'
const MySchema = z.object({
foo: z.string().transform(value => Number(value))
})
const input = { foo: "bar" } as unknown
const output = MySchema.parse(input)
export const check =
<Output = any, Def extends z.ZodTypeDef = z.ZodTypeDef, Input = Output>(
schema: z.ZodType<Output, Def, Input>,
data: unknown,
): data is Input =>
schema.safeParse(data).success
console.log('this is unknown:', input)
if (check(MySchema, input)) {
console.log('this is a string:', input.foo)
console.log('this is a number:', output.foo)
} playground: https://tsplay.dev/mZJz9m |
I am using v3 branch and would like to ask if it is possible for Zod to provide type guards? This will be handy when dealing with union types:
The text was updated successfully, but these errors were encountered: