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 }。
一个快速的检查清单
- 捕获一个丰富的真实样本并验证它。
- 生成与[JSON到TypeScript转换器](/tools/json的接口。
- 将类型更名为域语言。
- 修复可选与无效字段,字面结合和ID类型。
- 取而代之的是有歧视性的工会。
- 添加外部数据的运行时间验证。
- 当 OpenAPI 或 GraphQL 模式存在时切换到基于模式的生成。
按照这些步骤,每个终端需要几分钟,并且会在自动完成,更安全的调整器和被捕获的 bug 之前回报很多次。
本页内容由英文自动翻译而来,如发现错误,欢迎告诉我们。