Zod is great. Concise and simple and TypeScript oriented.
I recently ran into a scenario with an object with an optional property with a default. Thinking this schema would just work, I wrote up the schema and the tests and then suddenly it all fell a part.
For example, suppose you have a User with a name
and a role
. Then, name
is required while role
will default to guest
if nothing else is supplied.
Here’s an zod schema:
const UserSchema = z.object({
name: z.string(),
role: z.string().default("guest"),
});
Here’s a small test, that fails:
const testUser: z.infer<typeof UserSchema> = {
name: "name",
};
// Error
// Property 'role' is missing in type '{ name: string; }' but required in type '{ name: string; role: string; }'.
In theory, with .default
having been set on role
, not providing role
in the object should be OK. And yet we have an error here.
I thought this error was because .optional
was not defined in the schema:
const UserSchema = z.object({
name: z.string(),
- role: z.string().default("guest"),
+ role: z.string().default("guest").optional(),
});
This seemed to make progress, getting rid of the type error, however the output was wrong:
{"name":"name"}
Actually, I used the wrong input type. In fact, I did not use the input type, I used the output type. There’s a few hints in the docs, but it wasn’t very obvious to me either. The original schema was fine, but the test was wrong. Here’s how it should look:
- const testUser: z.infer<typeof UserSchema> = {
+ const testUser: z.input<typeof UserSchema> = {
name: "name",
};
With z.input
, you may safely omit the role
property from the object.
Finally, the resulting shape is what we wanted all along:
{"name":"name","role":"guest"}
Follow me on Mastodon @ryanmr@mastodon.cloud.
Follow me on Twitter @ryanmr.