TypeScript, Object.entries()의 타입을 추론해보자

status
publish
thumbnail
date
Apr 1, 2024
slug
typescript-object-entries
summary
Object.entries() 메소드 타입 추론

Object.entries()

Object.entries()enumerable 속성이 있는 값을 가진 [key, value]배열을 반환한다. 이를 통해 우리는 객체 내부를 반복문을 순회 및 탐색이 가능해진다.
const obj = { a: 10, b: 20, c: 30, }; Object.entries(obj).map(([key, value]) => { console.log([key, value]); }); // [LOG]: ["a", 10] // [LOG]: ["b", 20] // [LOG]: ["c", 30]

TypeScript 한계

entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
typescript v5.1.5 기준
TypeScript에서 Record를 이용해 객체 타입을 지정할 때, Object.entries()를 통과한 [key, value] 쌍 배열의 key 타입이 정확히 추론되지 않는 한계점이 존재한다.
이는 Object.entries() 내부 타입 추론 방식이 key 타입을 단순히 string으로 지정하고 있기 때문이다.
type AnimalKey = "dog" | "cat" type Animal = { age: number; character: { cute: boolean } } // const AnimalMap: Record<AnimalKey, Animal> const AnimalObj: Record<AnimalKey, Animal> = { dog: { age: 3, character: { cute: true, } }, cat: { age: 4, character: { cute: true, } }, }; // const AnimalArr: [string, Animal][] const AnimalArr = Object.entries(AnimalObj);
위와 같은 상황에서 AnimalArr 변수, [key, value] 쌍 배열의 key의 타입string으로 추론된다.
  • ✅  [AnimalKey, Animal][]
  • ❌  [string, Animal][]
위와 같은 정확한 타입 추론을 얻기 위해선 유틸리티 타입을 직접 만들어야 한다.

ObjectEntries<T> 유틸리티 타입 만들기

Object.entries() 타입은 [[key, value],[key, value],[key, value],…] 형태를 갖고 있어야한다.
이를 위해 튜플 형태를 value로 갖는 객체 타입으로 표현한 뒤, 튜플들을 배열 타입으로 뽑아야 한다.

1. 튜플 형식 객체 만들기

// { [K in keyof T]: [K, T[K]] } { dog: ["dog", Animal]; cat: ["cat", Animal]; }
객체 Tkey, value를 튜플 형태로 갖고 있는 새로운 객체를 만든다. 새로운 객체의 key객체 T의 key를 그대로 사용한다.

2. 튜플 형식 접근

// { [K in keyof T]: [K, T[K]] }[keyof T] ["dog", Animal] | ["cat", Animal]
Object.entries()[[key, value],[key, value],[key, value],…] 형태를 표현하기 위해 [keyof T] 를 통해서 객체의 value(튜플)만 가져온다.

3. 배열화

// { [K in keyof T]: [K, T[K]] }[keyof T][] [["dog", Animal], ["cat", Animal]]
가져온 튜플들을 배열 형태로 만들어주기 위해 []를 이용한다.

4. 완성

type ObjectEntries<T> = { [K in keyof T]: [K, T[K]]; }[keyof T][];

5. 사용하기

const AnimalArr = Object.entries(AnimalObj) as ObjectEntries<Record<AnimalKey, Animal>>;