I'm trying to write a decoder function (decode
) that takes in a Zod schema and an unknown
input. It should be able to both validate the input and transform it if the Zod codec's input and output types are different.
When I try to do it like this:
import { ZodSchema, z } from 'zod'
const DateFromString = z.string().transform(input => new Date(input))
const Event = z.object({ timestamp: DateFromString })
const decode = <T>(schema: ZodSchema<T>, data: unknown): T => schema.parse(data)
const { timestamp } = decode(Event, { timestamp: '2024-01-01' })
I get the following type error:
The types of '_input.timestamp' are incompatible between these types. Type 'string' is not assignable to type 'Date'.(2345)
Is there a way to fix this type error and make sure that the timestamp
variable at the last line is of type Date
?
Another way to ask my question is how can I define just one decode
function rather than repeating the implementation just for the sake of typing:
import { ZodArray, ZodObject, ZodRawShape, ZodUnion, ZodUnionOptions } from 'zod'
const decodeObject = <T extends ZodRawShape>(schema: ZodObject<T>, data: unknown) =>
schema.parse(data)
const decodeArray = <T extends ZodRawShape>(schema: ZodArray<ZodObject<T>>, data: unknown) =>
schema.parse(data)
const decodeUnion = <T extends ZodUnionOptions>(schema: ZodUnion<T>, data: unknown) =>
schema.parse(data)
The decode function is wrongly inferring the type as shown in the docs. This version will correctly infer ZodSchema, ZodPipeline and ZodEffects
function decode<T extends z.ZodTypeAny>(schema: T, data: unknown) {
return schema.parse(data) as z.infer<T>;
}
You probably want to validate the Date
type, for this use a zod.custom
:
// With 'custom' we need to provide our own validation
const DateType = z.custom<Date>(
(input) => new Date(input).toString() !== 'Invalid Date'
); // Weak validation
const DateFromString = z
.string() // validate string
.transform((v) => new Date(v)) // transform to Date
.pipe(DateType); // validate Date
Careful, just calling new Date() is not a proper way to validate dates. e.g: new Date(0) is 'valid'; new Date(12345) is also 'valid'.
Also, Zod has a validation for string in timestamp format:
const DateFromString = z.string().date();