Search code examples
typescripttypescript-genericstypecheckingtype-constraints

DeepPartial: use null instead of undefined


When applying the DeepPartial generic type from utility-types to Typescript type I notice that the type of all properties is automatically expanded to include undefined. I.e. a property that previous was of type T now become T | undefined.

E.g. the following code fails typechecking:

import { DeepPartial } from 'utility-types';

type A = { a1: number, a2: number};
type B = DeepPartial<A>;

const b: B = { a1: null };

Typescript playground is here.

I get it that this is what plain old Partial does as well. But is there a deeper reason why | undefined was chosen (in both cases) as opposed, e.g. to: | null or | null | undefined and is there a way I can customize the DeepPartial generic type so as to generate property types with | null instead?


Solution

  • The purpose of Partial<T> is to transform a type like {x: number, y: string} into a type where the properties are all optional, like {x?: number, y?: string}. In this case the types of the properties are number | undefined and string | undefined because ? means the property is allowed to be missing, and undefined is what you get when accessing a missing property.

    To get null instead of undefined, you can just take the source code for DeepPartial and adjust it:

    type DeepNullable<T> = T extends Function
        ? T
        : T extends Array<infer U>
        ? _DeepNullableArray<U>
        : T extends object
        ? _DeepNullableObject<T>
        : T | null;
    
    interface _DeepNullableArray<T> extends Array<DeepNullable<T>> {}
    
    type _DeepNullableObject<T> = { [P in keyof T]: DeepNullable<T[P]> | null };
    

    Demo:

    type Foo = {
        foo: number,
        bar: {
            baz: Array<{ quz: string }>
            qux: boolean
        }
    }
    
    // test has no type errors
    let test: DeepNullable<Foo> = {
        foo: null,
        bar: {
            baz: [{quz: null}, {quz: null}],
            qux: null
        }
    }
    

    Playground Link