colinhacks/zod

`z.coerce.string()` casts `undefined` to "undefined" string

andrew-sol opened this issue · 7 comments

Here are the rules:

{
  title: z.coerce.string(),
}

As you can see, the field "title" is required. But in case you don't pass it at all, coerce fills it with "undefined" and the validation passes. And later this "undefined" gets passed into the rest of the app's logic.

This is the expected behaviour, as corece under the hood runs String(input).

It's recommended to initialize your form with '' or write your own transform logic, e.g.

title: z.any()
  .transform(val => val == null ? '' : val.toString())
  .pipe(z.string().min(1))

Or you can implement your own preprocessor

title: z.preprocess(val => val == null ? '' : val.toString(), z.string().min(1))

I believe this is a bug. It's not obvious that this transformation may disable the required rule.

coerce.string() should cast null and undefined to an empty string.

Well, it's definitely not a bug. As you can see in the doc, coerce is just syntactic sugar to transform with Primitive Constructors.
https://zod.dev/?id=coercion-for-primitives

z.coerce.string(); // String(input)

So of course undefined will be evaluated into 'undefined', it's just how JavaScript works

Then it can be improved. Usage of coerce in its current form makes no sense unless you wanna shoot yourself in the leg (I use it on backend).

It's also possible to solve this issue from the other side - make the required rule run before the coercion, but it will be much harder to achieve.

Related: #2461

Well, I'd suggest using yup then if you want to work with form validation smoothly. zod is designed to mirror TypeScript at runtime, hence a lot of behaviours are weird when it comes to validating forms, where yup is designed for validating forms.

E.g. z.string().min(1) vs yup.string().required() is something you have to do if you want to reject empty string using zod. It doesn't make sense to me either, but you are forced to do so.

It's kind of a trade-off. If you choose zod, then you get the maximum type-safety but you somehow need to workaround a lot of uncomfortable scenarios when validating forms, but with yup you can almost write any kind of form validation smoothly, but the types are not guaranteed to be correct or even missing sometimes.