JSON a Zod
JSON a Zod genera un esquema Zod ejecutable desde un documento de ejemplo en tu navegador. A diferencia de una interfaz TypeScript, la salida es código que se ejecuta: parsea un valor en tiempo de ejecución y rechaza lo que la muestra no contenía. Pega varios registros para que los campos opcionales y nullable se infieran y no se adivinen.
El documento termina antes de tiempo; parece incompleto.
^
¿Cómo funciona JSON a Zod?
Una interfaz de TypeScript y un esquema Zod se parecen y hacen trabajos completamente distintos. La interfaz se borra cuando el código compila: le dice a tu editor cómo debería ser la forma y no le dice nada al programa en ejecución. Un esquema Zod es un valor que sobrevive hasta el tiempo de ejecución, así que puede parsear una respuesta real y rechazarla. Esa es la diferencia entre esperar que la API cumpliera su promesa y comprobar que la cumplió.
La misma inferencia, otra representación
Esta herramienta comparte su inferencia de tipos con los cinco generadores de lenguajes que tiene al lado — un solo recorrido de la muestra decide qué campos son opcionales, cuáles nullable, qué formas de objeto son idénticas y cuál es el tipo de elemento de un array mixto. Solo cambia la representación. Eso importa porque esas decisiones son la parte difícil, y una segunda implementación acabaría discrepando de la primera.
Opcional y nullable son dos cosas distintas
{"a": null} y {} son documentos distintos. Zod también los escribe de forma distinta — .nullable() para el primero y .optional() para el segundo — y ninguno implica al otro. Una clave que a veces falta y a veces es null necesita ambos, en ese orden: z.string().optional().nullable(). Equivocarse en esto es la razón habitual por la que un validador generado pasa tus fixtures y falla en la segunda semana de producción.
Las comprobaciones de formato son una afirmación sobre todos los valores
La inferencia se da cuenta cuando una cadena parece un UUID, un correo o una fecha-hora y puede emitir z.uuid(), z.email() o z.iso.datetime() en lugar de un z.string() a secas. Es genuinamente útil y ligeramente peligroso: es una afirmación sobre todos los valores que el campo guardará jamás, inferida de una muestra. Si una columna es un UUID en tu fixture de pruebas y un slug en producción, el validador rechazará datos reales. Es un interruptor por esa razón, y la tabla de referencia de abajo indica a qué validador corresponde cada formato detectado.
Zod 3 y Zod 4 escriben los formatos de forma distinta
Zod 4 movió los validadores de formato de cadena al nivel superior: z.uuid(), z.email(), z.url() y z.iso.datetime(), en sustitución de la familia obsoleta z.string().uuid(). Ambas formas siguen funcionando, así que el objetivo es una elección y no una suposición, y el valor por defecto es Zod 4 porque un esquema generado hoy va a una base de código que empieza hoy.
Muestra de JSON
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"email": "[email protected]",
"score": 148,
"deleted_at": null,
"tags": ["priority", "gift"]
}Esquema Zod
export const RootSchema = z.object({
id: z.uuid(),
email: z.email(),
score: z.number().int(),
deleted_at: z.unknown(),
tags: z.array(z.string()),
});
export type Root = z.infer<typeof RootSchema>;Muestra de JSON → Esquema Zod
Muestra de JSON
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"email": "[email protected]",
"score": 148,
"deleted_at": null,
"tags": ["priority", "gift"]
}Esquema Zod
import { z } from "zod";
export const RootSchema = z.object({
id: z.uuid(),
email: z.email(),
score: z.number().int(),
deleted_at: z.unknown(),
tags: z.array(z.string()),
});
export type Root = z.infer<typeof RootSchema>;
¿Qué opciones y casos límite admite JSON a Zod?
| Parámetro | Tipo | Valor predeterminado | Comportamiento y casos límite |
|---|---|---|---|
| Versión de Zod | Zod 3 | Zod 4 | Zod 4 | Zod 4 usa z.uuid(), z.email(), z.url() y z.iso.datetime() de nivel superior; Zod 3 usa la familia z.string().uuid(), que Zod 4 todavía acepta pero marca como obsoleta. Todo lo demás de la salida es idéntico. |
| objetos estrictos | booleano | desactivado | Añade .strict(), de modo que una clave que el esquema no conoce es un error en lugar de descartarse en silencio. Merece activarlo para el cuerpo de una petición que estés validando; normalmente es un error para una respuesta de terceros que añade campos sin avisarte. |
| tipos z.infer | booleano | activado | Emite export type X = z.infer<typeof XSchema> junto a cada esquema, de modo que el tipo de TypeScript y el validador en tiempo de ejecución no pueden divergir. Es el único punto en el que Zod es estrictamente mejor que una interfaz escrita a mano. |
| comprobaciones de formato | booleano | activado | Aplica z.uuid(), z.email(), z.url(), z.iso.datetime() y z.iso.date() donde la muestra parecía corresponder. Una inferencia con una sola muestra es una afirmación fuerte — desactívalo cuando una columna no sea homogénea en producción. |
| Opcionalidad | inferido | entre registros | Una clave ausente en al menos un registro de la misma forma recibe .optional(). Pega varios registros; un solo registro no puede expresarlo, y un esquema construido con uno solo rechazará el segundo. |
| Anulable | inferido | .nullable() | Un valor que es null en algunos registros y una cadena en otros se convierte en z.string().nullable(). Una clave que a veces falta y a veces es null recibe .optional().nullable(), que son dos requisitos distintos y no una redundancia. |
| Entero | inferido | z.number().int() | Un número JSON sin parte fraccional se convierte en un esquema entero, así que un float que llega donde la muestra tenía un número entero se rechaza en lugar de aceptarse en silencio. Un campo que es 1 en un registro y 1.5 en otro se convierte en un z.number() normal. |
| Array raíz | forma del documento | z.array(RootSchema) | Un array de registros en el nivel superior emite el esquema del elemento más un envoltorio RootSchema, de modo que el esquema del propio documento es una línea y no algo que tengas que montar tú. |
| Tamaño de entrada | bytes | 2 MB | La inferencia lee todos los valores. Unos cientos de registros representativos describen una forma tan bien como un millón, así que el techo no cuesta nada en la práctica. |
Preguntas frecuentes
¿Por qué generar un esquema Zod en lugar de una interfaz de TypeScript?
Porque un tipo de TypeScript se borra al compilar. Le dice a tu editor cómo debería ser la forma y no le dice nada al programa en ejecución, así que una respuesta que la viola pasa sin más. Un esquema Zod es un valor real que corre en tiempo de ejecución: safeParse devuelve o los datos tipados o las rutas exactas que fallaron. Si la API es tuya y está bien probada, una interfaz basta. Si es de otro, o es entrada de usuario, un validador es lo único que de verdad comprueba.
¿Por qué mi campo tiene .optional() si la API siempre lo envía?
Porque al menos un registro de la muestra no lo tenía. El generador solo puede describir los datos que le diste. Pega una muestra donde el campo aparezca siempre para que salga requerido, o borra el .optional() a mano — lo correcto cuando conoces el contrato mejor que la muestra. Ten en cuenta que este es el fallo contrario al de .nullable(): una clave ausente y un valor null son cosas distintas, y el esquema las distingue.
¿Es .nullable() lo mismo que .optional()?
No, y confundirlos es el error más común en los esquemas generados. {a: null} tiene la clave con un valor null; {} no tiene la clave en absoluto. .nullable() acepta el primero, .optional() acepta el segundo, y un campo que puede ser ambas cosas necesita las dos — z.string().optional().nullable(). El orden importa por legibilidad y no por comportamiento, pero la documentación de Zod escribe optional primero.
¿Por qué una cadena normal se convirtió en z.uuid() o z.email()?
Porque todos los valores que la inferencia vio en esa posición encajaban con el patrón, así que trató el formato como parte del tipo. Suele ser correcto y a veces no: una columna que guarda UUIDs en tu fixture y slugs en producción fallará ahora la validación con datos reales. Desactiva las comprobaciones de formato para obtener un z.string() a secas en todas partes, o edita esa línea. La tabla de referencia indica exactamente qué formato corresponde a qué validador.
¿A qué versión de Zod apuntar?
A Zod 4, salvo que estés atado a Zod 3. Zod 4 movió los validadores de formato al nivel superior — z.uuid() en lugar de z.string().uuid() — y marcó como obsoletas las formas antiguas, así que un esquema generado para Zod 4 no tiene avisos de obsolescencia en una base de código nueva. Las llamadas antiguas siguen funcionando en Zod 4, por eso un esquema de Zod 3 pegado en un proyecto de Zod 4 no está roto, solo desfasado.
¿Se sube mi JSON a algún sitio?
No. La inferencia y la representación corren en la pestaña que ya tienes abierta — abre DevTools, mira el panel «Network» y pega un documento. Esto importa más aquí que en la mayoría de las herramientas, porque los documentos que la gente convierte en validadores son respuestas de API de producción, frecuentemente con registros reales de clientes dentro.
¿Qué herramientas relacionadas debería usar después?
- Convertir JSON a TypeScriptGenera interfaces a partir de un ejemplo, con campos opcionales
- Generar esquema JSONInfiere un esquema Draft 2020-12 desde ejemplos y lo valida
- Validar JSONValida la sintaxis con línea y columna exactas y repara los errores
- Analizar JSONAnaliza un documento y mira qué contiene en realidad