openapi-to-typescript

openapi-to-typescript

熱門

將 OpenAPI 3.0 JSON/YAML 轉換為 TypeScript 介面與型別守衛。當使用者要求從 OpenAPI 產生型別、將 schema 轉換為 TS、建立 API 介面,或從 API 規格產生 TypeScript 型別時,應使用此技能。

2215星標
213分支
更新於 2026/3/5
SKILL.md
唯讀
名稱
openapi-to-typescript
描述

將 OpenAPI 3.0 JSON/YAML 轉換為 TypeScript 介面與型別守衛。當使用者要求從 OpenAPI 產生型別、將 schema 轉換為 TS、建立 API 介面,或從 API 規格產生 TypeScript 型別時,應使用此技能。

OpenAPI 轉 TypeScript

將 OpenAPI 3.0 規格轉換為 TypeScript 介面與型別守衛。

輸入: OpenAPI 檔案(JSON 或 YAML)
輸出: 包含介面與型別守衛的 TypeScript 檔案

使用時機

  • "從 openapi 產生型別"
  • "將 openapi 轉換為 typescript"
  • "建立 API 介面"
  • "從規格產生型別"

工作流程

  1. 要求提供 OpenAPI 檔案路徑(若未提供)
  2. 讀取並驗證檔案(必須是 OpenAPI 3.0.x)
  3. components/schemas 提取 schemas
  4. paths 提取端點(請求/回應型別)
  5. 產生 TypeScript(介面 + 型別守衛)
  6. 詢問儲存位置(預設:目前目錄下的 types/api.ts
  7. 寫入檔案

OpenAPI 驗證

處理前檢查:

- 必須存在 "openapi" 欄位,且開頭為 "3.0"
- 必須存在 "paths" 欄位
- 若存在型別,則必須存在 "components.schemas" 欄位

若無效,回報錯誤並停止。

型別對應

基本型別

OpenAPI TypeScript
string string
number number
integer number
boolean boolean
null null

格式修飾

格式 TypeScript
uuid string(註解 UUID)
date string(註解 date)
date-time string(註解 ISO)
email string(註解 email)
uri string(註解 URI)

複合型別

物件:

// OpenAPI: type: object, properties: {id, name}, required: [id]
interface Example {
  id: string;      // 必要:無 ?
  name?: string;   // 選填:有 ?
}

陣列:

// OpenAPI: type: array, items: {type: string}
type Names = string[];

列舉:

// OpenAPI: type: string, enum: [active, draft]
type Status = "active" | "draft";

oneOf(聯集):

// OpenAPI: oneOf: [{$ref: Cat}, {$ref: Dog}]
type Pet = Cat | Dog;

allOf(交集/繼承):

// OpenAPI: allOf: [{$ref: Base}, {type: object, properties: ...}]
interface Extended extends Base {
  extraField: string;
}

程式碼產生

檔案標頭

/**
 * 自動產生自:{source_file}
 * 產生時間:{timestamp}
 *
 * 請勿手動編輯 - 請從 OpenAPI schema 重新產生
 */

介面(來自 components/schemas)

針對 components/schemas 中的每個 schema:

export interface Product {
  /** 產品唯一識別碼 */
  id: string;

  /** 產品標題 */
  title: string;

  /** 產品價格 */
  price: number;

  /** 建立時間戳記 */
  created_at?: string;
}
  • 使用 OpenAPI 的 description 作為 JSDoc
  • required[] 中的欄位不加 ?
  • 不在 required[] 中的欄位加 ?

請求/回應型別(來自 paths)

針對 paths 中的每個端點:

// GET /products - 查詢參數
export interface GetProductsRequest {
  page?: number;
  limit?: number;
}

// GET /products - 回應 200
export type GetProductsResponse = ProductList;

// POST /products - 請求主體
export interface CreateProductRequest {
  title: string;
  price: number;
}

// POST /products - 回應 201
export type CreateProductResponse = Product;

命名慣例:

  • {Method}{Path}Request 用於參數/主體
  • {Method}{Path}Response 用於回應

型別守衛

針對每個主要介面,產生型別守衛:

export function isProduct(value: unknown): value is Product {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    typeof (value as any).id === 'string' &&
    'title' in value &&
    typeof (value as any).title === 'string' &&
    'price' in value &&
    typeof (value as any).price === 'number'
  );
}

型別守衛規則:

  • 檢查 typeof value === 'object' && value !== null
  • 對每個必要欄位:檢查 'field' in value
  • 對基本型別欄位:檢查 typeof
  • 對陣列:檢查 Array.isArray()
  • 對列舉:檢查 .includes()

錯誤型別(一律包含)

export interface ApiError {
  status: number;
  error: string;
  detail?: string;
}

export function isApiError(value: unknown): value is ApiError {
  return (
    typeof value === 'object' &&
    value !== null &&
    'status' in value &&
    typeof (value as any).status === 'number' &&
    'error' in value &&
    typeof (value as any).error === 'string'
  );
}

$ref 解析

遇到 {"$ref": "#/components/schemas/Product"} 時:

  1. 提取 schema 名稱(Product
  2. 直接使用該型別(不內聯解析)
// OpenAPI: items: {$ref: "#/components/schemas/Product"}
// TypeScript:
items: Product[]  // 引用,非內聯

完整範例

輸入(OpenAPI):

{
  "openapi": "3.0.0",
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "id": {"type": "string", "format": "uuid"},
          "email": {"type": "string", "format": "email"},
          "role": {"type": "string", "enum": ["admin", "user"]}
        },
        "required": ["id", "email", "role"]
      }
    }
  },
  "paths": {
    "/users/{id}": {
      "get": {
        "parameters": [{"name": "id", "in": "path", "required": true}],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/User"}
              }
            }
          }
        }
      }
    }
  }
}

輸出(TypeScript):

/**
 * 自動產生自:api.openapi.json
 * 產生時間:2025-01-15T10:30:00Z
 *
 * 請勿手動編輯 - 請從 OpenAPI schema 重新產生
 */

// ============================================================================
// 型別
// ============================================================================

export type UserRole = "admin" | "user";

export interface User {
  /** UUID */
  id: string;

  /** Email */
  email: string;

  role: UserRole;
}

// ============================================================================
// 請求/回應型別
// ============================================================================

export interface GetUserByIdRequest {
  id: string;
}

export type GetUserByIdResponse = User;

// ============================================================================
// 型別守衛
// ============================================================================

export function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    typeof (value as any).id === 'string' &&
    'email' in value &&
    typeof (value as any).email === 'string' &&
    'role' in value &&
    ['admin', 'user'].includes((value as any).role)
  );
}

// ============================================================================
// 錯誤型別
// ============================================================================

export interface ApiError {
  status: number;
  error: string;
  detail?: string;
}

export function isApiError(value: unknown): value is ApiError {
  return (
    typeof value === 'object' &&
    value !== null &&
    'status' in value &&
    typeof (value as any).status === 'number' &&
    'error' in value &&
    typeof (value as any).error === 'string'
  );
}

常見錯誤

錯誤 處理方式
OpenAPI 版本不是 3.0.x 回報僅支援 3.0
$ref 找不到 列出遺失的引用
未知型別 使用 unknown 並發出警告
循環引用 使用型別別名搭配延遲引用