[TypeScript] API 응답의 중첩 구조에서 최종 데이터 타입만 추출하기

Chan의 기술 블로그·2025년 11월 11일

TypeScript

목록 보기
7/10

이 글은 Chat GPT로 TypeScript를 공부하며 정리한 글입니다.

API 응답의 중첩 구조에서 최종 데이터 타입만 추출하기

앞선 글에서는 InstanceType<T>, ConstructorParameters<T>를 활용해
클래스의 타입 구조를 추론하는 메타 프로그래밍 패턴을 다뤘다.

이번 글에서는 그 개념을 API 응답 타입 분석으로 확장해
PromiseAxiosResponse처럼 중첩된 구조에서 “실제 데이터 타입만 추출”하는 패턴을 다룬다.

문제 상황 — 중첩된 API 응답 타입

interface ApiResponse<T> {
  status: number;
  data: {
    result: T;
    message: string;
  };
}

type User = { id: number; name: string };

type Response = Promise<ApiResponse<User>>;

Response의 실제 데이터는 User,
하지만 타입은 Promise<ApiResponse<User>>로 감싸져 있다.

💡 이럴 때 User만 추출하고 싶을 때가 많다.
→ 예: React Query useQuerydata 타입 정의.

1단계 — Promise 내부 타입 꺼내기

먼저 Promise<T>에서 T를 추출하는 UnwrapPromise를 만든다.

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>;          // number

2단계 — API Response 내부의 data.result 추출하기

API 응답 객체 내부 구조가 ApiResponse<T>라면
T를 자동 추출하도록 만든다.

type UnwrapApiResponse<T> =
  T extends { data: { result: infer R } } ? R : T;

type A = UnwrapApiResponse<ApiResponse<{ id: number }>>;
// { id: number }

3단계 — Promise + ApiResponse 중첩 모두 풀기

실무에서는 둘이 함께 쓰인다

type Response = Promise<ApiResponse<{ id: number; name: string }>>;

이 경우 UnwrapPromiseUnwrapApiResponse 순서로 재귀적으로 처리해야 한다.

type ExtractApiData<T> =
  T extends Promise<infer U> ? ExtractApiData<U> :
  T extends { data: { result: infer R } } ? R :
  T;

결과

type FinalData = ExtractApiData<Response>;
// { id: number; name: string }

✅ Promise가 몇 번 중첩되어 있어도 자동으로 최종 데이터 타입을 반환한다.

4단계 — AxiosResponse 대응 버전

Axios를 쓴다면 구조가 약간 다르다

import { AxiosResponse } from "axios";

type Response = Promise<AxiosResponse<{ user: { id: number; name: string } }>>;

이 경우 AxiosResponse의 구조를 타고 들어가야 한다.

type ExtractAxiosData<T> =
  T extends Promise<infer U> ? ExtractAxiosData<U> :
  T extends AxiosResponse<infer D> ? D :
  T;

결과

type A = ExtractAxiosData<Response>;
// { user: { id: number; name: string } }

5단계 — 중첩 객체 내부 데이터까지 추출하기 (Deep Extract)

이번엔 “result 안에 또 data가 있는” 구조도 처리해보자

type DeepResponse = Promise<{
  data: {
    result: {
      user: { id: number; name: string };
    };
  };
}>;

이를 재귀적으로 탐색하려면 다음과 같이 정의한다

type DeepExtract<T> =
  T extends Promise<infer U> ? DeepExtract<U> :
  T extends { data: infer D } ? DeepExtract<D> :
  T extends { result: infer R } ? DeepExtract<R> :
  T;

결과

type Final = DeepExtract<DeepResponse>;
// { user: { id: number; name: string } }

Promisedataresult 계층을 모두 순회하며 최종 데이터만 남김.

실무 예시 — React Query 타입 자동 추론

async function fetchUser() {
  return {
    data: {
      result: { id: 1, name: "chan" }
    }
  };
}

type FetchReturn = ReturnType<typeof fetchUser>; // Promise<...>
type UserData = DeepExtract<FetchReturn>;        // { id: number; name: string }

React Query의 useQuery<UserData>(...)와 완벽히 호환된다.


마무리

이제 Promise, AxiosResponse, ApiResponse
중첩된 구조를 모두 통과해 최종 데이터 타입만 추출할 수 있다.

이름설명예시
UnwrapPromise<T>Promise 내부 타입 추출Promise<string>string
UnwrapApiResponse<T>ApiResponse 내부 result 추출{ data: { result: T } }T
DeepExtract<T>중첩 구조를 재귀적으로 탐색Promise<ApiResponse<T>>T

💡 한 문장 요약
“타입스크립트는 중첩된 구조도 계산할 수 있다 — 타입은 결국 트리(tree)다.”

profile
퍼블리셔에서 프론트앤드로 전향하기

0개의 댓글