跳到主要内容

数据校验:JSON Schema To Zod Schema — Numio

使用 json-schema-to-zod 根据 JSON Schema 生成 Zod 校验代码。minLength、minimum、pattern、default 等约束会变为链式调用的 Zod 方法,未列在 "required" 中的属性会加上 .optional()。

你从其他团队拿到 JSON Schema,想在 TypeScript 服务中做运行时校验,而不引入 JSON Schema 校验器。

JSON Schema

Zod Schema

适用场景

  • 你从其他团队拿到 JSON Schema,想在 TypeScript 服务中做运行时校验,而不引入 JSON Schema 校验器。
  • 你正把表单校验从 Ajv 等 JSON Schema 校验器迁移到 Zod,需要一份生成的代码作为起点。

使用方法

  1. 将 JSON Schema 粘贴到 JSON Schema 面板。
  2. 可选:打开设置修改 Root Schema Name(默认为 "schema"),它会成为导出的 const 名称。
  3. 将生成的代码(从 "zod" 导入 z)复制到项目中。转换在浏览器中进行。

JSON Schema

{
  "type": "object",
  "properties": {
    "username": { "type": "string", "minLength": 3, "maxLength": 20 },
    "age": { "type": "integer", "minimum": 18 },
    "contact": {
      "oneOf": [
        { "type": "string", "format": "email" },
        { "type": "string", "pattern": "^\\+[0-9]+$" }
      ]
    },
    "newsletter": { "type": "boolean", "default": false }
  },
  "required": ["username", "contact"]
}

Zod schema

import { z } from "zod"

export const schema = z.object({
  username: z.string().min(3).max(20),
  age: z.number().int().gte(18).optional(),
  contact: z.any().superRefine((x, ctx) => {
    const schemas = [
      z.string().email(),
      z.string().regex(new RegExp("^\\+[0-9]+$")),
    ]
    const errors = schemas.reduce(
      (errors: z.ZodError[], schema) =>
        ((result) => ("error" in result ? [...errors, result.error] : errors))(
          schema.safeParse(x),
        ),
      [],
    )
    if (schemas.length - errors.length !== 1) {
      ctx.addIssue({
        path: ctx.path,
        code: "invalid_union",
        unionErrors: errors,
        message: "Invalid input: Should pass single schema",
      })
…

功能与限制

oneOf 会转换为 z.any().superRefine(...),检查是否恰好有一个子 schema 通过,如示例所示。

$ref 不会被解析:引用其他 schema 的属性会变为 z.any()。

integer 会加上 .int(),format "email" 加上 .email(),format "date-time" 加上 .datetime()。

additionalProperties: false 会变为 .catchall(z.never()),而不是 .strict()。

FAQ

可为空的类型如何处理?

"type": ["string", "null"] 会变为 z.union([z.string(), z.null()]),若该属性不是必需的,还会加上 .optional()。

可以重命名导出的 schema 吗?

可以。在设置中指定 Root Schema Name,输出会变为 export const <name> = ...,而不是 export const schema。

支持 definitions 和 $ref 吗?

不支持。使用 $ref 的属性会变为 z.any(),请在转换前把被引用的 schema 内联进来。

规范与参考资料