💎 Zod 4 现已稳定发布! 阅读公告。
Zod logo

JSON Schema

Edit this page
💎

新功能 — Zod 4 引入了一个新特性:原生的 JSON Schema 转换。JSON Schema 是一种用于描述 JSON 结构的标准(使用 JSON 本身)。它广泛应用于 OpenAPI 定义和为 AI 定义结构化输出

z.fromJSONSchema()

实验性z.fromJSONSchema() 函数是实验性 API,不属于 Zod 稳定接口。未来版本中可能会有实现上的变化。

Zod 提供了 z.fromJSONSchema() 用于将 JSON Schema 转换为 Zod schema。

import * as z from "zod";
 
const jsonSchema = {
  type: "object",
  properties: {
    name: { type: "string" },
    age: { type: "number" },
  },
  required: ["name", "age"],
};
 
const zodSchema = z.fromJSONSchema(jsonSchema);

z.toJSONSchema()

要将 Zod schema 转换为 JSON Schema,请使用 z.toJSONSchema() 函数。

import * as z from "zod";
 
const schema = z.object({
  name: z.string(),
  age: z.number(),
});
 
z.toJSONSchema(schema)
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, age: { type: 'number' } },
//   required: [ 'name', 'age' ],
//   additionalProperties: false,
// }

所有的 schema 和校验都会被转换为其最接近的 JSON Schema 等价形式。有些类型没有对应的表示,无法合理地表达。有关处理这类情况的更多信息,请参见下面的 unrepresentable 部分。

z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌

配置

第二个参数可用于自定义转换逻辑。

z.toJSONSchema(schema, {
  // ...参数
})

下面是每个支持参数的快速参考。每项参数在下文有更详细的说明。

interface ToJSONSchemaParams {
  /** 目标 JSON Schema 版本。
   * - `"draft-2020-12"` — 默认。JSON Schema Draft 2020-12
   * - `"draft-7"` — JSON Schema Draft 7
   * - `"draft-4"` — JSON Schema Draft 4
   * - `"openapi-3.0"` — OpenAPI 3.0 Schema Object */
  target?:
    | "draft-04"
    | "draft-4"
    | "draft-07"
    | "draft-7"
    | "draft-2020-12"
    | "openapi-3.0"
    | ({} & string)
    | undefined;
 
  /** 用于查找每个 schema 的元数据的注册表。
   * 任何包含 `id` 属性的 schema 都将被提取为 $def。 */
  metadata?: $ZodRegistry<Record<string, any>>;
 
  /** 如何处理不可表示的类型。
   * - `"throw"` — 默认。遇到不可表示类型时抛出错误
   * - `"any"` — 不可表示类型转换为 `{}` */
  unrepresentable?: "throw" | "any";
 
  /** 如何处理循环。
   * - `"ref"` — 默认。循环使用 $defs 断开引用
   * - `"throw"` — 遇到循环时抛出错误 */
  cycles?: "ref" | "throw";
 
  /* 如何处理复用的 schemas。
   * - `"inline"` — 默认。复用的 schemas 会被内联
   * - `"ref"` — 复用 schemas 会被提取成 $defs */
  reused?: "ref" | "inline";
 
  /** 用于将 `id` 值转换为用于 *外部* $ref 的 URI 的函数。
   *
   * 默认为 `(id) => id`。
   */
  uri?: (id: string) => string;
}

io

某些 schema 类型的输入和输出类型不同,例如 ZodPipeZodDefault 和强制类型转换的基本类型。默认情况下,z.toJSONSchema 生成的是 输出类型;使用 "io": "input" 来生成输入类型。

const mySchema = z.string().transform(val => val.length).pipe(z.number());
// ZodPipe
 
const jsonSchema = z.toJSONSchema(mySchema); 
// => { type: "number" }
 
const jsonSchemaInput = z.toJSONSchema(mySchema, { io: "input" }); 
// => { type: "string" }

target

要设置目标 JSON Schema 版本,使用 target 参数。默认情况下,Zod 目标版本为 Draft 2020-12。

z.toJSONSchema(schema, { target: "draft-07" });
z.toJSONSchema(schema, { target: "draft-2020-12" });
z.toJSONSchema(schema, { target: "draft-04" });
z.toJSONSchema(schema, { target: "openapi-3.0" });

metadata

如果还没有,请先阅读 元数据和注册表 页面,了解在 Zod 中存储元数据的背景。

在 Zod 中,元数据存储在注册表中。Zod 导出一个全局注册表 z.globalRegistry,可用于存储常用的元数据字段,如 idtitledescriptionexamples

import * as z from "zod";
 
// `.meta()` 是在 `z.globalRegistry` 中注册 schema 的便捷方法
const emailSchema = z.string().meta({ 
  title: "邮箱地址",
  description: "您的邮箱地址",
});
 
z.toJSONSchema(emailSchema);
// => { type: "string", title: "邮箱地址", description: "您的邮箱地址", ... } 

所有的元数据字段都会被复制到生成的 JSON Schema 中。

const schema = z.string().meta({
  whatever: 1234
});
 
z.toJSONSchema(schema);
// => { type: "string", whatever: 1234 }

unrepresentable

以下 API 在 JSON Schema 中无法表示。默认情况下,Zod 遇到它们会抛出错误。尝试将它们转换为 JSON Schema 是不合理的;你应该修改你的 schemas,因为它们在 JSON 中没有等价物。遇到任一类型都会抛出错误。

z.bigint(); // ❌
z.int64(); // ❌
z.symbol(); // ❌
z.undefined(); // ❌
z.void(); // ❌
z.date(); // ❌
z.map(); // ❌
z.set(); // ❌
z.transform(); // ❌
z.nan(); // ❌
z.custom(); // ❌

默认情况下,Zod 遇到它们会抛出错误。

z.toJSONSchema(z.bigint());
// => 抛出错误

你可以通过设置 unrepresentable 选项为 "any" 来改变此行为。这会将所有不可表示的类型转换为 {}(JSON Schema 中等价于 unknown)。

z.toJSONSchema(z.bigint(), { unrepresentable: "any" });
// => {}

cycles

如何处理循环引用。当 z.toJSONSchema() 遍历 schema 发现循环时,会使用 $ref 来表示。

const User = z.object({
  name: z.string(),
  get friend() {
    return User;
  },
});
 
z.toJSONSchema(User);
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, friend: { '$ref': '#' } },
//   required: [ 'name', 'friend' ],
//   additionalProperties: false,
// }

如果想遇到循环时报错,可以将 cycles 选项设置为 "throw"

z.toJSONSchema(User, { cycles: "throw" });
// => 抛出错误

reused

如何处理在同一 schema 中多次出现的 schemas。默认情况下,Zod 会将它们内联。

const name = z.string();
const User = z.object({
  firstName: name,
  lastName: name,
});
 
z.toJSONSchema(User);
// => {
//   type: 'object',
//   properties: { 
//     firstName: { type: 'string' }, 
//     lastName: { type: 'string' } 
//   },
//   required: [ 'firstName', 'lastName' ],
//   additionalProperties: false,
// }

你也可以将 reused 选项设置为 "ref",将这些 schema 抽取为 $defs

z.toJSONSchema(User, { reused: "ref" });
// => {
//   type: 'object',
//   properties: {
//     firstName: { '$ref': '#/$defs/__schema0' },
//     lastName: { '$ref': '#/$defs/__schema0' }
//   },
//   required: [ 'firstName', 'lastName' ],
//   additionalProperties: false,
//   '$defs': { __schema0: { type: 'string' } }
// }

override

要定义自定义的覆盖逻辑,使用 override。传入的回调可以访问原始 Zod schema 和默认的 JSON Schema。该函数应直接修改 ctx.jsonSchema

const mySchema = /* ... */
z.toJSONSchema(mySchema, {
  override: (ctx)=>{
    ctx.zodSchema; // 原始的 Zod schema
    ctx.jsonSchema; // 默认生成的 JSON Schema
 
    // 直接修改
    ctx.jsonSchema.whatever = "sup";
  }
});

注意,不可表示类型会在该函数调用之前抛出 Error。如果你想为不可表示类型定义自定义行为,需要同时设置 unrepresentable: "any" 并使用 override

// 支持将 z.date() 表示为 ISO 日期时间字符串
const result = z.toJSONSchema(z.date(), {
  unrepresentable: "any",
  override: (ctx) => {
    const def = ctx.zodSchema._zod.def;
    if(def.type ==="date"){
      ctx.jsonSchema.type = "string";
      ctx.jsonSchema.format = "date-time";
    }
  },
});

转换细节

以下是与 Zod JSON Schema 转换逻辑相关的额外细节。

字符串格式

Zod 会将以下 schema 类型转换为等价的 JSON Schema format

// 支持通过 `format`
z.email(); // => { type: "string", format: "email" }
z.iso.datetime(); // => { type: "string", format: "date-time" }
z.iso.date(); // => { type: "string", format: "date" }
z.iso.time(); // => { type: "string", format: "time" }
z.iso.duration(); // => { type: "string", format: "duration" }
z.ipv4(); // => { type: "string", format: "ipv4" }
z.ipv6(); // => { type: "string", format: "ipv6" }
z.uuid(); // => { type: "string", format: "uuid" }
z.guid(); // => { type: "string", format: "uuid" }
z.url(); // => { type: "string", format: "uri" }

这些 schema 通过 contentEncoding 支持:

z.base64(); // => { type: "string", contentEncoding: "base64" }

所有其他字符串格式通过 pattern 支持:

z.base64url();
z.cuid();
z.emoji();
z.nanoid();
z.cuid2();
z.ulid();
z.cidrv4();
z.cidrv6();
z.mac();

数值类型

Zod 将以下数值类型转换为 JSON Schema:

// number
z.number(); // => { type: "number" }
z.float32(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
z.float64(); // => { type: "number", exclusiveMinimum: ..., exclusiveMaximum: ... }
 
// integer
z.int(); // => { type: "integer" }
z.int32(); // => { type: "integer", exclusiveMinimum: ..., exclusiveMaximum: ... }

对象 schema

默认情况下,z.object() schema 包含 additionalProperties: false。这准确表示了 Zod 的默认行为,因为普通的 z.object() schema 会剥离额外属性。

import * as z from "zod";
 
const schema = z.object({
  name: z.string(),
  age: z.number(),
});
 
z.toJSONSchema(schema)
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, age: { type: 'number' } },
//   required: [ 'name', 'age' ],
//   additionalProperties: false,
// }

在以 "input" 模式转换为 JSON Schema 时,additionalProperties 不会被设置。更多内容参考 io 文档

import * as z from "zod";
 
const schema = z.object({
  name: z.string(),
  age: z.number(),
});
 
z.toJSONSchema(schema, { io: "input" });
// => {
//   type: 'object',
//   properties: { name: { type: 'string' }, age: { type: 'number' } },
//   required: [ 'name', 'age' ],
// }

相比之下:

  • z.looseObject() 永远不会 设置 additionalProperties: false
  • z.strictObject() 总是 设置 additionalProperties: false

文件 schema

Zod 将 z.file() 转换为以下符合 OpenAPI 的 schema:

z.file();
// => { type: "string", format: "binary", contentEncoding: "binary" }

大小和 MIME 类型校验也会被表示:

z.file().min(1).max(1024 * 1024).mime("image/png");
// => {
//   type: "string",
//   format: "binary",
//   contentEncoding: "binary",
//   contentMediaType: "image/png",
//   minLength: 1,
//   maxLength: 1048576,
// }

可空性

Zod 将 z.null() 在 JSON Schema 中都转换为 { type: "null" }

z.null(); 
// => { type: "null" }

注意,z.undefined() 在 JSON Schema 中是不可表示的(见下文不可表示部分)。

类似地,nullable 通过与 null 的联合表示:

z.nullable(z.string());
// => { oneOf: [{ type: "string" }, { type: "null" }] }

可选的 schema 则按原样表示,同时附带一个 optional 注解。

z.optional(z.string());
// => { type: "string" }

注册表

将 schema 传给 z.toJSONSchema() 会返回一个自包含的 JSON Schema。

在其他情况下,您可能有一组 Zod 模式,希望使用多个相互关联的 JSON 模式来表示,可能是为了写入 .json 文件并从 Web 服务器提供服务。

import * as z from "zod";
 
const User = z.object({
  name: z.string(),
  get posts(){
    return z.array(Post);
  }
});
 
const Post = z.object({
  title: z.string(),
  content: z.string(),
  get author(){
    return User;
  }
});
 
z.globalRegistry.add(User, {id: "User"});
z.globalRegistry.add(Post, {id: "Post"});

要实现这一点,您可以将一个 注册表 传递给 z.toJSONSchema()

重要 — 所有模式都应该在注册表中注册 id 属性!任何没有 id 的模式将被忽略。

z.toJSONSchema(z.globalRegistry);
// => {
//   schemas: {
//     User: {
//       id: 'User',
//       type: 'object',
//       properties: {
//         name: { type: 'string' },
//         posts: { type: 'array', items: { '$ref': 'Post' } }
//       },
//       required: [ 'name', 'posts' ],
//       additionalProperties: false,
//     },
//     Post: {
//       id: 'Post',
//       type: 'object',
//       properties: {
//         title: { type: 'string' },
//         content: { type: 'string' },
//         author: { '$ref': 'User' }
//       },
//       required: [ 'title', 'content', 'author' ],
//       additionalProperties: false,
//     }
//   }
// }

默认情况下,$ref URI 是简单的相对路径,如 "User"。要将这些转换为绝对 URI,请使用 uri 选项。这需要一个将 id 转换为完全合格 URI 的函数。

z.toJSONSchema(z.globalRegistry, {
  uri: (id) => `https://example.com/${id}.json`
});
// => {
//   schemas: {
//     User: {
//       id: 'User',
//       type: 'object',
//       properties: {
//         name: { type: 'string' },
//         posts: {
//           type: 'array',
//           items: { '$ref': 'https://example.com/Post.json' }
//         }
//       },
//       required: [ 'name', 'posts' ],
//       additionalProperties: false,
//     },
//     Post: {
//       id: 'Post',
//       type: 'object',
//       properties: {
//         title: { type: 'string' },
//         content: { type: 'string' },
//         author: { '$ref': 'https://example.com/User.json' }
//       },
//       required: [ 'title', 'content', 'author' ],
//       additionalProperties: false,
//     }
//   }
// }

On this page