TS unions operate in a specific manner that is intentional.
type Option1 = {
items: string[];
}
type Option2 = {
delete: true;
}
type Combined = Option1 | Option2;
type Keys = keyof Combined; // never
The result of keyof Combined
is never
, representing an empty set of keys.
This occurs because Option1
and Option2
do not share any common properties, making it difficult for TS to determine which property is permitted.
If you have a function that accepts either Option1
or Option2
, you should utilize custom typeguards when working with Combined
:
type Option1 = {
items: string[];
}
type Option2 = {
delete: true;
}
type Combined = Option1 | Option2;
type Keys = keyof Combined; // never
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
const handle = (union: Combined) => {
if (hasProperty(union, 'items')) {
const option = union; // Option1
} else {
const option = union; // Option2
}
}
Alternatively, you can introduce a common property:
type Option1 = {
tag: '1',
items: string[];
}
type Option2 = {
tag: '2',
delete: true;
}
type Combined = Option1 | Option2;
type CommonProperty = Combined['tag'] // "1" | "2"
const handle = (union: Combined) => {
if(union.tag==='1'){
const option = union // Option1
}
}
Another approach is to utilize StrictUnion
.
type Option1 = {
items: string[];
}
type Option2 = {
delete: true;
}
type Combined = Option1 | Option2;
// credits goes https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
T extends any
? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
type StrictUnion<T> = StrictUnionHelper<T, T>
type Union = StrictUnion<Combined>
const items_variable: Union['items'] = ["a", "b"]; // string[] | undefined
One drawback to note is that items_variable
could also be undefined
due to the nature of unions where they can hold one value or another interchangeably.