8000 v4: Type violation upon piping into coerced types · Issue #4236 · colinhacks/zod · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

v4: Type violation upon piping into coerced types #4236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
martinjlowm opened this issue Apr 19, 2025 · 1 comment
Closed

v4: Type violation upon piping into coerced types #4236

martinjlowm opened this issue Apr 19, 2025 · 1 comment

Comments

@martinjlowm
Copy link
martinjlowm commented Apr 19, 2025

edit: supposedly, this is covered in the migration announcement: https://v4.zod.dev/v4/changelog#zcoerce-updates

Testing out v4 I stumbled upon a chain piping into z.coerce.date().

const date = z.union([z.string(), z.number(), z.date()]).nullish().transform((v) => (v === null ? undefined : v)).pipe(z.coerce.date().default(() => new Date()))

Results in,

typescript [2345]: Argument of type 'ZodCoercedDate' is not assignable to parameter of type '$ZodType<any, string>'.
  The types of '_zod.input' are incompatible between these types.
    Type 'unknown' is not assignable to type 'string'.

The intention with this was to accept string | number | Date | null | undefined, map null -> undefined and convert undefined -> Date through the default call such that null and undefined would be converted to the current time.

Seeing this error made me think... and I ended up simplifying it with:

const date = z.null().transform(() => new Date()).or(z.coerce.date())

Handling null first and then afterwards coercing the rest. That being said, v3 typed the pipe correctly without errors, but it may overcomplicate things.

@colinhacks
Copy link
Owner

I like that schema :)

Zod 4 gets a little more clever with .pipe(). This is why unknown is not always better than any: sometimes the assignability characteristics of unknown causes issues, as in this example.

I used a trick to make the z.coerce APIs a little more flexible in situations like this. Upgrade the latest beta to give it a shot. That said, your code still won't work as is for reasons that are a bit complicated.

You can either, move the default before the pipe (I believe this is functionally identical to your current schema)::

const date = z
  .union([z.string(), z.number(), z.date()])
  .nullish()
  .transform((v) => (v === null ? undefined : v))
  .default(() => new Date())
  .pipe(z.coerce.date());

Alternatively, you can use the generic parameter of z.coerce.date (recently added) to loosen the input type and avoid the assignability issue altogether:

const date = z
  .union([z.string(), z.number(), z.date()])
  .nullish()
  .transform((v) => (v === null ? undefined : v))
  .pipe(z.coerce.date<any>().default(() => new Date()));

4EB3 @colinhacks colinhacks closed this as completed May 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants
0