I have another, much simpler, solution which is easier to use with a syntax inspired by destructuring. It also works well with intellisense when picking the keys from the original object.

typescript playground
Inspiration
const {a: x, b: y} = {a: 1, b: 'b'}
// x = 1
// y = 'b'
Proposal
type A = { a: string, b: number, c: boolean, d: any }
type B = PickAs<A, 'a:x' | 'b:y' | 'c'>
// B = { x: string; y: number; c: boolean; }
Solution
type PickAs<T, K extends Exclude<keyof T, K extends `${infer A}:${string}` ? A : never>> =
Normalize<
& UnionToIntersection<K extends `${infer A}:${infer B}` ? { [key in B]: T[A] } : never>
& Pick<T, Extract<K, keyof T>>
>
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends
(k: infer I) => void ? I : never
type Normalize<T> = { [K in keyof T]: T[K] }
How does this work?
The stripped type looks like this:
type PickAs<T, K extends keyof T> = // K = e.g. `'a:x' | 'b:y' | 'c'`
K extends `${infer A}:${infer B}` // 1
? { [P in B]: T[A] } // 2
: never // 3 - ignore everything else
// Compare to classic Pick
type Pick<T, K extends keyof T> =
{ [P in K]: T[P] }
- If
K
extends a string *:*
, extract it's constituents into A
and B
- Then construct a new type
{ [B]: T[A] }
- However, this only works for pairs
'a:c' | 'b:d'
, but not for 'a' | 'b'
.
To support original keys of T
, we add
Pick<T, Extract<K, keyof T>> // From `K` only extract original keys of `T` and Pick.
The resulting type may look like this
type A = { a: string, b: number, c: boolean, d: any }
type B = PickAs<A, 'a:x' | 'b:y' | 'c'>
// result
type B = ({
x: string;
} | {
y: number;
}) & Pick<A, "c">
Not perfect.
UnionToIntersection
resolves {x} | {y}
into {x} & {y}
.
Normalize
merges {x} & {y} & Pick<A,'c'>
into one object. { x, y, c }
And the last thing to do is to add intellisense support.
// Before:
type PickAs<T, K extends keyof T>;
// After:
type PickAs<T, K extends Exclude<keyof T, K extends `${infer A}:${string}` ? A : never> | `${string}:${string}`>;
- If a key
K
matches ?:?
, take the first constituent {A}:?
and return it (? A : never
) instead of the whole string ?:?
. Exclude<keyof T, A>
then removes it from the allowed pool of values, which makes it disappear from intellisense.
| `${string}:${string}`
then adds the string back, so that the extends clause doesn't give error that abc:xyz
"is not assignable to type keyof T"