0

It's trivial if you have 3 subtypes to put them together into one large one:

type Strings = {
  a: string;
  b: string;
  c: string;
  ...
};
type Numbers = {
  one: number;
  two: number;
  ...
};
type Booleans = {
  True: boolean;
  ...
};
type All = Strings & Numbers & Booleans;

but what if you start with the combined Type?

type All = {
  a: string;
  b: string;
  c: string;
  one: number; 
  two: number;
  True: boolean;
  ...
}

Is it possible to split All into the 3 sub types above based on the type of the values for each key?

I thought maybe this could work but it does not

type noNumbers= Exclude<All, { [key: string]: number }>;

TSplayground

lonewarrior556
  • 3,917
  • 2
  • 26
  • 55

2 Answers2

1

We can make custom type level function in order to achieve or omitting by value type or picking by value type. Below both options:

// picks given values
type PickByValue<T, V, _Keys extends keyof T = {
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]> = Pick<T, _Keys>

type Numbers = PickByValue<All, number>
type Strings = PickByValue<All, string>

// omits given values
type OmitByValue<T, V, _Keys extends keyof T = {
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]> = Omit<T, _Keys>

type Bools = OmitByValue<All, number | string>

The key to the success is how union type treads never, its neutral element for | operator, it means it is just skipped and has no impact at the type. Consider small proove:

type X = 'a' | never | 'b' // evaluates to just 'a' | 'b'

Thanks to that we can use never in order to skip some keys. That is exactly visible in this part:

{
  [K in keyof T]: T[K] extends V ? K : never
}[keyof T]

What is happening exactly is - we create mapped type with values equal keys, but only if the value type is ok, if not we use never, and after that we take these values by index [keyof T]. Lets recap step by step what is happening:

  • mapped type makes mapped type key-> (key | never)
  • [keyof T] gathers all value type as union (and never elements are not considered)
  • we now have all keys which were not skipped by never
  • we use these keys in utility type Pick or Omit
Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50
0

Not sure if there's an easier or better way but this works.

type PickOut<T, K> = {
    [P in keyof T]: T[P] extends K ? P : never
}[keyof T]

type NumberKeys = PickOut<All, number>
type BooleanKeys = PickOut<All, boolean>
type StringKeys = PickOut<All, string>

type justNumbers = Pick<All, NumberKeys>
type justBooleans = Pick<All, BooleanKeys >
type justStrings = Pick<All, StringKeys >

TSplayground

lonewarrior556
  • 3,917
  • 2
  • 26
  • 55