Given a countries
array like this:
const countries = [
{ name: "Australia", code: "AU" },
{ name: "Japan", code: "JP" },
{ name: "Norway", code: "NO" },
{ name: "Russian Federation", code: "RU" }
];
What's the easiest way to generate the following type?
type CountryCodes = "" | "AU" | "JP" | "NO" | "RU";
Note: there is an extra empty string.
First, of all, without a slight modification to your input data set type what you want cannot be achieved. As rightfully stated by jonsharpe's comment, the type of array members here is widened to { name: string; code: string; }
. This is easily fixed with a as const
assertion:
const countries = [
{ name: "Australia", code: "AU" },
{ name: "Japan", code: "JP" },
{ name: "Norway", code: "NO" },
{ name: "Russian Federation", code: "RU" }
] as const;
Now the array itself is considered a tuple, and each member's properties are made readonly
as well. After that, you only need a mapped type to extract the tuple's values (usually done with T[number]
), get the types of code
members and build a union out of them:
type CountryCodes<T> = {
[ P in keyof T ] : T[P] extends { code: string } ? T[P]["code"] : never
}[keyof T & number] | "";
Where T[P] extends { code: string }
constraint ensures we can index T[P]
with "code"
. The result is exactly what you want (note that everything is done purely in type system):
type cc = CountryCodes<typeof countries>; //type cc = "" | "AU" | "JP" | "NO" | "RU"
A more concise version utilizing 4.1's key remapping feature:
type CountryCodes<T> = keyof {
[ P in keyof T as T[P] extends { code: string } ? T[P]["code"] : never ] : T[P]
} | "";