Reputation: 171321
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.
Upvotes: 2
Views: 945
Reputation: 10345
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]
} | "";
Upvotes: 1
Reputation: 11485
Although the function is useless from the JS point of view. It is able to unify codes into one type.
function createCountries<T extends string>(
contries: { name: string; code: T }[],
): { name: string; code: T }[] {
return contries;
}
const countries = createCountries([
{ name: "Australia", code: "AU" },
{ name: "Japan", code: "JP" },
{ name: "Norway", code: "NO" },
{ name: "Russian Federation", code: "RU" }
]);
type CountryCodes = "" | (typeof countries)[number]["code"]; // "" | "AU" | "JP" | "NO" | "RU";
// Example - How to use the type
function getCountryByCode(code: CountryCode): Country | undefined {
return countries.find(country => country.code == code);
}
Type of countries
:
{
name: string;
code: "AU" | "JP" | "NO" | "RU";
}[]
Without further info, this is the best you can do...
type Countries = { name: string; code: string }[];
const countriesAbc = [
{ name: "Australia", code: "AU" },
{ name: "Japan", code: "JP" },
{ name: "Norway", code: "NO" },
{ name: "Russian Federation", code: "RU" }
] as const;
const countries: Countries = [...countriesAbc];
type CountryCodes = "" | (typeof countriesAbc)[number]["code"]; // "" | "AU" | "JP" | "NO" | "RU";
Upvotes: 6
Reputation: 326
I think it from domain viewpoint, it maybe expected country code to be less change and country name to be more flexible. If your issue is just make union string type from list of map like, another way is better.
Make union type CountryCode from const list(this is useful after):
const countryCodeList = ["AU", "JP", "NO", "RU"] as const;
type CountryCode = "" | (typeof countryCodeList)[number];
Define Country with CountryCode:
type Country = { name: string; code: CountryCode};
When use them with in, it is safe and flexible:
// mock of date access.
const fetchByCode = (code: CountryCode, lang: string) : string => {
return "name of " + code;
};
// create county data from code list.
const createCountries = () : Country[] => {
return countryCodeList.map((code) => {
const name = fetchByCode(code,'en');
return {name, code}
});
};
// use country data.
const countriesEn = createCountries()
console.log(countriesEn);
Upvotes: 0
Reputation: 2210
You can do it slightly other way
type CountryCodes = "" | "AU" | "JP" | "NO" | "RU";
interface Country {name: string; code: CountryCodes}
// Alternatively
// type Country = {name: string; code: CountryCodes}
const countries: Array<Country> = [
{ name: "Australia", code: "DE" }, // COMPILE ERROR
{ name: "Japan", code: "JP" },
{ name: "Norway", code: "NO" },
{ name: "Russian Federation", code: "RU" }
];
or even better
type NotEmptyCountryCodes = "AU" | "JP" | "NO" | "RU";
type CountryCodes = "" | NotEmptyCountryCodes;
interface Country {name: string; code: NotEmptyCountryCodes}
// Alternatively
// type Country = {name: string; code: NotEmptyCountryCodes}
const countries: Array<Country> = [
{ name: "Australia", code: "DE" }, // COMPILE ERROR
{ name: "Japan", code: "" }, // COMPILE ERROR
{ name: "Norway", code: "NO" },
{ name: "Russian Federation", code: "RU" }
];
Upvotes: 0