JSON 转 TypeScript
即时从 JSON 数据生成 TypeScript 接口
生成的 TypeScript 将显示在此处... JSON 转 TypeScript 转换器读取一段 JSON 样例,输出可直接使用的类型声明。嵌套对象会变成各自命名的接口,数组会被统一为单一元素类型,完全相同的对象形状会被复用而不是重复生成。一切都在浏览器中随输入即时运行,并带有轻微的防抖,因此你可以粘贴真实的 API 响应,把结果直接复制进项目。
推断如何工作
工具用 JSON.parse 解析文本。如果失败,它会显示解析器的消息并停止。成功后,它递归遍历该值:
- 字符串变成
string,数字变成number,布尔值变成boolean,而null和undefined都变成any; - 对象变成具名声明,其属性名会转换为 PascalCase 作为名称;
- 数组检查每个元素,收集不同的元素类型,再用
|连接它们。
名称由属性路径派生。名为 shipping_address 或 shipping-address 的键都会产生 ShippingAddress。如果某个名称已被另一种形状占用,工具会追加 2,再是 3,以此类推。属性名、类型和可选性完全相同的两个对象共享同一个声明,因此重复的形状只会输出一次。
转换带有大约 300 毫秒的防抖,所以声明会在你停止输入后不久稳定下来,而不是每次按键都重算。根名与所有其他标识符一样经过 PascalCase 转换:输入为 order response 的根变成 OrderResponse,这个名称既用于顶层声明,也用于派生的嵌套类型名。
实操示例:一个 API 响应
给定输入:
{
"id": 1,
"name": "Ada",
"tags": ["math", "code"],
"address": { "city": "London", "zip": "E1" }
}
输出为:
export interface Root {
id: number;
name: string;
tags: string[];
address: Address;
}
export interface Address {
city: string;
zip: string;
}
根对象总是最先出现,随后是按发现顺序排列的嵌套声明。tags 是同质数组,所以变成 string[];address 变成对其自身接口的引用。
处理数组与可选键
对象数组会合并为一个接口。每个键只出现一次,任何在至少一个元素中缺失的键都会标记为可选。例如:
{ "users": [
{ "id": 1, "email": "a@example.com" },
{ "id": 2, "name": "Bob" }
] }
生成:
export interface Root {
users: Users[];
}
export interface Users {
id: number;
email?: string;
name?: string;
}
元素接口命名为 Users,因为它派生自 users 键。如果元素对同一个键有冲突类型,这些类型会被合并为联合,因此在某个对象中是数字、在另一个对象中是字符串的字段会变成 number | string。像 [1, "two"] 这样的混合原始类型数组会变成 (number | string)[],而 [1, 2, 3] 保持为 number[]。
不是合法 JavaScript 标识符的属性名会用 JSON.stringify 加引号,所以 "first-name" 这样的键会输出为 "first-name": string;。
interface 与 type
声明开关改变的是语法,不是推断出的结构。在 interface 模式下工具写 export interface Name { ... };在 type 模式下写 export type Name = { ... };。接口支持声明合并,是描述对象形状的常规选择;当之后想把类型与联合组合时,类型别名更统一。导出复选框只是添加或移除 export 前缀,在粘贴到已导入这些名称的文件中时很有用。
这个开关有一个值得了解的后果:根数组无论设置如何,总是产生类型别名,因为数组不是对象形状。即使在 interface 模式下,你也会看到一行 type Root = RootItem[];,并与元素形状的接口配对出现。
准确性局限
null和undefined会塌缩为any,因此可空字段会丢失意图。- 空数组变成
any[],宽松而非精确。 - 标量根(例如
"hello"或[1, 2, 3])会输出为类型别名,而不是接口。 - 只有数组会揭示可选性。样例中缺失的字段不会被标记为可选。
- 整数和小数都变成
number;没有bigint、Date或十进制类型。 - 字符串值不会变成字面量联合,所以
"active"会被类型化为string。 - 输出仅作用于编译期,不执行任何运行时校验。
实用建议
粘贴你手上最完整的响应,最好能覆盖可选字段和每种数组变体。如果 JSON 是压缩过的,先用格式化器处理一遍,因为转换器会原样显示解析器错误。让根名有意义(UserResponse、Order),因为每个嵌套接口都从它命名。当推断出的类型过于宽松时,手动编辑特定字段来补充字面量联合或可空性;生成的代码只是起点,不是契约。
留意意外的形状共享。如果两个不相关的嵌套对象恰好拥有相同的字段名、类型和可选性,它们会被合并为一个声明。这通常正是你想要的,但这也意味着共享类型必须对两个调用点都保持有效;如果两种形状需要独立演进,请重命名某个键或拆分样例。
相关工具
- JSON Formatter — 转换前先格式化、校验和美化 JSON。
- JSON Schema Generator — 从数据推断 JSON Schema,用于运行时校验。
- YAML Converter — 先将 YAML 配置转为 JSON,再生成类型。
常见问题
为什么我的 JSON null 变成了 any 而不是 null?
工具把 null 和 undefined 都映射为 any,因为仅凭一个样例无法判断某字段是始终为 null,还是仅在这一个响应里为 null。如果想显式处理 null,请把生成的声明改成 `string | null` 或类似的联合类型,这是工具无法推断的。
生成器如何判定一个属性是可选的?
只有数组才会让它学到可选性。当对象数组合并为一个接口时,任何在至少一个元素中缺失的键都会带上问号。在普通对象中,每个键都被视为必需,因为输入里没有反例。
遇到空数组会怎么处理?
空数组没有元素可供检查,所以工具会输出 any[]。这是一个信号,提示你手动补全类型,否则之后返回非空的响应会被静默接受。请粘贴包含至少一个元素的代表性样例。
它会推断像 "active" | "paused" 这样的字符串字面量联合类型吗?
不会。JSON 字符串总是被推断为宽泛的 string 类型,数字则推断为 number。字面量联合、枚举、品牌化 id 和日期字符串都需要手动编辑,因为从单个值推断无法证明其他值不可能出现。
为什么我的根数组被声明为 type Root = Items[] 而不是接口?
接口描述的是对象形状,但这里的根是数组,所以工具输出类型别名。它还会以根名加 Item 后缀来命名元素接口,例如 RootItem,这样别名和元素类型就不会冲突。
可以直接把输出用于 Zod 或 io-ts 做运行时校验吗?
不能。生成器产出的是纯 TypeScript 声明,只存在于编译期,运行时会被擦除。如果需要在运行时拒绝格式错误的数据,请把输出与运行时 schema 库配合使用。
由 HandyTools Hub 团队审校 · 最后更新