TypeScript Coding Challenge #4 – Type for Required Fields

This time we want to implement a type in TypeScript that returns us a type with all required fields of an object:

type I = GetRequired<{ foo: number, bar?: string }> 
// expected to be { foo: number }

Intersection with undefined

Let’s build this step by step. What types are actually behind foo and bar? It looks as follows:

number // foo is obviously of type number
string | undefined // for bar we can either use string or undefined

An optional field basically means that the possible types are the defined type (string) or undefined. So, we need some way to detect something that can be undefined. We would like to do this with an intersection. Then we could later on check with extends. Think about the following two types:

type testFoo = number & undefined;
type testBar = (string | undefined) & undefined

An intersection of number & undefined can never happen, the type sets have no common elements. It’s different for the second case though. If we intersect (string | undefined) with undefined, the common type set is the one that contains just undefined. Our IDE helps us here as well:

What have we learnt so far? An optional field can be of a certain type or undefined. And intersection of something containing undefined with undefined itself results in undefined.

Looping through keys and checking intersection

Now let’s build the whole thing starting with the simple “key/value” type defined like:

type KeysAndValues<T> = {
    [K in keyof T]: T[K]
}
// So, nothing else than { foo: number, bar?: string }

Now let’s filter out bar? with the help of the intersection with undefined:

type II<T> = {
    [K in keyof T]: T[K] & undefined extends never ? K : never;
}

If the intersection with undefined extends never, we return the key K. In the other cases we return never. This leaves us with the following:

type IIa = II<{ foo: number, bar?: string }>;

// equal to { foo: "foo"; bar?: undefined; }

The left hand side of II<T> gives us just all the keys with [K in keyof T]. On the right hand side:

  • the intersection for foo extends never and therefore we return K which is in this case the key “foo”
  • for bar the intersection with undefined doesn’t extend never. So, we return never which leaves us with undefined in IIa

Putting everything together

At this moment we can determine the keys for required fields:

type RequiredKey<T> = {
    [K in keyof T]: T[K] & undefined extends never ? K : never;
}[keyof T];

type requiredKeysForI = RequiredKey<{ foo: number, bar?: string }>;
// "foo" | undefined

The expression { foo: “foo”; bar?: undefined; } accessed via keys [keyof T] gives “foo” | undefined.

Finally, we can use the required keys to construct the type:

type GetRequired<T> = {
    [K in RequiredKey<T>]: T[K];
};

If we only loop through the required keys, we only get foo. So, this finally leaves us with:

type I = GetRequired<{ foo: number, bar?: string }>

Our IDE shows us that we have found a good type to reduce something to the required fields in TypeScript:

If you’re interested in a type for chainable options, check out this post.