How to Restrict Possible String Arguments

eye-catch JavaScript/TypeScript

I want to limit the possible string arguments. How can I do it? This article is for you. I will show 4 ways to do it.

Sponsored links

The problem we want to solve

Let’s look at an example that we need to limit possible string arguments.

interface GreetingInterface {
    hello: () => void;
    goodbye: () => void;
    hey: () => void;
}
class Greeting implements GreetingInterface {
    public hello() { console.log("hello") }
    public goodbye() { console.log("good bye") }
    public hey() { console.log("hey") }
}
const greeting = new Greeting();

function func(key: string): void {
    if (key === "hello" || key === "goodbye" || key === "hey") {
        greeting[key]();
        return;
    }
    console.log("not executed.");
}
func("hey");
// hey
func("undefined-key");
// not executed.

We want to call one of the functions of the interface, so we need to check whether the specified string matches the function’s name or not. This example above of course works but when we have many possible values it has more number of conditions. It increases complexity and eslint or another tool might show an error.

Sponsored links

Limit possible string by Union type

The first way is using the union type. We can’t set another value.

function func1(key: "hello" | "goodbye" | "hey"): void {
    greeting[key]();
}
func1("hey");
func1("hogehoge"); // error
// Argument of type '"hogehoge"' is not assignable to parameter of type '"hello" | "goodbye" | "hey"'.ts(2345)

Limit possible string by User Defined Type

We can define user-defined type to extract the union type. Its implementation is basically the same but it’s more readable.

type GreetingType = "hello" | "goodbye" | "hey";
function func2(key: GreetingType): void {
    greeting[key]();
}
func2("hey");
func2("hogehoge"); // error
// Argument of type '"hogehoge"' is not assignable to parameter of type '"hello" | "goodbye" | "hey"'.ts(2345)

Limit possible string by Enum

Another way is using enum.

enum GreetingEnum {
    Hello = "hello",
    Goodbye = "goodbye",
    Hey = "hey",
}
function func3(key: GreetingEnum): void {
    greeting[key]();
}
func3(GreetingEnum.Hey);
// hey
func3(GreetingEnum.Hello);
// hello
func3("hey"); // error
// Argument of type '"hey"' is not assignable to parameter of type 'GreetingEnum'.ts(2345)

The function call is different from the previous two examples.

Limit possible string by keyof interface

The last way to do the same thing is using a combination of keyof and interface. keyof keyword creates a new type from the Object type.

type GreetingKey = keyof GreetingInterface;
function func4(greetingKey: GreetingKey) {
    greeting[greetingKey]();
}
func4("hey");
// hey
func4(GreetingEnum.Hello);
// hello
func4("hogehoge"); // error
// Argument of type '"hogehoge"' is not assignable to parameter of type '"hello" | "goodbye" | "hey"'.ts(2345)

It creates Greetingkey that is "hello" | "goodbye" | "hey". It means that we don’t have to add a new word when we add a new function into GreetingInterface.

Use value from function result

The examples above look fine but what if we set the value from function result?

function getHelloString(): string {
    return "hello";
}
const hello = getHelloString();
func2(hello); // error
// Argument of type 'string' is not assignable to parameter of type '"hello" | "hey" | "goodbye"'.ts(2345)

We need to check if the value is one of those 3 strings. If the function requires enum value it looks like this.

function isGreetingEnumValue(value: string): value is GreetingEnum {
    return Object.values(GreetingEnum).includes(value as any);
}
if (isGreetingEnumValue(hello)) {
    func2(hello);
    func3(hello);
    func4(hello);
}

Following code doesn’t work if the enum is string. It works only when the enum contains only number.

if (hello in GreetingEnum) {
    func2(hello as GreetingEnum);
}

You can learn more about enum here.

Comments

Copied title and URL