[TYPESCRIPT] Index Signature와 동적 객체 타입 - 키가 정해지지 않은 객체를 안전하게 다루는 방법

 

실무에서 JavaScript 객체를 다루다 보면, 객체의 key가 미리 정해져 있지 않은 경우를 자주 만나게 됩니다. 예를 들어 사용자 설정, 옵션 맵, 캐시 데이터, API 응답의 가변 필드 등이 그렇습니다.

TypeScript에서는 이런 상황을 위해 Index Signature(인덱스 시그니처)라는 문법을 제공합니다. 

 

Index Signature란?

Index Signature는 “이 객체는 어떤 key가 들어올 수 있고, value 타입은 무엇이다”를 명시하는 문법입니다.

type StringMap = {
  [key: string]: string;
};

위 타입은 다음과 같은 의미를 가집니다.

  • key는 string
  • value는 항상 string
const messages: StringMap = {
  hello: "안녕하세요",
  bye: "안녕히 가세요",
};

 

number Index Signature

배열과 유사한 구조에서는 number 인덱스를 사용할 수 있습니다.

type NumberMap = {
  [index: number]: boolean;
};

const flags: NumberMap = {
  0: true,
  1: false,
};

다만 JavaScript에서 객체의 key는 결국 string으로 변환되기 때문에, 실무에서는 string index signature가 훨씬 더 자주 사용됩니다.

 

Index Signature와 명시적 프로퍼티의 관계

Index Signature가 선언된 객체에서, 명시적으로 선언한 프로퍼티는 index signature의 타입을 반드시 만족해야 합니다.

type UserMap = {
  [key: string]: string;
  name: string;
  // age: number; X 오류 (string이 아님)
};

이 규칙은 다음과 같은 문제를 방지합니다.

  • 동적 접근 시 타입 불일치
  • 예상하지 못한 런타임 오류

 

Index Signature의 한계

Index Signature는 강력하지만, 지나치게 넓은 타입을 허용하는 단점도 있습니다.

type LooseObject = {
  [key: string]: any;
};

이 경우 사실상 타입 검사가 무력화되므로, 실무에서는 any 사용을 최대한 피하는 것이 중요합니다.

 

Index Signature vs Record

Index Signature와 Record는 매우 비슷하지만 목적이 조금 다릅니다.

type RoleMap1 = {
  [key: string]: number;
};

type RoleMap2 = Record<string, number>;

차이점을 정리하면 다음과 같습니다.

구분 Index Signature Record
표현 방식 객체 리터럴 문법 유틸리티 타입
key 범위 string / number 명시적 key 유니온 가능
가독성 낮음 높음

실무에서는 key 집합이 명확하면 Record, 완전히 동적이면 Index Signature를 사용하는 것이 일반적입니다.

 

keyof와 결합한 안전한 동적 접근

Index Signature를 사용하더라도, key를 제한하면 훨씬 안전한 설계가 가능합니다.

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

function getValue(obj: User, key: keyof User) {
  return obj[key];
}

이 패턴은 동적 접근 + 타입 안정성을 동시에 만족시키는 핵심 기법입니다.

 

실무 예제 — 설정(Config) 객체

type Config = {
  [key: string]: string | number | boolean;
};

const appConfig: Config = {
  debug: true,
  timeout: 3000,
  env: "production",
};

설정 키가 계속 추가될 수 있는 상황에서 Index Signature는 매우 현실적인 선택입니다.

 

Discriminated Union과의 조합 (고급 패턴)

동적 객체 내부의 value를 Discriminated Union으로 제한하면 유연성과 안정성을 동시에 확보할 수 있습니다.

type Value =
  | { type: "string"; value: string }
  | { type: "number"; value: number };

type Store = {
  [key: string]: Value;
};

동적 key + 안전한 value 분기라는 실무에서 매우 강력한 조합입니다.

 

실무에서의 사용 가이드

  • key 집합이 명확하면 Record 사용
  • 완전히 동적인 경우에만 Index Signature 사용
  • value 타입은 가능한 한 좁게 유지
  • any는 최후의 수단
  • keyof, Discriminated Union과 함께 사용하면 안정성 증가

 


 

 

Index Signature는 “정해지지 않은 key를 타입으로 표현하기 위한 도구”입니다. 잘 사용하면 유연한 설계를 가능하게 하지만, 잘못 사용하면 타입 안정성을 해칠 수도 있습니다.

  • Index Signature → 완전 동적 객체
  • Record → 제한된 key 집합
  • keyof / Union과 결합하면 안정성 강화