I’m trying to infer the right data types from a Zod schema collection from within a function
.
For this I defined two types for inferring a resulting plain JS object of Zod parsed types. That is, ValidationSchema
constitutes a base type and SafeData
the type for inference based on a generic parameter. I though that as SafeData
‘s props are mapped to ValidationSchema
‘s which are optional ?
then SafeData
‘s should too.
Also defined a simple testing function receiving a parameter of SafeData
type that inserts into a database values received in its body property. But upon finishing writing the database operation’s code I get that .body
should be an Object
type not Object | undefined
. Well, this had to be expected, but I find cumbersome to have to cast with the as
keyword or using Exclude
type from within any function to get rid of the undefined.
Clarification: While I added zod
library to the example, I’m not searching for a solution with zod, it’s only typescript based. I think it should be easily possible to express as I said below in the comments that : “if an obj.property
is an optional string according to the contract but its actual instance is a string then it should be a string and not string | undefined”. This way type inference is delegated to types and no need to add casting or typeguards within the callee.
The objective: to have either SafeData
or a helper type(s) determine “optionalitiness” of SafeData
properties based on its passed generic parameter.
export type ValidationSchema = {
bodySchema?: ZodTypeAny;
pathParamSchema?: ZodTypeAny;
queryParamSchema?: ZodTypeAny;
}
export type SafeData<S extends ValidationSchema = ValidationSchema> = {
body?:
S["bodySchema"] extends infer Type
? Type extends ZodTypeAny
? z.infer<Type>
: never
: never
pathParams?:
S extends { pathParams: ZodTypeAny }
? S extends { pathParams: ZodTypeAny }
? z.infer<S["pathParams"]>
: undefined
: never
queryParams?:
S["queryParamSchema"] extends infer Type
? Type extends ZodTypeAny
? z.infer<Type>
: undefined
: never
}
Testing function:
async function insertRestaurant(safeData: SafeData) {
await db.insert(restaurant).values(safeData.body) // Error! because db.insert(table).values() expects an actual object, not object | undefined.
}
I also wrote an alternative version to solve it. Still doesn’t work, nor do I know whether I am on the right track:
export type ValidationSchema = {
bodySchema?: ZodTypeAny;
pathParamSchema?: ZodTypeAny;
queryParamSchema?: ZodTypeAny;
}
type OptionalKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? K : never }[keyof T];
type RequiredKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T];
export type SafeData<S extends ValidationSchema = ValidationSchema> = {
[K in keyof S as K extends 'bodySchema' ? 'body' : K extends 'pathParams' ? 'pathParams' : K extends 'queryParamSchema' ? 'queryParams' : never]?:
K extends 'bodySchema'
? z.infer<S[K]>
: K extends 'pathParams'
? z.infer<S[K]>
: z.infer<S[K]>;
} & {
[K in RequiredKeys<S>]: z.infer<S[K]>;
};
5