0

I have a typeguard which take a string and I wanna know if it's part of a union type but if we add new string in the union type, I don't want to manage my typeguard by also adding the new string:

type GreatAnimal = 'Dog' | 'Cat'

function isGreatAnimal(pet: string): pet is GreatAnimal {
    return pet === 'Dog' || pet === 'Cat'; // Here I would prefer to do something like 'pet keyof GreatAnimal'
}

function Foo(animal:string){
    if (isGreatAnimal(animal)) {
        // do something

    }
    else {
        // do something else
    }
}

If I add Fish in the union type 'GreatAnimal', I don't want to have to update my type guard to manage Fish.

Im using last typescript version

John
  • 4,351
  • 9
  • 41
  • 57

2 Answers2

1
const GreatAnimalList = ['Dog', 'Cat', 'Fish'] as const; // TS3.4 syntax
type GreatAnimal = typeof GreatAnimalList[number]; // 'Dog'|'Cat'|'Fish';

function isGreatAnimal(pet: string): pet is GreatAnimal {
  // return GreatAnimalList.includes(pet)
  return GreatAnimalList.indexOf(pet as GreatAnimal) !== -1
}

function Foo(animal: string) {
  if (isGreatAnimal(animal)) {
    // do something
    console.log('isGreatAnimal true')
  }
  else {
    // do something else
    console.log('isGreatAnimal false')
  }
}

Foo('Dog');      // isGreatAnimal true
Foo('Turtle');   // isGreatAnimal false

Tomorrow if you add a new animal into GreatAnimalList, it will be automatically added into the union type GreatAnimal. And no change is needed in isGreatAnimal() function.

Nithin Kumar Biliya
  • 2,763
  • 3
  • 34
  • 54
  • includes doesn't exist on type GreatAnimalList with Typescript 3.6.3 (last version) – John Sep 21 '19 at 19:37
  • check [this answer](https://stackoverflow.com/a/45809606/1298824). If you don't want to change to es2016, then I have updated my answer to not use `Array.includes()`. – Nithin Kumar Biliya Sep 21 '19 at 19:44
  • it doesn't work with indexof. I got this error on typescript playground website: Argument of type 'string' is not assignable to parameter of type '"Dog" | "Cat" | "Fish"'. – John Sep 21 '19 at 19:47
  • how do you call this syntax: typeof GreatAnimalList[number]; I understand what is does in your example, but I want to find documentation about it to understand how it works – John Sep 21 '19 at 20:08
  • check [this answer](https://stackoverflow.com/a/45257357/1298824) and [this answer](https://stackoverflow.com/a/55505556/1298824) and the links in it – Nithin Kumar Biliya Sep 21 '19 at 20:14
0

You cannot use types programmatically, since they won't exist in the transpiled JavaScript code.

For example the first part of your code

type GreatAnimal = 'Dog' | 'Cat'

function isGreatAnimal(pet: string): pet is GreatAnimal {
    return pet === 'Dog' || pet === 'Cat';
}

transpiles to

function isGreatAnimal(pet) {
    return pet === 'Dog' || pet === 'Cat';
}

Therefore you need some programmatic reference to GreatAnimals in order to implicitly check for them. There are many ways to do this - one example would be with an enum (which is transpiled down to a plain object):

enum GreatAnimal {
  DOG = "Dog",
  CAT = "Cat"
}

function isGreatAnimal(pet: string): pet is GreatAnimal {
  return pet in Object.values(GreatAnimal);
}

function Foo(animal: string) {
  if (isGreatAnimal(animal)) {
    // do something
  } else {
    // do something else
  }
}

(note that this example uses Object.values, which is part of ES2017. Internet Explorer doesn't support it and node supports it since v7)

Cynigo
  • 374
  • 2
  • 11