I am working with a discriminated union type that includes a non-discriminant field in multiple members:
interface Foo {
type: 'foo';
foo: string;
}
interface OptionalFoo {
type: 'optionalfoo';
foo?: string;
}
interface RequiredNullableFoo {
type: 'requirednullable';
foo: string | null | undefined;
}
interface Bar {
type: 'bar';
bar: string;
}
type All = Foo | OptionalFoo | RequiredNullableFoo | Bar;
I want to create a new type using a type definition:
type Foos = UnionMembersWithField<All, 'foo'>;
// Foos = Foo | OptionalFoo | RequiredNullableFoo;
I have tried various ways to define UnionMembersWithField
, but none of them are completely successful:
// Utility type: `keyof All` is `'type'`, because it is the only field in common,
// but we want to accept any field from any union type.
type KeysOfUnion<T> = T extends T ? keyof T : never;
// This definition yields `Foo | RequiredNullableFoo` and drops `OptionalFoo`.
type UnionMembersWithField<T, K extends KeysOfUnion<T>> = T extends Record<K, any> ? T : never;
// This definition does not compile due to TS1170, but seems like the best expression of intent.
type UnionMembersWithField<T, K extends KeysOfUnion<T>> = T extends { [K]?: any } ? T : never;
// This definition yields `never`...
type UnionMembersWithField<T, K extends KeysOfUnion<T>> = Required<T> extends Record<K, any> ? T : never;
// ...which is surprising, because redefining it with a helper `DiscriminateUnion` type yields
// `Required<Foo> | Required<OptionalFoo> | Required<RequiredNullableFoo>`
type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = T extends Record<K, V> ? T : never;
type UnionMembersWithField<T, K extends KeysOfUnion<T>> = DiscriminateUnion<Required<T>, K, any>;
My other attempts either had no impact (resulting in the same output type as the input type) or resulted in never
.
Considering that All['foo']
is any
(because Typescript doesn't provide a clear type when not all union members have the specified field), I am unsure if such a type is achievable.