2

I have single type, I would like to reuse this type for concatenation data:

interface MetaData {
  user: {
    id: string;
    fullName: string;
  },
  stats: {
    following: number;
  },
  another: {
    any: any;
  }
}

I would like to combine some nested type by keys

(Ex: type Profile = PickAllNestedKey<MetaData, 'user' | 'stats'>

I have to try this code but it can be Pick only single key

type NestedPartial<T, K extends keyof T> = {
   [P in keyof T[K]]: T[K][P]; 
};
type Profile = NestedPartial<Metadata, 'user'>;

Expected type like:

interface Profile {
  id: string;
  fullName: string;
  following: number;
}
Trọng Nguyễn Công
  • 1,207
  • 1
  • 10
  • 23

2 Answers2

2

I'll approach this from two different angles.

The first one is to question the structure of the types. Why aren't the nested types explicitly defined? Maybe the type structure can be improved? Here is what I mean:

interface User {
  id: string;
  fullName: string;
}

interface Stats {
  following: number;
}

interface Another {
  any: any;
}

interface MetaData {
  user: User,
  stats: Stats,
  another: Another
}

type Profile = User & Stats

Defining smaller and atomic types which target one entity at a time greatly improves readability and code maintenance. You are then free to use the smaller types wherever you see fit, and don't need to create them from the large type. In other words, this switches the approach from a top-bottom approach (create a big type and then use Pick, Omit, etc. to build the smaller types) to a bottom-up approach (define the individual types which then are used as building blocks for more complex types).

That said, it is not always possible to be in control of the types we are working with. Therefore, here are a couple of places where you can find some guidance on how to work with nested Picks:

  1. https://gist.github.com/staltz/368866ea6b8a167fbdac58cddf79c1bf
  2. https://github.com/piotrwitek/utility-types
2

Based on your example, it looks like the desired behavior is to Pick the top-level properties of MetaData and then "flatten" the results.

The issue with your current attempt is the keyof T[K]. If K is a union like 'user' | 'stats' then T[K] is a union of two objects { id: string; fullName: string; } | { following: number; }. There are no shared keys on that union. Therefore there is no P assignable to P in keyof T[K].

If we just Pick the properties we want and index them by the keys then we get almost the desired type, except that it's a union and we want an intersection. Pick<T, K>[K] resolves to:

{
    id: string;
    fullName: string;
} | {
    following: number;
}

We can make use of @jcalz's UnionToIntersection<T> utility type to solve that.

type UnionToIntersection<U> =
   (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type NestedPartial<T, K extends keyof T> = UnionToIntersection<Pick<T, K>[K]>;

type Profile = NestedPartial<MetaData, 'user' | 'stats'>;

resolves to:

type Profile = {
    id: string;
    fullName: string;
} & {
    following: number;
}

Typescript Playground Link

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102