I want to "merge" multiple interfaces/types in Typescript. The outcoming interface should have all shared property mandatory and all "not-shared" propertys optional. Here an example:
interface ShopUser {
userId: string
boughtItems: string[]
}
interface BlogUser {
userId:string
viewedBlogs: string[]
}
interface AppUser {
userId: string
appSessions: string[]
}
The Outcome should be something like this:
interface DBUser {
userId: string // mandatory, because all childs have this property
boughtItems?:string[] // optional now
viewedBlogs?:string[] // optional now
appSessions?:string[] // optional now
}
I already tried these things:
type DBUser = ShopUser | BlogUser | AppUser // now only userId is usable. The Rest is unknown
type DBUser = ShopUser & BlogUser & AppUser // now all Properties are mandatory...
Using something like Omit<ShopUser, "boughtItems">
and then retyping seems somehow not elegant to me and since our Interfaces are much more complex it also ends in a mess and is not very reusable.
In a union A | B
, the accessible keys are those that are shared between A
and B
. Therefore, keyof (A | B)
gives us the shared keys. The type should be A[K] | B[K]
or (A | B)[K]
.
For the keys that aren't shared, we can just take the intersection A & B
and omit the shared keys to get the difference of the two types. Then we can add Partial
to make all the properties optional.
The resulting type is too complex and TypeScript will display the uncomputed type. You can force TypeScript to "evaluate" it using a conditional type and a mapped type.
type Merge<A, B> = {
[K in keyof (A | B)]: A[K] | B[K]; // shared keys
} & ( // intersect with
Partial<Omit<A & B, keyof (A | B)>> // the difference
extends infer O ? { [K in keyof O]: O[K] } : never // pretty display
);
Merge all three types now:
type DBUser = Merge<Merge<ShopUser, BlogUser>, AppUser>;
It is also possible to write a type Merge
such that it takes a tuple of types and merges them in the same way:
type DBUser = Merge<[ShopUser, BlogUser, AppUser]>;
But that is something for the reader to figure out ;')