In situations like this, it is common to utilize a distributive conditional type to break down the union input type into its individual members and then selectively keep or discard each member before combining them back into another union. Here's one approach:
type Filter<T> = T extends unknown ?
Exclude<keyof T, "name"> extends never ? T : never
: never
The expression T extends unknown ? ⋯ : never
distributes over unions in the generic type parameter T
. The filter used here is
Exclude<keyof T, "name"> extends never ? T : never
. The utility type
Exclude<X, Y>
eliminates union members of
X
that match
Y
, so
Exclude<keyof T, "name">
represents the keys of
T</code that are not named "name." If this set is empty (which means the impossible <code>never
type), we retain
T
; otherwise, we discard it.
Let's put it to the test:
type Test = { name: "a" } | { name: "b" } |
{ name: "c", other: 1 } | { name: "d", other: 2 }
type FilteredTest = Filter<Test>;
/* type FilteredTest = { name: "a"; } | { name: "b"; } */
let x: FilteredTest = { name: "c", other: 1 } // Error
let y: FilteredTest = { name: "a" } // Fine
Everything seems to be working as expected!
Try out the code yourself in the playground