Skip to content

Instantly share code, notes, and snippets.

@staltz
Created March 15, 2017 15:27
Show Gist options
  • Star 44 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save staltz/368866ea6b8a167fbdac58cddf79c1bf to your computer and use it in GitHub Desktop.
Save staltz/368866ea6b8a167fbdac58cddf79c1bf to your computer and use it in GitHub Desktop.
Nested Pick<T, K> in TypeScript 2.2

TypeScript supports Pick to allow you to get a "subset" object type of a given type, but there is no built-in Pick for deeper nested fields.

If you have a function that takes a large object as argument, but you don't use all of its fields, you can use Pick, Pick2, Pick3, etc to narrow down the input type to be only just what you need. This will make it easier to test your function, because when mocking the input object, you don't need to pass all fields of the "large" object.

type UserWithOnlyAddress = Pick<User, 'address'>;
type User = {
id: number,
name: string,
address: {
street: string,
zipcode: string,
geo: {
lat: string,
lng: string,
},
},
};
type UserWithOnlyStreetAddress = Pick2<User, 'address', 'street'>;
type User = {
id: number,
name: string,
address: {
street: string,
zipcode: string,
geo: {
lat: string,
lng: string,
},
},
};
type Pick2<T, K1 extends keyof T, K2 extends keyof T[K1]> = {
[P1 in K1]: {
[P2 in K2]: (T[K1])[P2];
};
};
type UserWithOnlyGeoLat = Pick3<User, 'address', 'geo', 'lat'>;
type User = {
id: number,
name: string,
address: {
street: string,
zipcode: string,
geo: {
lat: string,
lng: string,
},
},
};
type Pick3<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]> = {
[P1 in K1]: {
[P2 in K2]: {
[P3 in K3]: ((T[K1])[K2])[P3];
};
};
};
@arboleya
Copy link

@staltz Really amazing insight! Thank you very much for this.

@millsp Man, what a mind-blowing project! Jaw-dropping, to say the least. Congrats on such a feat!

@iSplasher
Copy link

iSplasher commented Jul 7, 2021

I managed to implement a combined version of the built-in Pick with nested support.
It only supports a depth of 1 but I'm sure it can be generalized to any depth.

type NestedPick<
  T,
  K extends keyof T | unknown,
  KN extends keyof T,
  KNK extends keyof T[KN]
> = Pick<T, K> & { [Key in KN]: Pick<T[KN], KNK> };


interface Address {
  name: string;
  ignore: number;
}

interface User {
  name: string;
  ignore: number;
  address: Address;
  address2: Address;
}

type T = NestedPick<User, "name", "address", "name"> // {name: '', address: {name: ''}}

// if you have multiple nested keys that you want to extract then you can just
type T2 = NestedPick<User, unknown, "address2", "name"> // {address2: {name: ''}}
type T3 = T & T2 // {name: '', address: {name: ''}, address2: {name: ''}}

@millsp would be nice if ts-toolbelt could include something like this

update

I updated it to now also support arrays

// unwrap up to one level
type Unarray<T> = T extends Array<infer U> ? U : T;

type NestedPick<
  T,
  K extends keyof T | unknown,
  KN extends keyof T,
  KNK extends keyof Unarray<T[KN]>
> = Pick<T, K> &
  {
    [Key in KN]: T[KN] extends Array<infer U>
      ? Pick<U, KNK>[]
      : Pick<T[KN], KNK>;
  };

interface Address {
  name: string;
  ignore: number;
}

interface User {
  name: string;
  ignore: number;
  address: Address;
  address2: Address[];
}

type T = NestedPick<User, 'name', 'address2', 'name'>; // {name: '', address2: [ {name: ''} ]}

@iSplasher
Copy link

Found a library that does this well: https://www.npmjs.com/package/ts-deep-pick

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment