Skip to content

General Guidelines for Zod Schema Engineering

When using Zod for schema definition and validation, adhere to principles ensuring clarity, modularity, and flexibility, similar to Pydantic.

  • Modularity: Construct self-contained schemas for reuse.
  • Self-Description: Describe fields using Zod's .describe() for clarity.
  • Optionality: Utilize z.union with z.undefined() for optional fields.
  • Standardization: Use z.enum for fields with a specific set of values, including a 'Other' option for ambiguity.
  • Dynamic Data: Apply z.record(z.string()) for arbitrary properties, with controlled key-value pairs.
  • Entity Relationships: Define relationships through explicit identifiers and relationship fields.
  • Contextual Logic: Add an optional 'chain of thought' field for context.

Modular Chain of Thought

Leverage Zod's flexibility for modular 'chain of thought', enhancing data quality.

import { z } from 'zod';

const Role = z.object({
  chainOfThought: z.string().describe("Sequential reasoning to determine the correct title"),
  title: z.string(),
});

const UserDetail = z.object({
  age: z.number(),
  name: z.string(),
  role: Role,
});

Utilizing Optional Attributes

For optional fields, use z.union with z.undefined().

const UserDetail = z.object({
  age: z.number(),
  name: z.string(),
  role: z.string().optional(),
});

Error Handling Within Schemas

Create a wrapper schema for handling both successful and error states.

const MaybeUser = z.object({
  result: UserDetail.optional(),
  error: z.boolean(),
  message: z.string().optional(),
});

// `MaybeUser` can now encapsulate both a result and an error state.

Simplification with Dynamic Patterns

Utilize Zod's dynamic schema creation for streamlining error handling.

const Maybe = (schema) => z.object({
  result: schema.optional(),
  error: z.boolean(),
  message: z.string().optional(),
});

const MaybeUser = Maybe(UserDetail);

Tips for Enumerations

Implement z.enum for standardized fields, including an 'Other' option.

const Role = z.enum(["PRINCIPAL", "TEACHER", "STUDENT", "OTHER"]);

const UserDetail = z.object({
  age: z.number(),
  name: z.string(),
  role: Role,
});

Reiterate Long Instructions

For complex attributes, restate instructions in the field's description.

const Role = z.object({
  instructions: z.string().describe("Repeat the rules for determining the title."),
  title: z.string(),
});

Handling Arbitrary Properties

Use z.record(z.string()) for undefined attributes.

const UserDetail = z.object({
  age: z.number(),
  name: z.string(),
  properties: z.record(z.string()).describe("Arbitrary key-value pairs"),
});

Limiting List Lengths

Control list lengths through Zod's array validations.

const Property = z.object({
  key: z.string(),
  value: z.string(),
});

const UserDetail = z.object({
  age: z.number(),
  name: z.string(),
  properties: z.array(Property).max(6).describe("Manageable set of properties"),
});

Defining Entity Relationships

Explicitly define relationships in your schemas, like user friends' IDs.

const UserDetail = z.object({
  id: z.number(),
  age: z.number(),
  name: z.string(),
  friends: z.array(z.number()).describe("List of friend IDs, representing user relationships"),
});

Reusing Components in Different Contexts

Reuse components in various contexts by defining them separately.

const TimeRange = z.object({
  startTime: z.number().describe("Start time in hours."),
  endTime: z.number().describe("End time in hours."),
});

const UserDetail = z.object({
  id: z.number(),
  age: z.number(),
  name: z.string(),
  workTime: TimeRange,
  leisureTime: TimeRange,
});

These guidelines should streamline and enhance your Zod schema creation and validation processes.