javascripttypescriptzod

zod "optional()" but disallow undefined value


I have a following zod example:

const objectSchema = z.object({
  optional: z.string().optional(),
  other: z.string(),
});
// the current default behavior: 
  objectSchema({ other: "other" }) //pass.
  objectSchema({ optional: undefined, other: "other" }) // pass
  objectSchema({ optional: "str", other: "other" }) // pass

In typescript, optional field key?: string will be converted to key?: string | undefined. But it's strictly different during the runtime check. The previous one during the runtime check could be interpreted as the optional must be string if it exists, hence optional: undefined is an invalid input.

So, is there any way I can do with zod that makes the undefined an invalid input.

hence

  objectSchema({ other: "other" }) //pass.
  objectSchema({ optional: undefined, other: "other" }) // fail
  objectSchema({ optional: "str", other: "other" }) // pass

That's my current attempt, which is fine if there is only one optional field, but it starts to become extremely messy and unusable (z.object().or().or().or()), basically (the number of optional field)^2 permutation.

const objectSchema = z.object({
  optional: z.string(),
  other: z.string(),
}).strict().or(z.object({
  other: z.string(),
}).strict());

Solution

  • Based the answer from the zod discord community/github collaborator.
    zod is strictly following the current typescript behavior.
    Typescript cannot distinguish the runtime difference between

    // key is either missing or key exists with type string, cannot be `undefined`
    type object {
     key?: string;
    }
    

    and (in fact the first one will be converted to the second one)

    // key may or may not exist with the type string or undefined.
    type object {
     key?: string | undefined;
    }
    

    Hence, if you want to strictly enforce the first behavior in runtime, you need to explicitly remove undefined key by yourself. There is no such a shortcut from zod.

     if (key in object && typeof obj.key === "undefined") { 
       delete obj.key 
     }