86

I have defined a custom literal type in TypeScript:

export type Market = 'au'|'br'|'de';

Now I want to iterate over each possible Market without having to create an array of Market[] in the first place as it feels redundant and I may forget to add one option:

const markets: Market[] = ['au', 'br', 'de'];
markets.forEach((market: Market) => {
    console.log(market);
});

Is there a way to achieve that with TypeScript?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
k0pernikus
  • 60,309
  • 67
  • 216
  • 347
  • 4
    It's not possible but I really would like the TS team add something like an introspection in types, available at runtime. – Paleo Nov 29 '16 at 10:48
  • 3
    The whole point of type alias (and interfaces) is that they don't get compiled into javascript. If you want something for compilation only then you can use it, but if you need it for runtime then you have to use something that exists in js. – Nitzan Tomer Nov 29 '16 at 11:10
  • @NitzanTomer The point of type alias and interface is to verify that structures are compliant with the expected "shape" of values. – Suncat2000 May 05 '22 at 19:14

5 Answers5

109

For those of you visiting this question using TypeScript >= 3.4, I believe the best practice is now to create a constant array of strings and then use the type of operator.

Example:

export const markets = ['au', 'br', 'de'] as const;
export type Market = typeof markets[number];

markets.forEach((market: Market) => {
    console.log(market);
});
Shunji Lin
  • 632
  • 6
  • 9
keithhackbarth
  • 9,317
  • 5
  • 28
  • 33
  • 3
    Thank's :-) What does the `as const` mean? – Gil Epshtain Mar 09 '21 at 08:13
  • 3
    It tells Typescript that it is immutable. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions – keithhackbarth Mar 09 '21 at 23:13
  • 1
    In which case `Market` type equals `string` (and not `'au'|'br'|'de'`). Doesn't it defeat the purpose? – scale_tone Mar 26 '21 at 17:07
  • 9
    @scale_tone - Incorrect. Market type does not equal `string`. ` const markets = ['au', 'br', 'de'] as const; type Market = typeof markets[number]; const test: Market = 'test'; Type '"test"' is not assignable to type '"br" | "au" | "de"'. ` – keithhackbarth Mar 26 '21 at 22:46
  • @keithhackbarth, indeed. I must have been fooled by vscode hinting. – scale_tone Mar 27 '21 at 14:04
  • 4
    The type will be `string` if you forget the `as const` clause. just happened to me... :/ – devio Jan 22 '23 at 16:29
22

No, you can't do that, as pure type information like that doesn't exist at runtime.

It's hypothetically plausible to do the other way (define a normal list of strings, and then derive the 'au'|'br'|'de' type from that), but I don't think the TypeScript compiler (either 2.0 or 2.1) will currently infer that for you - as far as I know the type of markets will always be string[] normally.

The right answer to this is to use enums. They define a type with each of their values, and it's possible to get a list of all their string values: How to programmatically enumerate an enum type in Typescript 0.9.5?.

The one downside to enums is that their runtime representation is different (under the hood they're actually numbers, not strings). Your code can still treat them as nice readable values though, it's just you'll have to translate them to strings if do you ever you need them as string names at runtime. That's easy though: given enum MarketEnum and a value myEnumValue, MarketEnum[myEnumValue] is the value's name as a string).

Community
  • 1
  • 1
Tim Perry
  • 11,766
  • 1
  • 57
  • 85
6

Complete example for TypeScript 3.9 that I think is the easiest to follow:

enum SupportedLangugesEnum {
    'au' = 'au',
    'br' = 'br',
    'de' = 'de',
}

for (let entry in SupportedLangugesEnum) {
    if (isNaN(Number(entry))) {
        console.log(entry);
    }
}

for (let entry of Object.keys(SupportedLangugesEnum)) {
    console.log(entry);
}

for (let entry of Object.values(SupportedLangugesEnum)) {
    console.log(entry);
}

Will print:

enter image description here

Source:

https://stackoverflow.com/a/39372911/3850405

Ogglas
  • 62,132
  • 37
  • 328
  • 418
4

Here's an alternative way:

enum Market {
   'eu' = 'eu',
   'us' = 'us',
}

const all_possible_market_values = getStringValuesFromEnum(Market)

function getStringValuesFromEnum<T>(myEnum: T): (keyof T)[] {
   return Object.keys(myEnum) as any
}

The underlying representation is no longer numbers. It's the string.

"use strict";
var Market;

(function (Market) {
    Market["eu"] = "eu";
    Market["us"] = "us";
})(Market || (Market = {}));

const all_possible_market_values = getStringValuesFromEnum(Market);

function getStringValuesFromEnum(myEnum) {
    return Object.keys(myEnum);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
-2

Here's what I would do to create an array that contains all of your values at run-time and is checked at compile time:

export type Market = 'eu' | 'us'

export const MARKET_TYPES: Market[] = (() => {
   const markets: Market[] = ['eu', 'us']

   return markets.map((x: Market) => {
      if (x === 'eu') {
         return x
      } else if (x === 'us') {
         return x
      } else {
         const _exhaustive_check: never = x
         return _exhaustive_check
      }
   })
})()
  • 1
    Doesn't your map always return `x`? I don't see how your map is different from this one: `.map((x: Market) => x)`. – ANeves Apr 15 '21 at 13:50
  • 1
    I wouldn't have downvoted this answer. I believe the intent was to show that you can create a type, and then create an array of available types to iterate over. What you do with it is arbitrary in the map method. I do really enjoy seeing the exhaustive check, more typescript developers need to do this. +1 – Travis Peterson Jan 07 '22 at 19:34
  • @TravisPeterson it seems to be trying to show that `markets` contains every value - which is not necessarily the case... [playground with no type error](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcCyBDKBrY8C8cA5MAK5FwA+xpAzkQFAOiSxwDGEAdrfGgIIAlANIBRACoB9cQE0ACqIDKALnRZcMANoBdOIQAU+gJR6AfHADeDODc494AW3V5aqzDjw69cTSXLamGzgoPFIoLjgnDxhaADonMEMQN2cYE3xzKyCggEsAMzh9ED18Qj8iEyzs7JCYMIiQa2qAXzhgABtaVHzC4tKyugrLJuqbWvq4RtHWjq7h0aC7XjhJUAALDDoYHIA3YEl2NeB2bFUuYD2obymF4NDwlfXN3l39w+PsEaDmkeajBj+xiAA) – somebody Mar 08 '23 at 00:16