callum
callum

Reputation: 37829

How to refine a string to a "string enum" in TypeScript?

Take this simple example of a string enum:

enum Animal {
  Dog = "Dog",
  Cat = "Cat",
  Sheep = 'Sheep'
}

const getNoise = (animal: Animal) => {
  switch (animal) {
    case Animal.Dog:
      return 'woof';
    case Animal.Cat:
      return 'meow';
    case Animal.Sheep:
      return 'baa';
  }
}

But let's say I want to make another function that processes an arbitrary string, originating from untrusted user input. The string should be a valid Animal, but it might contain typos, so we need to validate it at runtime.

Here's an example:

const getNoiseUntrusted = (animal: string) => {
  if (!(animal in Animal)) {
    throw new Error('Animal not recognised');
  }

  return getNoise(animal); // TypeScript error
}

The getNoise(animal) call causes a TypeScript error:

Argument of type 'string' is not assignable to parameter of type 'Animal'.

How can I get TypeScript to cast this string to an Animal?

Upvotes: 3

Views: 230

Answers (2)

CGundlach
CGundlach

Reputation: 671

Casting with as works:

const getNoiseUntrusted = (animal: string) => {
  if (!(animal in Animal)) {
    throw new Error('Animal not recognised');
  }
  return getNoise(animal as Animal);
}

You can also expand on the switch in your regular getter, since you'd be checking the input string against the value of the specific enums.

const getNoiseUntrustedSwitch = (animal: string) => {
  switch (animal) {
    case Animal.Dog:
      return 'woof';
    case Animal.Cat:
      return 'meow';
    case Animal.Sheep:
      return 'baa';
    default: 
    throw new Error('Animal not recognised');
  }
}

And finally, to build on @Daniel's answer, you can also cast while accessing the enum:

const getNoiseUntrustedEnumAcess = (animal: string) => {
  if (!(animal in Animal)) {
    throw new Error('Animal not recognised');
  }
  return getNoise(Animal[animal as Animal]);
}

See on TypeScript Playground

Although note that this is case-sensitive, so you may need to modify user input slightly before checking whether or not it is part of the enum.

Upvotes: 1

Daniel
Daniel

Reputation: 1042

Animal["Dog"] will return "Dog"

Animal["Puppy"] will return undefined


const getNoiseUntrusted = (animal: string) => {
    if(!Animal[animal]){
        throw new Error('Animal not recognised');
    }
}

Upvotes: 2

Related Questions