Closed danenania closed 2 years ago
Just to add for completion, if your typescript declared interface has optional fields you will want to remove that optionality:
export type TypeToZod<T> = {
[K in keyof T]-?: T[K] extends
| Date
| string
| number
| boolean
| null
| undefined
? undefined extends T[K]
? ZodOptional<ZodType<Exclude<T[K], undefined>>>
: ZodType<T[K]>
: ZodObject<TypeToZod<T[K]>>;
};
Might want to add
Date
in the mix?
[K in keyof T]: T[K] extends Date | boolean | number | string | null | undefined
An extension to @IliyanID comment above is this alternative approach that makes optional fields required with the
z.ZodDefault<>
type:export type TypeToZod<T> = Required<{ [K in keyof T]: T[K] extends string | number | boolean | null | undefined ? undefined extends T[K] ? z.ZodDefault<z.ZodType<Exclude<T[K], undefined>>> : z.ZodType<T[K]> : z.ZodObject<TypeToZod<T[K]>>; }>; export const createZodObject = <T>(obj: TypeToZod<T>) => { return z.object(obj); };
This forces you to give a
z.default()
value to optional properties, so when you go toparse()
the schema, you do not need to worry about undefined properties. For example: Given the type...type Body = { prompt: string; size?: number; };
I can create the schema...
const schema = createZodObject<Body>({ prompt: z.string(), size: z.number().default(512), });
Then do...
const { prompt, size } = schema.parse(body);
And
size
will be of typenumber
and notundefined
.
I think what you mean is, if you would want to remove the optional fields, you can use this approach?
Because removing optional fields, in my view, feels a bit weird because they can be there. They should by typed optionally with their type or undefined, so T | undefined
to say it simply.
Just to add for completion, if your typescript declared interface has optional fields you will want to remove that optionality:
export type TypeToZod<T> = { [K in keyof T]-?: T[K] extends | Date | string | number | boolean | null | undefined ? undefined extends T[K] ? ZodOptional<ZodType<Exclude<T[K], undefined>>> : ZodType<T[K]> : ZodObject<TypeToZod<T[K]>>; };
Might want to add
Date
in the mix?[K in keyof T]: T[K] extends Date | boolean | number | string | null | undefined
An extension to @IliyanID comment above is this alternative approach that makes optional fields required with the
z.ZodDefault<>
type:export type TypeToZod<T> = Required<{ [K in keyof T]: T[K] extends string | number | boolean | null | undefined ? undefined extends T[K] ? z.ZodDefault<z.ZodType<Exclude<T[K], undefined>>> : z.ZodType<T[K]> : z.ZodObject<TypeToZod<T[K]>>; }>; export const createZodObject = <T>(obj: TypeToZod<T>) => { return z.object(obj); };
This forces you to give a
z.default()
value to optional properties, so when you go toparse()
the schema, you do not need to worry about undefined properties. For example: Given the type...type Body = { prompt: string; size?: number; };
I can create the schema...
const schema = createZodObject<Body>({ prompt: z.string(), size: z.number().default(512), });
Then do...
const { prompt, size } = schema.parse(body);
And
size
will be of typenumber
and notundefined
.
Love this library! I'm just beginning to convert a large TS project with many types, and I'm wondering how feasible it would be to automatically run through a file and convert any TS types to a zod schema and the accompanying inferred type. Or perhaps using the language server somehow would be easier? Either way, it would remove a ton of tedium from adopting zod for an existing project.
I guess I'll just barrel through and convert them one by one, which will probably take me a few hours at least, but figured I'd suggest this for those who might end up in a similar spot.