String-literal Union Types to Array - or - How to kick Pluto out of the list of planets
Tags: typescript
Published: 2022-10-07 00:52:57

[Pluto](https://en.wikipedia.org/wiki/Pluto)! Once a member of our own solar system's list of 9 planets, is now considered a dwarf planet. Who thought that list would change? And if Pluto is ever re-considered to be more than a dwarf, what happens to [Planet Nine](https://en.wikipedia.org/wiki/Planet_Nine)'s name?

Similar to our solar system, we sometimes expect that some code or master data doesn't change. But when it does,
- `["Pluto"]` is often hardcoded in arrays for dropdown-lists etc.
- `{"Pluto": {"discovered":1930, "namedAfter": "Pluto"}}` can be found in keys or values of complex json objects
- or it is a parameter of a `function getLegendFromMythologyByDeity(f: "Pluto" | "Flora" | "Juno" | "Apollo" | "Minerva" | "Diana" | "Vulcan" | "Sol")`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2j2823v3eg968uzze0n.jpg)

So to improve the situation, we can fill these types by hand. They're called string literal types, more on that topic [here](https://mariusschulz.com/blog/string-literal-types-in-typescript):

```typescript
export type PlanetsType: "Mercury" | "Venus" | "Earth" | "Mars" | "Jupiter" | "Saturn" | "Uranus" 
  | "Neptune"; 
  //| "Pluto";
export type MythologicalDeityType: "Pluto" | "Flora" | "Juno" | "Apollo" | "Minerva" | "Diana" | "Vulcan" | "Sol";
export type MickeysFriendsType: "Pluto" | "Minnie" | "Donald";
```

If you additionally want an array that contains all possible permutations of a string literal union type, you could fill that by hand:
```typescript
export const PlanetsTypeKeys: PlanetsType[] = [
  "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus"
  , "Neptune"];
```
...but you would have to remember to update that each time a new string literal type enters that union.
```typescript
export type PlanetsType: "Mercury" | "Venus" | "Earth" | "Mars" | "Jupiter" | "Saturn" | "Uranus" 
  | "Neptune" 
  | "Planet9TheMisterious"; // <-- the new one, now the array has to be updated
```



# A self-sustaining solution

Remembering to update the "right part" of the code is usually hard, even if you leave a comment. Throwing type errors is thus much better - and because we can't create an Array (runtime - javascript) from a union type (compile time - typescript), we'll use a helper, that'll finally bring us back to the master data.

```typescript
/**
 * PlanetsType is a union type of string literals. This is useful for making switch-case statements exhaustive:
 * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
 * 
 * below however, we want to get an exhaustive list of all string literals in the union type. By building a helper
 * object with the key set to "s in PlanetsType", we effectively create an enum-like structure with all the
 * string literals of PlanetsType. When you create or change PlanetsType, this helps to keep the 
 * array in PlanetsTypeKeys up-to-date by throwing a typing error. The order of the entries is also preserved
 */
const PlanetsTypeHelperObj: { [s in PlanetsType ]: PlanetsType } = {
  "Mercury":"Mercury",
  "Venus":"Venus",
  "Earth":"Earth",
  "Mars":"Mars",
  "Jupiter":"Jupiter",
  "Saturn":"Saturn",
  "Uranus":"Uranus",
  "Neptune":"Neptune",
  //"Pluto":"Pluto",
  "Planet9TheMisterious":"Planet9TheMisterious",
}

export const PlanetsTypeKeys: PlanetsType[] = Object.keys(PlanetsTypeHelperObj) as PlanetsType[]
```
This turns `PlanetsTypeKeys` into an **exhaustive Array of string literals** in typescript. Because it's based on a json object (also called "dictionary" in this usage), there are no duplicates. Finally, the value-side of the json object (`{"VenusKey":"VenusValue"}`) can be anything you like. If you'd like to make the value-part your master data, the `PlanetsTypeKeys` will stay up to date, ready to be used ... with a dropdown for example.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9972rrr1y5eiy8cwpjzl.jpg)