💎 雨云 RainYun - 企业级云计算服务提供商
Zod logo

JSON Schema

编辑此页面
💎

[email protected] 起,Zod 支持原生 JSON Schema 转换。JSON Schema 是一种用于描述 JSON 结构的标准(用 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.number().multipleOf(0); // ❌ the divisor must be finite and non-zero

配置

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

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"` — 不可表示的类型会变为 `{}`
   * 函数 — 返回要使用的 JSON Schema,或 `"any"`/`"throw"` */
  unrepresentable?:
    | "throw"
    | "any"
    | ((ctx: {
        zodSchema: $ZodTypes;
        path: (string | number)[];
        message: string;
      }) => JSONSchema | "throw" | "any" | undefined);
 
  /** 如何处理循环。
   * - `"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 }

元数据优先于 Zod 生成的关键字。

z.toJSONSchema(z.string().meta({ type: "number" }));
// => { type: "number" }
 
z.toJSONSchema(z.date().meta({ type: "string", format: "date-time" }), { unrepresentable: "any" });
// => { type: "string", format: "date-time" }

要完全删除元数据,请传入一个空注册表:z.toJSONSchema(schema, { metadata: z.registry() })。要修改生成的 schema,请使用 override

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(); // ❌
z.number().multipleOf(0); // ❌ the divisor must be finite and non-zero

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

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

你可以将 unrepresentable 选项设置为 "any" 来改变此行为。这会将任何不可表示的类型转换为 {}(JSON Schema 中 unknown 的等价形式),并从包含该类型的 schema 中移除不可表示的校验。

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

如果要逐个决定处理方式,请传入一个函数。每遇到一个不可表示的 schema,该函数就会被调用一次。返回要替代使用的 JSON Schema、"any""throw"(未返回任何内容时的默认值)。

z.toJSONSchema(z.object({ createdAt: z.date(), id: z.bigint() }), {
  unrepresentable: ({ zodSchema }) =>
    zodSchema._zod.def.type === "date" ? { type: "string", format: "date-time" } : "throw",
});
// => 抛出错误(BigInt 无法在 JSON Schema 中表示)

处理函数还会接收 schema 的 path 以及 Zod 原本会抛出的 message,并且该函数抛出的任何错误都会继续向上传播,因此你可以用自己的措辞报告不可表示的类型。

z.toJSONSchema(schema, {
  unrepresentable: ({ path, message }) => {
    throw new Error(`${message} (at /${path.join("/")})`);
  },
});

两个 schema 可以共享同一个 zodSchema,但不一定共享同一个 message — 同一个字面量中的 undefined 成员和 bigint 成员都会以该字面量的形式传入 — 因此可以根据 message 分辨它们。

无法序列化为 JSON 的默认 会像 .default() schema 一样经过相同的处理函数。在 "any" 模式下,默认值会被丢弃,其余 schema 会照常生成;返回一个 JSON Schema 即可提供你自己的表示。

z.toJSONSchema(z.bigint().default(0n), {
  unrepresentable: ({ zodSchema }) => {
    const def = zodSchema._zod.def;
    if (def.type === "bigint") return { type: "integer", format: "int64" };
    if (def.type === "default") return { default: String(def.defaultValue) };
    return "throw";
  },
});
// => { type: "integer", format: "int64", default: "0" }

为字面量返回一个 JSON Schema 会替换整个字面量,并丢弃其中可表示的成员:z.literal(["a", 1n]) 会变成你返回的内容,而 "a" 会被移除。返回 "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 — 其中的处理函数会替换该类型,同时不会禁用对其他类型的错误处理。将 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.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" }

z.iso.datetime()local: trueprecision: -1 变体会回退到 pattern,因为 RFC 3339 date-time 要求同时包含偏移量和秒数。

以下 schema 通过 contentEncoding 支持:

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

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

z.iso.time();
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" 添加到可接受类型集合中。无法写成裸类型的内部 schema 会回退为 anyOf

z.nullable(z.string());
// => { type: ["string", "null"] }
 
z.nullable(z.string().min(5));
// => { anyOf: [{ type: "string", minLength: 5 }, { 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