工具指南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 字段的真实类型。实用的做法是:

  1. 如果数组中有多个对象,同一字段在某些对象中是 null、在另一些中有值,用有值的类型做联合:string | null
  2. 如果只有一个样本且值为 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. 重命名接口

自动生成的名字通常是根据字段路径推导的(如 RootObjectDataList 等),不够语义化。改成业务相关的名字:

// 生成的
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 codegenGraphQL API自动同步 schema仅限 GraphQL
运行时类型校验(如 zod、io-ts)需要运行时校验编译时+运行时双重保证学习成本高

JSON 转 TypeScript 最适合”快速出活”的场景:你有一段 JSON,需要立刻拿到类型定义开始写代码。它不需要后端配合,不需要额外工具链,粘贴进去就有结果。

在线工具推荐

AnyFreeTools 的 JSON 转 TypeScript 工具 支持以下功能:

  • 粘贴 JSON 自动生成 TypeScript interface
  • 处理嵌套对象和数组
  • 识别 null 值并生成联合类型
  • 支持自定义根类型名称
  • 据工具页面说明,所有转换在浏览器本地完成,数据不会上传服务器

整个过程只需要三步:粘贴 JSON → 点击转换 → 复制结果。对于日常开发中频繁出现的类型定义需求,能省下不少时间。


本系列其他文章