zod icon indicating copy to clipboard operation
zod copied to clipboard

Preprocess breaks relationship between TypeOf<T>/infer<T> and ZodType<T>

Open ConnorSinnott opened this issue 2 years ago • 1 comments

Given a generic T, I'm trying to type the result of a function as the schema which can create T. This breaks however if the schema has a preprocessed field. I've broken the issue down to:

import { z, ZodType, ZodtypeDef } from 'zod'

const noPreprocessObjSchema = z.object({
  date: z.date(),
});
type NoPreprocessObj = z.infer<typeof noPreprocessObjSchema>;

const preprocessObjSchema = z.object({
  date: z.preprocess(
    (arg) => (typeof arg === 'string' ? new Date(arg) : arg),
    z.date(),
  ),
});
type PreprocessObj = z.infer<typeof preprocessObjSchema>;

const noPreprocessParser: z.ZodType<NoPreprocessObj> = noPreprocessObjSchema;
const preprocessParser: z.ZodType<PreprocessObj> = preprocessObjSchema; // Error: The types of '_input.date' are incompatible between these types. Type 'unknown' is not assignable to type 'Date'

Is the recommend solution that I type the prepocessor as ZodType<Date, ZodTypeDef, Date>?

ConnorSinnott avatar Dec 12 '22 22:12 ConnorSinnott

Wanted to bump this since I see its getting a little attention but no answers. I see this change was implemented rather specifically on September 11th. Normally for a primitive parser like string() the zod type would have both its input and output marked as string correct?

ZodString extends ZodType<string, ZodStringDef>
...
ZodType<Output = any, Def extends ZodTypeDef = ZodTypeDef, Input = Output>

But even with Input typed as string, the .parse and .safeParse methods both accept unknown as the parse argument. So I'm not sure what value having .preprocesses input as unknown brings. Conceptually, yeah anything could go into a preprocessor, but the same could be said for the primitive parsers. I'd expect them all to fail if the value can't be parsed.

ConnorSinnott avatar Dec 20 '22 00:12 ConnorSinnott

@ConnorSinnott This is partially correct. Read until the end because there's a PR to fix this.

When you create a schema with preprocess, there's no way of knowing which input you will receive on runtime. I mean,

  date: z.preprocess(
    (arg) => (typeof arg === 'string' ? new Date(arg) : arg),
    z.date(),
  ),

How can you be sure that arg will be a Date object? You have even added a typeof arg === 'string' check that proves that uncertainty.

So, Input will be unknown.

However, we can fix preprocess to enable such thing.

Please take a look at this PR.

santosmarco-caribou avatar Dec 24 '22 06:12 santosmarco-caribou

@ConnorSinnott Is this what you are looking for?

const schema = z.object( {
    date: z.preprocess(
        arg => ( typeof arg === 'string' ? new Date( arg ) : arg ),
        z.date(),
    ),
} )
type Obj = z.infer<typeof schema>
// type Obj = {
//     date: Date
// }

const typeCheck: z.ZodType<Obj, z.ZodTypeDef, { date?: unknown }> = schema
// no ts errors, yay!

console.log( schema.safeParse( { date: '2023-01-01' } ).success ) // true
console.log( schema.safeParse( { date: '01/01/2023' } ).success ) // true
console.log( schema.safeParse( { date: new Date() } ).success ) // true

console.log( schema.safeParse( { date: 42 } ).success ) // false
console.log( schema.safeParse( { date: 'foo' } ).success ) // false

JacobWeisenburger avatar Jan 01 '23 20:01 JacobWeisenburger

Hey @JacobWeisenburger! My issue isn't with the parsing behavior, but with the types generated by z.infer and how they related to the schemas. @santosmarco-caribou answer resonates more closely, and actually in the PR makes similar points. Ultimately the schemas I've defined in OP expect a Date, and just because I have some logic to coerce an input, doesn't change that I'm ultimately looking for a date. I'd expect the inferred types to reflect this.

ConnorSinnott avatar Jan 04 '23 00:01 ConnorSinnott

Is there any chance https://github.com/colinhacks/zod/commit/2893e1659875944198aff28a3ba404858f834ed8 could be reverted to address this?

franzwilhelm avatar Jan 18 '23 14:01 franzwilhelm

@franzwilhelm there's discussion about that in 1752

ConnorSinnott avatar Jan 18 '23 18:01 ConnorSinnott

Thank you @ConnorSinnott

franzwilhelm avatar Jan 19 '23 11:01 franzwilhelm