zod
zod copied to clipboard
Built-in type coercion support
Would you be open to a PR, providing built-in implementation for processors, performing coercion to most common types? (numbers, string, booleans, arrays)?
This is how type coercion looks like right now:
const preprocessor = (value) => {
switch (typeof value) {
case 'bigint':
case 'boolean':
case 'number':
return value.toString();
case 'object':
if (value == null) {
return value;
}
if (value instanceof Date) {
return value.toISOString();
}
return value; // could not coerce, return the original and face the consequences during validation
case 'string':
return value.trim();
case 'undefined':
return value;
default:
return value; // could not coerce, return the original and face the consequences during validation
}
}
const CREATE_USER_SCHEMA = z.object({
age: z.preprocess(preprocessor, z.string().max(15))
})
const result = CREATE_USER_SCHEMA.parse({
age: 44
});
expect(result).toEqual({
age: '44'
})
This is pretty verbose and requires plenty of boilerplate every time.
This is a proposed API:
const CREATE_USER_SCHEMA = z.object({
age: z.string().max(3).coerce()
})
const result = CREATE_USER_SCHEMA.parse({
age: 44
});
expect(result).toEqual({
age: '44'
})
Resolution of a specific preprocessor would be based on ZodType typeName
, and throw an error if no preprocessor is implemented for a given type.
@colinhacks Could you please comment? We'd like to start working on it in the nearby future, and it would be great to know if such PR would be accepted.
I'll look into this in the near future. This decision is intertwined with the idea of some sort of "pipeline" feature:
z.string().transform(val => val.length).pipe(z.number()).min(5);
So hold off on the PR until I do some experiments on the feasibility of that.
This feature would be useful, and is implemented in some of other validation libraries.
For example, by default, joi.validate coerce values before validating :
https://joi.dev/api/?v=17.6.0
Superstruct also handles coercion by default : https://docs.superstructjs.org/api-reference/coercions Same for yup : it coerces values except if you use strict option : https://github.com/jquense/yup#schemastrictenabled-boolean--false-schema
Actually, when I have used zod the first time, I was surprised to run into an error when trying to parse a string like "12" as a number.
This would be very useful when you want to validate FormData (as in Remix), because in FormData, everything is a string.
Currently, we have to coerce manually the numbers, or use a wrapper on zod to coerce values like https://github.com/airjp73/remix-validated-form/tree/main/packages/zod-form-data but it's not ideal, because it adds a layer of complexity, and generators like zod-prisma don't take in account these kind of libraries.
Additional note :
I note this approach has been considered in https://github.com/colinhacks/zod/issues/100 :

but eventually it seems that only the "postprocessing" part has been kept.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
up
I think this would be very valuable addition from my perspective as my primary interest in zod is as an engine for api description (for example as used in tRPC, zodios). That kind of usage straddles the domain of typing and expressive communication and "cleanup" is a natural requirement of that. I don't love the idea of calling it coercion though, since the features in peer libs like joi 'cleanup' far more than just the primitive type of the input, the correct term would probably be sanitization.
Either way to me it seems like a natural fit with what zod does already, as many of the validation checks expressible with zod schema are also relatively standard fare sanitizations, like case fixing, empty string removal, stripping whitespace, etc.
I see that prerelease https://github.com/colinhacks/zod/releases/tag/v3.20.0 includes some coerce capabilities, but it seems restricted to primitives, yet it's would be very useful for arrays also
yet it's would be very useful for arrays also
Do you have a code example that would show what you mean?
Here's one example - depending on the setup of PHP, it can be unclear for JSON stringifyer whether an empty object should be an object or array. So Instead of receiving a {}
(e.g. for z.record(z.string())
, you may receive an empty array []
resulting in a validation error.
FWIW I coined a library that has a robust coercion mechanism, if it might help you or anyone else trying to set some default values on a complex object/schema. https://github.com/morgs32/zod-utils#zutilsincomplete
const result = zutils.incomplete({
schema: z.object({
string: z.string(),
nested1: z.object({
nested2: z.object({
string: z.string(),
array: z.array(z.string()),
enum: z.enum(['a', 'b', 'c']),
}),
}),
}),
defaultValue: null,
value: null,
});
expect(result).toMatchInlineSnapshot(`
{
"nested1": {
"nested2": {
"array": [],
"enum": null,
"string": null,
},
},
"string": null,
}
`);