ToolPal
파란색과 주황색 구문 강조가 있는 어두운 화면의 TypeScript 코드

TypeScript 인터페이스를 직접 작성하는 시대는 끝났습니다: JSON 생성기를 사용하세요

📷 Roman Synkevych / Pexels

TypeScript 인터페이스를 직접 작성하는 시대는 끝났습니다: JSON 생성기를 사용하세요

JSON 페이로드에서 TypeScript 인터페이스를 손으로 작성하는 것은 지루하고 오류가 발생하기 쉽습니다. 자동화하는 방법과 주의할 점을 알아보세요.

D작성: Daniel Park2026년 4월 7일5분 소요

API에서 받은 JSON 페이로드에서 TypeScript 인터페이스를 손으로 20분 동안 작성해 본 적이 있다면, 이것이 중요하지만 거의 완전히 기계적인 작업 중 하나라는 것을 이미 알고 있습니다. 생각하는 것이 아닙니다. 단순히 필드 이름을 복사하고 타입을 추론하는 것뿐입니다. 기계가 해야 할 작업입니다.

그런데도 많은 개발자들이 여전히 손으로 합니다. 이 안내서는 그것을 멈출 가치가 있는 이유, JSON to TypeScript 생성기가 실제로 무엇을 생성하는지, 그리고 — 더 중요하게는 — 무엇을 할 수 없는지를 다루므로 어디서 이어받아야 할지 알 수 있습니다.

TypeScript 인터페이스가 실제로 중요한 이유

도구에 들어가기 전에, 타입이 지정된 인터페이스에서 "버그 잡기" 이상으로 무엇을 얻는지 정확히 알 가치가 있습니다.

IDE 자동 완성은 과소평가된 것입니다. API 응답에 타입이 지정되면, 편집기는 어떤 필드가 있고 중첩된 객체가 어떤 형태인지 정확히 알 수 있습니다. user.profilePicture인지 user.profile_picture인지 user.avatar인지 추측하는 것을 멈출 수 있습니다.

리팩터링이 훨씬 안전해집니다. 백엔드 팀이 필드 이름을 userId에서 user_id로 변경하면, 코드베이스에서 이전 이름을 참조하는 모든 곳이 런타임에서 사용자가 오류를 만나지 않고 컴파일 시간에 중단됩니다.

최신 상태를 유지하는 문서. 잘 명명된 인터페이스는 종종 문서보다 더 유용합니다. 필드가 있는 interface PaginatedOrderResponse는 자명합니다.

수동 프로세스 vs. 생성기 사용

중간 복잡도의 API 응답에 대해 손으로 타입을 작성하는 것은 이렇습니다:

{
  "user": {
    "id": 1042,
    "email": "alice@example.com",
    "name": "Alice Nguyen",
    "roles": ["admin", "editor"],
    "profile": {
      "bio": "Frontend developer based in Berlin.",
      "avatarUrl": "https://cdn.example.com/avatars/1042.png",
      "joinedAt": "2023-06-15T08:30:00Z"
    },
    "settings": {
      "theme": "dark",
      "notifications": {
        "email": true,
        "push": false
      }
    }
  },
  "meta": {
    "requestId": "abc-123",
    "timestamp": 1712486400
  }
}

같은 JSON을 JSON to TypeScript 도구에 붙여넣으면 1초 미만에 다음을 얻습니다:

export interface Root {
  user: User;
  meta: Meta;
}

export interface User {
  id: number;
  email: string;
  name: string;
  roles: string[];
  profile: Profile;
  settings: Settings;
}

export interface Profile {
  bio: string;
  avatarUrl: string;
  joinedAt: string;
}

export interface Settings {
  theme: string;
  notifications: Notifications;
}

export interface Notifications {
  email: boolean;
  push: boolean;
}

export interface Meta {
  requestId: string;
  timestamp: number;
}

RootUserResponse 같은 의미 있는 것으로 이름을 바꾸기만 하면 됩니다.

실제 사용 사례

API 응답 타입 지정

이것이 주요 사용 사례입니다. 타사 API — 결제 처리기, CRM, 날씨 서비스 — 와 통합하고 반환되는 것에 대한 타입이 필요합니다. 문서의 샘플 응답을 복사하고, 붙여넣고, 루트 인터페이스의 이름을 바꾸면 끝입니다.

구성 파일

앱이 런타임에 JSON 구성 파일을 읽는 경우, config.database.host에 접근하는 것이 잘못 입력했을 때 컴파일 시간에 실패하도록 일치하는 TypeScript 인터페이스를 원합니다.

// config.interface.ts (config.json에서 생성)
export interface AppConfig {
  database: Database;
  redis: Redis;
  featureFlags: FeatureFlags;
}

export interface Database {
  host: string;
  port: number;
  name: string;
  ssl: boolean;
}

테스트 데이터 모킹

테스트를 작성할 때, 종종 타입이 지정된 목 객체가 필요합니다. 실제 데이터에서 인터페이스를 생성한 다음, 그에 대해 타입이 지정된 목을 만드세요. TypeScript는 목에 필수 필드가 누락된 경우 알려줍니다.

까다로운 경우 처리

자동 생성은 일반적인 경우를 잘 처리합니다. 까다로운 경우는 판단이 필요합니다.

Null 값

JSON은 null을 값으로 허용하며, 생성기는 해당 필드를 null로 타입을 지정합니다. 그러나 실제로 null 필드는 보통 필드가 선택적이라는 것을 의미합니다.

다음을 보면:

interface User {
  middleName: null;
}

원하는 것은 아마도:

interface User {
  middleName: string | null;
}

날짜

JSON에는 기본 Date 타입이 없습니다. "2023-06-15T08:30:00Z"는 JSON이 관련하는 한 string이므로, 생성기는 그것을 string으로 타입을 지정합니다. 일부 팀은 type alias를 만듭니다:

type ISODateString = string;

차별적 유니온

때로 API는 type 또는 status 필드에 따라 다른 형태를 반환합니다:

{ "type": "success", "data": { "orderId": 123 } }
{ "type": "error", "code": "NOT_FOUND", "message": "Order not found" }

생성기는 이 패턴을 감지할 수 없으므로 스스로 타입을 작성해야 합니다:

type ApiResult = SuccessResult | ErrorResult;

interface SuccessResult {
  type: "success";
  data: OrderData;
}

interface ErrorResult {
  type: "error";
  code: string;
  message: string;
}

생성된 타입에 대한 모범 사례

루트 인터페이스에 의미 있는 이름 지정

생성기는 Root 또는 비슷한 이름을 지정합니다. 커밋하기 전에 항상 이름을 변경하세요. UserProfileResponse, CheckoutSessionPayload, ProductListItem — 찾을 수 있고 자명한 이름.

전용 파일에 타입 보관

일반적인 패턴:

src/
  types/
    api/
      user.types.ts
      order.types.ts
      product.types.ts
    config.types.ts

런타임 검증을 위한 Zod

TypeScript 타입은 런타임에 제거됩니다 — API가 실제로 예상치 못한 데이터를 반환하는 것으로부터 보호하지 않습니다. 런타임 보장이 필요하다면 Zod를 고려하세요:

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  name: z.string(),
});

type User = z.infer<typeof UserSchema>;

생성기가 할 수 없는 것

비즈니스 로직을 모릅니다. number로 타입이 지정된 필드가 실제로는 항상 양의 정수일 수 있습니다.

스냅샷으로 작동합니다. 생성된 타입은 하나의 데이터 샘플을 반영합니다. 실제 API는 발전합니다.

필수와 선택적을 구별할 수 없습니다. 샘플의 모든 필드가 필수로 처리됩니다.

모든 문자열을 문자열로 처리합니다. "pending", "active", "cancelled"만 포함하는 status 필드는 더 정확한 "pending" | "active" | "cancelled" 대신 string으로 타입이 지정됩니다.

이것들은 도구를 피해야 할 이유가 아닙니다 — 출력을 완성된 제품이 아닌 초안으로 취급해야 하는 이유입니다. 생성기는 지루한 작업의 80%를 처리합니다. 나머지 20%는 데이터의 실제 의미를 이해해야 합니다.

정리하기

대부분의 프로젝트에서 의미 있는 워크플로:

  1. API, 구성 또는 다른 소스에서 실제(또는 대표적인) JSON 샘플 가져오기
  2. JSON to TypeScript 생성기에 붙여넣기
  3. Root를 의미 있는 것으로 이름 바꾸기
  4. 출력을 적절한 *.types.ts 파일로 복사
  5. 각 필드 검토: 선택적 필드에 ? 표시, 필요한 경우 | null 추가, 적절한 경우 리터럴 문자열을 유니온 타입으로 변환
  6. 소스 JSON이 축소되어 읽기 어렵다면 JSON 포매터를 사용한 다음 타입 생성
  7. YAML 구성도 유지 중이라면 JSON to YAML 변환기가 동기화에 도움이 됩니다

목표는 순전히 기계적인 전사에 시간을 소비하지 않고 정확하고, 명명되고, 구성된 TypeScript 타입을 갖는 것입니다. 생성기가 기계적인 부분을 처리합니다. 데이터가 실제로 무엇을 의미하는지에 대한 맥락은 당신이 가져오세요.

손으로 작성한 타입이 본질적으로 더 나은 것은 아닙니다 — 단지 더 느리고 오타가 생길 가능성이 높습니다. 도구를 사용하고, 출력을 검토하고, 실제로 인간이 필요한 부분에 시간을 쏟으세요.

자주 묻는 질문

D

작성자

Daniel Park

서울에서 활동하는 시니어 프런트엔드 엔지니어. 국내 SaaS 회사들에서 7년간 웹 애플리케이션을 개발하며 개발자 도구, 웹 성능 최적화, 프라이버시 중심 설계에 집중해 왔습니다. JavaScript 생태계 오픈소스 기여자이자 ToolPal 창립자입니다.

더 알아보기

이 글 공유하기

XLinkedIn

관련 글