JSON 转 JSON Schema

粘一段真实数据,反推出描述它的 JSON Schema,字段类型、必填项和 format 都会自动判断。

推断规则

类型按值来定:布尔是 boolean,整数是 integer,带小数点是 number,字符串是 string,null 单独写成 type: "null"。对象会展开成 properties,每层都递归推断。空数组没有元素可参考,只写 type: "array",items 留空表示元素可以是任意类型。

数组元素全是对象时会合并字段:取所有元素的字段并集,只有每个元素都带的字段才进 required。这比拿第一个元素当模板更安全——真实数据里后面的元素常常缺字段,按第一个元素推会把可选字段误判成必填。类型彻底不同的元素则生成 anyOf 分支。

format 只是注解

字符串会尝试识别常见格式:ISO 日期时间标 date-time,纯日期标 date,UUID、邮箱、IPv4、带协议头的 URL 也各有对应标记。判断用的是形状匹配,不是严格的语法校验,所以像 2026-13-45 这种「长得像日期但不存在」的值也会被标成 date。JSON Schema 规范里 format 默认只是注解,想让它参与校验需要在 ajv 里额外配置。

和手写 schema 的差距

样本归纳只能覆盖「出现过的形状」:数值范围、字符串长度、枚举取值、字段之间的依赖关系都不会被推断出来。所以生成的 schema 适合当起点,再手动补上 minimum、maxLength、enum 这些约束。反过来,如果已经有 schema,想看它对应的 TypeScript 类型,用JSON Schema 转 TypeScript只想看数据本身的结构,用JSON 格式化更直接。

常见问题

数组里的元素结构不一样,会生成什么?

如果每一项都是对象,会把字段取并集,只有每一项都出现的字段才写进 required,其余保持可选——这比随便挑第一项当模板更接近真实数据。如果类型完全不同(比如一会儿字符串一会儿对象),会生成 anyOf 分支。

为什么有些字符串被标成了 format?

只对高置信度的形态打标记:ISO 日期时间、纯日期、UUID、邮箱、IPv4、带协议头的 URL。普通文本不会乱标。这些 format 只是注解,不做校验,真要校验日期是否合法还得自己写逻辑或加 format 校验器。

required 是怎么决定的?

默认把样本里出现过的字段都标成必填,因为样本里既然有它,多半就是必需字段。数组元素会取「每一项都有」的字段。如果只是拿样本做参考、不想把字段锁死,可以关掉「标记为必填」,生成的 schema 就不会出现 required。

additionalProperties: false 要不要开?

开了以后,数据里出现 schema 没声明的字段就算校验失败,适合接口契约、配置文件的严格校验。不开则默认允许额外字段,兼容性更好,也更适合先跑起来再逐步收紧。注意开了以后,服务端多返回一个字段就会让校验整体失败。

整数和小数分得开吗?

分得开。样本里全是整数的字段写 integer,出现小数就写 number。但单个样本说明不了取值范围:样本里是 1,schema 只会写 integer,不会限制最小值和最大值。要约束范围得手动补 minimum / maximum。

能直接拿生成的 schema 做校验吗?

可以,它能被 ajv 这类校验器直接使用。但要知道它是从一份样本归纳出来的,样本没覆盖到的结构不会出现在 schema 里。字段多、分支多时,更靠谱的做法是手写或从接口定义(OpenAPI)生成。反过来要把 schema 转回 TypeScript 类型,用 JSON Schema 转 TypeScript。

相关:JSON Schema 转 TypeScript