Create your custom utility type called AtLeastOneTrue<K>
which accepts a union of key types K
and generates a new union where each member contains all boolean properties except for one that must be true. This allows multiple values to be true while ensuring that they cannot all be false. Here is an example implementation:
type AtLeastOneTrue<K extends string> =
{ [P in K]: { [Q in K]: Q extends P ? true : boolean } }[K]
This structure acts like a distributive object type (as introduced in ms/TS#47109). If you have a union of key-like types K
which resolves to K1 | K2 | K3
, and want to convert it to
F<K1> | F<K2> | F<K3>
, you can use a distributive object type of the form
{[P in K]: F<P>}[K]
. This technique creates a new object type and immediately retrieves a union of its properties, even if
F<P>
yields objects.
The inner operation F<P>
represents
{[Q in K]: Q extends P ? true : boolean}
. The conditional type
Q extends P ? true : boolean
serves to generate boolean properties for every key, except for the focused one which will always be true.
Let's illustrate this with the AgeDivisions
example:
type AgeDivisions = AtLeastOneTrue<
"youth" | "middleSchool" | "highSchool" | "college" | "open"
>;
/* Output:
{
youth: true;
middleSchool: boolean;
highSchool: boolean;
college: boolean;
open: boolean;
} | {
youth: boolean;
middleSchool: true;
highSchool: boolean;
college: boolean;
open: boolean;
} | { ⋯
highSchool: true;
⋯ } | { ⋯
college: true;
⋯ } | {
youth: boolean;
middleSchool: boolean;
highSchool: boolean;
college: boolean;
open: true;
}
*/
Ensure that the behavior aligns with expectations:
let a: AgeDivisions;
a = { college: false, highSchool: false, middleSchool: true,
open: false, youth: false }; // valid
a = { college: true, highSchool: false, middleSchool: false,
open: true, youth: false }; // valid
a = { college: false, highSchool: false, middleSchool: false,
open: false, youth: false }; // error
Looks promising! You can then define another type like EventStyles
:
type EventStyles = AtLeastOneTrue<
"folkstyle" | "freestyle" | "grecoRoman" | "jello" |
"noGiJits" | "giJits" | "judo" | "beach"
>;
Playground link to code