객체 T에 있는 타입을 주어진 형태의 R 타입에 의해 정의된 다른 타입으로 바꾸는 MapTypes<T, R>를 구현하라.
Implement MapTypes<T, R> which will transform types in object T to different types defined by type R which has the following structure.
type StringToNumber = {
mapFrom: string; // value of key which value is string
mapTo: number; // will be transformed for number
}
type Parser<T,R extends {mapFrom:any,mapTo:any},U extends {mapFrom:any,mapTo:any}=R>=
T extends U['mapFrom']?
R extends U?
T extends R['mapFrom']?
R['mapTo']
:never
:never
:T
type MapTypes<T extends object, R extends {mapFrom:any,mapTo:any}> =
{[K in keyof T]:Parser<T[K],R>}
우선 mapped 타입으로 순회하며 Parser에 넣은 값을 밸류 타입에 넣는다
Parser는 다음과 같이 동작한다.
1)T가 R에 있는 모든 mapFrom을 확장할 수 없으면 T를 그대로 반환
2)R을 분배법칙으로 분배
3)T가 분배된 R의 mapFrom을 확장가능하면 R의 mapTo, 아니면 never를 반환
3)의 결과로 T가 확장가능한 mapFrom들의 mapTo가 유니온 타입으로 나오게 된다.
이렇게 구현한 이유는 문제에서 R이 유니온 타입으로 들어올 때,
전체 객체의 union 타입이 아닌 바꾸는 값(mapFrom/mapTo)만 union 타입으로 반환하는 것을 요구했기 때문이다.
type MapTypes<T, R extends { mapFrom: any; mapTo: any }> = {
[K in keyof T]: T[K] extends R['mapFrom']
? R extends { mapFrom: T[K] }
? R['mapTo']
: never
: T[K]
}
Parser를 따로 구현하지 않고, 한 곳에 구현한 풀이이다.
다만, T[K]가 R을 확장하는 것이 아닌, R이 T[K]를 확장할 때 mapTo로 바꿔주는 방식이다.
그래서, 다음과 같이 T에 리터럴, R에 원 타입이 오는 것은 대응하지 못한다.
type test=MapTypes<{ stringToNumber: '1' }, { mapFrom: string, mapTo: number }>,
내 풀이에서는 test가 { stringToNumber : number}가 되고,
이 풀이에서는 test가 never가 된다.('1'은 string을 확장할 수 있지만, string은 '1'을 확장할 수 없기 때문
https://github.com/type-challenges/type-challenges/issues/14152