How can I write a generic type alias, Combine<A, B>, that combines any two interfaces, A and B, while keeping matching properties from both but merging the property types using union?
For example, take these two interfaces:
interface Interface1 {
type: string;
another: bool;
}
interface Interface2 {
type: number;
}
Then Combine<Interface1, Interface2> should evaluate to the type:
type Result = {
type: string | number;
another: bool;
}
first take all the keys in A that are not in B, as well as keys of B not in A, and for the intersection of keys explicitly define the union:
type Combine<A, B> =
Omit<A, keyof B> // items in A that aren't in B
& Omit<B, keyof A> // items in B that aren't in A
& { [K in keyof A & keyof B]: A[K] | B[K] }; // union of both.
Note that it may be helpful to indicate that the keys that are present in one but not the other may not exist so you would use Partial on the Omit ones:
type Combine<A, B> =
Partial<Omit<A, keyof B>> // possibly items in A that aren't in B
& Partial<Omit<B, keyof A>> // possibly items in B that aren't in A
& { [K in keyof A & keyof B]: A[K] | B[K] }; // union of both.
Edit: Aaronium asking about using unions has lead me to an even better way of doing this, since T extends any ? X : never will operate over every element of a union you can actually expand all the keys present in any element of the union and then make a union of the values corresponding to all the interfaces, so it boils down to just this:
// gets all viable keys from all interfaces in a union.
type AllKeysOf<T> = T extends any ? keyof T : never
// basically does T[K] but when T is a union it only gives T[K] for the members of the union for which it is a valid key.
type Get<T, K extends keyof any, Fallback=never> = T extends Record<K, any> ? T[K] : Fallback
// takes a union of interfaces and merges them so that any common key is a union of possibilities.
type Combine< T,
setThisToUndefinedToProperlyHandleOptionalFields = never
> = {[K in AllKeysOf<T>]: Get<T,K, setThisToUndefinedToProperlyHandleOptionalFields>}
type TEST = Combine<{a:1} | {a:2, b:10} | {b: 11}>
// TEST = {a: 1|2, b: 10|11}
type TEST2 = Combine<{a:1,b:10} | {a:2},undefined>
// TEST2 = { a: 1|2, b: 10|undefined }
Note that if you set Fallback generic in Get to never it will just ignore elements of the union that don't define that key, but you could set it to undefined to indicate that keys that are defined in some interfaces but not all could end up being undefined, doesn't quite say it is optional but gets pretty close. The exact meaning of different handling very much depends on your specific use case so I won't go into extreme detail here. (just try both never and undefined and see which one gives the behaviour you want)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With