NonNullableKeys Utility Type
Filtering an object type to only the keys whose values are not nullable comes up everywhere: required-fields lists, mandatory column sets, GraphQL non-null fields. This snippet builds a `NonNullableKeys<T>` that returns just those keys, then layers in a `RequiredFields<T>` that produces a sub-object type, and a runtime helper that strips nullable fields from a value at runtime.
1,095 views
20
type NonNullableKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? never : null extends T[K] ? never : K }[keyof T];
interface User {
id: string;
nickname?: string;
avatar: string | null;
score: number;
}
type RequiredKey = NonNullableKeys<User>;
const keys = ['id', 'score'] as RequiredKey[];
console.log(keys.join(','));The trick is the mapped-type-with-conditional pattern: build { [K in keyof T]: ... } where each value is K if the original type contains neither null nor undefined, and never otherwise. Indexing the result by [keyof T] collapses it into a union of just the surviving keys (the never entries are erased automatically). The -? removes optionality so an ?: field, which is implicitly T | undefined, is correctly classified as nullable. Use this whenever you need to derive a key list from an existing type rather than maintain it by hand.
type NonNullableKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? never : null extends T[K] ? never : K }[keyof T];
type RequiredFields<T> = Pick<T, NonNullableKeys<T>>;
interface UserB {
id: string;
nickname?: string;
avatar: string | null;
score: number;
}
const required = { 'id': 'u1', 'score': 99 } as RequiredFields<UserB>;
console.log(`required has ${Object.keys(required).length} keys`);Once you can extract the keys, Pick<T, NonNullableKeys<T>> projects a new type that retains only those entries. This is the right shape for an insert payload to a database (only the NOT NULL columns), a form's required-fields validator, or a query that has to fetch every non-nullable property. The runtime sentinel literal can omit nullable fields and still satisfy the static contract because nullable keys are no longer in the projected type. Pair this with a runtime stripper (next accordion) when you need value-level enforcement, not just a type guarantee.
type NonNullableKeys<T> = { [K in keyof T]-?: undefined extends T[K] ? never : null extends T[K] ? never : K }[keyof T];
type RequiredFields<T> = Pick<T, NonNullableKeys<T>>;
function stripNullable<T extends object>(value: T): RequiredFields<T> {
const out = {} as Record<string, unknown>;
for (const k of Object.keys(value)) {
const v = (value as Record<string, unknown>)[k];
if (v !== null && v !== undefined) out[k] = v;
}
return out as RequiredFields<T>;
}
const stripped = stripNullable({ 'id': 'u1', 'avatar': null, 'score': 99 });
console.log(`kept ${Object.keys(stripped).join(',')}`);Pairing the type with a runtime stripper closes the loop: callers pass a partially-populated object and get back a subset that matches the static RequiredFields<T> shape. The body is a plain key walk that drops null and undefined, which mirrors how most JSON-to-DB layers treat optional columns. The cast at the return is honest about the static-vs-runtime gap; the function trusts that the input fits T and returns the projected shape for callers downstream. Combine this with API request mappers, GraphQL resolvers, or any boundary that wants 'no nulls past this point'.
