I am working on a API for a library and I am using types that infer the keys of objects to a string and pick those back based on it. I was able to achieve it thanks to this and this threads, that I used as a starting point. Basically, given the following object
interface Obj {
prop0: string;
prop1: {
prop2: {
prop3: {prop4: "I am excluded"; prop5: string}[]
};
prop6: "I am excluded";
};
prop7: "I am excluded";
}
I am able to write a function like this
getAttributes(["prop0", "prop1.prop2.prop3[0].prop5"])
That returns
{
prop0: string;
prop1: {
prop2: {
prop3: [{prop5: string}?]
}
}
}
This is very nice and handy typing to have, BUT it is also very heavy. Using this with more intersections and unions gets to the point that sometimes the Language server crashes/hangs in IDEs (VSCode) and I am working on a relatively powerful machine. I am sure there is something that could be done to slim down those type I am using and get some performance benefit, but I don’t seem to find a way to do it. Here is what my code looks like:
type Divider<T> = T extends unknown[] ? `[${number}]` : ".";
type Paths<T> = T extends Record<string, unknown>
? {[K in keyof T]: `${Exclude<K, symbol>}${"" | `${Divider<T[K]>}${Paths<T[K]>}`}`}[keyof T]
: T extends (infer X)[]
? "" | `${Divider<X>}${Paths<X>}`
: never;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type DeepPick<TObject, TKey extends string> = UnionToIntersection<TObject extends object
? TKey extends `${infer Head}.${infer Tail}` | `${infer Head}[${number}].${infer Tail}`
? {
[P in Head & keyof TObject]: TObject[P] extends (infer A)[]
? [DeepPick<A, Tail>?]
: DeepPick<TObject[P], Tail>
}
: TKey extends `${infer Head}[${number}]`
? {
[P in Head & keyof TObject]: TObject[P] extends (infer A)[] ? [A?] : never
}
: TKey extends keyof TObject
? Pick<TObject, TKey>
: never
: TObject>;
declare function getAttributes<T, P extends Paths<T>>(attributes: P[]): DeepPick<T, P>;
Consider that the function signature above is heavily simplified. In my actual getAttributes
function I pass further computed and conditional types to Paths
and DeepPick
that I didn’t include to avoid making the reading even more complex. I want to add that I am not getting excessive type instantiation or too complex errors. This works, but I can feel (and sometimes see) the compiler struggling with it, with random crashes.