Zod logo

编解码器

✨ 新功能 - 在 zod@4.1 中引入

¥✨ New — Introduced in zod@4.1

所有 Zod 模式都可以处理正向和反向的输入:

¥All Zod schemas can process inputs in both the forward and backward direction:

  • 前进:InputOutput

    ¥Forward: Input to Output

    • .parse()

    • .decode()

  • 后退:OutputInput

    ¥Backward: Output to Input

    • .encode()

在大多数情况下,这是一个没有区别的区别。输入和输出类型相同,因此 "forward" 和 "backward" 之间没有区别。

¥In most cases, this is a distinction without a difference. The input and output types are identical, so there's no difference between "forward" and "backward".

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"

但是,某些模式类型会导致输入和输出类型出现分歧,尤其是 z.codec()。编解码器是一种特殊的模式,用于定义两个其他模式之间的双向转换。

¥However, some schema types cause the input and output types to diverge, notably z.codec(). Codecs are a special type of schema that defines a bi-directional transformation between two other schemas.

const stringToDate = z.codec(
  z.iso.datetime(),  // input schema: ISO date string
  z.date(),          // output schema: Date object
  {
    decode: (isoString) => new Date(isoString), // ISO string → Date
    encode: (date) => date.toISOString(),       // Date → ISO string
  }
);

在这种情况下,z.decode()z.encode() 的行为截然不同。

¥In these cases, z.decode() and z.encode() behave quite differently.

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

注意 — 这里的说明或术语没有什么特别之处。与其使用 A -> B 编解码器进行编码,不如使用 B -> A 编解码器进行解码。使用 "decode" 和 "encode" 术语只是一种约定。

¥—There's nothing special about the directions or terminology here. Instead of encoding with an A -> B codec, you could instead decode with a B -> A codec. The use of the terms "decode" and "encode" is just a convention.

这在解析网络边界数据时尤其有用。你可以在客户端和服务器之间共享单个 Zod 模式,然后使用这个模式在网络友好格式(例如 JSON)和更丰富的 JavaScript 表示之间进行转换。

¥This is particularly useful when parsing data at a network boundary. You can share a single Zod schema between your client and server, then use this single schema to convert between a network-friendly format (say, JSON) and a richer JavaScript representation.

Codecs encoding and decoding data across a network boundary

可组合性

¥Composability

注意 — 你可以将 z.encode()z.decode() 与任何架构一起使用。它不一定是 ZodCodec。

¥— You can use z.encode() and z.decode() with any schema. It doesn't have to be a ZodCodec.

编解码器与其他模式一样。你可以将它们嵌套在对象、数组、管道等中。对于它们的使用位置没有任何限制!

¥Codecs are a schema like any other. You can nest them inside objects, arrays, pipes, etc. There are no rules on where you can use them!

类型安全输入

¥Type-safe inputs

虽然 .parse().decode() 在运行时的行为相同,但它们具有不同的类型签名。.parse() 方法接受 unknown 作为输入,并返回与架构推断的输出类型匹配的值。相比之下,z.decode()z.encode() 函数具有强类型输入。

¥While .parse() and .decode() behave identically at runtime, they have different type signatures. The .parse() method accepts unknown as input, and returns a value that matches the schema's inferred output type. By constrast, the z.decode() and z.encode() functions have strongly-typed inputs.

stringToDate.parse(12345); 
// no complaints from TypeScript (fails at runtime)
 
stringToDate.decode(12345); 
// ❌ TypeScript error: Argument of type 'number' is not assignable to parameter of type 'string'.
 
stringToDate.encode(12345); 
// ❌ TypeScript error: Argument of type 'number' is not assignable to parameter of type 'Date'.

为什么会有差异?编码和解码意味着转换。在许多情况下,这些方法的输入……这里有一张图表,展示了 parse()decode()encode() 类型签名之间的区别。

¥Why the difference? Encoding and decoding imply transformation. In many cases, the inputs to these methods Here's a diagram demonstrating the differences between the type signatures for parse(), decode(), and encode().

Codec directionality diagram showing bidirectional transformation between input and output schemas

异步和安全变体

¥Async and safe variants

.transform().refine() 一样,编解码器支持异步转换。

¥As with .transform() and .refine(), codecs support async transforms.

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

与常规 parse() 一样,decode()encode() 也存在 "safe" 和 "async" 变体。

¥As with regular parse(), there are "safe" and "async" variants of decode() and encode().

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

编码的工作原理

¥How encoding works

某些 Zod 模式如何 "reverse" 其解析行为存在一些微妙之处。

¥There are some subtleties to how certain Zod schemas "reverse" their parse behavior.

编解码器

¥Codecs

这个相当不言自明。编解码器封装了两种类型之间的双向转换。在 z.decode() 期间,执行 decode 转换。在 z.encode() 期间,执行 encode 转换。

¥This one is fairly self-explanatory. Codecs encapsulate a bi-directional transformation between two types. During z.decode(), the decode transform is executed. During z.encode(), the encode transform is executed.

const stringToDate = z.codec(
  z.iso.datetime(),  // input schema: ISO date string
  z.date(),          // output schema: Date object
  {
    decode: (isoString) => new Date(isoString), // ISO string → Date
    encode: (date) => date.toISOString(),       // Date → ISO string
  }
);
 
stringToDate.decode("2024-01-15T10:30:00.000Z"); 
// => Date
 
stringToDate.encode(new Date("2024-01-15")); 
// => string

管道

¥Pipes

有趣的是 - 编解码器实际上是在内部实现的,是管道的子类,并添加了 "interstitial" 转换逻辑。

¥Fun fact — Codecs are actually implemented internally as subclass of pipes that have been augmented with "interstitial" transform logic.

在常规解码期间,ZodPipe<A, B> 模式将首先使用 A 解析数据,然后将其传递给 B。正如你所料,在编码过程中,数据首先使用 B 进行编码,然后传入 A

¥During regular decoding, a ZodPipe<A, B> schema will first parse the data with A, then pass it into B. As you might expect, during encoding, the data is first encoded with B, then passed into A.

细化

¥Refinements

所有检查(.refine().min().max() 等)仍然在两个方向上执行。

¥All checks (.refine(), .min(), .max(), etc.) are still executed in both directions.

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

为了避免自定义 .refine() 逻辑中出现意外错误,Zod 在 z.encode() 期间执行两次 "passes"。第一遍检查确保输入类型符合预期类型(无 invalid_type 错误)。如果通过了,Zod 将执行第二遍,即执行细化逻辑。

¥To avoid unexpected errors in your custom .refine() logic, Zod performs two "passes" during z.encode(). The first pass ensures the input type conforms to the expected type (no invalid_type errors). If that passes, Zod performs the second pass which executes the refinement logic.

这种方法也像 z.string().trim()z.string().toLowerCase() 一样支持 "修改变换":

¥This approach also supports "mutating transforms" like z.string().trim() or z.string().toLowerCase():

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

默认值和预置错误

¥Defaults and prefaults

默认值和预置错误仅适用于 "forward" 方向。

¥Defaults and prefaults are only applied in the "forward" direction.

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

当你将默认值附加到模式时,输入将变为可选(| undefined),但输出将变为可选。因此,undefined 不是 z.encode() 的有效输入,并且不会应用默认值/预置错误。

¥When you attach a default value to a schema, the input becomes optional (| undefined) but the output does not. As such, undefined is not a valid input to z.encode() and defaults/prefaults will not be applied.

捕获

¥Catch

同样,.catch() 仅适用于 "forward" 方向。

¥Similarly, .catch() is only applied in the "forward" direction.

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

Stringbool

注意Stringbool 早于 Zod 中编解码器的引入。它后来在内部被重新实现为编解码器。

¥— Stringbool pre-dates the introduction of codecs in Zod. It has since been internally re-implemented as a codec.

z.stringbool() API 将字符串值("true""false""yes""no" 等)转换为 boolean。默认情况下,在 z.encode() 期间,它会将 true 转换为 "true",将 false 转换为 "false"

¥The z.stringbool() API converts string values ("true", "false", "yes", "no", etc.) into boolean. By default, it will convert true to "true" and false to "false" during z.encode()..

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

如果你指定了一组自定义的 truthyfalsy 值,则将使用数组中的第一个元素。

¥If you specify a custom set of truthy and falsy values, the first element in the array will be used instead.

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

转换

¥Transforms

⚠️ — .transform() API 实现了单向转换。如果你的架构中存在任何 .transform(),尝试执行 z.encode() 操作将引发运行时错误(而不是 ZodError)。

¥⚠️ — The .transform() API implements a unidirectional transformation. If any .transform() exists anywhere in your schema, attempting a z.encode() operation will throw a runtime error (not a ZodError).

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

有用的编解码器

¥Useful codecs

以下是一些常用编解码器的实现。为了便于定制,这些 API 并未包含在 Zod 本身中。你应该将它们复制/粘贴到你的项目中,并根据需要进行修改。

¥Below are implementations for a bunch of commonly-needed codecs. For the sake of customizability, these are not included as first-class APIs in Zod itself. Instead, you should copy/paste them into your project and modify them as needed.

注意 — 所有这些编解码器实现都已测试正确性。

¥— All of these codec implementations have been tested for correctness.

stringToNumber

使用 parseFloat() 将数字的字符串表示形式转换为 JavaScript number 类型。

¥Converts string representations of numbers to JavaScript number type using 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

使用 parseInt() 将整数的字符串表示形式转换为 JavaScript number 类型。

¥Converts string representations of integers to JavaScript number type using 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

将字符串表示形式转换为 JavaScript bigint 类型。

¥Converts string representations to JavaScript bigint type.

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

将 JavaScript number 转换为 bigint 类型。

¥Converts JavaScript number to bigint type.

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

将 ISO 日期时间字符串转换为 JavaScript Date 对象。

¥Converts ISO datetime strings to JavaScript Date objects.

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

将 Unix 时间戳(自纪元以来的秒数)转换为 JavaScript Date 对象。

¥Converts Unix timestamps (seconds since epoch) to JavaScript Date objects.

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

将 Unix 时间戳(自纪元以来的毫秒数)转换为 JavaScript Date 对象。

¥Converts Unix timestamps (milliseconds since epoch) to JavaScript Date objects.

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)

将 JSON 字符串解析为结构化数据,并序列化回 JSON。这个通用函数接受一个输出模式来验证解析后的 JSON 数据。

¥Parses JSON strings into structured data and serializes back to JSON. This generic function accepts an output schema to validate the parsed JSON data.

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),
  });

特定模式的使用示例:

¥Usage example with a specific schema:

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

将 UTF-8 字符串转换为 Uint8Array 字节数组。

¥Converts UTF-8 strings to Uint8Array byte arrays.

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

Uint8Array 字节数组转换为 UTF-8 字符串。

¥Converts Uint8Array byte arrays to UTF-8 strings.

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

将 base64 字符串转换为 Uint8Array 字节数组,反之亦然。

¥Converts base64 strings to Uint8Array byte arrays and vice versa.

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

将 base64url 字符串(URL 安全的 base64)转换为 Uint8Array 字节数组。

¥Converts base64url strings (URL-safe base64) to Uint8Array byte arrays.

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

将十六进制字符串转换为 Uint8Array 字节数组,反之亦然。

¥Converts hexadecimal strings to Uint8Array byte arrays and vice versa.

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

将 URL 字符串转换为 JavaScript URL 对象。

¥Converts URL strings to JavaScript URL objects.

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

将 HTTP/HTTPS URL 字符串转换为 JavaScript URL 对象。

¥Converts HTTP/HTTPS URL strings to JavaScript URL objects.

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

使用 encodeURIComponent()decodeURIComponent() 对 URI 组件进行编码和解码。

¥Encodes and decodes URI components using encodeURIComponent() and 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!"