HandyTools Hub

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 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 团队审校 · 最后更新