Object with optional keys but with a mandatory value in TypeScript

Viewed 558

I am tring to have a map type with a Record in Typescript with the key in an Enum.

I have this Enum :

enum CatName {
  miffy = "miffy",
  boris = "boris",
  mordred = "mordred",
}

And this Interface :

interface CatInfo {
  age: number;
  breed: string;
}

If I use this type :

type Cats = Record<CatName, CatInfo>

I must use all of the keys of the enum and that is not what I want.

If I use this type :

type Cats = Partial<Record<CatName, CatInfo>>

I can create a map with only some keys of the Enum as I want but CatInfo can be now undefined and I need it to be mandatory.

Is there a way to achieve a map with some keys but with a mandatory value ?

Here is a link to a playground if it can help.

1 Answers

For versions of TypeScript up to and including 4.3, there is no straightforward way to do this. Optional properties are allowed to be missing, but also allowed to be present-but-undefined. From the point of view of someone just reading the value of such a property, there is no difference; but from the point of view of writing a value to such a property, or checking the existence via in or hasOwnProperty(), there is a difference. The issue microsoft/TypeScript#13195 asks for TypeScript to distinguish between missing and undefined. For such versions of TypeScript, the right thing to do is probably just accept undefined. If you want to check if the property is present-and-defined, you should not use in or hasOwnProperty():

if ("boris" in cats2) {
  cats2.boris.age.toFixed(2); // error in TS4.3-
//~~~~~~~~~~~
// Object is possibly 'undefined'.
}

if (cats2.boris !== undefined) {
  cats2.boris.age.toFixed(2); // okay in TS4.3-
}

Instead you should just index into the object with the key and make sure the value is not undefined:

if (cats2.boris !== undefined) {
  cats2.boris.age.toFixed(2); // okay in TS4.3-
}

Playground link to code


For versions of TypeScript 4.4 and greater, there will be a new compiler option that fixes microsoft/TypeScript#13195 if you enable it. See microsoft/TypeScript#43947 for details. (UPDATE 2021-06-10: This functionality was originally going to be enabled via a new --strictOptionalProperties compiler flag, which would have been included in the --strict suite of compiler options. However, according to this comment, the flag will be renamed and removed from --strict, so anyone who wants to see this behavior in TypeScript 4.4 and above will need to manually enable it, via some compiler flag whose name has not yet been decided.)

If you use this feature, you will no longer be allowed to assign undefined to an optional property (unless that property's type explicitly includes undefined), while you still need to check properties you read for undefined:

const cats2: Cats = {
  miffy: { age: 10, breed: "Persian" },
  boris: undefined, // error in TS4.4+!
  // Type 'undefined' is not assignable to type 'CatInfo'
};

and you will also be able to check for the presence of the key with in or hasOwnProperty() and eliminate the undefined possibility when reading:

if ("boris" in cats2) {
  cats2.boris.age.toFixed(2); // okay in TS4.4+
}

Playground link to code

Related