JSONSchemaTypeScript

深度解析:如何从复杂对象自动生成 JSON Schema?

2026-03-197 分钟阅读

JSON Schema 生成远不止是'推断类型'那么简单。本文深入讲解递归嵌套、联合类型推导和数组成员分析,揭秘 daima.life JSON Schema 生成器背后的算法逻辑。

JSON Schema 是描述 JSON 数据结构的强大标准。它驱动着 API 验证、表单生成、IDE 代码补全和文档生成。但从任意 JSON 数据自动生成合法的 Schema,其复杂度往往超出预期。

超越简单的类型推断

一个简陋的 Schema 生成器对每个字段只问一个问题:它是字符串、数字、布尔值、数组还是对象?这能生成一个草稿,但真实世界的 JSON 数据要混乱得多。请看下面的例子:

{
  "id": 42,
  "tags": ["typescript", "nodejs", null],
  "meta": {
    "createdAt": "2026-01-01T00:00:00Z",
    "flags": [true, false, true]
  },
  "optional_field": "sometimes here"
}

一个健壮的 Schema 生成器必须处理以下所有情况:

  • 数组中的可空字段:tags 同时包含字符串和 null,因此正确的 Schema 是 { "type": ["string", "null"] }
  • 嵌套对象:meta 需要一个嵌套的 properties 定义,递归遍历。
  • 日期时间格式检测:createdAt 符合 ISO 8601 格式——智能生成器会将其注释为 "format": "date-time"
  • 必填字段 vs 可选字段:如果某字段并非出现在数据集的每一条记录中,它就不应进入 required 数组。

递归算法

daima.life 的 Schema 推断引擎使用深度优先递归遍历。对于遇到的每个值:

  1. 基本类型检查:确定 JavaScript 的 typeof 结果,并将其映射到 JSON Schema 类型。
  2. Null 检查:JavaScript 中的 null 映射为 "type": "null"。当它与数组中的其他类型混合时,我们使用 type 的数组形式生成联合类型。
  3. 对象遍历:对每个键,我们递归推断其值的 Schema,并添加到 properties。发现的所有键默认成为 required
  4. 数组采样:我们不假设数组中的所有成员类型相同,而是对前 N 个成员进行采样。如果发现多种类型,则输出 oneOf 或联合类型数组。
  5. 格式检测:对字符串值,我们运行一系列基于正则的探测,检测常见格式:ISO 日期时间、Email、URI、UUID。

支持多个草案标准

JSON Schema 经历了多个版本:Draft-04、Draft-06、Draft-07、2019-09 和最新的 2020-12。关键差异会影响输出:

  • Draft-07 引入了 readOnlywriteOnlyif/then/else 关键字。
  • 2019-09definitions 替换为 $defs,并引入了 unevaluatedProperties
  • 2020-12 将数组 Schema 从 items(单一 Schema)改为 prefixItems(元组验证)+ items(附加成员)。

我们在 daima.life/tools/json-schema-gen 的生成器允许你选择目标草案,并相应地调整输出——确保与 Ajv(JavaScript)、Newtonsoft.Json(C#)、jsonschema(Python)等验证器的兼容性。

边缘情况与局限性

没有任何静态推断引擎是完美的。以下是一些值得注意的局限:

  • 多态对象:如果同一键在不同记录中持有不同的对象形状,生成器会产生一个可能过于宽松的联合类型。
  • 空数组:无法从空数组推断元素类型,因此我们输出 "items": {}——一个开放的 Schema。
  • 深度嵌套:出于性能考虑,递归深度上限为 20 层。实际的 JSON Schema 很少需要超过 5-6 层。

生成的 Schema 是一个起点,而非最终契约。资深开发者应在此基础上审查并收紧约束——根据领域知识添加 minLengthmaxLengthenumpattern