3

I want to write a simple function that looks like this

const options = {
  option1: "option1",
  option2: "option2",
  option3: "option3",
} as const;

function getOption(str: string) {
  if (options[str]) {
    /**
     * Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ readonly option1: "option1"; readonly option2: "option2"; readonly option3: "option3"; }'.
     * No index signature with a parameter of type 'string' was found on type '{ readonly option1: "option1"; readonly option2: "option2"; readonly option3: "option3"; }'.ts(7053)
     */
    return options[str];
  }

  return "defaultOption";
}

Now, I know I can solve it like this

const options = {
  option1: "option1",
  option2: "option2",
  option3: "option3",
} as const;

function getOption(str: keyof typeof options) {
  if (options[str]) {
    // Solved! No warning!
    return options[str];
  }

  return "defaultOption";
}

But it seems like I'm lying to my code...

For example, if I write something like this

function isArray(arg: any[]) {
  return Array.isArray(arg);
}

It will be weird to use this util because these two reasons

  1. If I know it's array before runtime, I don't need to use this util at all
  2. If I don't know if it's an array before runtime then TypeScript will yell again!

And here is another example:

Suppose I want to get the 'defaultOption' by passing argument null, it will be so weird

// TypeScript yells!
// Argument of type 'null' is not assignable to parameter of type '"option1" | "option2" | "option3"'.ts(2345)
console.log(getOption(null));
// => 'defaultOption'

I will have to do this to suppress the warning

// No more warning.
// Making null as any is weird
getOption(null as any);
// => 'defaultOption'

As you can see, I have to explicitly convert the null as any in order to get my 'defaultOption'

Is there any way I can improve my getOption util?

0

1 Answer 1

1

Ideally, you'd want to type getOption's argument to be a key of the options, to require callers to pass a sensible argument. But if you want to permit any argument may be passed to getOption, including a key, or a generic string, or any value at all like null, then type the argument as unknown, then have TS narrow down whether the argument is one of options keys:

function getOption(str: unknown) {
    if (str === 'option1' || str === 'option2' || str === 'option3') {
        return options[str];
    }
    return "defaultOption";
}
Sign up to request clarification or add additional context in comments.

2 Comments

You could also use a type guard function to be less repetitive, but it'd require a notable amount of code, so you might prefer to just write out every possible value unless there were a whole lot of keys
const keys: unknown[] = Object.keys(options); const isKey = (arg: unknown): arg is keyof typeof options => keys.includes(arg); then use return isKey(str) ? options[str] : "defaultOption"; The ugly unknown is needed is because of github.com/microsoft/TypeScript/issues/26255

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.