Generic type function converting property with a default value to optional
I have written a helper function that allows me to validate FormData objects using Zod. Since this can be used for various schemas, I have used a generic type.
The problem I'm having is, when using the helper function it seems to be turning the properties with default values set in the schemas to optional:
export const createUserSchema = z.object({
name: fullName(),
email: email(),
sendEmail: z.boolean().default(true)
});
Here is the code for the validate helper function:
export function validate<T>(
formData: FormData,
schema: z.ZodSchema<T>
): { validatedData: T; errors: null } | { validatedData: null; errors: ActionErrors<T> } {
const body = parseFormData(formData);
const validated = schema.safeParse(body);
if (!validated.success) {
return {
validatedData: null,
errors: formatZodErrors<T>(validated.error),
};
}
return { validatedData: validated.data, errors: null };
}
The fact this is being returned with an optional property just doesn't make any sense to me. Any explanation or help would be much appreciated 😊.
Worth noting that if I don't run this through the helper function and parse it directly in the action, it isn't returning as optional which you would expect.
+1
Hi, @jashiels99. I'm Dosu, and I'm helping the Zod team manage their backlog. I'm marking this issue as stale.
Issue Summary:
- You reported an issue with a TypeScript helper function using Zod to validate
FormData. - Properties with default values are being treated as optional, which is not the case when parsing directly in the action.
- @BenjaminLindberg has shown support for this issue by commenting "+1".
Next Steps:
- Please let me know if this issue is still relevant to the latest version of the Zod repository. If so, you can keep the discussion open by commenting here.
- Otherwise, this issue will be automatically closed in 7 days.
Thank you for your understanding and contribution!
Hi @jashiels99! I'm Dosu and I’m helping the zod team.
This happens because .default() in Zod makes a property optional at the input type level (so TypeScript sees it as sendEmail?: boolean | undefined before parsing), but after parsing, the output type is always required (so it should be sendEmail: boolean in the result) [docs].
Your helper function is generic over T and uses z.ZodSchema<T>, so TypeScript infers T as the input type, not the output type. That's why you see the property as optional in your result.
To fix this, type your validated data as z.output<typeof schema> instead of a generic T. For example:
export function validate<S extends z.ZodTypeAny>(
formData: FormData,
schema: S
): { validatedData: z.output<S>; errors: null } | { validatedData: null; errors: ActionErrors<z.output<S>> } {
const body = parseFormData(formData);
const validated = schema.safeParse(body);
if (!validated.success) {
return {
validatedData: null,
errors: formatZodErrors<z.output<S>>(validated.error),
};
}
return { validatedData: validated.data, errors: null };
}
This ensures that after parsing, properties with defaults are always present in the output type, matching what you see when parsing directly [test].
Let me know if this solves it or if you need a more specific example! If this answers your question, feel free to close the issue.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other