使用方法
- 打开页面时文本框里已经放好一份示例 JSON,八个字段:id、name、tags、score、active、meta、nested、uid。点「生成接口」,或者在文本框里按 Ctrl / Cmd + Enter,结果立刻出来。
- 「根接口名」默认 Root,改成 UserInfo 之类即可。填了以数字开头、含空格或含连字符的名字,会给出指明原因的提示,不会静默替换成别的名字。
- 「缩进」三选一:2 空格、4 空格、Tab。改了立刻重算,不用点按钮。
- 「未全覆盖的字段标成可选」默认关闭。打开后,出现次数少于样本数的字段会被标成 key?: T。比如 3 个对象里只有 2 个带某字段,可选率正好是 0.666666666667。
- 结果区分三层:上面一行是结论(多少个字段、多少个接口),中间是 interface 文本,下面是字段推断表——路径、推断类型、出现次数÷样本数、是否可选、示例值。
- 「复制接口」复制整段 interface 文本;「下载 .ts」存成文件,文件名取根接口名。这两个出口都不带字节序标记,TS 源文件加 BOM 会让部分编译器报错。
- 样本不是一个对象也认:顶层是数组时按数组元素合并出一个接口,根类型写作 Root[]。
- 文本框填了不合法 JSON 时,提示里带上解析器给的位置与原因,比如多余逗号、单引号、注释这类具体问题。
这个JSON转TypeScript接口工具能解决什么问题
第一类是后端还没出类型定义时的前端自保。接口文档只给了一段响应示例,手写 interface 要一个字段一个字段抄,几十个字段的响应抄一遍还要对齐类型:数字该写 number 还是 string、时间是时间戳还是字符串、某字段会不会是 null。把示例粘进来,类型一次推断完,再手工修几处就行。
第二类是把 null 挡在类型之外。JS 里 typeof null 得到 object,照搬会让 null 字段被写成 object,在 strictNullChecks 下编译直接报错。本页把 null 单独归为一档:样本只有 null 时输出 null,样本里 null 与对象混合时输出 T | null,两种写法不一样,页面分开处理。
第三类是数组。手写时最容易只看第一个元素:样本第一个元素是字符串就写 string[],后面夹着数字就漏了。本页把数组里所有元素都扫一遍再合并,异构数组输出 (number | string | boolean)[ ] 这样的联合类型,空数组因为样本不足只给 unknown[]。
第四类是 64 位 ID 的精度墙。示例里的 uid 写成 9007199254740993,JSON.parse 之后就已经变成 9007199254740992,值本身已经错了。本页在扫字段时把这个字段挑出来,在生成的文本里追加注释「建议后端改成字符串」,让人在联调前就发现它。
计算原理与变量说明
整页只有一套规则:递归遍历 JSON 值,把每个路径上的类型并起来,最后按路径生成接口。规则表如下。
| 样本形态 | 推断结果 | 说明 |
|---|---|---|
| string / boolean | string / boolean | 与 TS 同名,直接映射 |
| 1 与 95.5 | number | TS 只有 number,不分 int 与 float |
| null | null | typeof null 是 object,但 TS 里单独一档 |
| object | 内联接口 | 按路径取名,如 RootNested |
| array | T[] | 合并全部元素,不是取第一个 |
| [] | unknown[] | 空数组样本不足,保守写法 |
| 整数字段 | |v| ≥ 9007199254740992 | 提示建议后端改成字符串 |
| 字段名 | /^[A-Za-z_$][A-Za-z0-9_$]*$/ | 不匹配就加单引号 |
null 与 object 必须分开,这是本页最容易写错的一条。typeof null === 'object' 是 JS 早期实现留下的历史 bug,一直没能修掉,因为它已经被大量线上代码依赖。TypeScript 在 strictNullChecks 打开时不接受把 null 赋给 object,所以照搬 typeof 的结果会直接编译失败。样本只有 null 时输出 null;null 与对象混合时输出 T | null;关掉 strictNullChecks 之后可以简写成对象类型,但那意味着运行时的空值检查全部要靠人自己记着。
数组要合并,不能抽样。同构的 ["tool","web"] 得 string[];异构的 [1,"a",true] 得 (number | string | boolean)[],三个类型谁都不能丢;嵌套的 [[1,2],[3,4]] 得 number[][],第二层继续递归。空数组 [] 里没有任何元素可供观察,写成 never[] 是激进写法(意味着这个数组永远不该有元素),本页取保守的 unknown[]:先承认不知道,等拿到真实数据再收窄。
2 的 53 次方是 JSON 数字的精度墙。Number.MAX_SAFE_INTEGER 是 9007199254740991,再往上就是 9007199254740992,也就是 2 的 53 次方。IEEE 754 双精度只有 53 位有效尾数,超过它之后整数不再连续,9007199254740993 这种写法在 JSON.parse 之后就已经变成 9007199254740992。数据在进入 JS 的那一刻就错了,之后再怎么转类型都救不回来——所以本页的做法是提前点名:扫到绝对值达到 9007199254740992 的整数字段就追加注释,让后端把这个字段改成字符串输出。分布式 ID、订单号、手机号都是这条规则的高发区。
非法标识符加引号,不是改名。判定用的正则是 /^[A-Za-z_$][A-Za-z0-9_$]*$/:含空格的 "user name"、含连字符的 "data-id"、数字开头的 "2fa"、保留字 "class" 都过不了,全部写成 'user name'?: string 这种带单引号的形式。这里不加问号也合法,可选与否只看覆盖率:某字段在 3 个对象里只出现 2 次,可选率就是 0.666666666667,标可选之后调用方必须自己处理它不存在的情况。
JSON转TypeScript接口的嵌套结构按路径取名。Root 下的 nested 对象生成 interface RootNested,字段类型写 RootNested;tags 数组若装的是对象,则生成 interface RootTags,字段类型写 RootTags[]。同一个路径上的所有对象会被合并成一份字段表,出现次数用来判可选。
完整示例(数值仅作演示)
默认样本里 id 是 1、score 是 95.5,两者都映射到 number——TS 不区分整数与浮点,Number.isInteger(95.5) 是 false、Number.isInteger(1) 是 true,但类型层面都是 number。想区分只有自己加注释或者用 branded type。
tags 是 ["tool","web"],两个元素都是字符串,合并后得 string[];如果样本里再夹一个数字,结果立刻变成 (number | string)[],这就是「合并全部元素」与「取第一个元素」的差别。
meta 是 null,输出 meta: null。它不会被写成 object:typeof null 的结果本页不用。nested 是 { "k": 1 },生成独立的 interface RootNested,字段 k 是 number。
uid 写成 9007199254740993,JSON.parse 之后实际是 9007199254740992。本页在这一行后面追加注释「超过 2^53,建议后端改成字符串」,并在提示条里点名 uid。这是整页唯一一条「数据已经错了」的告警。
JSON 值到 TS 类型对照表
第一张表是本页的自测锚点,覆盖空数组、异构数组、保留字与大整数,「复制对照表」复制的就是它。
| 样本值 | 推断类型 | 说明 |
|---|---|---|
| "tool" | string | 字符串 |
| 1 | number | 整数也是 number |
| 95.5 | number | TS 不区分 int 与 float |
| true | boolean | 布尔 |
| null | null | typeof null 是 object,TS 单独一档 |
| [] | unknown[] | 样本不足,不猜 |
| [1,"a",true] | (number | string | boolean)[] | 异构数组合并成联合 |
| [[1,2],[3,4]] | number[][] | 嵌套数组 |
| 9007199254740991 | number | 安全整数上限 |
| 9007199254740993 | number | 解析后变成 9007199254740992 |
| "user name" | 'user name'?: string | 含空格,加引号 |
| "data-id" | 'data-id'?: string | 含连字符,加引号 |
| "2fa" | '2fa'?: string | 数字开头,加引号 |
| "class" | 'class'?: string | 保留字,加引号 |
| 0.666666666667 | number | 3 个样本里出现 2 次的可选率 |
第二张表是字段名与写法的对照,用来核对加引号的几种情形。
| 字段名 | 是否合法标识符 | 生成写法 |
|---|---|---|
| name | 是 | name: string; |
| user name | 否(含空格) | 'user name': string; |
| data-id | 否(含连字符) | 'data-id': string; |
| 2fa | 否(数字开头) | '2fa': string; |
| class | 否(保留字) | 'class': string; |
| $ref | 是($ 开头合法) | $ref: string; |
第三张表是「照抄 typeof 会错在哪」,本页的三条纠偏都在里面。
| 写法 | 照抄 typeof | 本页输出 |
|---|---|---|
| meta 为 null | meta: object | meta: null |
| null 与对象混合 | object | T | null |
| ["a","b"] | object | string[] |
| [] | object | unknown[] |
| uid 大整数 | number(已失真) | number + 建议改字符串 |
读法:第一张看类型映射对不对,第二张看引号加得对不对,第三张看三条纠偏有没有生效。
结果边界、舍入与适用说明
- 样本为空时提示请粘贴一段 JSON;只有空白字符也算空,不会拿去解析。
- JSON 解析失败时把解析器给的位置与原因原样带出来,并提示检查逗号、引号与括号是否配对。
- 根接口名为空、以数字开头、含空格或含连字符时,分别给出指名具体字符的提示;是保留字时提示换一个名字。
- 顶层是数组时按元素合并成一个接口,根类型写成名字加 [];顶层是字符串或数字这种标量时提示请给一个对象或数组。
- 整数字段绝对值达到 9007199254740992 就追加注释,这是双精度尾数 53 位的边界,不是随便定的阈值。
- 字段名判定用 /^[A-Za-z_$][A-Za-z0-9_$]*$/,不匹配就加单引号;键里的单引号会被原样保留,不会转义。
- 可选标记只在勾选后生效,默认关闭:样本往往只是一两条数据,覆盖率低不代表字段真的可选。
- 本页不做类型收窄之外的活:不生成枚举、不合并相似接口、不做命名风格转换,也不校验运行时数据。
- 同一样本里出现重复键时,JSON.parse 只保留最后一个,本页按解析后的结果推断,不会对重复键单独告警。
- 整页不联网、不写本地存储;下载的 .ts 文件不带字节序标记,CSV 才带。
常见问题
为什么 null 字段不是 object?
因为 typeof null === 'object' 是 JS 的历史 bug,而 TS 在 strictNullChecks 下不允许把 null 赋给 object。样本只有 null 时输出 null,null 与对象混合时输出 T | null。
空数组为什么是 unknown[] 而不是 any[]?
空数组里没有任何元素可供观察,是「样本不足」而不是「什么都行」。unknown[] 要求你先做类型检查再用,any[] 会关掉检查;never[] 则断言它永远为空,太激进。
为什么 uid 要提示改成字符串?
因为 9007199254740993 超过 2 的 53 次方,JSON.parse 之后就已经变成 9007199254740992,值在进 JS 的那一刻就错了。64 位 ID 只能用字符串承载。
95.5 和 1 都写成 number 吗?
是。TypeScript 只有一个 number,没有 int 与 float 之分。Number.isInteger 能区分运行时值,但类型层面两者都是 number。
字段名里的连字符一定要加引号吗?
要。data-id 不是合法标识符,正则 /^[A-Za-z_$][A-Za-z0-9_$]*$/ 匹配不上,只能写成 'data-id'?: string。含空格、数字开头、保留字同理。
数组里元素类型不一样怎么办?
全部合并成联合类型。[1,"a",true] 得 (number | string | boolean)[],不是只看第一个元素得 string[] 或 number[]。
可选字段是怎么判定的?
看覆盖率:某字段在 3 个对象里只出现 2 次,可选率 0.666666666667,勾选「标成可选」后就加 ?。默认关闭,因为样本少不代表字段真的可选。
样本会上传到服务器吗?
不会。JSON 只在浏览器里解析、遍历、生成文本,没有发起任何网络请求,也没有写入本地存储。
相关工具
- JSONPath 查询:先从大段 JSON 里截出要转型的那一段。
- JSON 校验工具:解析失败时先在这里定位语法错误。
- JSON 格式化工具:把压缩成一行的样本展开后再粘过来。
- JSON 转 Python dataclass:同一份样本要生成后端类型时用这个。
- CSV 转 JSON 转换:手上是表格数据时先转成 JSON 样本。
- 开发者工具:查看全部开发者工具。