아래 함수에 대해 Type을 정의해보자
function flattenObject(obj: any, result: any = {}){
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
flattenObject(obj[key], result);
} else {
result[key] = obj[key];
}
}
}
return result;
}
const a = {
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
f: 4
}
},
g: 5
f: [123]
}
const flatObj = flattenObject(a);
console.log(flatObj);
// Output:
// { 'a': 1, 'c': 2, 'e': 3, 'f': 4, 'g': 5, 'f': [123]}
방법
primitive와 배열을 한묶음, 그외 object를 한묶음으로 나눠 타입을 지정
primitive와 배열에 대한 타입
먼저 단순한 object로 만들어 보자
type SimpleObject<T> = {
[K in keyof T]: T[K]
}
모든 key & value pair가 그대로 들어온다.
여기서 primitive와 배열만 가져와보자
시도 1
type FilterValues<T> = T extends object ? T extends unknown[] ? T : never : T
type SimpleObject<T> = {
[K in keyof T]: FilterValues<T[K]>
}
//추론된 값을 보니 object일 때 never type으로 나오네
type b = {
a: 1;
b: never;
g: 5;
f: [123]
}
value에서 filter를 하지 말고 key에서 filter를 해보자
시도 2
type FilterKeys<T, K> =
K extends keyof T
? T[K] extends object
? T[K] extends unknown[]
? K : never
: K
: never
type SimpleObject<T> = {
[K in FilterKeys<T, keyof T>]: T[K]
}
// 추론이 잘 되었다.
type b = {
a: 1;
g: 5;
f: [123];
}
이제 object에 대해서 타입을 정의해보자
먼저 primitive와 배열을 제외하는 타입을 작성하자
type FilterNestedKeys<T, K> = K extends keyof T
? T[K] extends object
? T[K] extends unknown[]
? never : K
: never
: never
type NestedObject<T extends object> = {
[K in FilterNestedKeys<T, keyof T>]: T[K]
}
type c = NestedObject<{
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
f: 4
}
},
g: 5
f: [123]
}>
그다음 flatten 하게 타입을 만들어 보자
b object가 날라간것을 확인할 수 있다.
type Values<T extends object> = T[keyof T]
type FilterNestedKeys<T, K> = K extends keyof T
? T[K] extends object
? T[K] extends unknown[]
? never : K
: never
: never
type NestedObject<T extends object> = {
[K in FilterNestedKeys<T, keyof T>]: T[K]
}
type FlatNestedObject<T extends object> = Values<NestedObject<T>>
type c = FlatNestedObject<{
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
f: 4
}
},
g: 5,
f: [123],
e: {h: 1}
}>
// 추론된 타입
type c = {
c: [1, 2, 3];
d: {
e: 3;
i: 4;
};
} | {
h: 1;
}
추론된 타입이 union으로 되어 있어서 intersection으로 변경해보자
type ToIntersection<T> = (
T extends any
? (_:T) => void
: never
) extends (_: infer S) => void
? S
: never
type Values<T extends object> = T[keyof T]
type FilterNestedKeys<T, K> = K extends keyof T
? T[K] extends object
? T[K] extends unknown[]
? never : K
: never
: never
type NestedObject<T extends object> = {
[K in FilterNestedKeys<T, keyof T>]: T[K]
}
type FlatNestedObject<T extends object> = ToIntersection<Values<NestedObject<T>>>
type c = FlatNestedObject<{
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
f: 4
}
},
g: 5,
f: [123],
e: {h: 1}
}>
// 추론된 타입
type c = {
c: [1, 2, 3];
d: {
e: 3;
i: 4;
};
} & {
h: 1;
}
ToIntersection 함수에서 굳이 T extends any 냐고 물어보는 이유는 distributive law를 적용하기 위해서다
만약, T가 number | string으로 들어왔다면, 해당 법칙으로 인하여 ( _ : number | string) => void가 ( _ : number) => void | ( _ : string) => void로 변경된다.
이 때 S가 number와 string보다 작으면서 제일 큰 타입으로 설정된다.
number와 string보다 작으면서 제일큰 타입은 number & string이다. (현실엔 없겠지만...)
이젠 재귀타입을 만들어서 nested object를 flat 하게 만들어야 한다.
type Values<T extends object> = T[keyof T]
type FilterNestedKeys<T, K> = K extends keyof T
? T[K] extends object
? T[K] extends unknown[]
? never : K
: never
: never
type NestedObject<T> = {
[K in FilterNestedKeys<T, keyof T>]: T[K]
}
type ToIntersection<T> = (
T extends any
? (_:T) => void
: never
) extends (_: infer S) => void
? S
: never
type LazyRecursiveFlat<T> = T extends object ? FlatObject<T> : never
type FlatNestedObject<T> = ToIntersection<LazyRecursiveFlat<Values<NestedObject<T>>>>
type c = FlatNestedObject<{
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
i: 4
}
},
g: 5
f: [123],
e: {h: 1}
}>
// 추론된 타입
type c = {
c: [1, 2, 3];
e: 3;
i: 4;
} & {
h: 1;
}
그리고 아까 primitive와 합친 타입을 만들고 디버깅이 편하게 Roll 타입을 추가해서 넣어주자
typescript hack을 쓴것 같다고 한다. 난 잘 모름 ㅋㅋ
type Values<T extends object> = T[keyof T]
type FilterNestedKeys<T, K> = K extends keyof T
? T[K] extends object
? T[K] extends unknown[]
? never : K
: never
: never
type NestedObject<T> = {
[K in FilterNestedKeys<T, keyof T>]: T[K]
}
type ToIntersection<T> = (
T extends any
? (_:T) => void
: never
) extends (_: infer S) => void
? S
: never
type LazyRecursiveFlat<T> = T extends object ? FlatObject<T> : never
type FlatNestedObject<T> = ToIntersection<LazyRecursiveFlat<Values<NestedObject<T>>>>
type FlatObject<T> = SimpleObject<T> & FlatNestedObject<T>
type c = FlatObject<{
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
i: 4
}
},
g: 5
f: [123],
e: {h: 1}
}>
// 추론된 타입
type c = SimpleObject<{
a: 1;
b: {
c: [1, 2, 3];
d: {
e: 3;
i: 4;
};
};
g: 5;
f: [123];
e: {
h: 1;
};
}> & SimpleObject<{
c: [1, 2, 3];
d: {
e: 3;
i: 4;
};
}> & SimpleObject<...> & SimpleObject<...>
// Roll Type 추가
type Roll<T> = {
[K in keyof T]: T[K]
} & {}
type FlatObject<T> = Roll<SimpleObject<T> & FlatNestedObject<T>>
type c = FlatObject<{
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
i: 4
}
},
g: 5
f: [123],
e: {h: 1}
}>
// 추론된 타입
type c = {
a: 1;
g: 5;
f: [123];
c: [1, 2, 3];
e: 3;
i: 4;
h: 1;
}
이제 함수에 타입을 선언하고 마무리하자
function flattenObject<T extends object>(obj: T, result: any = {}): FlatObject<T> {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
flattenObject(obj[key], result);
} else {
result[key] = obj[key];
}
}
}
return result;
}
const a = {
a: 1,
b: {
c: [1, 2, 3],
d: {
e: 3,
f: 4
}
},
g: 5,
h: { hh: 1}
}
const flatObj = flattenObject(a);
console.log(flatObj);
// 추론된 타입
const flatObj: {
a: number;
g: number;
c: number[];
e: number;
f: number;
hh: number;
}
https://d2.naver.com/helloworld/7472830
'IT > typescript' 카테고리의 다른 글
[typescript] AtLeastOneRequired (0) | 2024.12.16 |
---|---|
[typescript] -? 의미 (0) | 2024.12.05 |
[typescript] infer (0) | 2024.08.14 |
[typescript] 함수타입(Function Type) 인자형 (0) | 2024.08.14 |
[typescript] subtype (0) | 2024.08.14 |