You can intersect the type with all optional members with a union of all properties, where all in each constituent of the union, one member is required. So basically you will have:
type WhatWeWant = {
apple?: number | undefined;
banana?: number | undefined;
coconut?: number | undefined;
} & (
| { apple: number; }
| { banana: number; }
| { coconut : number ;})
To get this type without writing it out we can use a mapped type:
type RequireOne<T> = T & { [P in keyof T]: Required<Pick<T, P>> }[keyof T]
type FruitCollection = RequireOne<{ [f in Fruit]?: number }>
Playground Link
The idea of the mapped type in RequireOne
is to create union in the WhatWeWant
type above (T
will be the original type will al the optional properties). So what we do, in the mapped type is we take each property in T
and type it as Required<Pick<T, P>>
. This means for each key, we get a type that only contains that key, basically this type for the example:
{
apple: { apple: number; }
banana: { banana: number; }
coconut: { coconut: number ;}
}
With this type, the matter of getting the union we want is just a matter with indexing keyof T
, to get a union of all property types in our object.