Reputation: 5625
I have two interfaces A
and B
and they have one property in common
interface A {
a: boolean;
b: number;
c: string;
d: boolean;
}
interface B {
d: string;
}
interface C extends A,B {}
Ideally I want C
to be
{
a: boolean;
b: number;
c: string;
d: boolean | string; <- merged
}
Is there a way to do that? I know I can use Pick
to left out the identical key and then extends it but is there any other way to do it?
Upvotes: 9
Views: 5509
Reputation: 3272
Here you go:
type Merge<X, Y> = {
[K in (keyof X | keyof Y)]:
(K extends keyof X ? X[K] : never)
| (K extends keyof Y ? Y[K] : never)
};
Example usage:
interface A {
a: boolean;
b: number;
c: string;
d: boolean;
}
interface B {
d: string;
}
type C = Merge<A, B>;
// Result:
type C = {
a: boolean;
b: number;
c: string;
d: string | boolean;
}
Upvotes: 5
Reputation: 3150
It is possible, and I'll try to explain each part of my answer.
/** Returns keys in the left type which are not in the right type. */
type UniqueLeftKeys<T, U> = Exclude<keyof T, keyof U>
/** Returns keys in the right type which are not in the left type. */
type UniqueRightKeys<T, U> = Exclude<keyof U, keyof T>
/** Returns keys that exist in BOTH the left and right types. */
type CommonKeys<T, U> = keyof T & keyof U
type Merge<T, U> = {
[k in UniqueLeftKeys<T, U>]: T[k] // First part
} & {
[k in UniqueRightKeys<T, U>]: U[k] // Second part
} & {
[k in CommonKeys<T, U>]: T[k] | U[k] // Third part
}
The Secret is in the Merge
type, which consists of 3 parts.
The first part takes the keys that are only in the LEFT type and not in the RIGHT type, and add those properties to our result. In your example, the properties a, b, c
are only in interface A
, so we add those three to our resulting type.
The second part takes the keys that are only in the RIGHT type and not in the LEFT type, and add those properties to our result. In your example, the right type B
does not contain any keys that aren't already in A
, so we don't add anything to the resultant type.
The third part gets the keys that are in BOTH types, and then add those to the resultant type, where the type of those keys is a union of both types.
You can now use this code like so:
type C = Merge<A, B>
const c: C = {
a: false,
b: 1,
c: 'abc',
d: false // NOTE: we can also use string here
}
You could easily simplify this code by inlining those extra type definitions (UniqueLeftKeys
, UniqueRightKeys
, and CommonKeys
), but I've left those in the example as it helps to explain what is happening at each step
Upvotes: 2
Reputation: 5262
Since A
and B
conflict, I believe you can't combine them. If you know what the conflicting keys are, you can omit them when declaring C:
interface A {
a: boolean;
b: number;
c: string;
d: boolean;
}
interface B {
d: string;
}
interface C extends Omit<A, 'd'>, Omit<B, 'd'> {
d: string | boolean
}
let c: C = {
a: true, b: 3, c: 'a', d: 'a'
}
Upvotes: 4