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

Códecs

Nuevo — Introducido en [email protected]

Todos los esquemas de Zod pueden procesar entradas tanto en dirección hacia adelante como hacia atrás:

  • Hacia adelante: Input a Output
    • .parse()
    • .decode()
  • Hacia atrás: Output a Input
    • .encode()

En la mayoría de los casos, esta es una distinción sin diferencia. Los tipos de entrada y salida son idénticos, por lo que no hay diferencia entre "hacia adelante" y "hacia atrás".

const schema = z.string();
 
type Input = z.input<typeof schema>;    // string
type Output = z.output<typeof schema>;  // string
 
schema.parse("asdf");   // => "asdf"
schema.decode("asdf");  // => "asdf"
schema.encode("asdf");  // => "asdf"

Sin embargo, algunos tipos de esquemas hacen que los tipos de entrada y salida diverjan, notablemente z.codec(). Los códecs son un tipo especial de esquema que define una transformación bidireccional entre otros dos esquemas.

const stringToDate = z.codec(
  z.iso.datetime(),  // esquema de entrada: cadena de fecha ISO
  z.date(),          // esquema de salida: objeto Date
  {
    decode: (isoString) => new Date(isoString), // cadena ISO → Date
    encode: (date) => date.toISOString(),       // Date → cadena ISO
  }
);

En estos casos, z.decode() y z.encode() se comportan de manera bastante diferente.

stringToDate.decode("2024-01-15T10:30:00.000Z")
// => Date
 
stringToDate.encode(new Date("2024-01-15T10:30:00.000Z"))
// => string

Nota — No hay nada especial sobre las direcciones o la terminología aquí. En lugar de codificar con un códec A -> B, podrías decodificar con un códec B -> A. El uso de los términos "decode" y "encode" es solo una convención.

Esto es particularmente útil cuando se analizan datos en un límite de red. Puedes compartir un solo esquema de Zod entre tu cliente y servidor, y luego usar este único esquema para convertir entre un formato amigable para la red (digamos, JSON) y una representación de JavaScript más rica.

Códecs codificando y decodificando datos a través de un límite de red

Componibilidad

Nota — Puedes usar z.encode() y z.decode() con cualquier esquema. No tiene que ser un ZodCodec.

Los códecs son un esquema como cualquier otro. ¡Puedes anidarlos dentro de objetos, matrices, tuberías (pipes), etc. No hay reglas sobre dónde puedes usarlos!

const payloadSchema = z.object({ 
  startDate: stringToDate 
});
 
payloadSchema.decode({
  startDate: "2024-01-15T10:30:00.000Z"
}); // => { startDate: Date }

Entradas con seguridad de tipos

Aunque .parse() y .decode() se comportan de manera idéntica en tiempo de ejecución, tienen firmas de tipo diferentes. El método .parse() acepta unknown como entrada, y devuelve un valor que coincide con el tipo de salida inferido del esquema. Por el contrario, las funciones z.decode() y z.encode() tienen entradas fuertemente tipadas.

stringToDate.parse(12345); 
// sin quejas de TypeScript (falla en tiempo de ejecución)
 
stringToDate.decode(12345); 
// ❌ Error de TypeScript: El argumento de tipo 'number' no es asignable al parámetro de tipo 'string'.
 
stringToDate.encode(12345); 
// ❌ Error de TypeScript: El argumento de tipo 'number' no es asignable al parámetro de tipo 'Date'.

¿Por qué la diferencia? Codificar y decodificar implican transformación. En muchos casos, las entradas a estos métodos ya están fuertemente tipadas en el código de la aplicación, por lo que z.decode/z.encode aceptan entradas fuertemente tipadas para mostrar errores en tiempo de compilación. Aquí hay un diagrama que demuestra las diferencias entre las firmas de tipo para parse(), decode() y encode().

Diagrama de direccionalidad de códecs que muestra la transformación bidireccional entre esquemas de entrada y salida

Variantes asíncronas y seguras

Al igual que con .transform() y .refine(), los códecs soportan transformaciones asíncronas.

const asyncCodec = z.codec(z.string(), z.number(), {
  decode: async (str) => Number(str),
  encode: async (num) => num.toString(),
});

Al igual que con el parse() regular, existen variantes "seguras" y "asíncronas" de decode() y encode().

stringToDate.decode("2024-01-15T10:30:00.000Z"); 
// => Date
 
stringToDate.decodeAsync("2024-01-15T10:30:00.000Z"); 
// => Promise<Date>
 
stringToDate.safeDecode("2024-01-15T10:30:00.000Z"); 
// => { success: true, data: Date } | { success: false, error: ZodError }
 
stringToDate.safeDecodeAsync("2024-01-15T10:30:00.000Z"); 
// => Promise<{ success: true, data: Date } | { success: false, error: ZodError }>

Cómo funciona la codificación

Hay algunas sutilezas en cómo ciertos esquemas de Zod "invierten" su comportamiento de análisis.

Códecs

Este se explica por sí mismo. Los códecs encapsulan una transformación bidireccional entre dos tipos. z.decode() activa la transformación decode para convertir la entrada en un valor analizado, mientras que z.encode() activa la transformación encode para serializarlo de nuevo.

const stringToDate = z.codec(
  z.iso.datetime(),  // esquema de entrada: cadena de fecha ISO
  z.date(),          // esquema de salida: objeto Date
  {
    decode: (isoString) => new Date(isoString), // cadena ISO → Date
    encode: (date) => date.toISOString(),       // Date → cadena ISO
  }
);
 
stringToDate.decode("2024-01-15T10:30:00.000Z"); 
// => Date
 
stringToDate.encode(new Date("2024-01-15")); 
// => string

Tuberías (Pipes)

Dato curioso — Los códecs se implementan internamente como una subclase de tuberías que han sido aumentadas con lógica de transformación "intersticial".

Durante la decodificación regular, un esquema ZodPipe<A, B> primero analizará los datos con A, luego los pasará a B. Como era de esperar, durante la codificación, los datos se codifican primero con B, luego se pasan a A.

Refinamientos

Todas las comprobaciones (.refine(), .min(), .max(), etc.) se ejecutan en ambas direcciones.

const schema = stringToDate.refine((date) => date.getFullYear() >= 2000, "Must be this millennium");
 
schema.encode(new Date("2000-01-01"));
// => Date
 
schema.encode(new Date("1999-01-01"));
// => ❌ ZodError: [
//   {
//     "code": "custom",
//     "path": [],
//     "message": "Must be this millennium"
//   }
// ]

Para evitar errores inesperados en tu lógica personalizada de .refine(), Zod realiza dos "pasadas" durante z.encode(). La primera pasada asegura que el tipo de entrada se ajuste al tipo esperado (sin errores invalid_type). Si eso pasa, Zod realiza la segunda pasada que ejecuta la lógica de refinamiento.

Este enfoque también soporta "transformaciones mutantes" como z.string().trim() o z.string().toLowerCase():

const schema = z.string().trim();
 
schema.decode("  hello  ");
// => "hello"
 
schema.encode("  hello  ");
// => "hello"

Valores predeterminados y prefaults

Los valores predeterminados (defaults) y prefaults solo se aplican en la dirección "hacia adelante".

const stringWithDefault = z.string().default("hello");
 
stringWithDefault.decode(undefined); 
// => "hello"
 
stringWithDefault.encode(undefined); 
// => ZodError: Expected string, received undefined

Cuando adjuntas un valor predeterminado a un esquema, la entrada se vuelve opcional (| undefined) pero la salida no. Como tal, undefined no es una entrada válida para z.encode() y los defaults/prefaults no se aplicarán.

Catch

De manera similar, .catch() solo se aplica en la dirección "hacia adelante".

const stringWithCatch = z.string().catch("hello");
 
stringWithCatch.decode(1234); 
// => "hello"
 
stringWithCatch.encode(1234); 
// => ZodError: Expected string, received number

Stringbool

NotaStringbool es anterior a la introducción de códecs en Zod. Desde entonces ha sido reimplementado internamente como un códec.

La API z.stringbool() convierte valores de cadena ("true", "false", "yes", "no", etc.) en boolean. Por defecto, convertirá true a "true" y false a "false" durante z.encode().

const stringbool = z.stringbool();
 
stringbool.decode("true");  // => true
stringbool.decode("false"); // => false
 
stringbool.encode(true);    // => "true"
stringbool.encode(false);   // => "false"

Si especificas un conjunto personalizado de valores truthy y falsy, se usará el primer elemento en la matriz en su lugar.

const stringbool = z.stringbool({ truthy: ["yes", "y"], falsy: ["no", "n"] });
 
stringbool.encode(true);    // => "yes"
stringbool.encode(false);   // => "no"

Transformaciones

⚠️ — La API .transform() implementa una transformación unidireccional. Si existe algún .transform() en cualquier lugar de tu esquema, intentar una operación z.encode() arrojará un error en tiempo de ejecución (no un ZodError).

const schema = z.string().transform(val => val.length);
 
schema.encode(1234); 
// ❌ Error: Encountered unidirectional transform during encode: ZodTransform

Códecs útiles

A continuación se presentan implementaciones para un grupo de códecs comúnmente necesarios. Por el bien de la personalización, estos no se incluyen como APIs de primera clase en Zod mismo. En su lugar, debes copiarlos/pegarlos en tu proyecto y modificarlos según sea necesario.

Nota — Todas estas implementaciones de códecs han sido probadas para su corrección.

stringToNumber

Convierte representaciones de cadena de números al tipo number de JavaScript usando parseFloat().

const stringToNumber = z.codec(z.string().regex(z.regexes.number), z.number(), {
  decode: (str) => Number.parseFloat(str),
  encode: (num) => num.toString(),
});
 
stringToNumber.decode("42.5");  // => 42.5
stringToNumber.encode(42.5);    // => "42.5"

stringToInt

Convierte representaciones de cadena de enteros al tipo number de JavaScript usando parseInt().

const stringToInt = z.codec(z.string().regex(z.regexes.integer), z.int(), {
  decode: (str) => Number.parseInt(str, 10),
  encode: (num) => num.toString(),
});
 
stringToInt.decode("42");  // => 42
stringToInt.encode(42);    // => "42"

stringToBigInt

Convierte representaciones de cadena al tipo bigint de JavaScript.

const stringToBigInt = z.codec(z.string(), z.bigint(), {
  decode: (str) => BigInt(str),
  encode: (bigint) => bigint.toString(),
});
 
stringToBigInt.decode("12345");  // => 12345n
stringToBigInt.encode(12345n);   // => "12345"

numberToBigInt

Convierte el tipo number de JavaScript al tipo bigint.

const numberToBigInt = z.codec(z.int(), z.bigint(), {
  decode: (num) => BigInt(num),
  encode: (bigint) => Number(bigint),
});
 
numberToBigInt.decode(42);   // => 42n
numberToBigInt.encode(42n);  // => 42

isoDatetimeToDate

Convierte cadenas de fecha y hora ISO a objetos Date de JavaScript.

const isoDatetimeToDate = z.codec(z.iso.datetime(), z.date(), {
  decode: (isoString) => new Date(isoString),
  encode: (date) => date.toISOString(),
});
 
isoDatetimeToDate.decode("2024-01-15T10:30:00.000Z");  // => Date object
isoDatetimeToDate.encode(new Date("2024-01-15"));       // => "2024-01-15T00:00:00.000Z"

epochSecondsToDate

Convierte marcas de tiempo Unix (segundos desde la época) a objetos Date de JavaScript.

const epochSecondsToDate = z.codec(z.int().min(0), z.date(), {
  decode: (seconds) => new Date(seconds * 1000),
  encode: (date) => Math.floor(date.getTime() / 1000),
});
 
epochSecondsToDate.decode(1705314600);  // => Date object
epochSecondsToDate.encode(new Date());  // => Unix timestamp in seconds

epochMillisToDate

Convierte marcas de tiempo Unix (milisegundos desde la época) a objetos Date de JavaScript.

const epochMillisToDate = z.codec(z.int().min(0), z.date(), {
  decode: (millis) => new Date(millis),
  encode: (date) => date.getTime(),
});
 
epochMillisToDate.decode(1705314600000);  // => Date object
epochMillisToDate.encode(new Date());     // => Unix timestamp in milliseconds

json(schema)

Analiza cadenas JSON en datos estructurados y los serializa de nuevo a JSON. Esta función genérica acepta un esquema de salida para validar los datos JSON analizados.

const jsonCodec = <T extends z.core.$ZodType>(schema: T) =>
  z.codec(z.string(), schema, {
    decode: (jsonString, ctx) => {
      try {
        return JSON.parse(jsonString);
      } catch (err: any) {
        ctx.issues.push({
          code: "invalid_format",
          format: "json",
          input: jsonString,
          message: err.message,
        });
        return z.NEVER;
      }
    },
    encode: (value) => JSON.stringify(value),
  });

Ejemplo de uso con un esquema específico:

const jsonToObject = jsonCodec(z.object({ name: z.string(), age: z.number() }));
 
jsonToObject.decode('{"name":"Alice","age":30}');  
// => { name: "Alice", age: 30 }
 
jsonToObject.encode({ name: "Bob", age: 25 });     
// => '{"name":"Bob","age":25}'
 
jsonToObject.decode('~~invalid~~'); 
// ZodError: [
//   {
//     "code": "invalid_format",
//     "format": "json",
//     "path": [],
//     "message": "Unexpected token '~', \"~~invalid~~\" is not valid JSON"
//   }
// ]

utf8ToBytes

Convierte cadenas UTF-8 a matrices de bytes Uint8Array.

const utf8ToBytes = z.codec(z.string(), z.instanceof(Uint8Array), {
  decode: (str) => new TextEncoder().encode(str),
  encode: (bytes) => new TextDecoder().decode(bytes),
});
 
utf8ToBytes.decode("Hello, 世界!");  // => Uint8Array
utf8ToBytes.encode(bytes);          // => "Hello, 世界!"

bytesToUtf8

Convierte matrices de bytes Uint8Array a cadenas UTF-8.

const bytesToUtf8 = z.codec(z.instanceof(Uint8Array), z.string(), {
  decode: (bytes) => new TextDecoder().decode(bytes),
  encode: (str) => new TextEncoder().encode(str),
});
 
bytesToUtf8.decode(bytes);          // => "Hello, 世界!"
bytesToUtf8.encode("Hello, 世界!");  // => Uint8Array

base64ToBytes

Convierte cadenas base64 a matrices de bytes Uint8Array y viceversa.

const base64ToBytes = z.codec(z.base64(), z.instanceof(Uint8Array), {
  decode: (base64String) => z.util.base64ToUint8Array(base64String),
  encode: (bytes) => z.util.uint8ArrayToBase64(bytes),
});
 
base64ToBytes.decode("SGVsbG8=");  // => Uint8Array([72, 101, 108, 108, 111])
base64ToBytes.encode(bytes);       // => "SGVsbG8="

base64urlToBytes

Convierte cadenas base64url (base64 segura para URL) a matrices de bytes Uint8Array.

const base64urlToBytes = z.codec(z.base64url(), z.instanceof(Uint8Array), {
  decode: (base64urlString) => z.util.base64urlToUint8Array(base64urlString),
  encode: (bytes) => z.util.uint8ArrayToBase64url(bytes),
});
 
base64urlToBytes.decode("SGVsbG8");  // => Uint8Array([72, 101, 108, 108, 111])
base64urlToBytes.encode(bytes);      // => "SGVsbG8"

hexToBytes

Convierte cadenas hexadecimales a matrices de bytes Uint8Array y viceversa.

const hexToBytes = z.codec(z.hex(), z.instanceof(Uint8Array), {
  decode: (hexString) => z.util.hexToUint8Array(hexString),
  encode: (bytes) => z.util.uint8ArrayToHex(bytes),
});
 
hexToBytes.decode("48656c6c6f");     // => Uint8Array([72, 101, 108, 108, 111])
hexToBytes.encode(bytes);            // => "48656c6c6f"

stringToURL

Convierte cadenas URL a objetos URL de JavaScript.

const stringToURL = z.codec(z.url(), z.instanceof(URL), {
  decode: (urlString) => new URL(urlString),
  encode: (url) => url.href,
});
 
stringToURL.decode("https://example.com/path");  // => URL object
stringToURL.encode(new URL("https://example.com"));  // => "https://example.com/"

stringToHttpURL

Convierte cadenas URL HTTP/HTTPS a objetos URL de JavaScript.

const stringToHttpURL = z.codec(z.httpUrl(), z.instanceof(URL), {
  decode: (urlString) => new URL(urlString),
  encode: (url) => url.href,
});
 
stringToHttpURL.decode("https://api.example.com/v1");  // => URL object
stringToHttpURL.encode(url);                           // => "https://api.example.com/v1"

uriComponent

Codifica y decodifica componentes URI usando encodeURIComponent() y decodeURIComponent().

const uriComponent = z.codec(z.string(), z.string(), {
  decode: (encodedString) => decodeURIComponent(encodedString),
  encode: (decodedString) => encodeURIComponent(decodedString),
});
 
uriComponent.decode("Hello%20World%21");  // => "Hello World!"
uriComponent.encode("Hello World!");      // => "Hello%20World!"