工具指南11-JSON转TypeScript类型生成器
拿到一个 API 返回的 JSON,第一件事是什么?写类型定义。
如果你用 TypeScript,这个步骤几乎无法跳过。没有类型定义,编辑器不给补全,拼错字段名要到运行时才会发现。但手写类型定义是件苦差事——特别是面对嵌套三四层、字段几十个的 JSON 数据时,机械地把每个字段的类型抄一遍,既无聊又容易出错。
这篇文章聊聊 JSON 到 TypeScript 类型的自动转换:它解决什么问题,背后的类型推断怎么工作,以及实际项目中怎么把这个流程跑顺。
手写类型定义的问题
先看一个典型场景。后端返回一个用户列表接口:
{
"code": 0,
"message": "success",
"data": {
"total": 156,
"page": 1,
"pageSize": 20,
"list": [
{
"id": 10234,
"name": "张三",
"email": "zhangsan@example.com",
"role": "admin",
"createdAt": "2026-01-15T08:30:00Z",
"profile": {
"avatar": "https://cdn.example.com/avatars/10234.jpg",
"bio": "全栈开发者",
"social": {
"github": "zhangsan",
"twitter": null
}
},
"permissions": ["read", "write", "admin"]
}
]
}
}
要给这段 JSON 手写完整的 TypeScript 类型,你需要:
interface Social {
github: string;
twitter: string | null;
}
interface Profile {
avatar: string;
bio: string;
social: Social;
}
interface User {
id: number;
name: string;
email: string;
role: string;
createdAt: string;
profile: Profile;
permissions: string[];
}
interface UserListData {
total: number;
page: number;
pageSize: number;
list: User[];
}
interface UserListResponse {
code: number;
message: string;
data: UserListData;
}
五个 interface,二十多个字段。这还只是一个接口。一个中型项目通常有几十个 API,手写全部类型定义可能需要大半天。更麻烦的是,后端改了字段你还得同步更新,漏改一个就是线上 bug。
类型推断的实现原理
JSON 转 TypeScript 的核心是类型推断——根据 JSON 值的实际内容,推导出对应的 TypeScript 类型。
基础类型映射
最直接的部分是基础类型:
JSON 值 → TypeScript 类型
"hello" → string
42 → number
true → boolean
null → null
这一步没什么悬念。typeof 一下就知道了。
对象推断
遇到 JSON 对象时,递归处理每个字段:
// 推断逻辑的简化版本
function inferType(value: unknown): string {
if (value === null) return "null";
if (Array.isArray(value)) return inferArrayType(value);
if (typeof value === "object") return inferObjectType(value);
return typeof value; // "string" | "number" | "boolean"
}
// 返回类型描述字符串(TypeScript 类型的文本表示)
function inferObjectType(obj: Record<string, unknown>): string {
const fields = Object.entries(obj).map(
([key, val]) => ` ${key}: ${inferType(val)};`
);
return `{\n${fields.join("\n")}\n}`;
}
对象推断本身不难,难的是命名。JSON 的 key 天然就是字段名,但嵌套对象的类型名需要额外生成。常见策略是用父字段名作为类型名的前缀,比如 profile 字段对应 Profile 接口,profile.social 对应 Social 接口。
数组推断
数组是最容易出问题的地方。考虑几种情况:
// 情况1: 简单数组
["read", "write", "admin"] → string[]
// 情况2: 对象数组
[{"id": 1, "name": "a"}, {"id": 2, "name": "b"}] → Item[]
// 情况3: 空数组
[] → unknown[]
// 情况4: 混合类型数组(实际 API 中很少出现,但工具需要处理)
[1, "hello", true] → (number | string | boolean)[]
空数组是个经典难题:没有元素就无法推断类型,只能退化为 unknown[]。这种情况下需要手动补充类型信息。
对象数组需要合并所有元素的字段来生成类型。如果不同元素的字段不完全一致(实际数据中很常见),还要处理可选字段:
// 元素1: { id: 1, name: "张三", email: "a@b.com" }
// 元素2: { id: 2, name: "李四" }
// 合并结果:
interface Item {
id: number;
name: string;
email?: string; // 只在部分元素中出现,标记为可选
}
null 处理
JSON 中的 null 值需要特殊处理。看到 "twitter": null,你不能简单地推断为 twitter: null——它大概率应该是 twitter: string | null,表示这个字段有值时是字符串,没值时是 null。
但仅凭单个 JSON 样本,你无法确定 null 字段的真实类型。实用的做法是:
- 如果数组中有多个对象,同一字段在某些对象中是 null、在另一些中有值,用有值的类型做联合:
string | null - 如果只有一个样本且值为 null,生成
unknown | null或直接any,让开发者手动修正
实际使用场景
场景一:对接新 API
最常见的用途。后端给了接口文档或者你直接调了一下拿到返回值,粘贴到在线转换工具里,一秒生成类型定义。
流程对比:
手动方式:
1. 看 JSON 结构
2. 逐个字段写 interface
3. 处理嵌套对象
4. 检查类型是否正确
耗时: 5-15 分钟 / 个接口
自动方式:
1. 粘贴 JSON
2. 复制生成的类型
3. 根据业务需要微调(重命名、加注释)
耗时: 1-2 分钟 / 个接口
中型项目 30 个接口,差距就是几小时的工作量。
场景二:处理第三方 SDK 的 JSON 配置
很多第三方服务的 SDK 配置是 JSON 格式,但不提供 TypeScript 类型。把配置示例粘贴进工具,就能得到类型定义:
{
"apiKey": "your-api-key",
"region": "us-east-1",
"retries": 3,
"timeout": 5000,
"features": {
"analytics": true,
"logging": false,
"cache": {
"enabled": true,
"ttl": 3600,
"maxSize": 1000
}
}
}
生成结果:
interface Cache {
enabled: boolean;
ttl: number;
maxSize: number;
}
interface Features {
analytics: boolean;
logging: boolean;
cache: Cache;
}
interface RootObject {
apiKey: string;
region: string;
retries: number;
timeout: number;
features: Features;
}
拿到类型后稍微改改名字(把 RootObject 改成 SDKConfig),就可以直接用了。
场景三:Mock 数据类型同步
前后端并行开发时,前端经常需要写 mock 数据。先用 JSON 定义 mock 结构,再用工具生成对应的类型,可以保证 mock 数据和类型定义始终一致:
// mock.json → 生成类型
import type { UserListResponse } from "./types";
// mock 数据直接满足类型约束
const mockData: UserListResponse = {
code: 0,
message: "success",
data: {
total: 2,
page: 1,
pageSize: 20,
list: [
// TypeScript 会校验每个字段
],
},
};
生成结果的优化建议
工具生成的类型定义能直接用,但在正式项目中建议做几步优化:
1. 重命名接口
自动生成的名字通常是根据字段路径推导的(如 RootObject、DataList 等),不够语义化。改成业务相关的名字:
// 生成的
interface RootObject { ... }
interface DataList { ... }
// 优化后
interface ApiResponse<T> { ... }
interface UserListData { ... }
2. 提取公共类型
多个接口经常共享相同的外层结构(code + message + data),把它抽成泛型:
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 各接口只定义 data 部分
type UserListResponse = ApiResponse<UserListData>;
type OrderDetailResponse = ApiResponse<OrderDetail>;
3. 细化 string 类型
自动推断只能给出 string,但实际业务中很多字符串字段是枚举值:
// 生成的
interface User {
role: string;
status: string;
}
// 优化后
type UserRole = "admin" | "editor" | "viewer";
type UserStatus = "active" | "inactive" | "banned";
interface User {
role: UserRole;
status: UserStatus;
}
4. 加上 JSDoc 注释
类型定义加注释,编辑器悬停时能直接看到字段含义:
interface User {
/** 用户唯一ID */
id: number;
/** 显示名称 */
name: string;
/** 注册邮箱 */
email: string;
/** 账号创建时间 (ISO 8601) */
createdAt: string;
}
与其他方案的对比
JSON 转 TypeScript 不是唯一的类型生成方案,简单对比一下:
| 方案 | 适用场景 | 优点 | 局限 |
|---|---|---|---|
| JSON 转 TypeScript | 已有 JSON 样本 | 零配置,粘贴即用 | 依赖样本质量 |
| OpenAPI/Swagger 生成 | 有 API 规范文档 | 类型最准确 | 需要后端维护文档 |
| GraphQL codegen | GraphQL API | 自动同步 schema | 仅限 GraphQL |
| 运行时类型校验(如 zod、io-ts) | 需要运行时校验 | 编译时+运行时双重保证 | 学习成本高 |
JSON 转 TypeScript 最适合”快速出活”的场景:你有一段 JSON,需要立刻拿到类型定义开始写代码。它不需要后端配合,不需要额外工具链,粘贴进去就有结果。
在线工具推荐
AnyFreeTools 的 JSON 转 TypeScript 工具 支持以下功能:
- 粘贴 JSON 自动生成 TypeScript interface
- 处理嵌套对象和数组
- 识别 null 值并生成联合类型
- 支持自定义根类型名称
- 据工具页面说明,所有转换在浏览器本地完成,数据不会上传服务器
整个过程只需要三步:粘贴 JSON → 点击转换 → 复制结果。对于日常开发中频繁出现的类型定义需求,能省下不少时间。
本系列其他文章:
- 工具指南1-在线图片压缩 - 图片压缩原理与实践
- 工具指南2-在线JSON格式化工具 - JSON 格式化与校验
- 工具指南3-在线正则表达式测试 - 正则表达式调试技巧
- 工具指南4-二维码生成工具 - 二维码生成与定制
- 工具指南5-Base64编解码工具 - Base64 编解码原理
- 工具指南6-JWT在线解码工具 - JWT 结构与安全
- 工具指南7-Unix时间戳转换工具 - 时间戳转换详解
- 工具指南8-在线密码生成器 - 密码生成与安全
- 工具指南9-URL编解码工具 - URL 编码规范
- 工具指南10-在线哈希生成器 - 哈希函数原理与应用