question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Could Zod provide type guards for narrowing union types?

See original GitHub issue

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:

const ASchema = z.object({a: z.string()});
type A = z.infer<typeof ASchema>;

const BSchema = z.object({b: z.string()});
type B = z.infer<typeof BSchema>;

const ABSchema = z.union([ASchema, BSchema])
const AB = z.infer<typeof ABSchema>;  // { a: string } | { b: string}


function f(x: AB) {
   if (ASchema.guard(x)) {
       x.a   // x is A
   } else {
       x.b 
   }
}

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
scotttrinhcommented, May 10, 2021

No type guards in Zod@3 due to how they interact with the transform feature, however you could definitely do this with parsing:

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;
    }
  }
}
1reaction
colinhackscommented, May 15, 2021

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;
  }
Read more comments on GitHub >

github_iconTop Results From Across the Web

colinhacks/zod: TypeScript-first schema validation ... - GitHub
With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into...
Read more >
narrowing types via type guards and assertion functions - 2ality
TypeScript's type inference supports type guards by narrowing the static type of an operand when the result is true .
Read more >
Type-Safe TypeScript with Type Narrowing - Rainer Hahnekamp
Whenever we deal with a variable that can be of multiple types, like an unknown or a union type, we can apply type...
Read more >
Narrowing Types in TypeScript - Formidable Labs
Type narrowing is just what it sounds like—narrowing down a general type into something more precise. Here's how to do it.
Read more >
Understanding the discriminated union pattern
The discriminated union pattern is a way of narrowing a union type. A requirement for this pattern is for the types in the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found