In this comment, jcalz points to the fact that Pick<T, keyof T>
when T
is a union type provides a type which only has the common parts:
type TypeA = { a: string; b?: string; };
type TypeB = { a: string; c?: string; };
type UnionType = TypeA | TypeB;
type Common<T> = Pick<T, keyof T>;
type X = Common<UnionType>;
// ^? − type X = { a: string; }
Playground link. (For the avoidance of doubt, it's true even when the other properties — b
and c
in your example — aren't optional.)
You can implement
the result:
type TypeA = { a: string; b?: string; };
type TypeB = { a: string; c?: string; };
type UnionType = TypeA | TypeB;
type Common<T> = Pick<T, keyof T>;
class UnionClass implements Common<UnionType> {
checkUnionProperties() {
let x: UnionType = { a: '' };
console.log(x);
}
a = "a";
}
Playground link
Here's an example without a = "a";
in the class, so the class doesn't implement the interface correctly — and it is indeed an error as you'd hope.
User makeitmorehuman points to this question in a comment, which has this excellent answer from qiu that handles it differently if TypeA
and TypeB
both have a property with the same name but different types. For instance, if you had x: string
in TypeA
but x: number
in TypeB
, the Common<TypeA | TypeB>
above would result in a type with x: string | number
. That may be what you want, but if not, qiu's answer has you covered with the SharedProperties
type (see the answer for details), which would leave x
out entirely:
// `OmitNever` and `SharedProperties` from: https://stackoverflow.com/a/68416189/157247
type OmitNever<T extends Record<string, unknown>> = {
[K in keyof T as T[K] extends never ? never : K]: T[K];
};
type SharedProperties<A, B> = OmitNever<Pick<A & B, keyof A & keyof B>>;
Your class
could use that, like this:
class UnionClass implements SharedProperties<TypeA, TypeB> {
checkUnionProperties() {
let x: UnionType = { a: "" };
console.log(x);
}
a = "a";
}
Playground link
It doesn't make a difference for the TypeA
and TypeB
shown, since they don't have properties with the same names but different types (like x
in my description above), but it would if they did — Common<TypeA | TypeB>
would include x
as string | number
, but SharedProperties<TypeA, TypeB>
leaves it out entirely.