Derive union of string literal types with lookup types in Typescript
Mathias Remshardt
Posted on September 4, 2021
Recently I started playing with unions of string literals as an alternative to enums. I did not experience any issues while using enums. I just prefer the string literals as these, in contrast to enums, do not lead additional code generated during compilation.
One thing that has bothered me so far is that I could not find an easy way to store all strings of a string literal union in an array. This can be useful if you want to e.g. randomly select one of the string literals in the union as part of creating mock/fixture data.
Of course you can define both the unions of string literals and an array with the same strings:
type PostType = 'article' | 'podcast';
const postTypes = ['article', 'podcast'];
This duplication is error prone in case a new option needs to be added, removed and the like. So I was searching for a means to either derive the array from the type or the type from the array.
With lookup types this is exactly possible:
const postTypes = ['article', 'podcast'] as const;
type PostTypes = typeof postTypes[number];
First typeof
in combination with as const
is used to infer the type of the defined array. as const
is important here as Typescript otherwise defines the type as Array<string>
instead of an array of string literals.
Using indexed access types/lookup types gives the union of string literals. This is somehow equivalent to using an index in an "normal" Javascript/Typescript to get a specific element like list[0]
.
Lookup types can be used for more sophisticated use case e.g. like deriving all properties in an object the values of which are e.g. a string
:
type Author = {
firstName: string;
lastName: string;
};
type Post = {
title: string;
description: string;
views: number;
author: Author;
};
type PostStringKeys = {
[P in keyof Post]: Post[P] extends string ? P : never;
}[keyof Post];
Let's quickly break this down:
-
P keyof Post
gives all keys ofPost
(title
anddescription
). -
Post[P] extends string ? P : never
checks if the value of propertyP
inPost
is of typestring
. If true the property name is set as value otherwise the property is not included in the newly created type. - With the help of lookup types the union of property names/string literals is derived using
keyof Post
.- The set of keys in
Post
is a superset of the keys of the derived type and can therefor be used as an index
- The set of keys in
This can be made generic like so:
type KeysOfType<T, K> = { [P in keyof T]: T[P] extends K ? P : never }[keyof T];
type PostStringKeys = KeysOfType<Post, string>;
type PostNumberKeys = KeysOfType<Post, number>;
Compare to the previous example T == Post
and K == string
. This provides the additional possibility to include properties with different value types like string
and Author
using unions.
type PostStringAndAuthorKeys = KeysOfType<Post, number | Author>;
The code snippets can be found here.
That's it and as always, thanks for reading.
Posted on September 4, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024