💎 Zod 4 is now stable!  Read the announcement.
Zod logo

JSON Schema

💎

Nuevo — Zod 4 introduce una nueva característica: conversión nativa a JSON Schema. JSON Schema es un estándar para describir la estructura de JSON (con JSON). Es ampliamente utilizado en definiciones OpenAPI y para definir salidas estructuradas para IA.

Para convertir un esquema de Zod a JSON Schema, usa la función z.toJSONSchema().

import * as z from "zod";
 
const schema = z.object({
  name: z.string(),
  age: z.number(),
});
 
z.toJSONSchema(schema)
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, age: { type: 'number' } },
//   required: [ 'name', 'age' ],
//   additionalProperties: false,
// }

Todos los esquemas y comprobaciones se convierten a su equivalente más cercano en JSON Schema. Algunos tipos no tienen análogo y no pueden representarse razonablemente. Consulta la sección unrepresentable a continuación para obtener más información sobre cómo manejar estos casos.

z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌

Formatos de cadena

Zod convierte los siguientes tipos de esquema al format equivalente de JSON Schema:

// Soportado vía `format`
z.email(); // => { type: "string", format: "email" }
z.iso.datetime(); // => { type: "string", format: "date-time" }
z.iso.date(); // => { type: "string", format: "date" }
z.iso.time(); // => { type: "string", format: "time" }
z.iso.duration(); // => { type: "string", format: "duration" }
z.ipv4(); // => { type: "string", format: "ipv4" }
z.ipv6(); // => { type: "string", format: "ipv6" }
z.uuid(); // => { type: "string", format: "uuid" }
z.guid(); // => { type: "string", format: "uuid" }
z.url(); // => { type: "string", format: "uri" }

Estos esquemas son soportados vía contentEncoding:

z.base64(); // => { type: "string", contentEncoding: "base64" }

Todos los otros formatos de cadena son soportados vía pattern:

z.base64url();
z.cuid();
z.emoji();
z.nanoid();
z.cuid2();
z.ulid();
z.cidrv4();
z.cidrv6();
z.mac();

Tipos numéricos

Zod convierte los siguientes tipos numéricos a JSON Schema:

// number
z.number(); // => { type: "number" }
z.float32(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
z.float64(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
 
// integer
z.int(); // => { type: "integer" }
z.int32(); // => { type: "integer", exclusiveMinimum: ..., exclusiveMaximum: ... }

Esquemas de objetos

Por defecto, los esquemas z.object() contienen additionalProperties: "false". Esta es una representación precisa del comportamiento predeterminado de Zod, ya que el esquema z.object() simple elimina las propiedades adicionales.

import * as z from "zod";
 
const schema = z.object({
  name: z.string(),
  age: z.number(),
});
 
z.toJSONSchema(schema)
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, age: { type: 'number' } },
//   required: [ 'name', 'age' ],
//   additionalProperties: false,
// }

Al convertir a JSON Schema en modo "input", additionalProperties no se establece. Consulta la documentación de io para más información.

import * as z from "zod";
 
const schema = z.object({
  name: z.string(),
  age: z.number(),
});
 
z.toJSONSchema(schema, { io: "input" });
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, age: { type: 'number' } },
//   required: [ 'name', 'age' ],
// }

Por el contrario:

  • z.looseObject() nunca establecerá additionalProperties: false
  • z.strictObject() siempre establecerá additionalProperties: false

Esquemas de archivos

Zod convierte z.file() al siguiente esquema compatible con OpenAPI:

z.file();
// => { type: "string", format: "binary", contentEncoding: "binary" }

Las comprobaciones de tamaño y MIME también se representan:

z.file().min(1).max(1024 * 1024).mime("image/png");
// => {
//   type: "string",
//   format: "binary",
//   contentEncoding: "binary",
//   contentMediaType: "image/png",
//   minLength: 1,
//   maxLength: 1048576,
// }

Nulabilidad

Zod convierte z.null() a { type: "null" } en JSON Schema.

z.null();
// => { type: "null" }

Ten en cuenta que z.undefined() no se puede representar en JSON Schema (ver abajo).

De manera similar, nullable se representa mediante una unión con null:

z.nullable(z.string());
// => { oneOf: [{ type: "string" }, { type: "null" }] }

Los esquemas opcionales se representan tal cual, aunque están decorados con una anotación optional.

z.optional(z.string());
// => { type: "string" }

Configuración

Se puede utilizar un segundo argumento para personalizar la lógica de conversión.

z.toJSONSchema(schema, {
  // ...params
})

A continuación se muestra una referencia rápida para cada parámetro admitido. Cada uno se explica con más detalle a continuación.

interface ToJSONSchemaParams {
  /** La versión de JSON Schema a la que apuntar.
   * - `"draft-2020-12"` — Predeterminado. JSON Schema Draft 2020-12
   * - `"draft-7"` — JSON Schema Draft 7
   * - `"draft-4"` — JSON Schema Draft 4
   * - `"openapi-3.0"` — Objeto de esquema OpenAPI 3.0 */
  target?: "draft-4" | "draft-7" | "draft-2020-12" | "openapi-3.0";
 
  /** Un registro utilizado para buscar metadatos para cada esquema. 
   * Cualquier esquema con una propiedad `id` se extraerá como un $def. */
  metadata?: $ZodRegistry<Record<string, any>>;
 
  /** Cómo manejar tipos irrepresentables.
   * - `"throw"` — Predeterminado. Los tipos irrepresentables lanzan un error
   * - `"any"` — Los tipos irrepresentables se convierten en `{}` */
  unrepresentable?: "throw" | "any";
 
  /** Cómo manejar ciclos.
   * - `"ref"` — Predeterminado. Los ciclos se romperán usando $defs
   * - `"throw"` — Los ciclos lanzarán un error si se encuentran */
  cycles?: "ref" | "throw";
 
  /* Cómo manejar esquemas reutilizados.
   * - `"inline"` — Predeterminado. Los esquemas reutilizados se insertarán en línea
   * - `"ref"` — Los esquemas reutilizados se extraerán como $defs */
  reused?: "ref" | "inline";
 
  /** Una función utilizada para convertir valores de `id` a URIs para usar en $refs *externas*.
   *
   * El valor predeterminado es `(id) => id`.
   */
  uri?: (id: string) => string;
}

target

Para establecer la versión de destino de JSON Schema, usa el parámetro target. Por defecto, Zod apuntará a Draft 2020-12.

z.toJSONSchema(schema, { target: "draft-7" });
z.toJSONSchema(schema, { target: "draft-2020-12" });
z.toJSONSchema(schema, { target: "draft-4" });
z.toJSONSchema(schema, { target: "openapi-3.0" });

metadata

Si aún no lo has hecho, lee la página de Metadatos y registros para obtener contexto sobre el almacenamiento de metadatos en Zod.

En Zod, los metadatos se almacenan en registros. Zod exporta un registro global z.globalRegistry que se puede usar para almacenar campos de metadatos comunes como id, title, description y examples.

import * as z from "zod";
 
// `.meta()` es un método de conveniencia para registrar un esquema en `z.globalRegistry`
const emailSchema = z.string().meta({ 
  title: "Email address",
  description: "Your email address",
});
 
z.toJSONSchema(emailSchema);
// => { type: "string", title: "Email address", description: "Your email address", ... } 

Todos los campos de metadatos se copian en el JSON Schema resultante.

const schema = z.string().meta({
  whatever: 1234
});
 
z.toJSONSchema(schema);
// => { type: "string", whatever: 1234 }

unrepresentable

Las siguientes API no son representables en JSON Schema. Por defecto, Zod lanzará un error si se encuentran. No es sólido intentar una conversión a JSON Schema; debes modificar tus esquemas ya que no tienen equivalente en JSON. Se lanzará un error si se encuentra alguno de estos.

z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌

Por defecto, Zod lanzará un error si se encuentra alguno de estos.

z.toJSONSchema(z.bigint());
// => throws Error

Puedes cambiar este comportamiento estableciendo la opción unrepresentable en "any". Esto convertirá cualquier tipo irrepresentable en {} (el equivalente a unknown en JSON Schema).

z.toJSONSchema(z.bigint(), { unrepresentable: "any" });
// => {}

cycles

Cómo manejar ciclos. Si se encuentra un ciclo mientras z.toJSONSchema() recorre el esquema, se representará usando $ref.

const User = z.object({
  name: z.string(),
  get friend() {
    return User;
  },
});
 
z.toJSONSchema(User);
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, friend: { '$ref': '#' } },
//   required: [ 'name', 'friend' ],
//   additionalProperties: false,
// }

Si prefieres lanzar un error, establece la opción cycles en "throw".

z.toJSONSchema(User, { cycles: "throw" });
// => throws Error

reused

Cómo manejar esquemas que ocurren múltiples veces en el mismo esquema. Por defecto, Zod insertará estos esquemas en línea.

const name = z.string();
const User = z.object({
  firstName: name,
  lastName: name,
});
 
z.toJSONSchema(User);
// => {
//   type: 'object',
//   properties: { 
//     firstName: { type: 'string' }, 
//     lastName: { type: 'string' } 
//   },
//   required: [ 'firstName', 'lastName' ],
//   additionalProperties: false,
// }

En su lugar, puedes establecer la opción reused en "ref" para extraer estos esquemas en $defs.

z.toJSONSchema(User, { reused: "ref" });
// => {
//   type: 'object',
//   properties: {
//     firstName: { '$ref': '#/$defs/__schema0' },
//     lastName: { '$ref': '#/$defs/__schema0' }
//   },
//   required: [ 'firstName', 'lastName' ],
//   additionalProperties: false,
//   '$defs': { __schema0: { type: 'string' } }
// }

override

Para definir alguna lógica de anulación personalizada, usa override. La devolución de llamada proporcionada tiene acceso al esquema Zod original y al JSON Schema predeterminado. Esta función debe modificar directamente ctx.jsonSchema.

const mySchema = /* ... */
z.toJSONSchema(mySchema, {
  override: (ctx)=>{
    ctx.zodSchema; // el esquema Zod original
    ctx.jsonSchema; // el JSON Schema predeterminado
 
    // modificar directamente
    ctx.jsonSchema.whatever = "sup";
  }
});

Ten en cuenta que los tipos irrepresentables lanzarán un Error antes de que se llame a esta función. Si estás tratando de definir un comportamiento personalizado para un tipo irrepresentable, deberás usar la configuración unrepresentable: "any" junto con override.

// soportar z.date() como cadenas de fecha y hora ISO
const result = z.toJSONSchema(z.date(), {
  unrepresentable: "any",
  override: (ctx) => {
    const def = ctx.zodSchema._zod.def;
    if(def.type ==="date"){
      ctx.jsonSchema.type = "string";
      ctx.jsonSchema.format = "date-time";
    }
  },
});

io

Algunos tipos de esquema tienen diferentes tipos de entrada y salida, por ejemplo, ZodPipe, ZodDefault y primitivos coercionados. Por defecto, el resultado de z.toJSONSchema representa el tipo de salida; usa "io": "input" para extraer el tipo de entrada en su lugar.

const mySchema = z.string().transform(val => val.length).pipe(z.number());
// ZodPipe
 
const jsonSchema = z.toJSONSchema(mySchema); 
// => { type: "number" }
 
const jsonSchema = z.toJSONSchema(mySchema, { io: "input" }); 
// => { type: "string" }

Registros (Registries)

Pasar un esquema a z.toJSONSchema() devolverá un JSON Schema autocontenido.

En otros casos, es posible que tengas un conjunto de esquemas de Zod que te gustaría representar utilizando múltiples JSON Schemas interconectados, tal vez para escribir en archivos .json y servirlos desde un servidor web.

import * as z from "zod";
 
const User = z.object({
  name: z.string(),
  get posts(){
    return z.array(Post);
  }
});
 
const Post = z.object({
  title: z.string(),
  content: z.string(),
  get author(){
    return User;
  }
});
 
z.globalRegistry.add(User, {id: "User"});
z.globalRegistry.add(Post, {id: "Post"});

Para lograr esto, puedes pasar un registro a z.toJSONSchema().

Importante — ¡Todos los esquemas deben tener una propiedad id registrada en el registro! Cualquier esquema sin un id será ignorado.

z.toJSONSchema(z.globalRegistry);
// => {
//   schemas: {
//     User: {
//       id: 'User',
//       type: 'object',
//       properties: {
//         name: { type: 'string' },
//         posts: { type: 'array', items: { '$ref': 'Post' } }
//       },
//       required: [ 'name', 'posts' ],
//       additionalProperties: false,
//     },
//     Post: {
//       id: 'Post',
//       type: 'object',
//       properties: {
//         title: { type: 'string' },
//         content: { type: 'string' },
//         author: { '$ref': 'User' }
//       },
//       required: [ 'title', 'content', 'author' ],
//       additionalProperties: false,
//     }
//   }
// }

Por defecto, las URIs de $ref son rutas relativas simples como "User". Para convertirlas en URIs absolutas, usa la opción uri. Esto espera una función que convierta un id en una URI totalmente calificada.

z.toJSONSchema(z.globalRegistry, {
  uri: (id) => `https://example.com/${id}.json`
});
// => {
//   schemas: {
//     User: {
//       id: 'User',
//       type: 'object',
//       properties: {
//         name: { type: 'string' },
//         posts: {
//           type: 'array',
//           items: { '$ref': 'https://example.com/Post.json' }
//         }
//       },
//       required: [ 'name', 'posts' ],
//       additionalProperties: false,
//     },
//     Post: {
//       id: 'Post',
//       type: 'object',
//       properties: {
//         title: { type: 'string' },
//         content: { type: 'string' },
//         author: { '$ref': 'https://example.com/User.json' }
//       },
//       required: [ 'title', 'content', 'author' ],
//       additionalProperties: false,
//     }
//   }
// }

On this page