TypeScript: Restricts arguments of a function

Viewed 31

could someone give me a hint on how to restrict the valid arguments of the function getMessage:

type Translations = {
    readonly [key: string]: Localize;
}

interface Localize {
    readonly "en": string,
    readonly "de"?: string,
    readonly "fr"?: string
}

const translationsObj: Translations = {
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
};

type TranslationKeys = keyof typeof translationsObj; // = string | number

function getMessage(key: TranslationKeys) {
    return translationsObj[key]["de"];
}

const msg1 = getMessage("keyOne"); // OK
const msg2 = getMessage("notAPropOfTranslationsObj"); // should be marked as error

Only the properties of the object translationsObj should be allowed to be passed.

Roland

2 Answers

Get rid of the Translations type. By defining it as a map, you're basically saying its keys are unbounded and can be any string.

Instead, just define it in terms of what keys are available and create a record out of that.

type TranslationKeys = "keyOne" | "keyTwo";

const translationsObj: Record<TranslationKeys, Localize> = {
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
};

function getMessage(key: TranslationKeys) {
    return translationsObj[key]["de"];
}

const msg1 = getMessage("keyOne"); // OK
const msg2 = getMessage("notAPropOfTranslationsObj"); // error

playground


On the other hand, if you have a lot of keys you map out, then you might want to create a helper function to get the right types.

const translations = <T extends { [key: string]: Localize }>(t: T) => t as Record<keyof T, Localize>;

const translationsObj = translations({
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
});

type TranslationKeys = keyof typeof translationsObj;

playground

If you were to use Typescript 4.9 beta, you could use the satisfies operator; it does exactly what you want.

type Translations = {
    readonly [key: string]: Localize;
}

interface Localize {
    readonly "en": string,
    readonly "de"?: string,
    readonly "fr"?: string
}

const translationsObj = {
    "keyOne": { "en":"one", "de":"eins" },
    "keyTwo": { "en":"two", "de":"zwei" }
} satisfies Translations;

type TranslationKeys = keyof typeof translationsObj; // = string | number

function getMessage(key: TranslationKeys) {
    return translationsObj[key]["de"];
}

const msg1 = getMessage("keyOne"); // OK
const msg2 = getMessage("notAPropOfTranslationsObj"); // error

Playground link

Related