colinhacks / zod

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

Error trying to reuse inferred type #3847

Open mauroviniciussilva opened 2 weeks ago

mauroviniciussilva commented 2 weeks ago

I'm trying to use a type inferred from a zod schema, but when I need to reuse the inferred type in other generic function I just couldn't find a way of doing it.

Considering the following code:

import { z } from 'zod';

const Schema = z.object({
  id: z.number(),
  name: z.string(),
  nickname: z.string().optional().default('')
});

export type SchemaDto = z.infer<typeof Schema>;

const parsedSchema: z.Schema<SchemaDto> = Schema;

I got the following error:

Type 'ZodObject<{ id: ZodNumber; name: ZodString; nickname: ZodDefault<ZodOptional<ZodString>>; }, "strip", ZodTypeAny, { ...; }, { ...; }>' is not assignable to type 'ZodType<{ name: string; id: number; nickname: string; }, ZodTypeDef, { name: string; id: number; nickname: string; }>'.
  The types of '_input.nickname' are incompatible between these types.
    Type 'string | undefined' is not assignable to type 'string'.
      Type 'undefined' is not assignable to type 'string'.ts(2322)

I can understand that the inferred type assumed that, once I have the default '' the field wouldn't be undefined in the generated type:

type SchemaDto = {
    name: string;
    id: number;
    nickname: string;
}

But, if I need to use the generated type to force the usage of a Schema that is compatible with a type, I cannot use the inferred type to accomplish my goal.

In the documentation there's no information on how to infer a type without considering the default to be able to reuse the inferred type in other generic functions. Is this a relevant functionality to the package?

AlexChadwickP commented 2 weeks ago

Hi @mauroviniciussilva that's correct, I looked into the documentation and found .input.

To work around this and get the input type (before defaults are applied), you can use z.input<typeof Schema> instead of z.infer. This will preserve the optionality of nickname, so it’s typed as string | undefined, matching what you need for your generic function.

This is what your code would look like:

import { z } from 'zod';

const Schema = z.object({
  id: z.number(),
  name: z.string(),
  nickname: z.string().optional().default('')
});

type SchemaInput = z.input<typeof Schema>;  //  nickname will be string | undefined

const parsedSchema: z.ZodType<SchemaInput> = Schema;