Utilo

JSON到TypeScript接口:编写API响应的实用指南

学习如何将JSON转化为准确的TypeScript接口,处理可选字段,零值,联结和数组,并将类型与真正的API同步。

· 8 分钟阅读

每个与API交谈的TypeScript项目最终都会面临同样的问题:留下any的响应,就丢掉了TypeScript的主要优势。编写200行有效载荷的接口是繁的,容易出现错误。这本指南介绍了从JSON生成接口,完善并随着时间的推移保持其诚实性的可靠工作流程。

为什么JSON类型很重要

当你调用await res.json()时,TypeScript会给你any。从那时起,像user.adress.city这样的错误编译没有任何抱怨,并且只会在运行时失败,。在后端更名一个字段时,输入的响应会给你提供自动完成,安全的重构和编译时的错误。

类型也作为文档。阅读interface Order { id: string; items: OrderItem[]; total: number; couponCode?: string }的新团队成员在五秒钟内学到的更多,

步骤1:从一个真实,代表性的样本开始

产生的类型只有你输入的样本。在转换之前,收集尽可能多的字段的响应:

  • 选择一个填写可选字段的记录,例如一个有优惠券,发货地址和礼品信息的订单。
  • 包含至少一个元素的数组;一个空数组对生成器没有任何信息。
  • 如果一个字段可以是 null,试着包括一个为 null 的例子,一个为非 null 的例子。

复制响应体从浏览器的网络选项卡或curl中,将其粘贴到[JSON格式化器](/tools/json中,并确认其有效。然后将模式切换为**→TypeScript**。

步骤2:创建第一个草案

根据这个输入:

{
  "id": "ord_9f2",
  "createdAt": "2026-09-30T12:01:44Z",
  "customer": { "id": 42, "email": "ana@example.com" },
  "items": [
    { "sku": "TSHIRT-M", "qty": 2, "price": 19.5 },
    { "sku": "MUG", "qty": 1, "price": 12 }
  ],
  "coupon": null
}

一个发电机可以产生:

export interface Customer {
  id: number;
  email: string;
}

export interface Item {
  sku: string;
  qty: number;
  price: number;
}

export interface Root {
  id: string;
  createdAt: string;
  customer: Customer;
  items: Item[];
  coupon: null;
}

这是一个坚实的起点,但它只反映了一个样本所包含的内容。接下来的步骤将其转换为与API合同匹配的类型。

步骤3:更名和改进

生成器以键命名嵌套接口。将它们更名为域名:Root变成Order,Item变成OrderItem。然后用这些问题来回顾每一个字段。

如果API在某些响应中省略了一个密钥,请以?标记为可选。选择性 (coupon?: Coupon) 和无效性 (coupon: Coupon | null) 是不同的:第一个意味着密钥可能不存在,第二个意味着它总是存在,但可能持有null。Many APIs do both,which you express as coupon?: Coupon | null。

**null是唯一的值吗?**字面上输入为null的字段几乎总是意味着样本是不代表性的。取而代之的是真正的类型加上| null。

**一个字符串真的是一个字符串吗?**日期是ISO 8601字符串。在运输类型中将它们保存为string;转换为Date属于映射层。您可以用type ISODateString = string的别名来记录意图。

**一个数字是数?**如果status总是几个值中的一个,使用字母的联合:status: "pending" | "paid" | "shipped"。这样可以彻底检查switch。

**ID作为数字安全吗?**数据库ID超过2^53在JavaScript中失去了精度。如果您的后端使用64位整数,请它以字符串形式发送ID,并将其输入为string。

精细的结果是:

export type ISODateString = string;

export interface Order {
  id: string;
  createdAt: ISODateString;
  status: "pending" | "paid" | "shipped" | "cancelled";
  customer: Customer;
  items: OrderItem[];
  coupon?: Coupon | null;
}

步骤4:处理含有混合内容的数组

一些API返回异构数组,例如具有不同事件类型的活动源。一个简单的发电机会产生一个单独的接口,。一个没有歧视的工会更好:

type FeedEvent =
  | { type: "comment"; id: string; text: string }
  | { type: "like"; id: string; userId: string }
  | { type: "follow"; id: string; followerId: string };

现在TypeScript自动缩小if (event.type === "comment")内部的类型。当转换混合数组时,工具会结合推断的项目类型;使用该输出作为提示,以手动设计分化联盟。

步骤 5:在运行时验证

在编译时TypeScript类型会消失。如果后端发生变化,代码仍然会得到新的形状,。对于跨越信任边界的数据,添加运行时验证,使用Zod,Valibot或ArkType等方案库,并从方案中导出静态类型:

import { z } from "zod";

const OrderSchema = z.object({
  id: z.string(),
  createdAt: z.string().datetime(),
  items: z.array(z.object({ sku: z.string(), qty: z.number().int(), price: z.number() })),
  coupon: z.object({ code: z.string() }).nullable().optional(),
});

export type Order = z.infer<typeof OrderSchema>;
const order = OrderSchema.parse(await res.json());

您仍然可以使用生成器启动:生成接口,然后将其逐个字段转换到模式中。

第六步:当真相存在时,选择真相来源

如果您的 API 发布了 OpenAPI (Swagger) 或 GraphQL 模式,请从中生成类型,而不是从样本中生成。像openapi-typescript和GraphQL代码生成器这样的工具产生了包括选项和编号在内的确切类型,并且它们可以在CI中运行,因此每当模式发生变化时都会更新类型。基于样本的生成可以用于第三方API,而无方案,快速原型,网络链接和旧服务。

接口或类型?

对于物体形状,interface和type几乎可以互换。接口可以扩展和合并,并且错误消息将它们命名,这对大型对象有帮助。类型别名对于联合体,元组和映射类型都需要。一个常见的惯例是对象形状的接口,对其他所有东西的类型别名。

常见的陷

  • 它感觉很安全,但强制无效检查到处,。只有当API实际上省略它们时,
  • 相信单个样本. 在最终确定选项之前,请检查文档或多个答案。
  • 混合运输和域类型. 保持一个原始的OrderDTO,反映JSON,并将其映射成一个域Order,包含真正的Date对象和计算的字段。
  • Forgetting about pagination wrappers. Many APIs wrap lists in { data: [...], nextCursor: "..." }。这是一个通用型号:interface Page<T> { data: T[]; nextCursor: string | null }。

一个快速的检查清单

  1. 捕获一个丰富的真实样本并验证它。
  2. 生成与[JSON到TypeScript转换器](/tools/json的接口。
  3. 将类型更名为域语言。
  4. 修复可选与无效字段,字面结合和ID类型。
  5. 取而代之的是有歧视性的工会。
  6. 添加外部数据的运行时间验证。
  7. 当 OpenAPI 或 GraphQL 模式存在时切换到基于模式的生成。

按照这些步骤,每个终端需要几分钟,并且会在自动完成,更安全的调整器和被捕获的 bug 之前回报很多次。

本页内容由英文自动翻译而来,如发现错误,欢迎告诉我们。

相关指南