TypeScript: Transforming optional properties to required properties that may be undefined
Here’s a handy TypeScript generic for transforming a type with optional properties into a new type where all the properties exist, but the optional properties on the original type may be undefined on the new one. First, a little background…
When defining a type in TypeScript, we can specify that a property is optional with a question mark after the name:
Or we can specify that a property may be undefined:
These two interfaces seem nearly identical. In either case, accessing the property foo
may return the value undefined
. They are subtly different, though, as evidenced by the fact that TypeScript won’t actually allow us to assign InterfaceWithOptional
to InterfaceWithUndefined
:
TypeScript reports:
Type ‘InterfaceWithOptional’ is not assignable to type ‘InterfaceWithUndefined’.
Property ‘foo’ is optional in type ‘InterfaceWithOptional’ but required in type ‘InterfaceWithUndefined’.ts(2322)
This makes sense if you consider that foo?: number
means that the property foo
may not exist at all on instances of InterfaceWithOptional
. 'foo' in instance
for example, will return false.
Transforming
We can use conditional types to automatically transform InterfaceWithOptional
into InterfaceWithUndefined
. It looks like this:
We can use this to transform a type with a mix of required and optional properties into a new type where all the properties are required but some of them may be undefined:
This works by mapping over Required<T>
, a version of our original type where all the optional properties have been replaced by required ones. Then, for each property, we use a conditional to check whether a type created by extracting just that one property extends (read: is assignable to) the same type where the property has been made required.
If the property was already required, we’re effectively testing if a type is assignable to itself. It is, so the type of the property in our “output” is exactly the same as the “input”. If the property was optional on input, however, the extends
test will fail, much like howInterfaceWithOptional
was not assignable to InterfaceWithUndefined
above. In that case, we’ll add an | undefined
to the property’s type.
While this is all a bit esoteric and there probably aren’t too many situations where you need to do this transformation, I hope this post saves you some time if you do need to work out how to do it.