چطور پاسخ API را در TypeScript تایپ کنیم

fetch داده را با تایپ any به شما می‌دهد، و این یعنی TypeScript دقیقاً همان‌جایی خاموش می‌شود که باگ‌ها از آن می‌آیند. در این راهنما می‌بینید چطور سریع و امن به داده‌های API تایپ واقعی بدهید.

همین حالا امتحان کنید: تبدیل JSON به TypeScript تبدیل JSON به تایپ

TypeScript کد شما را بررسی می‌کند، اما نمی‌تواند داده‌ای را که در زمان اجرا می‌رسد بررسی کند. به‌محض اینکه await response.json() را صدا می‌زنید، مقداری از نوع any می‌گیرید، و از آن به بعد کامپایلر اجازه می‌دهد user.adress.city را با غلط املایی بخوانید، بی‌آنکه اعتراضی کند. تایپ کردن پاسخ‌های API همین شکاف را می‌بندد. این راهنما نشان می‌دهد چطور تایپ‌ها را سریع از روی یک نمونه بنویسید، بخش‌های دردسرساز را چطور مدل کنید، و کجا تایپ به‌تنهایی کافی نیست.

مرحلهٔ ۱: تایپ‌ها را از یک نمونهٔ واقعی بسازید

نوشتن دستی interface برای یک پاسخ بزرگ کند است و پر از خطا. به‌جای آن یک پاسخ واقعی را کپی کنید، مثلاً از تب Network مرورگر یا از مستندات API، و آن را در تبدیل JSON به TypeScript بچسبانید. پاسخی مثل این:

{
  "id": 1024,
  "name": "Ada",
  "tags": ["admin", "beta"],
  "address": { "city": "London", "zip": "N1 9GU" },
  "orders": [
    { "order_id": "A-17", "total": 42, "shipped_at": "2026-09-01T10:00:00Z" },
    { "order_id": "A-18", "total": 18.5, "shipped_at": null, "coupon": "WELCOME" }
  ]
}

وقتی نام ریشه را روی User بگذارید، به این تبدیل می‌شود:

export interface Address {
  city: string;
  zip: string;
}

export interface Order {
  order_id: string;
  total: number;
  shipped_at: string | null;
  coupon?: string;
}

export interface User {
  id: number;
  name: string;
  tags: string[];
  address: Address;
  orders: Order[];
}

ببینید مولد از روی دو سفارش چه چیزهایی را فهمیده است: shipped_at در یکی از آیتم‌ها null بود، پس تایپش string | null شده؛ coupon در یکی از آیتم‌ها وجود نداشت، پس اختیاری شده. شیءهای تودرتو هر کدام interface نام‌دار خودشان را گرفته‌اند و آیتم‌های orders به Order تبدیل شده‌اند. اگر این کار را دستی انجام می‌دادید، باید همهٔ آیتم‌های آرایه را یکی‌یکی با هم مقایسه می‌کردید تا ببینید کدام کلید همیشه هست و کدام نه. همهٔ این کارها داخل مرورگر انجام می‌شود، پس پاسخی که داده‌های واقعی مشتری‌ها را دارد به جایی فرستاده نمی‌شود.

مرحلهٔ ۲: تایپ را با fetch به کار ببرید

ساده‌ترین راه وصل کردن تایپ به داده، یک تابع کمکی کوچک است:

async function getJson<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return (await res.json()) as T;
}

const user = await getJson<User>('/api/users/1024');
user.address.city;   // string, checked by the compiler

عبارت as T قولی است که شما به کامپایلر می‌دهید، نه یک بررسی. اگر سرور چیز دیگری بفرستد، TypeScript متوجه نمی‌شود. برای APIهای داخلی که کنترلشان دست خودتان است، این مصالحه اغلب قابل قبول است؛ برای هر چیز دیگری، ادامه را تا مرحلهٔ ۴ بخوانید. به زبان ساده، as T فقط برچسبی است که روی داده می‌زنید؛ کامپایلر از آن به بعد به این برچسب اعتماد می‌کند، اما خود داده در زمان اجرا همان چیزی است که سرور فرستاده، چه با برچسب جور باشد چه نباشد.

مرحلهٔ ۳: بخش‌های دردسرساز را درست مدل کنید

در JSONدر TypeScriptچرا
کلیدی که گاهی وجود نداردcoupon?: stringاختیاری: ممکن است این ویژگی اصلاً وجود نداشته باشد
مقداری که گاهی null استshipped_at: string | nullNullable: ویژگی وجود دارد اما می‌تواند خالی باشد
هر دو حالتnote?: string | nullممکن است API آن را حذف کند یا null بفرستد
تاریخstringJSON نوع داده‌ای برای تاریخ ندارد؛ هر جا لازم بود با new Date() تبدیلش کنید
شناسهٔ بزرگstring، اگر API آن را داخل گیومه می‌فرستدعددهای بزرگ‌تر از 2^53 در جاوااسکریپت دقتشان را از دست می‌دهند
آرایهٔ خالی در نمونهunknown[]نمونه نشان نمی‌دهد آیتم‌ها چه شکلی دارند

برای هر چیزی که نمونه درباره‌اش چیزی نمی‌گوید، unknown را به any ترجیح دهید: unknown شما را وادار می‌کند پیش از استفاده بررسی کنید، اما any بی‌سروصدا بررسی را خاموش می‌کند. تایپی که ساخته می‌شود فقط به اندازهٔ نمونه‌اش کامل است، پس از پاسخی استفاده کنید که فیلدهای اختیاری را هم دارد، یا آرایه‌ای با چند آیتم متنوع.

مرحلهٔ ۴: وقتی داده مال شما نیست، در زمان اجرا اعتبارسنجی کنید

APIهای شخص ثالث تغییر می‌کنند، پراکسی‌ها صفحهٔ خطای HTML برمی‌گردانند و کلاینت‌های قدیمی با سرورهای جدید روبه‌رو می‌شوند. بررسی در زمان اجرا این موارد را درست در مرز ورود داده و با یک خطای روشن می‌گیرد، نه جایی در عمق رابط کاربری. دو روش رایج:

  • JSON Schema: از همان نمونه با ساخت JSON Schema یک اسکیما بسازید و با کتابخانه‌ای مثل Ajv اعتبارسنجی کنید. اسکیما به زبان برنامه‌نویسی خاصی وابسته نیست، پس بک‌اندی که به زبان دیگری نوشته شده هم می‌تواند از همان فایل استفاده کند.
  • اعتبارسنج مخصوص TypeScript: کتابخانه‌هایی مثل Zod اجازه می‌دهند ساختار داده را یک بار توصیف کنید و تایپ TypeScript را از همان توصیف استخراج کنید، تا تایپ و بررسی هیچ‌وقت از هم فاصله نگیرند.

در هر دو حالت، یک بار و همان جایی که داده وارد برنامه می‌شود اعتبارسنجی کنید و بگذارید بقیهٔ کد به تایپ تکیه کند. به این ترتیب بررسی فقط در یک نقطه انجام می‌شود و بقیهٔ توابع لازم نیست هر بار دوباره شکل داده را وارسی کنند.

وقتی API تغییر می‌کند

تایپ‌هایی که یک بار ساخته شده‌اند، با رشد API کم‌کم از واقعیت فاصله می‌گیرند. نمونه‌ای را که تایپ‌ها را از روی آن ساخته‌اید، کنار تایپ‌ها در مخزن کد نگه دارید تا نفر بعدی بتواند دوباره آن‌ها را بسازد و تغییرات را در یک diff ببیند. اگر API توصیف OpenAPI منتشر می‌کند، ساختن تایپ از روی آن قابل‌اعتمادتر از ساختن از روی نمونه است، چون همهٔ فیلدهای اختیاری و همهٔ مقدارهای ممکن را فهرست می‌کند، از جمله آن‌هایی که هیچ پاسخ منفردی نشانشان نمی‌دهد. برای APIهایی که چنین توصیفی ندارند، اعتبارسنج زمان اجرا از مرحلهٔ ۴ یک ناهماهنگی بی‌صدا را به خطایی تبدیل می‌کند که در لاگ‌ها دیده می‌شود.

interface یا type؟

برای داده‌های API هر دو کار می‌کنند. interfaceها را می‌شود گسترش داد و اگر دو بار تعریف شوند با هم ادغام می‌شوند، که برخی تیم‌ها برای تایپ‌های عمومی می‌پسندند؛ type aliasها علاوه بر این می‌توانند union و mapped type را هم بیان کنند. برای پروژه یک سبک انتخاب کنید و به آن پایبند بمانید. مولد هر دو را می‌سازد.

زبان‌های دیگر از همان نمونه

همان نمونهٔ JSON را می‌شود در تبدیل JSON به تایپ به ساختارهای Go، مدل‌های Pydantic یا کلاس‌های C# هم تبدیل کرد، که کمک می‌کند فرانت‌اند و بک‌اند روی یک ساختار داده بمانند. اگر خود نمونه خراب است، فرمت‌کنندهٔ JSON نشان می‌دهد مشکل کجاست.

سوالات متداول

چطور از JSON تایپ TypeScript بسازم؟

یک نمونهٔ واقعی از JSON را در یک مبدل JSON به TypeScript بچسبانید. مبدل برای هر شیء یک interface می‌سازد و فیلدهای اختیاری و nullable را از روی نمونه تشخیص می‌دهد.

چرا تایپ res.json() برابر any است؟

چون TypeScript در زمان کامپایل نمی‌تواند بداند سرور چه چیزی خواهد فرستاد. تایپ نتیجه را خودتان تعیین می‌کنید، با یک تابع کمکی جنریک یا یک اعتبارسنج.

آیا تایپ TypeScript داده را اعتبارسنجی می‌کند؟

خیر. تایپ‌ها هنگام کامپایل کد حذف می‌شوند. برای بررسی داده در زمان اجرا، از یک اعتبارسنج JSON Schema یا کتابخانه‌ای مثل Zod استفاده کنید.

از فیلد اختیاری استفاده کنم یا null؟

مطابق API عمل کنید: وقتی ممکن است کلید وجود نداشته باشد از علامت سؤال استفاده کنید، و وقتی کلید با مقدار null حاضر است از union با null.

تاریخ‌های داخل JSON را چه تایپی بدهم؟

string، چون JSON نوع داده‌ای برای تاریخ ندارد. هر جا به محاسبات تاریخ نیاز دارید، آن‌ها را به شیء Date تبدیل کنید.
همین حالا امتحان کنید: تبدیل JSON به TypeScript تبدیل JSON به تایپ