Created
May 6, 2023 12:09
-
-
Save mmazzarolo/20dcfeff4b12a6b2e908c74abad59cfa to your computer and use it in GitHub Desktop.
WIP - Type-safe TypeScript ISO 3166 country codes (names, two-letters codes, and three-letters codes)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ========================================== | |
// (WIP) TYPE-SAFE ISO 3166 COUNTRY CODES | |
// ========================================== | |
/** | |
* Countries data. | |
* This is the source that should be updated if you want to add/remove new country codes. | |
*/ | |
const countriesData = [ | |
["Afghanistan", "AF", "AFG"], | |
["AlandIslands", "AX", "ALA"], | |
["Albania", "AL", "ALB"], | |
["Algeria", "DZ", "DZA"], | |
["AmericanSamoa", "AS", "ASM"], | |
["Andorra", "AD", "AND"], | |
["Angola", "AO", "AGO"], | |
["Anguilla", "AI", "AIA"], | |
["Antarctica", "AQ", "ATA"], | |
["AntiguaAndBarbuda", "AG", "ATG"], | |
["Argentina", "AR", "ARG"], | |
["Armenia", "AM", "ARM"], | |
["Aruba", "AW", "ABW"], | |
["Australia", "AU", "AUS"], | |
["Austria", "AT", "AUT"], | |
["Azerbaijan", "AZ", "AZE"], | |
["Bahamas", "BS", "BHS"], | |
["Bahrain", "BH", "BHR"], | |
["Bangladesh", "BD", "BGD"], | |
["Barbados", "BB", "BRB"], | |
["Belarus", "BY", "BLR"], | |
["Belgium", "BE", "BEL"], | |
["Belize", "BZ", "BLZ"], | |
["Benin", "BJ", "BEN"], | |
["Bermuda", "BM", "BMU"], | |
["Bhutan", "BT", "BTN"], | |
["Bolivia", "BO", "BOL"], | |
["BonaireSintEustatiusSaba", "BQ", "BES"], | |
["BosniaAndHerzegovina", "BA", "BIH"], | |
["Botswana", "BW", "BWA"], | |
["BouvetIsland", "BV", "BVT"], | |
["Brazil", "BR", "BRA"], | |
["BritishIndianOceanTerritory", "IO", "IOT"], | |
["BruneiDarussalam", "BN", "BRN"], | |
["Bulgaria", "BG", "BGR"], | |
["BurkinaFaso", "BF", "BFA"], | |
["Burundi", "BI", "BDI"], | |
["Cambodia", "KH", "KHM"], | |
["Cameroon", "CM", "CMR"], | |
["Canada", "CA", "CAN"], | |
["CapeVerde", "CV", "CPV"], | |
["CaymanIslands", "KY", "CYM"], | |
["CentralAfricanRepublic", "CF", "CAF"], | |
["Chad", "TD", "TCD"], | |
["Chile", "CL", "CHL"], | |
["China", "CN", "CHN"], | |
["ChristmasIsland", "CX", "HKG"], | |
["CocosKeelingIslands", "CC", "MAC"], | |
["Colombia", "CO", "CXR"], | |
["Comoros", "KM", "CCK"], | |
["Congo", "CG", "COL"], | |
["CongoDemocraticRepublic", "CD", "COM"], | |
["CookIslands", "CK", "COG"], | |
["CostaRica", "CR", "COD"], | |
["CoteDIvoire", "CI", "COK"], | |
["Croatia", "HR", "CRI"], | |
["Cuba", "CU", "CIV"], | |
["Curaçao", "CW", "HRV"], | |
["Cyprus", "CY", "CUB"], | |
["CzechRepublic", "CZ", "CUW"], | |
["Denmark", "DK", "CYP"], | |
["Djibouti", "DJ", "CZE"], | |
["Dominica", "DM", "DNK"], | |
["DominicanRepublic", "DO", "DJI"], | |
["Ecuador", "EC", "DMA"], | |
["Egypt", "EG", "DOM"], | |
["ElSalvador", "SV", "ECU"], | |
["EquatorialGuinea", "GQ", "EGY"], | |
["Eritrea", "ER", "SLV"], | |
["Estonia", "EE", "GNQ"], | |
["Ethiopia", "ET", "ERI"], | |
["FalklandIslands", "FK", "EST"], | |
["FaroeIslands", "FO", "ETH"], | |
["Fiji", "FJ", "FLK"], | |
["Finland", "FI", "FRO"], | |
["France", "FR", "FJI"], | |
["FrenchGuiana", "GF", "FIN"], | |
["FrenchPolynesia", "PF", "FRA"], | |
["FrenchSouthernTerritories", "TF", "GUF"], | |
["Gabon", "GA", "PYF"], | |
["Gambia", "GM", "ATF"], | |
["Georgia", "GE", "GAB"], | |
["Germany", "DE", "GMB"], | |
["Ghana", "GH", "GEO"], | |
["Gibraltar", "GI", "DEU"], | |
["Greece", "GR", "GHA"], | |
["Greenland", "GL", "GIB"], | |
["Grenada", "GD", "GRC"], | |
["Guadeloupe", "GP", "GRL"], | |
["Guam", "GU", "GRD"], | |
["Guatemala", "GT", "GLP"], | |
["Guernsey", "GG", "GUM"], | |
["Guinea", "GN", "GTM"], | |
["GuineaBissau", "GW", "GGY"], | |
["Guyana", "GY", "GIN"], | |
["Haiti", "HT", "GNB"], | |
["HeardIslandMcdonaldIslands", "HM", "GUY"], | |
["HolySeeVaticanCityState", "VA", "HTI"], | |
["Honduras", "HN", "HMD"], | |
["HongKong", "HK", "VAT"], | |
["Hungary", "HU", "HND"], | |
["Iceland", "IS", "HUN"], | |
["India", "IN", "ISL"], | |
["Indonesia", "ID", "IND"], | |
["Iran", "IR", "IDN"], | |
["Iraq", "IQ", "IRN"], | |
["Ireland", "IE", "IRQ"], | |
["IsleOfMan", "IM", "IRL"], | |
["Israel", "IL", "IMN"], | |
["Italy", "IT", "ISR"], | |
["Jamaica", "JM", "ITA"], | |
["Japan", "JP", "JAM"], | |
["Jersey", "JE", "JPN"], | |
["Jordan", "JO", "JEY"], | |
["Kazakhstan", "KZ", "JOR"], | |
["Kenya", "KE", "KAZ"], | |
["Kiribati", "KI", "KEN"], | |
["Korea", "KR", "KIR"], | |
["KoreaDemocraticPeoplesRepublic", "KP", "PRK"], | |
["Kuwait", "KW", "KOR"], | |
["Kyrgyzstan", "KG", "KWT"], | |
["LaoPeoplesDemocraticRepublic", "LA", "KGZ"], | |
["Latvia", "LV", "LAO"], | |
["Lebanon", "LB", "LVA"], | |
["Lesotho", "LS", "LBN"], | |
["Liberia", "LR", "LSO"], | |
["LibyanArabJamahiriya", "LY", "LBR"], | |
["Liechtenstein", "LI", "LBY"], | |
["Lithuania", "LT", "LIE"], | |
["Luxembourg", "LU", "LTU"], | |
["Macao", "MO", "LUX"], | |
["Macedonia", "MK", "MKD"], | |
["Madagascar", "MG", "MDG"], | |
["Malawi", "MW", "MWI"], | |
["Malaysia", "MY", "MYS"], | |
["Maldives", "MV", "MDV"], | |
["Mali", "ML", "MLI"], | |
["Malta", "MT", "MLT"], | |
["MarshallIslands", "MH", "MHL"], | |
["Martinique", "MQ", "MTQ"], | |
["Mauritania", "MR", "MRT"], | |
["Mauritius", "MU", "MUS"], | |
["Mayotte", "YT", "MYT"], | |
["Mexico", "MX", "MEX"], | |
["Micronesia", "FM", "FSM"], | |
["Moldova", "MD", "MDA"], | |
["Monaco", "MC", "MCO"], | |
["Mongolia", "MN", "MNG"], | |
["Montenegro", "ME", "MNE"], | |
["Montserrat", "MS", "MSR"], | |
["Morocco", "MA", "MAR"], | |
["Mozambique", "MZ", "MOZ"], | |
["Myanmar", "MM", "MMR"], | |
["Namibia", "NA", "NAM"], | |
["Nauru", "NR", "NRU"], | |
["Nepal", "NP", "NPL"], | |
["Netherlands", "NL", "NLD"], | |
["NewCaledonia", "NC", "ANT"], | |
["NewZealand", "NZ", "NCL"], | |
["Nicaragua", "NI", "NZL"], | |
["Niger", "NE", "NIC"], | |
["Nigeria", "NG", "NER"], | |
["Niue", "NU", "NGA"], | |
["NorfolkIsland", "NF", "NIU"], | |
["NorthernMarianaIslands", "MP", "NFK"], | |
["Norway", "NO", "MNP"], | |
["Oman", "OM", "NOR"], | |
["Pakistan", "PK", "OMN"], | |
["Palau", "PW", "PAK"], | |
["PalestinianTerritory", "PS", "PLW"], | |
["Panama", "PA", "PSE"], | |
["PapuaNewGuinea", "PG", "PAN"], | |
["Paraguay", "PY", "PNG"], | |
["Peru", "PE", "PRY"], | |
["Philippines", "PH", "PER"], | |
["Pitcairn", "PN", "PHL"], | |
["Poland", "PL", "PCN"], | |
["Portugal", "PT", "POL"], | |
["PuertoRico", "PR", "PRT"], | |
["Qatar", "QA", "PRI"], | |
["Reunion", "RE", "QAT"], | |
["Romania", "RO", "REU"], | |
["RussianFederation", "RU", "ROU"], | |
["Rwanda", "RW", "RUS"], | |
["SaintBarthelemy", "BL", "RWA"], | |
["SaintHelena", "SH", "BLM"], | |
["SaintKittsAndNevis", "KN", "SHN"], | |
["SaintLucia", "LC", "KNA"], | |
["SaintMartin", "MF", "LCA"], | |
["SaintPierreAndMiquelon", "PM", "MAF"], | |
["SaintVincentAndGrenadines", "VC", "SPM"], | |
["Samoa", "WS", "VCT"], | |
["SanMarino", "SM", "WSM"], | |
["SaoTomeAndPrincipe", "ST", "SMR"], | |
["SaudiArabia", "SA", "STP"], | |
["Senegal", "SN", "SAU"], | |
["Serbia", "RS", "SEN"], | |
["Seychelles", "SC", "SRB"], | |
["SierraLeone", "SL", "SYC"], | |
["Singapore", "SG", "SLE"], | |
["SintMaarten", "SX", "SGP"], | |
["Slovakia", "SK", "SXM"], | |
["Slovenia", "SI", "SVK"], | |
["SolomonIslands", "SB", "SVN"], | |
["Somalia", "SO", "SLB"], | |
["SouthAfrica", "ZA", "SOM"], | |
["SouthGeorgiaAndSandwichIsl", "GS", "ZAF"], | |
["SouthSudan", "SS", "SGS"], | |
["Spain", "ES", "SSD"], | |
["SriLanka", "LK", "ESP"], | |
["Sudan", "SD", "LKA"], | |
["Suriname", "SR", "SDN"], | |
["SvalbardAndJanMayen", "SJ", "SUR"], | |
["Swaziland", "SZ", "SJM"], | |
["Sweden", "SE", "SWZ"], | |
["Switzerland", "CH", "SWE"], | |
["SyrianArabRepublic", "SY", "CHE"], | |
["Taiwan", "TW", "SYR"], | |
["Tajikistan", "TJ", "TWN"], | |
["Tanzania", "TZ", "TJK"], | |
["Thailand", "TH", "TZA"], | |
["TimorLeste", "TL", "THA"], | |
["Togo", "TG", "TLS"], | |
["Tokelau", "TK", "TGO"], | |
["Tonga", "TO", "TKL"], | |
["TrinidadAndTobago", "TT", "TON"], | |
["Tunisia", "TN", "TTO"], | |
["Turkey", "TR", "TUN"], | |
["Turkmenistan", "TM", "TUR"], | |
["TurksAndCaicosIslands", "TC", "TKM"], | |
["Tuvalu", "TV", "TCA"], | |
["Uganda", "UG", "TUV"], | |
["Ukraine", "UA", "UGA"], | |
["UnitedArabEmirates", "AE", "UKR"], | |
["UnitedKingdom", "GB", "ARE"], | |
["UnitedStates", "US", "GBR"], | |
["UnitedStatesOutlyingIslands", "UM", "USA"], | |
["Uruguay", "UY", "UMI"], | |
["Uzbekistan", "UZ", "URY"], | |
["Vanuatu", "VU", "UZB"], | |
["Venezuela", "VE", "VUT"], | |
["Vietnam", "VN", "VEN"], | |
["VirginIslandsBritish", "VG", "VNM"], | |
["VirginIslandsUS", "VI", "VIR"], | |
["WallisAndFutuna", "WF", "WLF"], | |
["WesternSahara", "EH", "ESH"], | |
["Yemen", "YE", "YEM"], | |
["Zambia", "ZM", "ZMB"], | |
["Zimbabwe", "ZW", "ZWE"], | |
] as const; | |
/** | |
* Utility type to prettify the type displayed on IDE by unwrapping it. | |
*/ | |
type Prettify<T> = { | |
[K in keyof T]: T[K]; | |
} & {}; | |
/** | |
* Utility. Build a const object (a pseudo Enum) given a read-only array. | |
* We use it to expose country names and codes allowing autocomplete. | |
*/ | |
const constArrayToConstObject = <T extends readonly any[], K extends T[number]>( | |
arr: T | |
): Prettify<Readonly<{ [P in K]: P }>> => { | |
return arr.reduce((acc, elem) => { | |
acc[elem] = elem; | |
return acc; | |
}, {}); | |
}; | |
/** | |
* Extract a list & type union of country names, two-letter codes, and three-letter codes. | |
*/ | |
const countryNames = countriesData.map((data) => data[0]); | |
type CountryName = (typeof countryNames)[number]; | |
const twoLetterCountryCodes = countriesData.map((data) => data[1]); | |
type TwoLetterCountryCode = (typeof twoLetterCountryCodes)[number]; | |
const threeLetterCountryCodes = countriesData.map((data) => data[2]); | |
type ThreeLetterCountryCode = (typeof threeLetterCountryCodes)[number]; | |
/** | |
* Country data. | |
*/ | |
interface Country { | |
name: CountryName; | |
twoLetterCode: TwoLetterCountryCode; | |
threeLetterCode: ThreeLetterCountryCode; | |
} | |
/** | |
* Build support maps to make the country retrival O(1). | |
* TODO: Lazy load them when they're requested for the first time. | |
*/ | |
const countryByName = new Map<CountryName, Country>(); | |
const countryByTwoLetterCode = new Map<TwoLetterCountryCode, Country>(); | |
const countryByThreeLetterCode = new Map<ThreeLetterCountryCode, Country>(); | |
Array.from({ length: countriesData.length }).forEach((_, i) => { | |
const name = countryNames[i]; | |
const twoLetterCode = twoLetterCountryCodes[i]; | |
const threeLetterCode = threeLetterCountryCodes[i]; | |
const country: Country = { name, twoLetterCode, threeLetterCode }; | |
countryByName.set(name, country); | |
countryByTwoLetterCode.set(twoLetterCode, country); | |
countryByThreeLetterCode.set(threeLetterCode, country); | |
}); | |
/** | |
* Any country name, two-letter code, or three-letter code is a valid input. | |
*/ | |
type CountryInput = CountryName | TwoLetterCountryCode | ThreeLetterCountryCode; | |
/** | |
* Type guards to distinguish between the various input types. | |
* Luckily, all country names are > 2 characters long, so we can can use the input lenght as our discriminating factor. | |
*/ | |
function isCountryName(input: CountryInput): input is CountryName { | |
return input.length > 2; | |
} | |
function isTwoLetterCountryCode(input: CountryInput): input is TwoLetterCountryCode { | |
return input.length === 2; | |
} | |
function isThreeLetterCountryCode(input: CountryInput): input is ThreeLetterCountryCode { | |
return input.length === 3; | |
} | |
/** | |
* Returns a country data given any of its identifiers (name, two-letter code, or three-letter code). | |
* @param input A country name, two-letter code, or three-letter code. | |
* @returns The country data. | |
*/ | |
export function getCountry(input: CountryInput): Country { | |
if (isCountryName(input)) { | |
return countryByName.get(input)!; | |
} else if (isTwoLetterCountryCode(input)) { | |
return countryByTwoLetterCode.get(input)!; | |
} else if (isThreeLetterCountryCode(input)) { | |
return countryByThreeLetterCode.get(input)!; | |
} | |
throw new Error(`getCountry() - Invalid input: "${input}"`); | |
} | |
/** | |
* Returns a three-letter country-code given any of its identifiers (name, two-letter code, or three-letter code). | |
* @param input A country name, two-letter code, or three-letter code. | |
* @returns The three-letter country-code. | |
*/ | |
export function getThreeLetterCountryCode( | |
input: CountryInput | CountryInput[] | |
): ThreeLetterCountryCode | ThreeLetterCountryCode[] { | |
return Array.isArray(input) ? input.map((x) => getCountry(x).threeLetterCode) : getCountry(input).threeLetterCode; | |
} | |
/** | |
* An enum-like object with all the country names — helpful for IDE autocomplete. | |
*/ | |
export const countryName = constArrayToConstObject(countryNames); | |
/** | |
* An enum-like object with all the country two-letter codes — helpful for IDE autocomplete. | |
*/ | |
export const twoLetterCountryCode = constArrayToConstObject(twoLetterCountryCodes); | |
/** | |
* An enum-like object with all the country three-letter codes — helpful for IDE autocomplete. | |
*/ | |
export const threeLetterCountryCode = constArrayToConstObject(threeLetterCountryCodes); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@mmazzarolo You have some wrong alpha-3 codes. For example, for Venezuela is not 'VUT', but 'VEN'; for Vietnam is not 'VEN', but 'VNM'.