typescriptgenericsmappingunion-types

How to filter object-like union types in TypeScript?


Let's say that I have 3 types and a union type of them:

type Cat = {
  owner: string;
  color: string;
  lives: number;
}

type Dog = {
  owner: string;
  color: string;
}

type Wolf = {
  color: string;
}

type Animals = Cat | Dog | Wolf;

I want a filter AnimalsFilter<U, K> that would work like this:

type FilterAnimals<U, K extends string> = ...;

type WithOwner = FilterAnimals<Animals, 'owner'>;
// WithOwner = Cat | Dog

So that after filtering this union type it would only have Cat and Dog. How can I achieve that?

I tried mapping, like this:

type FilterAnimals<U, K extends string> = {
    [Key in keyof U]: Key extends K? U[K] : never;
}[keyof U];

But it does not work as intended. Am I even close to answer with this?


Solution

  • You can write HasKey<T, K> as a distributive conditional type to split the union type T into its members, then perform another conditional check via keyof to just keep those members with a key from K. Like this:

    type HasKey<T, K extends PropertyKey> =
      T extends unknown ? K extends keyof T ? T : never : never;
    
    type AnimalsWithOwner = HasKey<Animals, "owner">;
    // type AnimalsWithOwner = Cat | Dog
    

    Playground link to code