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 推断引擎使用深度优先递归遍历。对于遇到的每个值:
- 基本类型检查:确定 JavaScript 的
typeof结果,并将其映射到 JSON Schema 类型。 - Null 检查:JavaScript 中的
null映射为"type": "null"。当它与数组中的其他类型混合时,我们使用type的数组形式生成联合类型。 - 对象遍历:对每个键,我们递归推断其值的 Schema,并添加到
properties。发现的所有键默认成为required。 - 数组采样:我们不假设数组中的所有成员类型相同,而是对前 N 个成员进行采样。如果发现多种类型,则输出
oneOf或联合类型数组。 - 格式检测:对字符串值,我们运行一系列基于正则的探测,检测常见格式:ISO 日期时间、Email、URI、UUID。
支持多个草案标准
JSON Schema 经历了多个版本:Draft-04、Draft-06、Draft-07、2019-09 和最新的 2020-12。关键差异会影响输出:
- Draft-07 引入了
readOnly、writeOnly和if/then/else关键字。 - 2019-09 将
definitions替换为$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 是一个起点,而非最终契约。资深开发者应在此基础上审查并收紧约束——根据领域知识添加 minLength、maxLength、enum 或 pattern。