Zod logo

编解码器

— 在 zod@4.1 中引入

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

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

  • 转发InputOutput
    • .parse()
    • .decode()
  • 向后OutputInput
    • .encode()

在大多数情况下,这是一个没有区别的区分。输入和输出类型是相同的,所以“前向”和“后向”之间没有区别。

🌐 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.

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

注意 —这里的指示或术语没有什么特别之处。你可以不用 A -> B 编解码器来编码,而是使用 B -> A 编解码器来解码。使用“解码”和“编码”这些术语只是一个约定而已。

当在网络边界解析数据时,这尤其有用。你可以在客户端和服务器之间共享一个 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

注意 — 你可以在任何 schema 中使用 z.encode()z.decode()。它不必是 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!

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

类型安全输入

🌐 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 contrast, 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'.

为什么会有差异?编码和解码意味着转换。在许多情况下,这些方法的输入在应用代码中已经是强类型的,因此 z.decode/z.encode 接受强类型输入以在编译时显示错误。 下面是一个图表,展示了 parse()decode()encode() 的类型签名之间的差异。

🌐 Why the difference? Encoding and decoding imply transformation. In many cases, the inputs to these methods are already strongly typed in application code, so z.decode/z.encode accept strongly typed inputs to surface mistakes at compile time. 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() 也有“安全”和“异步”变体。

🌐 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.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 }>

编码的工作原理

🌐 How encoding works

某些 Zod 模式如何“反转”它们的解析行为存在一些微妙之处。

🌐 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. z.decode() triggers the decode transform to convert input into a parsed value, while z.encode() triggers the encode transform to serialize it back.

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

有趣的事实 — 编解码器实际上在内部是作为管道的子类实现的,这些管道经过增强,加入了“中间”转换逻辑。

在常规解码过程中,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 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"
//   }
// ]

为了避免在自定义 .refine() 逻辑中出现意外错误,Zod 在 z.encode() 期间会执行两次“遍历”。第一次遍历确保输入类型符合预期类型(没有 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

默认值和预取页仅在“前向”方向上应用。

🌐 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() 仅在“正向”方向应用。

🌐 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 引入编解码器之前。此后,它已在内部被重新实现为一个编解码器。

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)。

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

有用的编解码器

🌐 Useful codecs

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

🌐 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.

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

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!"