- Published on
Define a Zod Schema with Non-Optional but Possibly Undefined Fields
- Authors
- Name
- Ripal & Zalak
Define a Zod Schema with Non-Optional but Possibly Undefined Fields
Problem
In TypeScript, there's a subtle difference between the following interfaces:
interface IFoo1 {
somefield: string | undefined
}
interface IFoo2 {
somefield?: string | undefined
}
Here, IFoo1
ensures that the somefield
property is always present, but its value can explicitly be undefined
. On the other hand, IFoo2
makes somefield
optional, which means it may not exist at all.
Zod Schema for Non-Optional Undefined Field
To create a Zod schema resembling IFoo1
, you can use z.string().optional()
combined with a transformation to ensure the field is always present but may hold undefined
. Here's how:
import { z } from 'zod'
const schema = z
.object({
somefield: z.string().optional(),
})
.transform((obj) => ({ ...obj, somefield: obj.somefield }))
// Type inference
type IFoo1 = z.infer<typeof schema>
// Usage example
const validObject = schema.parse({ somefield: undefined }) // Passes validation
const invalidObject = schema.parse({}) // Fails validation
Common Use Case: Configuration Objects
A practical scenario for this pattern is enforcing strictness in configuration objects:
interface IConfig {
name: string
emailPreference: boolean | undefined
}
const configSchema = z
.object({
name: z.string(),
emailPreference: z.boolean().optional(),
})
.transform((obj) => ({ ...obj, emailPreference: obj.emailPreference }))
type Config = z.infer<typeof configSchema>
This ensures developers explicitly consider the emailPreference
field, preventing accidental omission.
FAQs
null
instead of undefined
?
1. Why not use While null
can represent an intentional "no value," undefined
often signifies the absence of initialization. Choosing between them depends on your application's conventions.
2. Can this approach be reused for multiple schemas?
Yes, you can encapsulate this logic in a utility function for reusability:
const nonOptional = <T extends z.ZodTypeAny>(schema: T) =>
schema.optional().transform((value) => value)
const schema = z.object({
somefield: nonOptional(z.string()),
})
.transform
?
3. What happens if I don't use Without .transform
, Zod treats the field as optional, meaning it may not exist in the parsed object, which doesn't align with IFoo1
behavior.