Suppose I have a type where all properties are optional:
type T = {
x?: number;
y?: string;
z?: number;
...
}
Is there a way to construct a type that will allow all values of type T but requires at least one property? E.g.:
let a: NotEmpty<T> = { z: 1 }; // ok
a = {}; // error
Of course one way to do this would be a union of all combinations with at least one required property, but this becomes kind of awkward when the number of properties is large. Is there a more general way?
The union you're talking about of "all combinations with at least one required property" isn't too bad to write, especially since you can write the NotEmpty<T>
utility type to compute it automatically. And that type only has one union member per property key, so it scales well with number of properties (it's not a factorial or combinatorial explosion or anything).
Here's one way to write it:
type NotEmpty<T> = T &
{ [K in keyof T]-?: { [P in K]-?: T[K] } }[keyof T];
So a NotEmpty<T>
is T
intersected with something, so we know that, at minimum, NotEmpty<T>
is a subtype of T
. The thing we intersect it with,
{ [K in keyof T]-?: { [P in K]-?: T[K] } }[keyof T]
is a distributive object type (as coined in microsoft/TypeScript#47109), a mapped type into which we immediately index with all of its keys to get a union. In the above, we get the union of {[P in K]-?: T[K]}
for every K
in keyof T
.
And that type {[P in K]-?: T[K]}
is equivalent to Required<Pick<T, K>>
(see Required
and Pick
utility types): an object which definitely has a defined K
property from T
.
So the whole thing is then: NotEmpty<T>
is a T
which is also known to have some defined property from the properties in T
.
Let's test it out on your example:
type NotEmptyT = NotEmpty<T>
/* type NotEmptyT = T & ({ x: number; } | { y: string; } | { z: number; }) */
Looks good, let's make sure it behaves as desired:
let a: NotEmpty<T>;
a = { z: 1 }; // ok
a = {}; // error
Also looks good!
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With