TypeScript Strict Mode

JSON → TypeScript Interface: 계약을 명확히 하는 방법

API 응답 JSON을 붙여 넣어 즉시 TypeScript 인터페이스를 만드는 방법을 정리했습니다. optional/readonly 규칙, union 타입, nullability를 어떻게 다뤄야 하는지 실제 코드와 함께 확인하세요.

변환기로 이동 Strict · Optional · Union aware

TypeScript로 변환하는 이유

프런트엔드 팀은 런타임 데이터 구조를 컴파일 타임에 검증하기 위해 인터페이스를 사용합니다. JSON2Class는 붙여 넣은 JSON을 기반으로 필드를 optional/required로 구분하고, readonly 여부까지 고려해 안전한 타입을 만들어 줍니다.

React/Vue/Svelte 어떤 스택이든, 명확한 인터페이스는 API 변경을 빠르게 감지하게 해줍니다. 계약이 깨질 때는 타입 오류가 발생하므로, QA 단계나 CI에서 문제를 미리 발견할 수 있습니다.

팀 내 다른 언어(Java/Kotlin) 모델과 비교하며 작성하면, 협업 시 동일한 계약을 공유하게 되어 클라이언트/서버 간 분기 처리를 줄일 수 있습니다. 가벼운 실험이라도 인터페이스를 먼저 정의해 두면 리팩터링 비용이 크게 줄어듭니다.

자주 쓰는 사용 사례

  1. 상태 관리: 서버 상태를 Zustand, Redux, Vuex 등과 동기화할 때 인터페이스로 스키마를 고정.
  2. 폼 바인딩: 폼 데이터와 API 요청/응답 타입을 동일하게 유지해 유효성 검사와 자동완성을 단순화.
  3. API 클라이언트 생성: fetch/Axios 래퍼 함수의 반환 타입을 인터페이스로 지정해 호출부 자동완성 강화.
  4. 테스트: Mock 서버나 MSW 핸들러에서 JSON 샘플을 재사용하고, 인터페이스로 스키마를 보장.

JSON → TypeScript 변환 예시

다음 JSON은 optional과 readonly가 섞여 있는 전형적인 응답입니다.

{
  "id": "prod-9301",
  "name": "Noise cancelling headset",
  "price": 129.9,
  "inStock": true,
  "badges": ["hot", "giftable"],
  "vendor": {
    "name": "Acme",
    "rating": 4.6
  }
}
export interface Product {
  readonly id: string;
  name: string;
  price: number;
  inStock?: boolean;
  badges?: string[];
  vendor?: Vendor;
}

export interface Vendor {
  name?: string;
  rating?: number;
}

기본 규칙은 다음과 같습니다: 값이 항상 존재하면 required, 응답마다 비거나 사라질 수 있으면 optional(`?`). 변환 후 readonly 여부는 팀 스타일에 맞게 조정하세요. 상태를 불변으로 유지하고 싶다면 모든 필드에 readonly를 추가하는 것도 좋습니다.

타입 매핑과 세부 설정

  • 문자열 → `string`, 숫자 → `number`, 불리언 → `boolean`, 배열 → `T[]`, 객체 → 별도 인터페이스.
  • union이 의심되는 필드는 `string | number`처럼 직접 선언하거나, `type Status = "ready" | "waiting" | "fail";`로 좁힙니다.
  • null과 undefined를 구분하려면 `value: string | null`과 `value?: string`을 달리 표현하세요.
  • Date를 문자열로 받는다면 `string`으로 두고, 런타임에서 dayjs/date-fns로 파싱하는 패턴이 안전합니다.

팀에서 zod/yup 같은 런타임 스키마를 쓴다면, 생성된 인터페이스를 기준으로 스키마를 작성해도 되고, 반대로 스키마에서 타입을 추론해 인터페이스를 유지할 수도 있습니다. 중요한 것은 소스가 하나라는 점입니다.

실무 팁과 안티패턴

  • any 남용 금지: 임시로 `any`를 쓰면 타입 검증이 무력화됩니다. 가능한 좁은 타입을 유지하세요.
  • 선언 병합 주의: 전역 인터페이스 이름이 충돌하지 않도록 네임스페이스나 파일 모듈을 활용하세요.
  • 에러 응답 모델: 성공/실패 응답이 다른 구조라면 `type ApiResponse = Success | Failure;` 형태의 union으로 모델링합니다.
  • pagination 재사용: `Paginated` 제네릭을 만들어 어디서나 같은 응답 형태를 공유하세요.

또한, 비동기 함수의 반환 타입을 명시적으로 작성하면 `await` 사용 시 자동완성과 오류 검출이 훨씬 수월해집니다. Axios/Fetch 래퍼에서 인터페이스를 반환 타입으로 지정해 두세요.

런타임 검증과 스키마

타입은 컴파일 시점에만 존재합니다. 런타임에도 검증이 필요하다면 zod, yup, ajv를 통해 JSON 스키마를 정의하거나, 생성된 인터페이스를 바탕으로 스키마를 작성하세요. 런타임 검증 결과를 에러 바운더리나 토스트로 노출하면 사용자 경험을 해치지 않으면서도 안정성을 높일 수 있습니다.

백엔드가 OpenAPI를 제공한다면 코드젠 도구를 사용하고, 제공하지 않는 경우 JSON2Class로 만든 인터페이스를 역으로 공유해 계약을 협의하는 방식도 효과적입니다.

성능, 번들, 유지보수 고려사항

  • 거대한 응답을 모두 메모리에 담기보다 필요한 필드만 선언해 두고, 나머지는 `Partial`로 관리하세요.
  • 프런트엔드 번들 크기가 중요하다면, 타입 정의는 빌드 시 제거되므로 걱정할 필요가 없습니다. 다만 런타임 스키마 라이브러리는 트리쉐이킹 여부를 확인하세요.
  • 폴더 구조는 `/models/api`, `/models/view`처럼 API 모델과 화면 모델을 분리하면 추후 도메인 개편 시 대응이 쉽습니다.

테스트와 협업을 위한 체크리스트

대규모 팀에서는 타입 정의가 곧 커뮤니케이션 수단입니다. 백엔드가 Java/Kotlin, 프런트엔드가 TypeScript라면 동일한 JSON 샘플을 공유해\n+ 각 언어에서 생성된 모델을 비교하고, PR에 링크를 남기세요. 디자이너나 PO가 읽기 쉬운 Markdown 표를 함께 제공하면 요구사항 조율이 간단해집니다.

  1. 변환 결과를 팀 위키나 Storybook docs 탭에 붙여, UI와 API 계약을 한 번에 설명한다.
  2. MSW/Mock Service Worker 핸들러에서 동일 JSON을 사용해 스키마가 어긋나면 테스트가 실패하도록 설정한다.
  3. 백엔드 팀이 Java/Kotlin을 쓴다면, 동일 JSON을 각 언어 탭에 붙여 DTO와 인터페이스를 동시에 비교한다.
  4. 필드가 빠질 때 UI가 어떻게 반응해야 하는지(placeholder, skeleton, fallback)를 명시적으로 결정한다.

이 체크리스트를 지키면 API 변화에도 프런트엔드가 견고해집니다. 모든 스키마 변동은 타입 에러나 테스트 실패로 곧바로 드러나므로, 배포 전에 대응할 시간을 벌 수 있습니다.

JSON2Class는 브라우저에서만 실행되어 입력 데이터가 전송되지 않습니다. 민감한 샘플은 익명화한 뒤 실험하고, 확정된 인터페이스를 팀 저장소에 추가해 린터/포매터와 함께 관리하세요. 타입은 문서이자 계약서입니다.

인터페이스 생성 시작