General way of picking nullable properties and making them non-nullable

Viewed 824

Let's have a following example type:

interface A {
    a?: number;
    b: string;
}

My goal is to have a generic way of creating following type:

interface ExpectedA {
    a: number;
}

So I want to remove all non-nullable fields (those which can contain null and/or undefined) and make those remaining nullable fields non-nullable.

This is how I imagine it should work:

const expA1: ExpectedA = {}; // should NOT pass
const expA2: ExpectedA = {a: 1}; // should pass
const expA3: ExpectedA = {b: ''}; // should NOT pass
const expA4: ExpectedA = {c: 0}; // should NOT pass
const expA5: ExpectedA = {a: 1, b: ''}; // should NOT pass

This is my non working attempt (annotated in comments what it does and what it should do):

export type ExtractNullable<T> = {
    [K in keyof T]: T[K] extends undefined | null ? NonNullable<T[K]> : never;
};

const a1: ExtractNullable<A> = {}; // should NOT pass, wrong error "prop. b is missing"
const a2: ExtractNullable<A> = {a: 1}; // should pass, wrong - "number not undefined"
const a3: ExtractNullable<A> = {b: ''}; // should NOT pass, wrong - "string not never"
const a4: ExtractNullable<A> = {c: 0}; // should NOT pass, ok - "c not on ..."
const a5: ExtractNullable<A> = {a: 1, b: ''}; // should NOT pass, wrong error "number not undefined, string not never"

I think the problem is in the conditional type, but looking at the docs, I have no idea what to change.

1 Answers

You need to select only the nullable keys first and then map them.

interface A {
    a?: number;
    b: string;
}

export type NullableKeys<T> = {
    [P in keyof T]-? :  Extract<T[P], null | undefined> extends never ? never: P
}[keyof T]
export type ExtractNullable<T> = {
    [P in NullableKeys<T>]: NonNullable<T[P]>
}

const a1: ExtractNullable<A> = {}; // err
const a2: ExtractNullable<A> = {a: 1}; //ok
const a3: ExtractNullable<A> = {b: ''}; // err
const a4: ExtractNullable<A> = {c: 0}; // err
const a5: ExtractNullable<A> = {a: 1, b: ''}; //err

The above approach works with strictNullChecks since the type of optional properties is changed to include undefined. A version that picks optional properties and works without this compiler option is:

export type NullableKeys<T> = {
    [P in keyof T]-?:  Pick<T,P> extends Required<Pick<T, P>> ? never: P
}[keyof T]
Related