Take a look at this solution. It addresses a similar scenario.
When TypeScript resolves union types, it identifies the best common type to use - which is the type that is common among all elements in the union. This approach ensures safety.
Let's analyze your union:
interface FruitBox {
name: string
desc: {
'orange': number;
'banana': number;
}
}
interface IceBox {
name: string
}
interface Vegabox {
name: string
desc: {
'tomato': number;
'potato': number;
}
}
type UnionBox = FruitBox | Vegabox | IceBox;
type AllowedKeys = keyof UnionBox // name
By defining AllowedKeys
, you are restricted to only using the name
property from the UnionBox
. This limitation exists because name
is present in each part of the union.
To handle this situation, consider implementing the StrictUnion
helper function.
// credits goes to 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>
Entire code snippet:
interface FruitBox {
name: string
desc: {
'orange': number;
'banana': number;
}
}
interface IceBox {
name: string
}
interface Vegabox {
name: string
desc: {
'tomato': number;
'potato': number;
}
}
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 UnionBox = StrictUnion<FruitBox | Vegabox | IceBox>;
type AllowedKeys = keyof UnionBox // name
type Ship = Record<string, (description: UnionBox['desc'] | UnionBox['name']) => void>; // ok
const record: Ship = {
foo: (elem) => { }
}
Playground