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 之前回報很多次。

本頁內容由英文自動翻譯而來,如發現錯誤,歡迎告訴我們。

相關指南