'Typing a partial record where any present key must be defined

How could one type a record/object where the properties are optional, but if they are present they must be defined?

For example:

type K = "foo" | "bar";

type R = {
    // The props are optional, great, but I don't want to allow undefined values
    [T in K]?: number;
};

const r1: R = { foo: 1 }; // Ok, great
const r2: R = { foo: undefined }; // I want this to be an error

// Error: Type '(number | undefined)[]' is not assignable to type 'number[]'
// I want this to be ok, as all present keys should be defined
const values: number[] = Object.values(r1);

Playground Link

After using such typing I want to be able use Object.values() and not have to deal with any undefined types, only number in the example.



Solution 1:[1]

TypeScript 4.4:

This is made possible with and implemented in this PR released in TypeScript 4.4! ?

Compile the example with --exactOptionalPropertyTypes to get the desired behavior, no other changes needed!

In --exactOptionalPropertyTypes mode for an optional property to also be assignable undefined it needs to be typed with undefined. For example: { porp?: number | undefined }.

An example from the --exactOptionalPropertyTypes PR:

// Compile in --strictOptionalProperties mode
function f2(obj: { a?: string }) {
    obj = obj;
    obj.a = obj.a;  // Error,  'string | undefined' not assignable to 'string'
    if ('a' in obj) {
        obj.a = obj.a;  // Ok
    }
    if (obj.hasOwnProperty('a')) {
        obj.a = obj.a;  // Ok
    }
}

TypeScript <=4.3:

andrewbranch of TypeScript commented in Jan 2021:

[...] there’s no way to say that some property must be either string or not present on the object at all. (You can do that for fresh object literals via a union type, but can easily get around it by indirect assignment.)

And there is still an open issue on "Distinguish missing and undefined" from 2016.

So it seems there is unfortunately no way to do this in TypeScript, in a general way anyway, at the moment.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1