Saltar al contenido

Convertir JSON Schema a Zod — Numio

Genera código de validación Zod a partir de un JSON Schema con json-schema-to-zod. Restricciones como minLength, minimum, pattern y default pasan a métodos de Zod encadenados, y las propiedades que no figuran en "required" reciben .optional().

Recibes un JSON Schema de otro equipo y quieres validación en tiempo de ejecución en un servicio TypeScript sin un validador de JSON Schema.

JSON Schema

Zod Schema

Cuándo usarla

  • Recibes un JSON Schema de otro equipo y quieres validación en tiempo de ejecución en un servicio TypeScript sin un validador de JSON Schema.
  • Estás migrando la validación de formularios de un validador de JSON Schema como Ajv a Zod y quieres código generado como punto de partida.

Cómo usarla

  1. Pega un JSON Schema en el panel JSON Schema.
  2. Si quieres, abre la configuración y cambia Root Schema Name (por defecto "schema"); pasa a ser el nombre de la const exportada.
  3. Copia el código generado, que importa z de "zod", en tu proyecto. La conversión se ejecuta en el navegador.

JSON Schema

{
  "type": "object",
  "properties": {
    "username": { "type": "string", "minLength": 3, "maxLength": 20 },
    "age": { "type": "integer", "minimum": 18 },
    "contact": {
      "oneOf": [
        { "type": "string", "format": "email" },
        { "type": "string", "pattern": "^\\+[0-9]+$" }
      ]
    },
    "newsletter": { "type": "boolean", "default": false }
  },
  "required": ["username", "contact"]
}

Esquema Zod

import { z } from "zod"

export const schema = z.object({
  username: z.string().min(3).max(20),
  age: z.number().int().gte(18).optional(),
  contact: z.any().superRefine((x, ctx) => {
    const schemas = [
      z.string().email(),
      z.string().regex(new RegExp("^\\+[0-9]+$")),
    ]
    const errors = schemas.reduce(
      (errors: z.ZodError[], schema) =>
        ((result) => ("error" in result ? [...errors, result.error] : errors))(
          schema.safeParse(x),
        ),
      [],
    )
    if (schemas.length - errors.length !== 1) {
      ctx.addIssue({
        path: ctx.path,
        code: "invalid_union",
        unionErrors: errors,
        message: "Invalid input: Should pass single schema",
      })
…

Detalles y limitaciones

oneOf se convierte en z.any().superRefine(...), que comprueba que pase exactamente un subesquema, como muestra el ejemplo.

$ref no se resuelve: una propiedad referenciada pasa a z.any().

integer añade .int(), format "email" añade .email() y format "date-time" añade .datetime().

additionalProperties: false pasa a .catchall(z.never()) en lugar de .strict().

FAQ

¿Cómo se tratan los tipos anulables?

"type": ["string", "null"] pasa a z.union([z.string(), z.null()]), con .optional() añadido si la propiedad no es requerida.

¿Puedo renombrar el esquema exportado?

Sí. Define Root Schema Name en la configuración; la salida dirá entonces export const <name> = ... en lugar de export const schema.

¿Se admiten definitions y $ref?

No. Una propiedad que usa $ref pasa a z.any(), así que inserta el esquema referenciado en línea antes de convertir.

Especificaciones y referencias