toZod (v3.0.0) does not infer nullable+optional (nullish) number type properly
waful opened this issue · comments
In my source interface I have:
interface ContrivedInterface {
problematicNumber?: number | null;
}
I would write it in zod as follows:
const contrivedZod: toZod<ContrivedInterface> = z.object({
problematicNumber: z.number().nullable().optional()
})
Problem is, the type of problematicNumber
from z.infer
is z.ZodOptional<z.ZodNullable<z.ZodNumber>>
, whereas the type of problematicNumber
generated by toZod<ContrivedInterface>
is ZodOptional<never> | ZodOptional<ZodNumber>
causing a type mismatch.
The exact ts error:
Type '{ problematicNumber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>; }' is not assignable to type '{ problematicNumber: ZodOptional<never> | ZodOptional<ZodNumber>; }'.
My usecase is, I want to be able to send null
from client to server as an indicator that I want problematicNumber
to be unset on backend because undefined
is not a valid JSON value so I can't use it in my payload. So undefined
represents a lack of value whereas null
represents intentional removal of value.
I'm having the same problem, did you solve it?
@sebastiangon11 As suggested by some of the other issues, I replaced zod with superstruct and found no issues.
I ran into the same issue, where toZod
would not recognize that T | null | undefined
is in fact a nullable T
.
Here is a small change that fixes this:
import * as z from 'zod';
declare type isAny<T> = [any extends T ? 'true' : 'false'] extends ['true'] ? true : false;
declare type equals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;
type MakeNullish<T> = z.ZodOptional<z.ZodNullable<toZod<Exclude<T, undefined | null>>>>
type isNullish<T> =
null extends T
? undefined extends T
? true
: false
: false
export declare type toZod<T> =
isAny<T> extends true
? never
: [T] extends [boolean]
? z.ZodBoolean
: isNullish<T> extends true
? MakeNullish<T>
: [undefined] extends [T]
? T extends undefined
? never
: z.ZodOptional<toZod<T>>
: [null] extends [T]
? T extends null
? never
: z.ZodNullable<toZod<T>>
: T extends Array<infer U>
? z.ZodArray<toZod<U>>
: T extends Promise<infer U>
? z.ZodPromise<toZod<U>>
: equals<T, string> extends true
? z.ZodString : equals<T, bigint> extends true
? z.ZodBigInt
: equals<T, number> extends true
? z.ZodNumber
: equals<T, Date> extends true
? z.ZodDate
: T extends {
[k: string]: any;
}
? z.ZodObject<{
[k in keyof T]-?: toZod<T[k]>;
}, 'strip', z.ZodTypeAny, T, T>
: never;