zod icon indicating copy to clipboard operation
zod copied to clipboard

Generic type function converting property with a default value to optional

Open jashiels99 opened this issue 1 year ago • 2 comments

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

Screenshot 2024-06-17 at 15 39 38

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.

jashiels99 avatar Jun 17 '24 14:06 jashiels99

+1

BenjaminLindberg avatar Jul 19 '24 23:07 BenjaminLindberg

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!

dosubot[bot] avatar Jun 17 '25 16:06 dosubot[bot]

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  Join Discord Share on X

dosubot[bot] avatar Jul 21 '25 23:07 dosubot[bot]