Franziskus Karsunke
Franziskus Karsunke

Reputation: 5208

How to iterate a string literal type in typescript

How can I iterate a string literal type in typescript?

For example i define this type

type Name = "Bill Gates" | "Steve Jobs" | "Linus Torvalds";

I want to iterate like this

for (let name of Name) {
    console.log("Possible name: " + name);
}

Or is this simply not possible in typescript?

Upvotes: 84

Views: 43167

Answers (6)

Paul Thompson
Paul Thompson

Reputation: 1223

Rather than iterating a (union of) string literal types as the OP requested, you can instead define an array literal and if marked as const then the entries' type will be a union of string literal types.

Since typescript 3.4 you can define const assertions on literal expressions to mark that:

  • no literal types in that expression should be widened (e.g. no going from "hello" to string)
  • array literals become readonly tuples

For example:

const names = ["Bill Gates", "Steve Jobs", "Linus Torvalds"] as const;
type Name = typeof names[number];

Iterating that at runtime:

for(const n of names) {
  const val : Name = n;
  console.log(val);
}

It can often be more useful to define it as an object, especially if there's value information to associate to the entries:

const companies = {
  "Bill Gates" : "Microsoft",
  "Steve Jobs" : "Apple",
  "Linus Torvalds" : "Linux",
} as const;

type Person = keyof typeof companies;
type Company = typeof companies[Person];

for(const n of names) {
  const p : Person = n;
  const c : Company = companies[p];
  console.log(p, c);
}

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions

https://mariusschulz.com/blog/const-assertions-in-literal-expressions-in-typescript

https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html

Upvotes: 98

n8m8
n8m8

Reputation: 41

To add to @Paul Thompson's answer, a pattern I'm trying (to consolidate a large model that we want safety for at compile and runtime) is:

// GiftBasket.ts
// Before:
export type Fruit = 'Apple' | 'Banana';

// After:
export class GiftBasketForm {
  // ...
  edibleGift: Fruit,
  bonusGift: Toy | Fruit,
  // ...
  static AllowedValues: Record<string, readonly string[]> = {
    Fruit: [ 'Apple', 'Banana' ] as const,
    // ... a lot of models/allowed values to organize
  }
}

export type Fruit = typeof GiftBasketForm.AllowedValues.Fruit[number]

// Now you can both:
const fruitSafeAtCompileTime: Fruit = 'Apple';
const myFormAllowedValuesAtRuntime: Fruit[] = GiftBasketForm.AllowedValues.Fruit;

// Or an example case where you might use both at once:
const makeRandomFruit: Fruit = getRandom(GiftBasketForm.AllowedValues.Fruit);

Upvotes: 1

Maurici Abad
Maurici Abad

Reputation: 1718

You can't by using Union types. But you can do it with enums:

enum Name {
  'Bill Gates' = 'Bill Gates',
  'Steve Jobs' = 'Steve Jobs',
  'Linus Torvalds' = 'Linus Torvalds'
}

for (const name of Object.keys(Name)) {
  console.log('Possible name: ' + name)
}

Upvotes: 4

Aron
Aron

Reputation: 9248

Since TypeScript is just a compiler, none of the typing information is present at runtime. This means that unfortunately you cannot iterate through a type.

Depending on what you're trying to do it could be possible for you to use enums to store indices of names that you can then retrieve in an array.

Upvotes: 23

Franziskus Karsunke
Franziskus Karsunke

Reputation: 5208

Since typescript 2.4 it is possible to use string typed enums. These enums can easily be iterated:

https://blogs.msdn.microsoft.com/typescript/2017/06/27/announcing-typescript-2-4/

Upvotes: 3

Jokester
Jokester

Reputation: 5617

AFAIK there is no way to "lift" a string union (type) to runtime JS (value).

The closest solution I found was to use enum: example / related issue.

Upvotes: 3

Related Questions