DEV Community

Cover image for String-literal Union Types to Array - or - How to kick Pluto out of the list of planets
Jonathan Schneider
Jonathan Schneider

Posted on • Edited on

String-literal Union Types to Array - or - How to kick Pluto out of the list of planets

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'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

So to improve the situation, we can fill these types by hand. They're called string literal types, more on that topic here:

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";
Enter fullscreen mode Exit fullscreen mode

If you additionally want an array that contains all possible permutations of a string literal union type, you could fill that by hand:

export const PlanetsTypeKeys: PlanetsType[] = [
  "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus"
  , "Neptune"];
Enter fullscreen mode Exit fullscreen mode

...but you would have to remember to update that each time a new string literal type enters that union.

export type PlanetsType: "Mercury" | "Venus" | "Earth" | "Mars" | "Jupiter" | "Saturn" | "Uranus" 
  | "Neptune" 
  | "Planet9TheMisterious"; // <-- the new one, now the array has to be updated
Enter fullscreen mode Exit fullscreen mode

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.

/**
 * 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[]
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)