Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to require a type to be not empty

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?

like image 381
Yevgeniy P Avatar asked Sep 19 '25 08:09

Yevgeniy P


1 Answers

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

like image 108
jcalz Avatar answered Sep 21 '25 14:09

jcalz