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.
Top comments (0)