2

I would like to implement deep pick in typescript.

My example code is:

interface TestBook {
    id: string;
    name: string;
}
interface TestUser {
    id: string;
    email: string;
    books: TestBook[];
}

I and I would like to use deep pick like:

const foo: DeepPick<TestUser, 'id' | 'books.name'> = {...
/*

{
  id: ..
  books: [{name: ...}]
}

*/

Problem: There is only Pick in standard typescript and there is no library implement this DeepPick.

How can I do it? Which technic should I use?

I tried to find on google and SO.

teteyi3241
  • 171
  • 2
  • 12

2 Answers2

5

Let's first define some utility types to get the "head" or "tail" of a path:

type Head<T extends string> = T extends `${infer First}.${string}` ? First : T;

type Tail<T extends string> = T extends `${string}.${infer Rest}` ? Rest : never;

Then our DeepPick can take the heads of the paths and then deep pick the tail:

type DeepPick<T, K extends string> = T extends object ? {
  [P in Head<K> & keyof T]: T[P] extends readonly unknown[] ? DeepPick<T[P][number], Tail<Extract<K, `${P}.${string}`>>>[] : DeepPick<T[P], Tail<Extract<K, `${P}.${string}`>>>
} : T

If it's not an object, we shouldn't do anything to it. Inside the mapped type, I also added a case for arrays.

Playground

kelsny
  • 23,009
  • 3
  • 19
  • 48
0

I tried @zenly's version and found I was losing type hints, so I've modified it slightly. Here's a more opinionated version (zenly's handles a wider set of inputs) that persisted typehints better

type Head<T extends string> = T extends `${infer First}.${string}` ? First : T;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type Tail<T extends string> = T extends `${string}.${infer Rest}` ? Rest : never;

type DeepPick<TObject, TKey extends string> = UnionToIntersection<TObject extends object
    ? TKey extends `${infer A}.${infer B}`
        ? {
              [P in Head<TKey> & keyof TObject]: DeepPick<TObject[P], Tail<Extract<TKey, `${P}.${string}`>>>;
          }
        : TKey extends keyof TObject
        ? Pick<TObject, TKey>
        : never
    : TObject>;