colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.09k stars 1.15k forks source link

Type narrowing not working with generic zod type #3746

Open pjdon opened 1 week ago

pjdon commented 1 week ago

If we use zod to parse an API response:

function foo(subSchema: z.ZodTypeAny) {
  fetch('example/api')
    .then(res => res.json())
    .then(json => {

      const resSchema = z.union([
        z.object({
          passed: z.literal(true),
          value: subSchema
        }),
        z.object({
          passed: z.literal(false),
          msg: z.string()
        })
      ])

      const parsed = resSchema.safeParse(json);

      if (parsed.success) {
        if (parsed.data.passed) {
          console.log(parsed.data.value);
        }
      }
    })
}

We see that when we specify a conditional for when the parse is successful, the type of parsed is narrowed. We specify another conditional inside of that for when the API result passed === true, and the type is narrowed further to one where the value key is present.

Let's say we want to make foo generic so that we can potentially return the parsed API response value.

function foo<TSubSchema extends z.ZodTypeAny>(subSchema: TSubSchema) {
  fetch('example/api')
    .then(res => res.json())
    .then(json => {

      const resSchema = z.union([
        z.object({
          passed: z.literal(true),
          value: subSchema
        }),
        z.object({
          passed: z.literal(false),
          msg: z.string()
        })
      ])

      const parsed = resSchema.safeParse(json);

      if (parsed.success) {
        if (parsed.data.passed) {
          console.log(parsed.data.value);
        }
      }
    })
}

Now we have specified that subSchema is of a type TSubSchema, which is an extension of ZodTypeAny.

But we get an error on the line

console.log(parsed.data.value);

indicating that

Property 'value' does not exist on type '{ [k in keyof addQuestionMarks<baseObjectOutputType<{ passed: ZodLiteral<true>; value: TSubSchema; }>, any>]: addQuestionMarks<baseObjectOutputType<{ passed: ZodLiteral<...>; value: TSubSchema; }>, any>[k]; } | { ...; }'.
  Property 'value' does not exist on type '{ passed: false; msg: string; }'.ts(2339)

This tells that us that the type narrowing is not working, as it the type checker is complaining about value potentially not being a key of parsed.data.

taranpreet-singh-29 commented 1 week ago

Stuck in the same issue. No solution so far