I was trying to create a generic function transforming an object by filtering only one property - sth along the lines of:
function pickOnePropertyUntyped(data: any, key: any): any {
return {
[key]: data[key]
}
}
Thus desired behaviour is:
const a: A = {
a: 'a',
b: 1
}
const r = pickOnePropertyUntyped(a, 'a'); // {'a': 'a'}
I am having problems with getting desired types and implementation.
My attempt is:
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type NoUnion<Key> =
[Key] extends [UnionToIntersection<Key>] ? Key : never;
type PickOneProperty<T, P extends keyof T & string> = {
[K in keyof T as K extends NoUnion<P> ? K : never]: T[K]
}
function pickOneProperty<T, K extends keyof T & string>(
data: T,
key: K,
): PickOneProperty<T, K> {
return {
[key]: data[key]
}
}
Which:
- correctly restricts input property to only one key (I used Is there a way to prevent union types in TypeScript?)
- correctly infer output type
Unfortunately, it flags return type as incorrect.
CodePudding user response:
What you are observing here is more or less a missing feature in TypeScript. Most of the times when you are trying to use a computed property key, TypeScript automatically adds an index signature to the type of the object. There are only some exceptions; for example when the type of the key is already a known string literal type (as shown here). See discussions about this problem here.
The index signature breaks the typing here. As far as I know, there are no "satisfying" solutions to this problem. Even a normal type assertion does not work here as TypeScript sees no overlap between the two types. What you can do is to assert to unkown
first and then to PickOneProperty<T, K>
function pickOneProperty<T, K extends keyof T & string>(
data: T,
key: K,
): PickOneProperty<T, K> {
return {
[key]: data[key]
} as unknown as PickOneProperty<T, K>
}
Just be aware that there is pretty much no type-safety in this implementation now.