Home > Software design >  Why a Typescript Record with a union of a primitive and a specific string makes the string mandatory
Why a Typescript Record with a union of a primitive and a specific string makes the string mandatory

Time:01-21

I have this type:

type ErrorMessages = Record<number | 'default', string>;

Then, when I define a variable as const text: ErrorMessages = {403: 'forbidden'}, Typescript says that default is missing in the type:

Playground example

Why a Typescript Record with a union of a primitive and a specific string makes the string mandatory?

CodePudding user response:

Record distributes the union you give for the key argument, creating an object type requiring all of those keys. In your IDE or the TypeScript playground, if you hover over ErrorMessages, you'll see the expanded definition of it, which makes the problem clear:

type ErrorMessages = {
    [x: number]: string;
    default: string;
}

Similarly, Reocrd<"a" | "b", string> requires both a and b properties.

Instead, you can define ErrorMessages as an object type directly, explicitly while making default optional via a postfix ?, like this:

type ErrorMessages = {
    [key: number]: string;
    default?: string;
};

That allows both of your assignments:

const text1: ErrorMessages = { 403: "forbidden" };
const text2: ErrorMessages = { default: "something else" };

That will also allow multiple messages, which I think is correct given the type name ErrorMessages (plural):

const text3: ErrorMessages = {
    default: "something else",
    403: "forbidden",
};

...while disallowing other string keys:

// Error as desired
const text4: ErrorMessages = { foo: "bar" };
//                             ^^^^^^^^^^ Type '{ foo: string; }' is not assignable to type 'ErrorMessages'.
//                                        Object literal may only specify known properties, and 'foo' does not exist in type 'ErrorMessages'. (2322)

Playground link

CodePudding user response:

You can use built-in Partial utility type, like so:

type ErrorMessages = Partial<Record<number | 'default', string>>
  • Related