JSON 转 TypeScript
将 JSON 数据自动转换为 TypeScript 接口或类型定义
// 输入 JSON 后自动生成 TypeScript 类型定义什么是 JSON 转 TypeScript?
JSON 转 TypeScript 工具会分析 JSON 数据结构,并生成对应的 TypeScript interface 或 type 定义。它适合把 API 响应、配置文件、示例 payload、Mock 数据快速接入前端或 Node.js 项目,让编辑器补全、类型检查和重构更可靠。工具可以识别对象、数组、嵌套字段、可选属性和基础值类型,把原始数据转成类型定义的起点。但生成结果仍需要人工判断:一份示例 JSON 未必覆盖所有字段,空数组无法说明元素结构,真实接口也可能返回 null 或多种对象形态。
使用方法
如何使用
- 在左侧输入框粘贴 JSON 数据,或点击“示例”按钮加载示例
- 配置选项:设置根类型名称、选择 interface/type 样式、是否添加 export 等
- 右侧会自动生成对应的 TypeScript 类型定义
- 点击“复制”按钮将生成的类型定义复制到剪贴板
- 将类型定义粘贴到 TypeScript 项目中即可使用
类型生成提示
- 请使用具有代表性的 JSON 样本。单个示例无法反映真实 API 中所有可选字段、可空值或联合类型。
- 提交前请检查生成的名称,特别是嵌套对象的自动接口名称可能过于通用。
使用场景
技术原理
JSON 转 TypeScript 本质上是一个「结构推断」过程。工具递归遍历 JSON 树的每个节点,将每个值的运行时类型映射为 TypeScript 类型:string -> string,number -> number,boolean -> boolean,null -> null,object -> 子接口,array -> 元素类型的数组。输出是一组 TypeScript `interface` 声明(如果选择了 type 输出模式则为 `type` 别名)加上从对象值字段中提取的嵌套接口。 推断的难点在于数组的联合类型与单一类型判断。当数组的每个元素类型相同时,输出为 `T[]`(同构数组)。当元素类型不同时,输出为这些类型的联合:`[1, 'a', true]` 变为 `(number | string | boolean)[]`。当输入是混合字面值和结构化值的对象(如 `{ id: 1, name: 'Alice', tags: ['admin', 'editor'] }`)时,每个字段获得自己的类型;对于混合原始值的对象,页面发出单个接口而非联合,因为这是 TypeScript 代码所期望的(也符合 JSON 对象作为记录类型的本质)。 多个样本对象(典型用例:粘贴 JSON 数组)通过交集合并其形状。在每个样本中都存在的字段成为必填;在任何样本中缺失的字段成为可选(`fieldName?: T`)。这是 API 响应的正确行为:200 OK 响应是一种形状,404 响应是另一种,两者的联合才是正确的 TS 类型。对于单个样本,所有字段都是必填的。 命名规范化是大多数 JSON 转 TS 工具的短板。JSON 键通常使用 snake_case(`user_id`、`created_at`)或 camelCase(`userId`、`createdAt`);TypeScript 惯例是接口名用 PascalCase(`UserProfile`、`AuditEntry`),属性名用 camelCase。页面将 JSON 键转换为 camelCase(如果已经是 camelCase 或单词则保留),接口名转换为 PascalCase。内部对象被提取为命名接口:`{ user: { name, age } }` 变为 `interface User { name: string; age: number; }`,父级变为 `interface Root { user: User; }`。递归结构(带有 `children: TreeNode[]` 字段的树节点)使用前置 `type` 声明而非 `interface` 实现自引用,以确保循环定义良好。 Root 开关决定了数组根与对象根的区别。JSON 数组变为 `type Root = Item[]` 或 `interface Root { items: Item[]; ... }`,取决于 wrap-array 开关。JSON 对象在顶层变为扁平接口。日期字符串(RFC 3339 / ISO 8601)可标记为 `Date` 而非 `string`;当严格模式开关打开时,UUID、email 和 URL 可标记为品牌类型(`type UUID = string & { readonly __brand: 'UUID' };`)。 JSDoc 生成是启发式的:名为 `email`(且值匹配基本 email 正则)的字段会在声明上方获得 `/** user email */` 注释。`id`、`name`、`url`、`created_at`/`createdAt`、`updated_at`/`updatedAt`、`description`、`title` 同理。注释是尽力而为的,不假装是人工编写的;它们是人工编辑的起点。页面还发出 `export` 修饰符,使输出可直接放入任何 TS 文件,并附带 `// generated by json-to-typescript, do not edit by hand` 头部以避免手动编辑在下次 Schema 重新生成时被覆盖。
- 原子类型映射:JSON 的 string/number/boolean/null 与 TypeScript 的 string/number/boolean/null 一一对应。大整数(>2^53)需要 BigInt;当值超过 Number.MAX_SAFE_INTEGER 时页面标记为 `bigint`。
- 数组类型推断:遍历所有元素取其类型的联合;相同类型折叠为 `T[]`,不同类型组成 `(A | B | C)[]`。空数组变为 `unknown[]`,因为元素类型未知。
- 嵌套对象提取:内部对象自动提升为命名接口。递归结构使用 `type Name = ...`(而非 `interface`)以确保在 TypeScript 类型检查器中前置引用定义良好。
- 可选字段检测:当多个样本对象合并时,在每个样本中都存在的字段成为必填;在任何样本中缺失的字段成为可选(`fieldName?: T`)。单样本输入标记所有字段为必填。
- 命名规范:JSON 键转换为 camelCase 作为属性名(user_id -> userId,created_at -> createdAt),接口名转换为 PascalCase(UserProfile、AuditEntry)。单词键保留不变;键中的数字保留;前导数字添加下划线前缀以保持有效的 TS 标识符。
- Root 开关:数组根 -> `export type Root = Item[]`(如果 wrap-array 开启则包装为 `{ items: Item[]; }`);对象根 -> 单个 `export interface Root { ... }`。递归结构先获得存根前置声明。
- JSDoc 注释:对已知字段名(email、id、name、url、createdAt、updatedAt、description、title)进行启发式注释生成。输出包含 `export` 修饰符和 `// generated, do not edit by hand` 头部以避免手动编辑。
- 严格模式:可选启用品牌类型(UUID、Email、URL、Date)、readonly 修饰符、exactOptionalPropertyTypes 和 noUncheckedIndexedAccess。开启严格模式时页面发出 `type UUID = string & { readonly __brand: 'UUID' };`,使下游代码可区分原始字符串和类型化值。
示例
简单对象转 interface
{"id": 1, "name": "Alice", "active": true}
->
interface User {
id: number;
name: string;
active: boolean;
}嵌套对象生成子接口
{"user": {"name": "Alice", "address": {"city": "NYC"}}}
->
interface Root {
user: User;
}
interface User {
name: string;
address: Address;
}
interface Address {
city: string;
}数组转 readonly 元组
[{"id": 1}, {"id": 2}]
->
interface Item {
id: number;
}
type Root = readonly Item[];常见问题
它会生成哪些 TypeScript 结构?
默认使用 interface——每个对象结构对应一个。可选字段使用 ? 标记。数组转为 T[]。混合类型的数组会变成联合类型。可识别格式的字符串(如 ISO 日期)默认仍为 string,除非启用品牌类型。部分页面提供 type 别名替代 interface 的选项。
嵌套对象是怎么处理的?
每个不同的嵌套结构都会生成独立的 interface,按属性路径命名(如 User、UserAddress、UserPreferences)。如果两个属性结构完全相同,是否去重取决于设置——请检查输出,必要时手动合并。
属性是必填还是可选?
默认情况下,样例中存在的属性都会被标为必填。出现在部分数组元素中而非全部时,会用 ? 标为可选。如果你的样例只是众多可能结构中的一种,可开启「全部可选」。
它如何处理 null 和 undefined?
JSON 中的 null 在 TypeScript 中仍为 null(而非 undefined)。某属性有时是 string,有时为 null,会变成 string | null。JSON 中没有 undefined,因此生成器不会直接输出 undefined。
联合类型和判别联合类型怎么处理?
当数组包含多种对象结构时,生成器会输出 interface 的联合类型。它不会自动识别判别字段;如果想要穷尽式类型收窄,请手动改写为判别联合(例如加一个 kind 标签)。
JSON 会被上传吗?
不会。转换通过基于 JS 的 JSON 解析器和字符串模板在浏览器内完成。粘贴的 JSON 不会离开你的设备。
生成结果会和我后端返回的数据一致吗?
只能与样例一致。请始终用 schema(Zod、io-ts、Yup)在运行时校验数据——TypeScript 类型在运行时会被擦除,无法捕获后端偏离样例的情况。