Module 1 - Lesson 3f: Structured Output

Validated JSON output using Zod.

Published: 1/3/2026

Example 6: Structured Output with Zod

Structured output ensures the AI returns data in a specific, validated format. We use Zod schemas to define and validate the structure.

What It Does

Uses Zod to define a schema for the response, ensuring it's always in the correct format. Perfect for APIs, databases, or when you need consistent data structures.

Code Snippet

Create src/structured-output-prompt.ts:

import OpenAI from "openai";
import dotenv from "dotenv";
import { zodTextFormat } from "openai/helpers/zod";
import { z } from "zod";

dotenv.config();

// Define the expected output structure using Zod
const ChristmasMarkets = z.object({
  country: z.string().describe("Country where the market is located"),
  cityMarkets: z
    .array(
      z.object({
        city: z.string().describe("City where the market is located"),
        markets: z
          .array(
            z.object({
              name: z.string().describe("Name of the Christmas market"),
              description: z
                .string()
                .describe("Description of the Christmas market"),
              address: z.string().describe("Address of the Christmas market"),
            })
          )
          .describe("List of Christmas markets in the city"),
      })
    )
    .describe("List of cities with their Christmas markets"),
});

const openai = new OpenAI();

async function structuredOutputPrompt(country: string): Promise<void> {
  try {
    console.log("Testing OpenAI connection...");

    // Use parse() instead of create() for structured output
    const response = await openai.responses.parse({
      model: "gpt-5-nano",
      input: [
        {
          role: "system",
          content:
            "You are a helpful travel assistant. Extract the relevant Christmas market information based on user input for a country and information in a structured JSON format.",
        },
        {
          role: "user",
          content: `provide a list of famous Christmas markets in ${country} with a brief description and address for each market.`,
        },
      ],
      // Use zodTextFormat to enforce the schema
      text: { format: zodTextFormat(ChristmasMarkets, "markets") },
    });

    // Get the parsed, validated output
    const markets = response.output_parsed;

    console.log("✅ Structured Output Success!");
    console.log("AI Response (validated JSON):");
    console.log(JSON.stringify(markets, null, 2));
    console.log("Tokens used:");
    console.dir(response.usage, { depth: 10 });
  } catch (error) {
    if (error instanceof OpenAI.APIError) {
      console.log("❌ API Error:", error.status, error.message);
    } else if (error instanceof Error) {
      console.log("❌ Error:", error.message);
    }
  }
}

// Run with country argument (defaults to "Austria")
const country = process.argv[2] ?? "Austria";
structuredOutputPrompt(country).catch(console.error);

Run It

# Default (Austria)
pnpm tsx src/structured-output-prompt.ts

# Custom country
pnpm tsx src/structured-output-prompt.ts "Germany"

Key Points

  • Zod schemas: Define expected structure
  • Type safety: TypeScript knows the exact shape
  • Validation: Ensures response matches schema
  • Use case: APIs, databases, data processing

Zod Schema Tips

// Simple object
z.object({
  name: z.string(),
  age: z.number(),
});

// With descriptions (helps AI understand)
z.object({
  name: z.string().describe("Person's full name"),
  age: z.number().describe("Age in years"),
});

// Arrays
z.array(z.string()); // Array of strings

// Nested objects
z.object({
  address: z.object({
    street: z.string(),
    city: z.string(),
  }),
});