Suppose I have the two types:
interface A {
a: string;
b: string;
}
interface B {
a: string;
c: string;
}
I would like to define type X
as A OR B
, i.e.
{
a: string;
b: string;
}
// OR
{
a: string;
c: string;
}
// and NOT:
{
a: string;
b: string;
c: string;
} // (equivalent to X | Y)
// and NOT:
{
a: string;
} // (equivalent to X & Y)
I have been trying to define some type function which is able to return a type which is either of the two interfaces passed. Here is what I have so far:
type InvertedKeys<A extends object, B extends object> = {
[P in keyof Omit<B, keyof A>]: never;
} & {
[P in keyof A]: A[P];
}
// ExclusiveUnion<A, B> should in theory return A OR B
type ExclusiveUnion<A extends object, B extends object> = InvertedKeys<A, B> & InvertedKeys<B, A>;
However, the above is unfortunately too restrictive - it does not allow the base cases
{
a: string;
b: string;
}
or
{
a: string;
c: string;
}
as it complains that Type 'string' is not assignable to type 'never'
.
The reason I am looking to do this is because I would like to have two different options for extending a class when defining a type: for instance:
interface CommonUser {
username: string;
}
interface UserRegisteredByPhone extends CommonUser {
phoneNo: string;
}
interface UserRegisteredByEmail extends CommonUser {
email: string;
}
// What should this function `ExclusiveUnion` be defined as?
type User = ExclusiveUnion<UserRegisteredByPhone, UserRegisteredByEmail>;
function handleUserData(user: User) {
// The user either has a phone number or an email address, but not both.
/*
I am aware that type guards/type checking functions could be used here, but
I (as well as others using my library) would like to be able to see suggestions
in the IDE/at build-time that it should be one or the other, to avoid confusion -
e.g. if I provided both a phone number AND an email address, which one would
be used? Would it throw a runtime error? Would it silently fail? One should not
have to check the source code of this function in order to answer these.
*/
}
Is this even possible in Typescript? If so, how could I amend my function to correctly identify one interface or the other, but not both? If possible, I would like to avoid using runtime type guards, in order to maximise ease of use and IDE compatibility.