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.