Zod object schema with nullable fields

2.5k Views Asked by At

I want to define a Zod schema where all the properties are nullable. Currently I'm defining it as below, and repeating the nullable() for every property. Is there a better way of doing this without repetition? PS. For those who suggest using .Partial: it does NOT make object fields nullable.

const MyObjectSchema = z
  .object({
    p1: z.string().nullable(),
    p2: z.number().nullable(),
    p3: z.boolean().nullable(),
    p4: z.date().nullable(),
    p5: z.symbol().nullable(),
  })
  .partial();
2

There are 2 best solutions below

1
On

You can make use of the partial method or deepPartial method on the object.

const MyObjectSchema = z.object({
 p1: z.string(),
 p2: z.string(),
 p3: z.string(),
 p4: z.string(),
 p5: z.string(),
}).partial();

In the case above, you only need the partial method because you're not dealing with nested structures.

If you were dealing with a nested object, using partial or deepPartial depends on you, as seen below.

/*
    DeepPartialObjectSchema will allow the location field to be optional.
    If an object is provided for the location field,
    the houseNo and country field will also be optional.
    You can provide the location object and still omit or not include any of the fields in the location object.
*/

const DeepPartialObjectSchema = z.object({
 name: z.string(),
 age: z.string(),
 location: z.object({
    houseNo: z.string(),
    country: z.string()
  })
}).deepPartial();


/*
    PartialObjectSchema will also allow the location field to be optional.
    However, if an object is provided for the location field,
    then the object must include the houseNo and country field.
    Either you provide all the required fields in the location object
    or don't provide the location object at all.
*/

const PartialObjectSchema = z.object({
 name: z.string(),
 age: z.string(),
 location: z.object({
    houseNo: z.string(),
    country: z.string()
  })
}).partial();

For both the partial and deepPartial method, the location field will be optional. But using the partial method or deepPartial method depends on whether or not you want the houseNo and country fields to be required when the location field is provided.

Note: It is also possible to make only selected fields optional

// only p1 and p2 will be made optional.
// p3, p4, p5 will remain required.
const MyObjectSchema = z.object({
 p1: z.string(),
 p2: z.string(),
 p3: z.string(),
 p4: z.string(),
 p5: z.string(),
}).partial({ p1: true, p2: true });
0
On

you can use this function below. it makes all of the fields of the zod object nullable and also preserves the field types.

function nullable<TSchema extends z.AnyZodObject>(schema: TSchema) {
    const entries = Object.entries(schema.shape) as [keyof TSchema['shape'], z.ZodTypeAny][];

    const newProps = entries.reduce((acc, [key, value]) => {
        acc[key] = value.nullable();
        return acc;
    }, {} as {
        [key in keyof TSchema['shape']]: z.ZodNullable<TSchema['shape'][key]>
    });

    return z.object(newProps)
}

usage:

nullable(
  z.object({
    field1: z.string(),
    field2: z.number()
  }),
)