0

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 types 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>;

playground link.

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.

To add some context, this function is actually a method on a database entity, called (approximately) like this getItem<P extends Paths<Entity>(key: Key<Entity>, attributes?: P[]): DeepPick<Entity, P>. It retrieves the item from the db, but only with the attributes (if) specified. It doesn't strip them from the item after retrieving but implements an actual specific db functionality that only sends those attributes via the network, reducing the response size.

7
  • What's the point of this? If I had a obj of that type, why wouldn't I just write obj.prop0 and obj.prop1.prop2.prop3[0].prop5? Commented Feb 10, 2024 at 1:58
  • I didn't mention nor include what the function does at runtime. If you're interested, the array is a list of attributes to pick when retrieving the object from a db. Commented Feb 10, 2024 at 2:00
  • Sometimes typesafety ain't worth it? JS is a language, too. Types reduce but don't eliminate errors. In a non-typesafe language, people write tests for things that would be caught by the compiler in a typesafe language. Don't get me wrong, type safety is very nice to have....until it becomes burdensome, which it sounds like it is. Your function signature also looks like lodash's get or pick - hard to understand the practical purpose of what you've invented here. Commented Feb 10, 2024 at 2:25
  • I added some context information at the end of the post, hope it's clearer now! Commented Feb 10, 2024 at 2:35
  • 1
    This might be beyond what you can expect to get answered on Stack Overflow. Right now your example doesn't seem to actually demonstrate the problem, so it's not a minimal reproducible example. And if the minimal example that shows the actual problem is very complex, then it's probably something you need one-on-one help with and not the sort of thing that's going to benefit future readers much. Do you think it's possible to find some relatively small demo of the issue (like, less than a screenful of code) that others can work on to test out any fixes they might suggest? Commented Feb 10, 2024 at 4:07

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.