Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

z.coerce.string() casts undefined to "undefined" string #2520

Closed
andrew-sol opened this issue Jun 20, 2023 · 7 comments
Closed

z.coerce.string() casts undefined to "undefined" string #2520

andrew-sol opened this issue Jun 20, 2023 · 7 comments
Labels
closeable? This might be ready for closing

Comments

@andrew-sol
Copy link

andrew-sol commented Jun 20, 2023

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.

@xsjcTony
Copy link

xsjcTony commented Jun 21, 2023

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))

@andrew-sol
Copy link
Author

andrew-sol commented Jun 21, 2023

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.

@xsjcTony
Copy link

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

@andrew-sol
Copy link
Author

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

@xsjcTony
Copy link

xsjcTony commented Jun 21, 2023

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.

@nlindley
Copy link

nlindley commented Jul 6, 2023

In my opinion, this is surprising behavior, but it also interacts in surprising ways with optional() and when used to parse object properties.

const foo = undefined;

z.coerce.string().parse(foo);
// 'undefined'

z.coerce.string().optional().parse(foo);
// undefined

z.object({
  foo: z.coerce.string(),
}).parse({});
// { foo: 'undefined' }

z.object({
  foo: z.coerce.string().optional(),
}).parse({});
// {}

z.object({
  foo: z.coerce.string(),
}).parse({ foo });
// { foo: 'undefined' }

z.object({
  foo: z.coerce.string().optional(),
}).parse({ foo });
// { foo: undefined }

@JacobWeisenburger
Copy link
Contributor

Is this issue resolved? I'd like to close it if it is.

@JacobWeisenburger JacobWeisenburger added the closeable? This might be ready for closing label Sep 22, 2023
Repository owner locked and limited conversation to collaborators Sep 25, 2023
@JacobWeisenburger JacobWeisenburger converted this issue into discussion #2804 Sep 25, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
closeable? This might be ready for closing
Projects
None yet
Development

No branches or pull requests

4 participants