Making optional properties nullable in TypeScript
Let’s say you follow the TypeScript project coding guidelines and only use undefined
. Your types are defined with non-nullable optional properties (e.g., x?: number
), but the data coming from the API returns null
instead.
You decide to write a function to strip all these null
values from the response, so that they conform to your types:
function stripNullableProperties(obj) {
// Return a new object without null properties
}
How can you strongly type such helper without duplicating your input and output types? You could try:
function stripNullableProperties<T extends {}>(obj: T): T
But it won’t work in strict null checking mode, since obj
might have null
values that are not assignable to the non-nullable optional properties in T
:
type A = {
x: number
y?: number
}
stripNullableProperties<A>({
x: 1,
y: null, // Error: Type 'null' is not assignable to type 'number | undefined'.
})
What you really need is something like:
function stripNullableProperties<T extends {}>(obj: NullableOptional<T>): T
The NullableOptional<T>
type
The NullableOptional<T>
type constructs a type with all optional properties of T
set to nullable:
type A = {
x: number
y?: number
}
type B = NullableOptional<A>
// {
// x: number
// y?: number | null
// }
You won’t find NullableOptional
in the TypeScript documentation, and that’s because it’s a custom type. It actually looks like this:
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K
}[keyof T]
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends { [P in K]: T[K] } ? K : never
}[keyof T]
type PickRequired<T> = Pick<T, RequiredKeys<T>>
type PickOptional<T> = Pick<T, OptionalKeys<T>>
type Nullable<T> = { [P in keyof T]: T[P] | null }
type NullableOptional<T> = PickRequired<T> & Nullable<PickOptional<T>>
In short:
- pick the required properties from
T
; - pick the optional properties from
T
and make them nullable; - intersect (1) with (2).
With this, you can remove all nullable properties from an object whose interface should only have non-nullable optional properties, while still ensuring type safety:
type A = {
x: number
y?: number
}
stripNullableProperties<A>({
x: 1,
y: null,
})
// {
// x: 1,
// }: A
You could always do a type assertion and avoid all this trouble, but ensuring type safety—even in seemingly unharmful cases like this one—pays off in the long run.