Reputation: 1817
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.
Upvotes: 5
Views: 2403
Reputation: 2277
A simple and nice solution in my case with today's available utilities for me was to create an interface that extends on the Pick, that also includes the nullable (to be made non-nullable) fields. Then on the interface itself declare the fields that should be non-nullable again, basically overwriting it. Like so:
interface Dog
extends Pick<
Animal,
"name" | "birthDate" | "breed"
> {
breed: string;
}
It's straightforward and readable.
Upvotes: 0
Reputation: 249606
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]
Upvotes: 5