Deriving a Typescript Union Type From an Array or Object
3 min read
I've been working with a number of datasets recently, within my React app. They normally take the form of an array of values, or an object with key-value pairs, where the values in both the array and object tend to be objects themselves. So for example:
// Array of objects
export const FAV_MOVIES = [
{
name: 'Fight Club',
genre: 'action',
bookAuthor: 'Chuck Palahniuk',
summary: "It's the same person!",
},
{
name: 'Harry Potter',
genre: 'fantasy',
bookAuthor: 'JK Rowling',
summary: 'Love conquers all',
},
{
name: 'The Godfather',
genre: 'crime',
bookAuthor: 'Mario Puzo',
summary: "You can't get away from the family business",
},
]
// Object of objects
export const MOVIES = {
'Fight Club': {
imageName: 'fightclub',
genre: 'action',
bookAuthor: 'Chuck Palahniuk',
},
'Harry Potter': {
imageName: 'harrypotter',
genre: 'fantasy',
bookAuthor: 'JK Rowling',
},
'The GodFather': {
imageName: 'thegodfather',
genre: 'crime',
bookAuthor: 'Mario Puzo',
},
'The Hobbit': {
imageName: 'thehobbit',
genre: 'fantasy',
bookAuthor: 'JRR Tolkien',
},
'Ready Player One': {
imageName: 'readyplayerone',
genre: 'sci-fi',
bookAuthor: 'Ernest Cline',
},
}
As I'm using Typescript, I was trying to work out how you go about deriving union types from the values of certain keys from these objects. To be sure, the simplest thing to do would've been to just declare these values as a string
, say if I wanted to pass these values into a MoviesComponent
.
interface MoviesComponentProps {
movieName: string
movieGenres: string[]
}
This doesn't utilise the best that Typescript offers however, so I wanted to impose stricter typing by limiting what the values of these strings could be. As of Typescript 3.4, there's a fairly easy way of doing this, with the as const
syntax. So taking the MOVIES
object, we can add as const
after the closing brace, which locks MOVIES
to becoming a read-only constant.
export const MOVIES = {
'Fight Club': { imageName: 'fightclub', genre: 'action', bookAuthor: 'Chuck Palahniuk' },
'Harry Potter': { imageName: 'harrypotter', genre: 'fantasy', bookAuthor: 'JK Rowling' },
'The GodFather': { imageName: 'thegodfather', genre: 'crime', bookAuthor: 'Mario Puzo' },
'The Hobbit': { imageName: 'thehobbit', genre: 'fantasy', bookAuthor: 'JRR Tolkien' },
'Ready Player One': { imageName: 'readyplayerone', genre: 'sci-fi', bookAuthor: 'Ernest Cline' },
} as const
This allows us to create a couple of new types, firstly to define the keys (MovieName
), which we can then use to access each of the values (i.e. the objects). So for the movie genre...
type MovieName = keyof typeof MOVIES // 'Fight Club', 'Harry Potter', ...
type MovieGenre = typeof MOVIES[MovieName]['genre'] // 'action', 'fantasy', ...
...which we can add to our MoviesComponentProps
for stricter typing.
interface MoviesComponentProps {
movieName: MovieName
movieGenres: MovieGenre[]
}
So what about if we're dealing with an array of objects? We can do something similar as well, through the as const
syntax again.
export const FAV_MOVIES = [
{
name: 'Fight Club',
genre: 'action',
bookAuthor: 'Chuck Palahniuk',
summary: "It's the same person!",
},
{
name: 'Harry Potter',
genre: 'fantasy',
bookAuthor: 'JK Rowling',
summary: "Love conquers all",
},
{
name: 'The Godfather',
genre: 'crime',
bookAuthor: 'Mario Puzo',
summary: "You can't get away from the family business",
},
] as const
To create a type for the book authors, we can access each item in the array with [number]
:
export type BookAuthor = typeof FAV_MOVIES[number]['bookAuthor']; // 'Chuck Palahniuk', ...
And that's it! It keeps everything strongly typed and has the nice bonus of IDE auto-completion when passing these values around the app.