@johnmorrisdotca/address-plus
AddressAbbreviations AddressComparisonOptions AddressComparisonResult AddressComparisonTestCase AddressDifference AddressFormattingOptions AddressFormattingTestCase AddressMatchType AddressParser AddressParsingTestCase AddressSimilarityResult AddressValidationResult AddressValidationTestCase AustralianAddressFields AustralianPostcodeRange AustralianState AustralianStateCode BatchParseError BatchParseOptions BatchParseResult BatchParseStats BatchProcessingTestCase buildRegexFromDict CA_PROVINCE_ALTERNATIVES CA_PROVINCE_NAMES CA_PROVINCE_NAMES_EN CA_PROVINCE_NAMES_FR CA_PROVINCES CA_REGIONS CA_STREET_TYPES CanadaPostFormattingOptions CANADIAN_POSTAL_CODE_PATTERN CANADIAN_POSTAL_LIBERAL_PATTERN capitalizeStreetName capitalizeWords CITY_PATTERNS cleanAddress cleanAddressDetailed CleanAddressOptions CleanAddressResult CleanAddressTestCase COMMON_PARSER_PATTERNS COMMON_STREET_NAMES_PATTERN compareAddresses CONNECTOR_WORDS COUNTRIES CountryCode CountryComparison CountryDifference CountryModule CountryValidation default detectCountry DIRECTION_EXPANSIONS DIRECTIONAL_MAP FACILITY_DELIMITER_PATTERN FACILITY_DELIMITER_PATTERNS FACILITY_INDICATORS FACILITY_PATTERNS findMunicipalitiesByName findMunicipalitiesByRomaji findMunicipalityByCode findPrefecture formatAddress formatCanadaPost formatJapanese formatJapaneseEnglish FormattedAddress formatUSPS FRENCH_PREPOSITIONS FrenchAddressFields FrenchCollectivity FrenchDepartment FrenchPostalCountry FrenchPostcode FuzzyMatchOptions GENERAL_DELIVERY_PATTERNS GermanAddressFields GermanPostcode GermanState GermanStateCode getAddressAbbreviations getAddressSimilarity getPostalPrefixesForPrefecture getPostalPrefixesForProvince getPrefectureFromJapanesePostalCode getProvinceFromPostalCode getStateFromZip getValidationErrors getZipPrefixesForState hasValidAddressComponents INTERSECTION_PATTERNS ISLAND_TYPE_PATTERN isSameAddress isValidAddress JapaneseAddressFields JapaneseEnglishFormattingOptions JapaneseFormattingOptions JapaneseMunicipality JapanesePrefecture JapaneseValidation JP_DESIGNATED_CITIES JP_MUNICIPALITIES JP_POSTAL_EXCEPTIONS JP_POSTAL_PREFIXES JP_PREFECTURES kanjiNumeralsToDigits looksJapanese municipalitiesOf MUSIC_SQUARE_EAST_PATTERN normalizeJapaneseAddressText normalizeRegion normalizeStateProvinceName normalizeText PARENTHETICAL_PATTERN parseAddress parseAddresses parseAddressesBatch ParsedAddress ParsedIntersection parseDirectional parseFacility parseInformalAddress parseInformalAddresses parseInformalAddressesBatch parseIntersection parseIntersections parseIntersectionsBatch parseJapaneseAddress parseLocation parseLocations parseLocationsBatch ParseOptions parseParenthetical parsePostalCode parseSecondaryUnit parseStateProvince parseStreetNumber parseStreetType PO_BOX_PATTERNS POSTAL_CODE_TO_PROVINCE PostalValidationResult PROVINCE_EXPANSIONS PROVINCE_EXPANSIONS_EN PROVINCE_EXPANSIONS_FR Region SECONDARY_UNIT_PATTERN SECONDARY_UNIT_TYPES setValidatedPostalCode StateCode STREET_NAME_ACRONYMS STREET_TYPE_DETECTION_PATTERN STREET_TYPE_EXPANSIONS STREET_TYPE_PROPER_CASE SubRegion TERRITORY_POSTAL_PREFIXES TERRITORY_POSTAL_RANGES TestCase TestCaseBase UKAddressFields UKNation UKNationCode UKPostcode UNIT_TYPE_KEYWORDS UNIT_TYPE_NUMBER_PATTERN US_REGIONS US_STATE_ALTERNATIVES US_STATE_EXPANSIONS US_STATE_NAMES US_STATES US_STREET_TYPES USPSFormattingOptions validateAddress validateJapaneseAddress validatePostalCode VALIDATION_PATTERNS ValidationError ValidationOptions WRITTEN_NUMBERS ZIP_CODE_PATTERN ZIP_CODE_REGEX_PATTERN ZIP_VALIDATION_PATTERNS
type AddressAbbreviations
interface AddressAbbreviations {
streetTypes: Record<string, string>; // Street type abbreviation mappings
directions: Record<string, string>; // Directional abbreviation mappings
states: Record<string, string>; // State abbreviation mappings
provinces: Record<string, string>; // Province abbreviation mappings
unitTypes: Record<string, string>; // Unit type abbreviation mappings
}
What getAddressAbbreviations returns: one map per kind of abbreviation.
Object.keys(getAddressAbbreviations())
// ["streetTypes","directions","states","provinces","unitTypes"]
type AddressComparisonOptions
interface AddressComparisonOptions {
ignoreCase?: boolean; // Whether to ignore case when comparing text
ignorePunctuation?: boolean; // Whether to ignore punctuation marks
normalizeStreetTypes?: boolean; // Whether to normalize street type abbreviations
normalizeDirections?: boolean; // Whether to normalize directional abbreviations
normalizeStates?: boolean; // Whether to normalize state/province names
fuzzyMatching?: boolean; // Whether to use fuzzy string matching
strictPostalCode?: boolean; // Whether postal codes must match exactly
requireExactMatch?: boolean; // Whether all fields must match exactly
}
Options for comparing addresses: what to normalize before comparing, whether to allow small typos, and whether every field must match exactly.
isSameAddress(parseLocation("123 Main St, Anytown, NY 12345"), parseLocation("123 Main Stret, Anytown, NY 12345"), { fuzzyMatching: false })
// false
type AddressComparisonResult
interface AddressComparisonResult {
isSame: boolean; // Whether addresses are considered the same
matchType: AddressMatchType; // Type of match found
similarity: AddressSimilarityResult; // Detailed similarity analysis
normalizedAddress1: ParsedAddress; // First address after normalization
normalizedAddress2: ParsedAddress; // Second address after normalization
}
What compareAddresses returns: the verdict, the match type and the similarity.
compareAddresses(parseLocation("123 Main St, Anytown, NY 12345"), parseLocation("123 Main Street, Anytown, NY 12345")).isSame
// true
type AddressComparisonTestCase
interface AddressComparisonTestCase extends TestCaseBase {
input: {
address1: string;
address2: string;
}; // Two addresses to compare
expected: {
isSame?: boolean;
similarity?: number;
differences?: string[];
[key: string]: unknown;
}; // Expected comparison results
}
A comparison case in the package's JSON test files: two addresses and the match expected.
({ input: ["123 Main St", "123 Main Street"], expected: { isSame: true } }).expected.isSame
// true
type AddressDifference
interface AddressDifference {
field: keyof ParsedAddress; // Which address field differs
value1: string | undefined; // Value from first address
value2: string | undefined; // Value from second address
type: "missing" | "different" | "similar" | "typo"; // Type of difference
confidence: number; // Confidence in the difference assessment
}
One part on which two addresses differ, with both values and the kind of difference.
getAddressSimilarity(parseLocation("123 Main St, Anytown, NY 12345"), parseLocation("125 Main St, Anytown, NY 12345")).differences[0]
// {"field":"number","value1":"123","value2":"125","type":"typo","confidence":0.6}
type AddressFormattingOptions
interface AddressFormattingOptions {
includeCountry?: boolean; // Whether to include country in formatted address
includeSecondaryUnit?: boolean; // Whether to include unit/suite information
upperCase?: boolean; // Whether to format in uppercase
separator?: string; // Line separator for multi-line formatting
abbreviateStreetTypes?: boolean; // Whether to abbreviate street types (Street -> St)
abbreviateDirections?: boolean; // Whether to abbreviate directions (North -> N)
abbreviateStates?: boolean; // Whether to abbreviate state/province names
usePlusCode?: boolean; // Whether to include Plus Code in formatting
}
Options for formatAddress and cleanAddress: what to abbreviate, capitals, the unit, the country and the line separator.
formatAddress(parseLocation("123 Main Street, Anytown, NY 12345"), { upperCase: true }).singleLine
// "123 MAIN ST, ANYTOWN NY 12345"
type AddressFormattingTestCase
interface AddressFormattingTestCase extends TestCaseBase {
input: string; // Input address to format
expected: string; // Expected formatted address string
options?: {
format?: string;
abbreviate?: boolean;
[key: string]: unknown;
}; // Optional formatting configuration
}
A formatting case in the package's JSON test files: an address and the text expected.
({ input: "123 main st", expected: "123 Main St" }).expected
// "123 Main St"
type AddressMatchType
type AddressMatchType = "exact" | "strong" | "moderate" | "weak" | "none";
How strongly two addresses match, from exact to none.
compareAddresses(parseLocation("123 Main St, Anytown, NY 12345"), parseLocation("456 Oak Ave, Portland, OR 97201")).matchType
// "none"
type AddressParser
interface AddressParser {
parseAddress(address: string, options?: ParseOptions): ParsedAddress | null;
parseInformalAddress(address: string, options?: ParseOptions): ParsedAddress | null;
parseIntersection(address: string, options?: ParseOptions): ParsedIntersection | null;
parseLocation(address: string, options?: ParseOptions): ParsedAddress | null;
}
The shape of the default export: the four parsers parse-address's users call on one object.
Object.keys(parser)
// ["parseLocation","parseIntersection","parseInformalAddress","parseAddress"]
type AddressParsingTestCase
interface AddressParsingTestCase extends TestCaseBase {
input: string; // Input address string to parse
expected: {
number?: string;
prefix?: string;
street?: string;
type?: string;
suffix?: string;
city?: string;
state?: string;
zip?: string;
country?: string;
[key: string]: unknown;
}; // Expected parsed address components
options?: {
strict?: boolean;
country?: string;
[key: string]: unknown;
}; // Optional parsing configuration
}
A parsing case in the package's JSON test files: an address and the fields expected from it.
({ input: "123 Main St, Anytown, NY 12345", expected: { number: "123", state: "NY" } }).expected.state
// "NY"
type AddressSimilarityResult
interface AddressSimilarityResult {
score: number; // 0-1 similarity score
isMatch: boolean; // Whether addresses are considered a match
confidence: number; // 0-1 confidence in the match
details: {
streetScore: number; // Street name similarity score
cityScore: number; // City name similarity score
stateScore: number; // State/province similarity score
postalScore: number; // Postal code similarity score
overallScore: number; // Combined overall similarity score
};
differences: AddressDifference[];
suggestions?: string[];
}
How alike two addresses are: an overall score from 0 to 1, a score for each part, and the differences.
getAddressSimilarity(parseLocation("123 Main St, Anytown, NY 12345"), parseLocation("123 Main Street, Anytown, NY 12345")).score
// 1
type AddressValidationResult
interface AddressValidationResult {
isValid: boolean; // Whether the address passed validation
confidence: number; // 0-1 score indicating parsing confidence
completeness: number; // 0-1 score indicating how complete the address is
errors: ValidationError[]; // List of validation errors found
warnings: ValidationError[]; // List of validation warnings
suggestions: string[]; // Suggestions for improving the address
parsedAddress: import("./parsed-address").ParsedAddress | null; // Parsed address result or null if parsing failed
}
What validateAddress returns.
validateAddress("1600 Pennsylvania Ave NW, Washington, DC 20500").isValid
// true
type AddressValidationTestCase
interface AddressValidationTestCase extends TestCaseBase {
input: string; // Input address string to validate
expected: {
isValid: boolean;
confidence?: number;
completeness?: number;
errors?: string[];
warnings?: string[];
[key: string]: unknown;
}; // Expected validation results
}
A validation case in the package's JSON test files: an address and the verdict and codes expected.
({ input: "123 Main St", expected: { isValid: false } }).expected.isValid
// false
type AustralianAddressFields
interface AustralianAddressFields {
floorType?: string; // A level or floor: Level, Floor, Ground Floor, Lower Ground Floor, Upper Ground Floor, Basement, Mezzanine
lot?: string; // A lot number where a street number is not yet given: the 12 in "Lot 12 Smith Rd"
}
The fields an Australian address fills beside the shared ones. The shared fields keep their meaning: number is the street number, street and type the street's name and its type (Australia Post's abbreviation, St, Pde, Cres), secUnitType and secUnitNum the unit (Unit 3) or the postal delivery (PO Box 37, Locked Bag 801), city the suburb or town, state the state's code and zip the postcode.
parseAustralianAddress("Level 6, 51 Jacobson St, Brisbane QLD 4000")?.floorType
// "Level"
type AustralianPostcodeRange
interface AustralianPostcodeRange {
state: AustralianStateCode;
from: string; // First postcode of the block: "2000"
to: string; // Last postcode of the block: "2599"
use: "delivery" | "po-box"; // Street delivery, or PO boxes and large-volume receivers (NSW 1000 to 1999, VIC 8000 to 8999, QLD 9000 to 9999)
}
One block of postcodes Australia Post allocates to a state or territory: every postcode from from to to, inclusive, written as four digits.
AU_POSTCODE_RANGES.find((range) => range.state === "TAS")
// {"state":"TAS","from":"7000","to":"7999","use":"delivery"}
type AustralianState
interface AustralianState {
code: AustralianStateCode; // The code on an envelope: VIC
iso: string; // ISO 3166-2: AU-VIC
name: string; // English: Victoria
nameJa: string; // Japanese: ビクトリア州
kind: "state" | "territory"; // The ACT and the NT are territories
}
An Australian state or territory in the tables: Australia Post's code, the ISO 3166-2 code, and its name in English and in Japanese (from kuni, which takes them from Unicode CLDR and Wikidata).
findAustralianState("Victoria")
// {"code":"VIC","iso":"AU-VIC","name":"Victoria","nameJa":"ビクトリア州","kind":"state"}
type AustralianStateCode
type AustralianStateCode = "ACT" | "NSW" | "NT" | "QLD" | "SA" | "TAS" | "VIC" | "WA";
The code of an Australian state or territory, as Australia Post writes it on the last line of an address.
getStateFromAustralianPostcode("3000")
// "VIC"
type BatchParseError
interface BatchParseError {
index: number; // Index of the failed address in the input array
error: string; // Error message describing what went wrong
input: string; // Original input that failed to parse
}
One address a batch could not parse: its index, the input and the reason.
parseLocationsBatch([""]).errors[0].index
// 0
type BatchParseOptions
interface BatchParseOptions extends ParseOptions {
stopOnError?: boolean; // Whether to stop processing on first error (default: false)
parallel?: boolean; // Process addresses in parallel where possible (default: false)
chunkSize?: number; // Size of chunks for parallel processing (default: 100)
includeStats?: boolean; // Include performance statistics in result (default: true)
}
Options for the batch parsers: the parse options, plus whether to stop at the first error and whether to include the statistics.
parseLocationsBatch(["123 Main St, Anytown, NY 12345"], { country: "US" }).stats.total
// 1
type BatchParseResult
interface BatchParseResult<T = ParsedAddress | ParsedIntersection> {
results: (T | null)[]; // Array of parsed results (null for failed parses)
errors: BatchParseError[]; // Array of errors that occurred during parsing
stats: BatchParseStats; // Performance and processing statistics
}
What a batch parser returns: the results in order, the errors and the statistics.
parseLocationsBatch(["123 Main St, Anytown, NY 12345"]).results.length
// 1
type BatchParseStats
interface BatchParseStats {
total: number; // Total number of addresses processed
successful: number; // Number of successfully parsed addresses
failed: number; // Number of failed parsing attempts
duration: number; // Total processing time in milliseconds
averagePerAddress: number; // Average processing time per address in milliseconds
addressesPerSecond: number; // Addresses processed per second
}
The counts and timing of a batch: how many were parsed, how many failed, and how long it took.
parseLocationsBatch(["123 Main St, Anytown, NY 12345", ""]).stats.failed
// 1
type BatchProcessingTestCase
interface BatchProcessingTestCase extends TestCaseBase {
input: string[]; // Array of input addresses
expected: {
results?: unknown[];
statistics?: {
total: number;
successful: number;
failed: number;
};
errors?: Array<{
index: number;
error: string;
}>;
[key: string]: unknown;
}; // Expected batch processing results
}
A batch case in the package's JSON test files: several addresses and the counts expected.
({ input: ["123 Main St"], expected: { successful: 1 } }).expected.successful
// 1
function buildRegexFromDict
buildRegexFromDict(dict: Record<string, string>, capture?: boolean): RegExp
A regular expression matching any key of a dictionary as a whole word, longest first. Words match in any alphabet, so québec is found at the start of a string and al is not found inside Montréal.
buildRegexFromDict({ street: "St", avenue: "Ave" }).test("avenue")
// true
const CA_PROVINCE_ALTERNATIVES
CA_PROVINCE_ALTERNATIVES: Record<string, string>
Other ways Canadian provinces are written (old and informal abbreviations such as PQ, Que., Nfld.), in lower case, to their codes.
CA_PROVINCE_ALTERNATIVES["pq"]
// "QC"
const CA_PROVINCE_NAMES
CA_PROVINCE_NAMES: Record<string, string>
Every Canadian province and territory by its English or French name, to its code.
CA_PROVINCE_NAMES["québec"]
// "QC"
const CA_PROVINCE_NAMES_EN
CA_PROVINCE_NAMES_EN: Record<string, string>
Every Canadian province and territory by its English name in lower case, to its two-letter code.
CA_PROVINCE_NAMES_EN["british columbia"]
// "BC"
const CA_PROVINCE_NAMES_FR
CA_PROVINCE_NAMES_FR: Record<string, string>
Every Canadian province and territory by its French name in lower case, to its two-letter code.
CA_PROVINCE_NAMES_FR["colombie-britannique"]
// "BC"
const CA_PROVINCES
CA_PROVINCES: Record<string, string>
Every name and other spelling of a Canadian province, to its code: the names and the alternatives together.
CA_PROVINCES["nfld"]
// "NL"
const CA_REGIONS
CA_REGIONS: Region[]
Every name and other spelling of a Canadian province or territory as a Region object, for fuzzy matching by normalizeRegion.
CA_REGIONS.filter((region) => region.abbr === "QC").map((region) => region.name)
// ["quebec","québec","pq","que"]
const CA_STREET_TYPES
CA_STREET_TYPES: Record<string, string>
Every street type Canada Post lists, in English and French, with common spellings, in lower case, to the abbreviation the parser reports. Where USPS Publication 28 has the same word, the USPS abbreviation is used (Court is Ct, not Canada Post's Crt; see the conventions in docs/TEST_COVERAGE.md); the words Publication 28 lacks keep Canada Post's abbreviation.
CA_STREET_TYPES["croissant"]
// "crois"
type CanadaPostFormattingOptions
interface CanadaPostFormattingOptions {
includeDeliveryLine?: boolean; // Whether to include delivery line
includeLastLine?: boolean; // Whether to include city/province/postal line
bilingualLabels?: boolean; // Whether to include bilingual labels
standardizeCase?: boolean; // Whether to standardize case per Canada Post guidelines
}
Options for formatCanadaPost: which lines to include, bilingual labels, and whether to set the letter case the Canada Post way.
formatCanadaPost(parseLocation("100 Queen St W, Toronto, ON M5H 2N2"), { includeDeliveryLine: false }).lines
// ["TORONTO ON M5H 2N2"]
const CANADIAN_POSTAL_CODE_PATTERN
CANADIAN_POSTAL_CODE_PATTERN: RegExp
A Canadian postal code with the letters Canada Post assigns, with or without its space.
CANADIAN_POSTAL_CODE_PATTERN.test("M5H 2N2")
// true
const CANADIAN_POSTAL_LIBERAL_PATTERN
CANADIAN_POSTAL_LIBERAL_PATTERN: RegExp
A Canadian postal code of any letters, for finding one before it is checked.
CANADIAN_POSTAL_LIBERAL_PATTERN.test("m5h2n2")
// true
function capitalizeStreetName
capitalizeStreetName(text: string): string
Capitalizes a street name the way it is signed. A word written in mixed case is kept as written (O'Farrell, McKinley, d'Youville); a word all in lower case or all in capitals is title-cased, except a French particle written in lower case, which stays so (rue des Jardins).
capitalizeStreetName("o'brien")
// "O'Brien"
function capitalizeWords
capitalizeWords(text: string): string
Capitalizes the first letter of each word.
capitalizeWords("new york city")
// "New York City"
const CITY_PATTERNS
CITY_PATTERNS: { readonly BASIC_CITY: RegExp; readonly MULTI_WORD_CITY: RegExp; readonly SINGLE_WORD_CITY: RegExp; readonly TWO_WORD_CITY: RegExp; }
Patterns for a city of one or more words at the end of a text, after a space, used where no comma marks it. A word is letters of any alphabet, with apostrophes and hyphens inside it.
CITY_PATTERNS.SINGLE_WORD_CITY.exec("Pine St Tacoma")?.[1]
// "Tacoma"
function cleanAddress
cleanAddress(addressString: string, options?: CleanAddressOptions): string
Tidies an address typed in a hurry: spaces, commas, letter case, the street type and the state, without changing what it says.
cleanAddress("350 FIFTH AVENUE, NEW YORK, NY 10118")
// "350 Fifth Ave, New York NY 10118"
function cleanAddressDetailed
cleanAddressDetailed(addressString: string, options?: CleanAddressOptions): CleanAddressResult
Tidies an address like cleanAddress, and says what it changed.
cleanAddressDetailed("742 evergreen terrace,springfield , il 62704").cleanedAddress
// "742 Evergreen Ter, Springfield IL 62704"
type CleanAddressOptions
interface CleanAddressOptions extends AddressFormattingOptions {
format?: "standard" | "usps" | "canada-post"; // Desired output format
removeExtraSpaces?: boolean; // Whether to remove redundant spaces
standardizeCase?: "upper" | "lower" | "title" | "none"; // Case standardization option
expandAbbreviations?: boolean; // Whether to expand abbreviations to full forms
}
Options for cleanAddress: which tidying to do, and the letter case to set.
cleanAddress("123 main st, anytown, ny 12345", { standardizeCase: "upper" })
// "123 MAIN ST, ANYTOWN NY 12345"
type CleanAddressResult
interface CleanAddressResult {
cleanedAddress: string; // The cleaned address string
wasModified: boolean; // Whether any modifications were made
changes: string[]; // List of changes made to the address
}
What cleanAddressDetailed returns: the tidied address and what changed.
cleanAddressDetailed("123 main st, anytown, ny 12345").wasModified
// true
type CleanAddressTestCase
interface CleanAddressTestCase extends TestCaseBase {
input: string; // Input address string to clean
expected: {
cleaned: string;
changes?: string[];
[key: string]: unknown;
}; // Expected cleaned address and changes
options?: {
format?: string;
abbreviate?: boolean;
[key: string]: unknown;
}; // Optional cleaning configuration
}
A cleaning case in the package's JSON test files: an address and the tidied text expected.
({ input: "123 MAIN ST", expected: "123 Main St" }).expected
// "123 Main St"
const COMMON_PARSER_PATTERNS
COMMON_PARSER_PATTERNS: { readonly ZIP_AT_END: (zipPattern: string) => RegExp; readonly STATE_AT_END: (statePattern: string) => RegExp; readonly CITY_STATE_PATTERN: (stateAbbrevPattern: string) => RegExp; readonly POSTAL_AT_END: (postalPattern: string) => RegExp; }
Pattern builders the parsers share: a ZIP, a state or a postal code at the end of a text, and a city before a state. Each takes the source of a pattern wrapped in one pair of parentheses or anchors, which it strips.
COMMON_PARSER_PATTERNS.ZIP_AT_END("(\\d{5})").exec("Tacoma WA 98402")?.[1]
// "98402"
const COMMON_STREET_NAMES_PATTERN
COMMON_STREET_NAMES_PATTERN: RegExp
Street names common enough that they must not be taken as part of a city's name.
COMMON_STREET_NAMES_PATTERN.test("Main")
// true
function compareAddresses
compareAddresses(address1: ParsedAddress, address2: ParsedAddress, options?: AddressComparisonOptions): AddressComparisonResult
Compares two parsed addresses field by field, allowing for abbreviations (Street and St), state names and codes, letter case and small typos.
compareAddresses(parseLocation("123 Main Street, Anytown, NY 12345"), parseLocation("123 Main St, Anytown, New York 12345")).matchType
// "exact"
const CONNECTOR_WORDS
CONNECTOR_WORDS: Set<string>
Small words (of, the, and) that may be in lower case inside a facility's name written in title case.
CONNECTOR_WORDS.has("of")
// true
const COUNTRIES
COUNTRIES: { readonly CANADA: "CA"; readonly JAPAN: "JP"; readonly UNITED_STATES: "US"; }
The country codes the parser reports: US, CA and JP.
COUNTRIES.JAPAN
// "JP"
type CountryCode
type CountryCode = (typeof COUNTRIES)[keyof typeof COUNTRIES];
A country code the parser reports: US, CA or JP.
parseLocation("100 Queen St W, Toronto, ON M5H 2N2")?.country
// "CA"
type CountryComparison
interface CountryComparison {
isSame: boolean;
differences: CountryDifference[];
}
What a country module's comparer returns: whether the two addresses are the same delivery point, and every field that differs once both are in the same form (letter case, punctuation, a street type written out or abbreviated).
compareAustralianAddresses(parseAustralianAddress("3/12 Smith Street, Parramatta NSW 2150"), parseAustralianAddress("Unit 3, 12 Smith St, PARRAMATTA NSW 2150")).isSame
// true
type CountryDifference
interface CountryDifference {
field: string;
first?: string;
second?: string;
}
One way two addresses differ, as a country module's comparer reports it: the field, and its value in each address after both were put in the same form.
compareUKAddresses(parseUKAddress("10 High Street, Bath BA1 1AA"), parseUKAddress("12 High St, Bath BA1 1AA")).differences
// [{"field":"number","first":"10","second":"12"}]
type CountryModule
interface CountryModule {
code: string; // The country's ISO 3166-1 code: AU, GB
codes: readonly string[]; // Every country code the module reads; GB also reads Jersey (JE), Guernsey (GY) and the Isle of Man (IM)
name: string; // The country's name in English
detect(address: string): boolean; // Whether the address is surely this country's, with no hint
parse(address: string, options?: ParseOptions): ParsedAddress | null;
validate(address: ParsedAddress, options?: ValidationOptions): CountryValidation;
format(address: ParsedAddress): FormattedAddress;
compare(first: ParsedAddress, second: ParsedAddress): CountryComparison;
}
A country's address module: its codes, how to tell its addresses apart, and its parser, validator, formatter and comparer. Import one from its entry point (australia from @johnmorrisdotca/address-plus/au, unitedKingdom from @johnmorrisdotca/address-plus/gb) and hand it to parseLocation and validateAddress in countries.
australia.codes
// ["AU"]
type CountryValidation
interface CountryValidation {
errors: ValidationError[];
warnings: ValidationError[];
}
What a country module's validator returns: the errors and the warnings it found.
validateAustralianAddress(parseAustralianAddress("1 Main St, Sydney VIC 2000")).warnings.map((one) => one.code)
// ["POSTAL_REGION_MISMATCH"]
const default
default: AddressParser
The default export, shaped like parse-address's module: parseLocation, parseAddress, parseIntersection and parseInformalAddress on one object, so import parser from "@johnmorrisdotca/address-plus" works where parse-address was imported. Named imports work too.
parser.parseLocation("123 Main St, New York, NY 10001").zip
// "10001"
function detectCountry
detectCountry(address: ParsedAddress): "US" | "CA" | undefined
Which country a parsed address is in, from its postal code, then its state or province.
detectCountry({ zip: "M5H 2N2" })
// "CA"
const DIRECTION_EXPANSIONS
DIRECTION_EXPANSIONS: Record<string, string>
Each directional abbreviation in lower case, to the word in full.
DIRECTION_EXPANSIONS["ne"]
// "Northeast"
const DIRECTIONAL_MAP
DIRECTIONAL_MAP: Record<string, string>
Each directional word, in English or French, in lower case, to its abbreviation (northwest and nord-ouest to NW and NO).
DIRECTIONAL_MAP["northwest"]
// "NW"
const FACILITY_DELIMITER_PATTERN
FACILITY_DELIMITER_PATTERN: RegExp
A facility's name followed by a comma or a dash and then the address.
FACILITY_DELIMITER_PATTERN.test("City Hall - 100 Queen St W")
// true
const FACILITY_DELIMITER_PATTERNS
FACILITY_DELIMITER_PATTERNS: { readonly PARENTHETICAL: RegExp; readonly DELIMITED: RegExp; readonly TRAILING_ISLAND: RegExp; }
The ways a facility's name is set off from an address: in parentheses, before a delimiter, or as a trailing island.
FACILITY_DELIMITER_PATTERNS.PARENTHETICAL.test("(City Hall)")
// true
const FACILITY_INDICATORS
FACILITY_INDICATORS: readonly ["center", "centre", "building", "tower", "plaza", "square", "garden", "gardens", "park", "university", "college", "school", "hospital", "library", "museum", "station", "airport", "mall", "market", "stadium", "arena", "theater", "theatre", "hotel", "resort", "memorial", "monument", "bridge", "tunnel", "complex"]
Words that mark a place's name as a facility (center, tower, hospital, université), in lower case.
FACILITY_INDICATORS.includes("hospital")
// true
const FACILITY_PATTERNS
FACILITY_PATTERNS: RegExp[]
The patterns that find a facility's name, in English and French.
FACILITY_PATTERNS.some((pattern) => pattern.test("Empire State Building"))
// true
function findMunicipalitiesByName
findMunicipalitiesByName(name: string, prefectureCode?: string): JapaneseMunicipality[]
The municipalities a Japanese name could mean: several when the name is shared (府中市 is in Tokyo and in Hiroshima). A town or village may be named without its district (当別町).
findMunicipalitiesByName("府中市").map((one) => one.code + " " + one.romaji)
// ["13206 Fuchu-shi","34208 Fuchu-shi"]
function findMunicipalitiesByRomaji
findMunicipalitiesByRomaji(text: string, prefectureCode?: string): JapaneseMunicipality[]
The municipalities a romaji name could mean, written with or without macrons and designators (Chiyoda-ku, Chiyoda City, Sapporo-shi Chuo-ku, Chuo-ku, Sapporo). A town written without its district, and then a ward written without its city, are looked up when the full name finds nothing.
findMunicipalitiesByRomaji("Chuo-ku, Sapporo").map((one) => one.name)
// ["札幌市中央区"]
function findMunicipalityByCode
findMunicipalityByCode(code: string): JapaneseMunicipality | null
The municipality with a JIS X 0402 code, a designated city's own code included.
findMunicipalityByCode("13101")?.name
// "千代田区"
function findPrefecture
findPrefecture(text: string): JapanesePrefecture | null
The prefecture a name, reading, romaji spelling or JIS code refers to: 東京都, 東京, トウキョウト, Tokyo, Osaka Prefecture, 13.
findPrefecture("Osaka Prefecture")?.name
// "大阪府"
function formatAddress
formatAddress(address: ParsedAddress, options?: AddressFormattingOptions): FormattedAddress
Writes a parsed address back out as lines and as one line, with abbreviations or in full.
formatAddress(parseLocation("123 Main Street, Anytown, NY 12345")).singleLine
// "123 Main St, Anytown NY 12345"
function formatCanadaPost
formatCanadaPost(address: ParsedAddress, options?: CanadaPostFormattingOptions): FormattedAddress
Writes a parsed address the way Canada Post asks: capitals, the unit before the civic number joined by a hyphen, and the postal code two spaces after the province.
formatCanadaPost(parseLocation("100 Queen Street West, Toronto, Ontario M5H 2N2")).lines
// ["100 QUEEN ST W","TORONTO ON M5H 2N2"]
function formatJapanese
formatJapanese(address: ParsedAddress, options?: JapaneseFormattingOptions): string
Writes a Japanese address in Japanese order, as an envelope is addressed: 〒100-0005, then 東京都千代田区丸の内1-2-3, then サンプルビル5階501号室. The prefecture and municipality are always in kanji, from the tables. A town or building parsed from romaji keeps its romaji, set off by spaces so the scripts do not run together. Kyoto's street directions are written before the town.
formatJapanese(parseLocation("〒100-0005 東京都千代田区丸の内1-2-3"), { blockStyle: "markers", multiline: false })
// "〒100-0005 東京都千代田区丸の内1丁目2番3号"
function formatJapaneseEnglish
formatJapaneseEnglish(address: ParsedAddress, options?: JapaneseEnglishFormattingOptions): string
Writes a Japanese address in English order, as a form from abroad expects: building, room, block, town, municipality, prefecture, postal code, Japan. The prefecture and municipality are always romaji, from the tables; the town and building are written as they were parsed, since the tables hold no romaji for towns.
formatJapaneseEnglish(parseLocation("1-2-3 Marunouchi, Chiyoda-ku, Tokyo 100-0005"))
// "1-2-3 Marunouchi, Chiyoda-ku, Tokyo 100-0005, Japan"
type FormattedAddress
interface FormattedAddress {
lines: string[]; // Individual address lines
singleLine: string; // Single-line representation
deliveryLine?: string; // Street address line
lastLine?: string; // City/state/postal line
country?: string; // Country designation
format:
| "standard"
| "usps"
| "canada-post"
| "international"
| "australia-post"
| "royal-mail"
| "la-poste"
| "deutsche-post"; // Formatting standard used
}
A formatted address: its lines, and the same on one line.
formatUSPS(parseLocation("123 Main St, Anytown, NY 12345"))
// {"lines":["123 MAIN ST","ANYTOWN NY 12345"],"singleLine":"123 Main St, Anytown NY 12345","deliveryLine":"123 Main St","lastLine":"Anytown NY 12345","country":"US","format":"usps"}
function formatUSPS
formatUSPS(address: ParsedAddress, options?: USPSFormattingOptions): FormattedAddress
Writes a parsed address the way USPS Publication 28 asks: capitals, standard abbreviations, the unit on the street line, and the city, state and ZIP+4 on the last.
formatUSPS(parseLocation("123 Main Street Apt 4, Anytown, NY 12345")).lines
// ["123 MAIN ST APT 4","ANYTOWN NY 12345"]
const FRENCH_PREPOSITIONS
FRENCH_PREPOSITIONS: Map<string, string>
French particles that can open a street name, each with the space after it, to the way it is written there (de la as De la ).
FRENCH_PREPOSITIONS.get("de la ")
// "De la "
type FrenchAddressFields
interface FrenchAddressFields {
numberExtension?: string; // The indice de répétition after the number: bis, ter, quater, or a letter (the B of 12 B)
staircase?: string; // The staircase: the B of "Escalier B"
entrance?: string; // The entrance: the A of "Entrée A"
lieuDit?: string; // A lieu-dit: a named place (a hamlet, a farm) that goes on a line of its own
postalBoxType?: "BP" | "CS" | "TSA"; // A boîte postale, a course spéciale or a tri service arrivée
postalBoxNum?: string; // The number of the box: 123 in "BP 123"
cedex?: string; // The CEDEX the commune line ends with, as La Poste writes it: "CEDEX 09", or "CEDEX" alone
arrondissement?: string; // The arrondissement of Paris, Lyon or Marseille, as a number: 8 for "Paris 8e"
careOf?: string; // The "chez" line: who the address is care of
}
The fields an address in France fills beside the shared ones. The shared fields keep their meaning: number is the street number, street the name of the street without its type and type the type in full (Rue; the name is de la Paix), secUnitType and secUnitNum an apartment or a door, floorType and floor a floor, building the building's or the residence's name, city the commune, state the department's code and zip the postcode.
parseFrenchAddress("12 bis rue de la Paix, 75002 Paris")?.numberExtension
// "bis"
type FrenchCollectivity
interface FrenchCollectivity {
code: string; // 988
country: FrenchPostalCountry; // NC
name: string; // Nouvelle-Calédonie
}
An overseas collectivity of France in the tables: its code (987), the ISO 3166-1 country code an address in it reads as (PF) and its name.
FR_COLLECTIVITIES.find((one) => one.code === "988")
// {"code":"988","country":"NC","name":"Nouvelle-Calédonie"}
type FrenchDepartment
interface FrenchDepartment {
code: string; // 75, 2A, 971
name: string; // Paris
region: string; // Île-de-France
}
A department of France in the tables: its code (75, 2A, 971), its name and its region's, from INSEE's Code officiel géographique.
FR_DEPARTMENTS.find((department) => department.code === "75")
// {"code":"75","name":"Paris","region":"Île-de-France"}
type FrenchPostalCountry
type FrenchPostalCountry = "BL" | "FR" | "MC" | "MF" | "NC" | "PF" | "PM" | "WF";
The country an address in La Poste's base belongs to: FR for France (the metropolis and the overseas departments), MC for Monaco, and the ISO 3166-1 code of an overseas collectivity (PM, BL, MF, WF, PF, NC), which is French but has a country code of its own and is listed apart by the Universal Postal Union.
parseFrenchAddress("Avenue Pouvanaa a Oopa, 98713 Papeete, Polynésie française")?.country
// "PF"
type FrenchPostcode
interface FrenchPostcode {
postcode: string; // 75008
place: string; // 75, 2B, 971, 987, 99
country: FrenchPostalCountry;
known: boolean; // Whether La Poste's base lists it
department?: string; // The department's code, absent for a collectivity and for Monaco
}
A postcode taken apart: the code of the department or territory its number belongs to, the country it delivers to, and whether La Poste's base has it. place is a department's code (75, 2A, 971), a collectivity's (987) or 99 for Monaco.
parseFrenchPostcode("20200")
// {"postcode":"20200","place":"2B","country":"FR","known":true,"department":"2B"}
type FuzzyMatchOptions
interface FuzzyMatchOptions {
threshold: number; // 0-1 minimum similarity threshold
maxDistance: number; // Maximum edit distance for string matching
enableSoundex?: boolean; // Use soundex for phonetic matching
enableMetaphone?: boolean; // Use metaphone for phonetic matching
}
Settings for fuzzy string matching: the lowest similarity that counts, the largest edit distance, and phonetic matching.
({ threshold: 0.8, maxDistance: 2 }).maxDistance
// 2
const GENERAL_DELIVERY_PATTERNS
GENERAL_DELIVERY_PATTERNS: { readonly STANDARD: RegExp; readonly WITH_CITY: RegExp; }
General delivery written alone or before a city.
GENERAL_DELIVERY_PATTERNS.STANDARD.test("General Delivery")
// true
type GermanAddressFields
interface GermanAddressFields {
careOf?: string; // The "c/o", "bei" or "z. Hd." line: who the address is care of
}
The fields an address in Germany fills beside the shared ones. The shared fields keep their meaning: street is the whole name of the street as written (Hauptstraße, Berliner Str., Am Markt), number the house number with its letter (12a) or range (12-14), secUnitType and secUnitNum a flat (Wohnung 12), a box (Postfach 12 34 56) or a Packstation, floorType and floor a floor (OG and 2), building a wing, a house or a name (Hinterhaus, Haus B), locality the Ortsteil, city the place, state the Land's code and zip the postcode.
parseGermanAddress("c/o Weber, Hauptstraße 12a, 10115 Berlin")?.careOf
// "Weber"
type GermanPostcode
interface GermanPostcode {
postcode: string; // 10115
state?: GermanStateCode; // BE; absent for a postcode that is not in the list
known: boolean; // Whether GeoNames' list has it
}
A postcode taken apart: the Land it is in, and whether GeoNames' list has it.
parseGermanPostcode("10115")
// {"postcode":"10115","state":"BE","known":true}
type GermanState
interface GermanState {
code: GermanStateCode; // BY
iso: string; // DE-BY
name: string; // Bavaria
nameJa: string; // バイエルン自由州
}
A Land of Germany in the tables: its code, its ISO 3166-2 code, and its name in English and in Japanese (from kuni, which takes them from Unicode CLDR and Wikidata).
DE_STATES.find((state) => state.code === "BY")
// {"code":"BY","iso":"DE-BY","name":"Bavaria","nameJa":"バイエルン自由州"}
type GermanStateCode
type GermanStateCode =
"BB" | "BE" | "BW" | "BY" | "HB" | "HE" | "HH" | "MV" | "NI" | "NW" | "RP" | "SH" | "SL" | "SN" | "ST" | "TH";
The code of one of the sixteen Länder of Germany, as ISO 3166-2:DE writes it after DE-.
getStateFromGermanPostcode("80331")
// "BY"
function getAddressAbbreviations
getAddressAbbreviations(): AddressAbbreviations
Every abbreviation the formatters use: street types, directionals, secondary units, states and provinces.
getAddressAbbreviations().streetTypes.avenue
// "Ave"
function getAddressSimilarity
getAddressSimilarity(address1: ParsedAddress, address2: ParsedAddress, options?: AddressComparisonOptions): AddressSimilarityResult
The similarity of two parsed addresses, part by part, without a verdict: the score and each part's score, and the differences.
getAddressSimilarity(parseLocation("123 Main Street, Anytown, NY 12345"), parseLocation("125 Main Street, Anytown, NY 12345")).differences
// [{"field":"number","value1":"123","value2":"125","type":"typo","confidence":0.6}]
function getPostalPrefixesForPrefecture
getPostalPrefixesForPrefecture(prefecture: string): string[]
The three-digit postal prefixes a prefecture's codes begin with, the reverse of getPrefectureFromJapanesePostalCode. A prefix on a border is listed under the prefecture most of its codes belong to.
getPostalPrefixesForPrefecture("沖縄県")
// ["900","901","902","903","904","905","906","907"]
function getPostalPrefixesForProvince
getPostalPrefixesForProvince(province: string): string[]
The postal code prefixes a province or territory uses, the reverse of getProvinceFromPostalCode. Every code starting with one of them belongs to that province.
getPostalPrefixesForProvince("NU")
// ["X0A","X0B","X0C"]
function getPrefectureFromJapanesePostalCode
getPrefectureFromJapanesePostalCode(postalCode: string): string | null
The prefecture a Japanese postal code delivers to, from Japan Post's data: by its first three digits, and for the codes on the far side of a prefix that straddles a border, by the whole code.
getPrefectureFromJapanesePostalCode("530-0001")
// "27"
function getProvinceFromPostalCode
getProvinceFromPostalCode(postalCode: string): string | null
The province or territory a Canadian postal code is in, from its first letter, and for the X codes of the north, its first three characters.
getProvinceFromPostalCode("H3G 1P1")
// "QC"
function getStateFromZip
getStateFromZip(zip: string | number): StateCode | undefined
The state or territory a US ZIP code is in.
getStateFromZip("98101")
// "WA"
function getValidationErrors
getValidationErrors(addressString: string, options?: ValidationOptions): ValidationError[]
The errors validateAddress finds, without the rest of its result.
getValidationErrors("123 Main St, Seattle, NY 98101", { strictPostalValidation: true }).map((error) => error.code)
// ["POSTAL_REGION_MISMATCH"]
function getZipPrefixesForState
getZipPrefixesForState(state: string): string[]
The ZIP code prefixes a state or territory uses, the reverse of getStateFromZip: three digits where a whole block of a hundred belongs to it, five where only part of one does. Every ZIP starting with one of them resolves to that state.
getZipPrefixesForState("RI")
// ["028","029"]
function hasValidAddressComponents
hasValidAddressComponents(address: string): boolean
Whether a string looks like an address at all: a number and a street, a PO box, a postal code or another recognised part.
hasValidAddressComponents("123 Main St, Anytown, NY 12345")
// true
const INTERSECTION_PATTERNS
INTERSECTION_PATTERNS: { readonly BASIC_CITY: RegExp; readonly CITY_WITH_COMMA: RegExp; readonly STREET_WITH_TYPE: (directionalPattern: string, streetTypePattern: string) => RegExp; readonly STREET_SIMPLE: (directionalPattern: string) => RegExp; }
The regular expressions and pattern builders the intersection parser uses to find the city and each street with its type.
INTERSECTION_PATTERNS.CITY_WITH_COMMA.exec("Pine St, Tacoma")?.[1]
// "Tacoma"
const ISLAND_TYPE_PATTERN
ISLAND_TYPE_PATTERN: RegExp
The words for an island, for addresses on one (Island, Isle, Île).
ISLAND_TYPE_PATTERN.test("Island")
// true
function isSameAddress
isSameAddress(address1: ParsedAddress, address2: ParsedAddress, options?: AddressComparisonOptions): boolean
Whether two parsed addresses are the same place, by the same rules as compareAddresses.
isSameAddress(parseLocation("東京都千代田区丸の内1丁目2番3号"), parseLocation("東京都千代田区丸の内1-2-3"))
// true
function isValidAddress
isValidAddress(addressString: string, options?: ValidationOptions): boolean
Whether an address passes validateAddress.
isValidAddress("123 Main St, Seattle, NY 98101", { strictPostalValidation: true })
// false
type JapaneseAddressFields
interface JapaneseAddressFields {
postalCode?: string; // 〒 code as NNN-NNNN
prefecture?: string; // 東京都
prefectureCode?: string; // JIS code: "13"
prefectureRomaji?: string; // Tokyo
municipality?: string; // 千代田区
municipalityCode?: string; // JIS code: "13101"
municipalityRomaji?: string; // Chiyoda-ku
streetDirections?: string; // Kyoto's street directions before the town (通り名): 寺町通御池上る
town?: string; // 丸の内 (大字・町名), without the chome
chome?: string; // 丁目: "1"
ban?: string; // 番 (番地): "2"
go?: string; // 号: "3"
block?: string; // The numbered block as one string: "1-2-3"
building?: string; // サンプルビル
floor?: string; // 階: "5"
room?: string; // 号室: "501"
}
The fields a Japanese address fills on top of the shared ones. Every value is normalised: full-width and kanji numerals become ASCII digits, and the block is split into chome, ban and go whichever way it was written.
parseLocation("〒100-0005 東京都千代田区丸の内1丁目2番3号")?.municipalityCode
// "13101"
type JapaneseEnglishFormattingOptions
interface JapaneseEnglishFormattingOptions {
includeCountry?: boolean; // ", Japan" at the end; default true
includePostalCode?: boolean; // Default true
}
Options for formatJapaneseEnglish.
formatJapaneseEnglish(parseLocation("〒100-0005 東京都千代田区丸の内1-2-3"), { includeCountry: false })
// "1-2-3 丸の内, Chiyoda-ku, Tokyo 100-0005"
type JapaneseFormattingOptions
interface JapaneseFormattingOptions {
blockStyle?: "hyphen" | "markers"; // 1-2-3 (default) or 1丁目2番3号
includePostalCode?: boolean; // 〒100-0005 on its own line; default true
multiline?: boolean; // Lines joined with newlines (default) or one line with spaces
}
Options for formatJapanese.
formatJapanese(parseLocation("〒100-0005 東京都千代田区丸の内1-2-3"), { blockStyle: "markers", includePostalCode: false })
// "東京都千代田区丸の内1丁目2番3号"
type JapaneseMunicipality
interface JapaneseMunicipality {
code: string; // JIS X 0402 code, five digits; the first two are the prefecture's
prefecture: string; // The prefecture's JIS code
name: string; // Official name, with the district for towns and villages in one: 千代田区, 札幌市中央区, 石狩郡当別町
kana: string; // Reading in katakana
romaji: string; // Romaji with designators hyphenated on: Chiyoda-ku, Sapporo-shi Chuo-ku, Ishikari-gun Tobetsu-cho
}
A municipality (市区町村) in the tables: its JIS code, its prefecture's code, its official name with the district for a town or village in one, its reading and its romaji.
findMunicipalityByCode("13101")
// {"code":"13101","prefecture":"13","name":"千代田区","kana":"チヨダク","romaji":"Chiyoda-ku"}
type JapanesePrefecture
interface JapanesePrefecture {
code: string; // JIS X 0401 code, "01" (Hokkaido) to "47" (Okinawa)
name: string; // Official name with its designator: 東京都, 大阪府, 北海道, 愛知県
kana: string; // Reading in katakana: トウキョウト
romaji: string; // Romaji with the designator hyphenated on: Tokyo-to
}
A prefecture (都道府県) in the tables: its JIS code, official name, katakana reading and romaji.
findPrefecture("13")
// {"code":"13","name":"東京都","kana":"トウキョウト","romaji":"Tokyo-to"}
type JapaneseValidation
interface JapaneseValidation {
errors: ValidationError[];
warnings: ValidationError[];
}
What validateJapaneseAddress returns: the errors and the warnings found.
validateJapaneseAddress(parseLocation("東京都大阪市北区梅田1-1")).warnings.map((one) => one.code)
// ["MUNICIPALITY_PREFECTURE_MISMATCH"]
const JP_DESIGNATED_CITIES
JP_DESIGNATED_CITIES: readonly JapaneseMunicipality[]
The twenty designated cities (政令指定都市) as municipalities of their own. Geolonia lists only their wards, but addresses often name the city alone (大阪市, Sapporo), and the city has a JIS code of its own: its wards' codes with the last digit 0 (札幌市 01100).
JP_DESIGNATED_CITIES.length
// 20
const JP_MUNICIPALITIES
JP_MUNICIPALITIES: readonly JapaneseMunicipality[]
Every municipality (市区町村) by JIS X 0402 code, with its prefecture, official name, reading and romaji; a designated city's wards are listed, and the city itself is in JP_DESIGNATED_CITIES. Generated from Geolonia 住所データ (MIT).
JP_MUNICIPALITIES.find((one) => one.code === "13101")?.name
// "千代田区"
const JP_POSTAL_EXCEPTIONS
JP_POSTAL_EXCEPTIONS: Readonly<Record<string, string>>
The postal codes that deliver to another prefecture than the rest of their three-digit prefix, each to that prefecture's JIS code. Generated from Japan Post's KEN_ALL.CSV through jp-postal (MIT).
Object.keys(JP_POSTAL_EXCEPTIONS).length > 0
// true
const JP_POSTAL_PREFIXES
JP_POSTAL_PREFIXES: Readonly<Record<string, string>>
Each three-digit postal prefix, to the JIS code of the prefecture most of its codes deliver to. Generated from Japan Post's KEN_ALL.CSV through jp-postal (MIT).
JP_POSTAL_PREFIXES["530"]
// "27"
const JP_PREFECTURES
JP_PREFECTURES: readonly JapanesePrefecture[]
The 47 prefectures in JIS X 0401 order, each with its code, official name, katakana reading and romaji. Generated from Geolonia 住所データ (MIT).
JP_PREFECTURES.length
// 47
function kanjiNumeralsToDigits
kanjiNumeralsToDigits(text: string): string
Turns the kanji numerals that stand for block, floor or room numbers into digits: 一丁目二番三号 becomes 1丁目2番3号. A numeral that is part of a name stays: 北一条西, 三番町, 麻布十番, 二階堂.
kanjiNumeralsToDigits("二丁目十五番")
// "2丁目15番"
function looksJapanese
looksJapanese(text: string): boolean
Whether a text is a Japanese address: in Japanese script, ending with Japan, or naming a prefecture beside a Japanese postal code, a romaji designator (-ku, -shi) or a municipality written with an English word (Chiyoda City). A US address that only mentions a Japanese name (100 Tokyo Ave) does not count.
looksJapanese("1-2-3 Marunouchi, Chiyoda-ku, Tokyo")
// true
function municipalitiesOf
municipalitiesOf(prefectureCode: string): readonly JapaneseMunicipality[]
Every municipality of a prefecture, the designated cities included.
municipalitiesOf("47").length
// 41
const MUSIC_SQUARE_EAST_PATTERN
MUSIC_SQUARE_EAST_PATTERN: RegExp
Nashville's Music Square East, whose name ends in a directional word that is part of it.
MUSIC_SQUARE_EAST_PATTERN.test("1 Music Square East")
// true
function normalizeJapaneseAddressText
normalizeJapaneseAddressText(text: string): string
Makes the text of a Japanese address uniform, as the parser reads it: widths folded, spaces tidied, numerals as digits, and 1の2の3, 1-2-3 or any other dash written 1-2-3. The postal mark 〒 is kept.
normalizeJapaneseAddressText("〒100-0005 東京都千代田区丸の内一丁目二番三号")
// "〒100-0005 東京都千代田区丸の内1丁目2番3号"
function normalizeRegion
normalizeRegion(input: string): { abbr: string; country: "CA" | "US"; } | null
Finds the US state or Canadian province a name, code or misspelling means: Calfornia, Que., British Columbia, nfld.
normalizeRegion("Calfornia")
// {"abbr":"CA","country":"US"}
function normalizeStateProvinceName
normalizeStateProvinceName(stateName: string): string | undefined
The code of a US state or Canadian province written in full, in lower case.
normalizeStateProvinceName("Nova Scotia")
// "ns"
function normalizeText
normalizeText(text: string): string
Lower-cases a string, turns its periods, commas and semicolons into spaces, folds runs of spaces to one and trims it: the form the parsers compare words in.
normalizeText(" 123 Main St ")
// "123 main st"
const PARENTHETICAL_PATTERN
PARENTHETICAL_PATTERN: RegExp
Words in parentheses inside an address; group 1 is what is inside them.
PARENTHETICAL_PATTERN.exec("123 Main St (Rear)")?.[1]
// "Rear"
function parseAddress
parseAddress(address: string, options?: ParseOptions): ParsedAddress | null
Parses a street address. The same as parseLocation, kept under the name parse-address's users know.
parseAddress("123 Main St Apt 4, Anytown, NY 12345")?.secUnitNum
// "4"
function parseAddresses
parseAddresses(addresses: string[], options?: ParseOptions): (ParsedAddress | null)[]
Parses many street addresses with parseAddress, in order.
parseAddresses(["10 Main St, Anytown, NY 12345", "PO Box 12, Springfield, IL 62701"]).map((one) => one?.city)
// ["Anytown","Springfield"]
function parseAddressesBatch
parseAddressesBatch(addresses: string[], options?: BatchParseOptions): BatchParseResult<ParsedAddress>
Parses many street addresses with parseAddress, and reports which failed and how long it took.
parseAddressesBatch(["10 Main St, Anytown, NY 12345", "PO Box 12, Springfield, IL 62701"]).stats.successful
// 2
type ParsedAddress
interface ParsedAddress extends JapaneseAddressFields, AustralianAddressFields, FrenchAddressFields, UKAddressFields {
city?: string; // City name, or the municipality in Japan; APO, FPO or DPO in a military address
compartment?: string; // Compartment on a Canadian rural route (the 10 in "SITE 6 COMP 10 RR 8")
country?: "CA" | "US" | "JP" | "AU" | "GB" | "GY" | "IM" | "JE" | FrenchPostalCountry | "DE"; // Detected country; AU, GB (with Jersey, Guernsey and the Isle of Man), FR (with Monaco and the overseas collectivities) and DE only from their modules
fraction?: string; // Fractional address number (e.g., 1/2 in "123 1/2 Main St")
generalDelivery?: boolean; // General delivery indicator
highwayContract?: string; // Highway contract route number (the 68 in "HC 68 BOX 23A"); ruralRoute holds "HC 68"
locality?: string; // Sub-city locality (borough, district, neighborhood), or a Puerto Rico urbanization
military?: string; // Military delivery line ("PSC 802 Box 74", "Unit 2050 Box 4190"); state is AA, AE or AP
number?: string; // Street number
place?: string; // Place name (landmark, POI, building, monument, etc.)
plus4?: string; // Extended ZIP+4 code
postalValid?: boolean; // Postal code validation status
postalType?: "zip" | "postal"; // Postal code type (zip or postal)
prefix?: string; // Directional prefix (N, S, E, W, etc.)
rpo?: string; // Retail Postal Outlet (Canada Post) identifier
rr?: string; // Rural Route number (RR/R.R.)
ruralRoute?: string; // Rural route or similar
secUnitNum?: string; // Secondary unit number
secUnitType?: string; // Secondary unit type (apt, suite, etc.)
secondary?: string; // Legacy properties for backward compatibility
site?: string; // Site number on a Canadian rural route (the 6 in "SITE 6 COMP 10 RR 8")
state?: string; // State/Province code; AA, AE or AP for a military address
station?: string; // Station or Succursale identifier (e.g., Station A, Succ. Centre-ville)
street?: string; // Street name
suffix?: string; // Directional suffix
type?: string; // Street type/suffix (St, Ave, Rd, etc.)
unit?: string; // Legacy unit property for backward compatibility
zip?: string; // ZIP or postal code
zipValid?: boolean; // ZIP/postal code format validation (true if format is valid)
}
What parseLocation returns: every part it found, each absent when the address has none. A Japanese address fills its own fields and the shared ones that stand for them: state the prefecture's JIS code, city the municipality, street the town, number the block, zip the postal code.
parseLocation("123 Main St Apt 4, Anytown, NY 12345")
// {"number":"123","secUnitType":"Apartment","secUnitNum":"4","unit":"Apt 4","street":"Main","type":"St","city":"Anytown","state":"NY","zip":"12345","zipValid":true,"country":"US"}
type ParsedIntersection
interface ParsedIntersection {
street1?: string; // First street
type1?: string; // First street type
prefix1?: string; // First street prefix
suffix1?: string; // First street suffix
street2?: string; // Second street
type2?: string; // Second street type
prefix2?: string; // Second street prefix
suffix2?: string; // Second street suffix
city?: string; // City
state?: string; // State/Province
zip?: string; // ZIP/Postal code
plus4?: string; // Extended ZIP+4 code
country?: "CA" | "US"; // Country
postalValid?: boolean; // Postal code validation status
postalType?: "zip" | "postal"; // Postal code type (zip or postal)
}
What parseIntersection returns: two streets, each with its name, type and directionals, and the place.
parseIntersection("Hollywood Blvd and Vine St, Los Angeles, CA")
// {"state":"CA","city":"Los Angeles","street1":"Hollywood","type1":"Blvd","street2":"Vine","type2":"St"}
function parseDirectional
parseDirectional(text: string): { direction: string | undefined; remaining: string; }
Takes a leading directional off a street (NW Main St), abbreviated.
parseDirectional("NW Main St")
// {"direction":"NW","remaining":"Main St"}
function parseFacility
parseFacility(text: string): { facility: string | undefined; remaining: string; }
Takes a facility's name (a building, a park, a hospital) off the start of an address.
parseFacility("Empire State Building, 350 5th Ave")
// {"facility":"Empire State Building","remaining":", 350 5th Ave"}
function parseInformalAddress
parseInformalAddress(address: string, options?: ParseOptions): ParsedAddress | null
Reads an address written loosely, as a fallback when parseLocation finds no street address: a description such as Downtown near City Hall is kept whole as the street, with any ZIP code after it.
parseInformalAddress("Downtown near City Hall, Springfield IL 62701")
// {"street":"Downtown near City Hall","zip":"62701","country":"US"}
function parseInformalAddresses
parseInformalAddresses(addresses: string[], options?: ParseOptions): (ParsedAddress | null)[]
Parses many loosely written addresses with parseInformalAddress, in order.
parseInformalAddresses(["Downtown near City Hall, Springfield IL 62701"]).map((one) => one?.zip)
// ["62701"]
function parseInformalAddressesBatch
parseInformalAddressesBatch(addresses: string[], options?: BatchParseOptions): BatchParseResult<ParsedAddress>
Parses many loosely written addresses with parseInformalAddress, and reports which failed and how long it took.
parseInformalAddressesBatch(["Main St, Anytown NY"]).stats.successful
// 1
function parseIntersection
parseIntersection(address: string, options?: ParseOptions): ParsedIntersection | null
Parses an intersection of two streets, joined by &, and, at or @, with the city, state and ZIP that may follow. With no comma before the city, the second street ends at its type, so the city may be any number of words.
parseIntersection("Main St and Pine St Tacoma WA")
// {"state":"WA","city":"Tacoma","street1":"Main","type1":"St","street2":"Pine","type2":"St"}
function parseIntersections
parseIntersections(addresses: string[], options?: ParseOptions): (ParsedIntersection | null)[]
Parses many intersections with parseIntersection, in order.
parseIntersections(["Yonge St and Bloor St, Toronto, ON"]).map((one) => one?.street2)
// ["Bloor"]
function parseIntersectionsBatch
parseIntersectionsBatch(addresses: string[], options?: BatchParseOptions): BatchParseResult<ParsedIntersection>
Parses many intersections with parseIntersection, and reports which failed and how long it took.
parseIntersectionsBatch(["Yonge St and Bloor St, Toronto, ON"]).stats.successful
// 1
function parseJapaneseAddress
parseJapaneseAddress(text: string, options?: ParseOptions): ParsedAddress | null
Parses a Japanese address, in Japanese script or in romaji, into the Japanese fields and the shared ones. parseLocation calls it for any address that looks Japanese; call it directly to skip the detection.
parseJapaneseAddress("〒100-0005 東京都千代田区丸の内1丁目2番3号 サンプルビル5階501号室")?.block
// "1-2-3"
function parseLocation
parseLocation(address: string, options?: ParseOptions): ParsedAddress | null
Parses a US, Canadian or Japanese address into its parts. The country is detected from the text (a state, a province, a postal code, Japanese script or romaji designators) unless options.country names it. A Japanese address fills its own fields (prefecture, municipality, town, chome, ban, go) and the shared ones that stand for them. Australia and the United Kingdom are read too when their modules are passed in options.countries (australia from /au, unitedKingdom from /gb): options.country picks one, or each module's own detection decides, before the US, Canada and Japan are tried.
parseLocation("1600 Pennsylvania Ave NW, Washington, DC 20500")
// {"number":"1600","street":"Pennsylvania","type":"Ave","suffix":"NW","city":"Washington","state":"DC","zip":"20500","zipValid":true,"country":"US"}
function parseLocations
parseLocations(addresses: string[], options?: ParseOptions): (ParsedAddress | null)[]
Parses many addresses with parseLocation, in order.
parseLocations(["100 Queen St W, Toronto, ON M5H 2N2", "大阪府大阪市北区梅田3-1-1"]).map((one) => one?.country)
// ["CA","JP"]
function parseLocationsBatch
parseLocationsBatch(addresses: string[], options?: BatchParseOptions): BatchParseResult<ParsedAddress>
Parses many addresses with parseLocation, and reports which failed and how long it took. A failure is recorded and the batch goes on, unless options.stopOnError is set.
parseLocationsBatch(["100 Queen St W, Toronto, ON M5H 2N2", "", "大阪府大阪市北区梅田3-1-1"]).stats.successful
// 2
type ParseOptions
interface ParseOptions {
country?:
| "CA"
| "US"
| "JP"
| "AU"
| "GB"
| "GY"
| "IM"
| "JE"
| FrenchPostalCountry
| "DE"
| "GP"
| "MQ"
| "GF"
| "RE"
| "YT"
| "auto"; // Country to optimize parsing for; JP skips the detection and parses as Japanese; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly CountryModule[]; // Country modules to read beside the US, Canada and Japan: australia from "/au", unitedKingdom from "/gb", france from "/fr", germany from "/de"
normalize?: boolean; // Whether to normalize street types and directions
validatePostalCode?: boolean; // Whether to validate postal/ZIP codes
language?: "auto" | "en" | "fr"; // Language preference for bilingual parsing (Canada)
extractFacilities?: boolean; // Whether to extract facility names
parseParenthetical?: boolean; // Whether to parse parenthetical information
strict?: boolean; // Whether to only extract valid ZIP/postal codes (strict mode) - true: Only extract codes that pass format validation, false (default): Extract all codes but indicate validity with zipValid field
useSnakeCase?: boolean; // Whether to return field names in snake_case format for backward compatibility - true: Return snake_case field names (sec_unit_type, sec_unit_num, etc.), false (default): Return camelCase field names (secUnitType, secUnitNum, etc.)
}
Options for every parser: the country, strict postal codes, snake_case keys and the rest. Every one is optional.
parseLocation("東京都千代田区丸の内1-2-3", { country: "JP", useSnakeCase: true })?.prefecture_code
// "13"
function parseParenthetical
parseParenthetical(text: string): { secondary: string | undefined; remaining: string; }
Takes words in parentheses out of an address, such as (Rear Entrance).
parseParenthetical("123 Main St (Rear Entrance)")
// {"secondary":"Rear Entrance","remaining":"123 Main St"}
function parsePostalCode
parsePostalCode(text: string): { zip: string | undefined; plus4: string | undefined; remaining: string; detectedCountry?: "US" | "CA"; detectedProvince?: string; }
Takes a ZIP code, ZIP+4 or Canadian postal code off the end of a text.
parsePostalCode("Toronto ON M5H 2N2")
// {"zip":"M5H 2N2","remaining":"Toronto ON","detectedCountry":"CA","detectedProvince":"ON"}
function parseSecondaryUnit
parseSecondaryUnit(text: string): { unit: string | undefined; secUnitType: string | undefined; secUnitNum: string | undefined; remaining: string; }
Takes a secondary unit (apartment, suite, floor and the rest) off a street line.
parseSecondaryUnit("123 Main St Apt 4")
// {"unit":"Apartment 4","secUnitType":"Apartment","secUnitNum":"4","remaining":"123 Main St"}
function parseStateProvince
parseStateProvince(text: string): { state: string | undefined; remaining: string; detectedCountry?: "US" | "CA"; }
Takes a US state or Canadian province off the end of a text, by code or by name.
parseStateProvince("Anytown NY")
// {"state":"NY","remaining":"Anytown","detectedCountry":"US"}
function parseStreetNumber
parseStreetNumber(text: string): { number: string | undefined; remaining: string; }
Takes the house number off the start of a street line, with a fraction or a letter if it has one.
parseStreetNumber("123 Main St")
// {"number":"123","remaining":"Main St"}
function parseStreetType
parseStreetType(text: string, country?: "US" | "CA"): { type: string | undefined; remaining: string; }
Takes the street type off the end of a street, abbreviated the USPS way.
parseStreetType("Main Street")
// {"type":"st","remaining":"Main"}
const PO_BOX_PATTERNS
PO_BOX_PATTERNS: { readonly US_PO_BOX: RegExp; readonly STATION_PATTERN: RegExp; readonly LEADING_BOX_NUMBER: RegExp; readonly TRAILING_COMMA: RegExp; }
The regular expressions the PO box parser uses: the box itself, a station after it, a box number first, and a trailing comma.
PO_BOX_PATTERNS.US_PO_BOX.exec("PO Box 123, Springfield, IL 62701")?.slice(1)
// ["123","Springfield","IL","62701"]
const POSTAL_CODE_TO_PROVINCE
POSTAL_CODE_TO_PROVINCE: Record<string, string>
Each first letter of a Canadian postal code, to the province or territory it is assigned to. X is shared by the Northwest Territories and Nunavut and is resolved with TERRITORY_POSTAL_RANGES.
POSTAL_CODE_TO_PROVINCE["V"]
// "BC"
type PostalValidationResult
interface PostalValidationResult {
isValid: boolean;
type: "zip" | "postal" | null;
formatted?: string;
message?: string;
}
What validatePostalCode returns: whether the code is well formed, its type, and the code written the standard way.
validatePostalCode("98101")
// {"isValid":true,"type":"zip","formatted":"98101","message":"Valid US ZIP code format"}
const PROVINCE_EXPANSIONS
PROVINCE_EXPANSIONS: Record<string, string>
Each Canadian province's code in lower case, to its name in lower case: English by default, the French names under their own keys.
PROVINCE_EXPANSIONS["on"]
// "ontario"
const PROVINCE_EXPANSIONS_EN
PROVINCE_EXPANSIONS_EN: Record<string, string>
Each Canadian province's code in lower case, to its English name in lower case.
PROVINCE_EXPANSIONS_EN["qc"]
// "quebec"
const PROVINCE_EXPANSIONS_FR
PROVINCE_EXPANSIONS_FR: Record<string, string>
Each Canadian province's code in lower case, to its French name in lower case.
PROVINCE_EXPANSIONS_FR["qc"]
// "québec"
type Region
type Region = {
abbr: string;
country: "CA" | "US";
name: string;
};
A US state or Canadian province: its code, its country and its name, as normalizeRegion matches them.
CA_REGIONS[0]
// {"abbr":"AB","country":"CA","name":"alberta"}
const SECONDARY_UNIT_PATTERN
SECONDARY_UNIT_PATTERN: RegExp
A unit at the end of a street line: group 1 is the street before it, group 2 the unit.
SECONDARY_UNIT_PATTERN.exec("123 Main St Apt 4")?.[2]
// "Apt 4"
const SECONDARY_UNIT_TYPES
SECONDARY_UNIT_TYPES: Record<string, string>
Each secondary unit designator, abbreviated or in full, in lower case, to the word the parser reports in full: USPS Publication 28 Appendix C2, and Canada Post's French unit words, which stay French.
SECONDARY_UNIT_TYPES["ste"]
// "Suite"
function setValidatedPostalCode
setValidatedPostalCode(result: ParsedAddress | ParsedIntersection, zipCode: string, options: ParseOptions): void
Sets a ZIP or postal code on a result, the way the parsers do: split into ZIP and ZIP+4, a Canadian code in capitals with one space, with zipValid set, and in strict mode only when the code is well formed. It changes the object it is given.
const parsed = { city: "Toronto", state: "ON" };
setValidatedPostalCode(parsed, "m5h2n2", {});
parsed
// {"city":"Toronto","state":"ON","zip":"M5H 2N2"}
type StateCode
type StateCode =
| "AL"
| "AK"
| "AZ"
| "AR"
| "CA"
| "CO"
| "CT"
| "DC"
| "DE"
| "FL"
| "GA"
| "HI"
| "ID"
| "IL"
| "IN"
| "IA"
| "KS"
| "KY"
| "LA"
| "ME"
| "MD"
| "MA"
| "MI"
| "MN"
| "MS"
| "MO"
| "MT"
| "NE"
| "NV"
| "NH"
| "NJ"
| "NM"
| "NY"
| "NC"
| "ND"
| "OH"
| "OK"
| "OR"
…
A US state, DC or territory code that getStateFromZip can return.
getStateFromZip("00901")
// "PR"
const STREET_NAME_ACRONYMS
STREET_NAME_ACRONYMS: Map<string, string>
Acronyms written in capitals inside a street name (US, FBI), from their lower-case form, for capitalizeStreetName.
STREET_NAME_ACRONYMS.get("fbi")
// "FBI"
const STREET_TYPE_DETECTION_PATTERN
STREET_TYPE_DETECTION_PATTERN: RegExp
Whether a text has a common street type in it, used to judge that a parse found a street.
STREET_TYPE_DETECTION_PATTERN.test("123 Main Street")
// true
const STREET_TYPE_EXPANSIONS
STREET_TYPE_EXPANSIONS: Record<string, string>
Each USPS street type abbreviation in lower case, to the word in full.
STREET_TYPE_EXPANSIONS["blvd"]
// "Boulevard"
const STREET_TYPE_PROPER_CASE
STREET_TYPE_PROPER_CASE: Record<string, string>
Each street type abbreviation in lower case, to the way the parser reports it (Ave, Xing).
STREET_TYPE_PROPER_CASE["xing"]
// "Xing"
type SubRegion
interface SubRegion {
name: string; // Primary normalized name (lowercase, trimmed)
parentCity: string; // Parent city name (empty if not applicable)
state: string; // State/province code (e.g., "NY", "QC")
country: "US" | "CA"; // Country code
type: "borough" | "parish" | "district" | "ward" | "arrondissement" | "quadrant"; // Administrative type
aliases?: string[]; // Alternative names, abbreviations, bilingual variants, no-space versions
}
An administrative part of a city (a borough, a parish, a ward, an arrondissement) that may be written where the city is expected.
({ name: "brooklyn", parentCity: "new york", state: "NY", country: "US", type: "borough" }).type
// "borough"
const TERRITORY_POSTAL_PREFIXES
TERRITORY_POSTAL_PREFIXES: Record<string, string[]>
The first three characters of each territory's postal codes, since the territories share a first letter.
TERRITORY_POSTAL_PREFIXES["NU"]
// ["X0A","X0B","X0C"]
const TERRITORY_POSTAL_RANGES
TERRITORY_POSTAL_RANGES: { pattern: RegExp; province: string; }[]
The patterns that tell the Northwest Territories' X codes from Nunavut's, by their first three characters.
TERRITORY_POSTAL_RANGES.find((range) => range.pattern.test("X0A"))?.province
// "NU"
type TestCase
type TestCase =
| AddressParsingTestCase
| AddressFormattingTestCase
| AddressValidationTestCase
| AddressComparisonTestCase
| CleanAddressTestCase
| BatchProcessingTestCase;
Any case in the package's JSON test files.
({ input: "98101", expected: "WA" }).input
// "98101"
type TestCaseBase
interface TestCaseBase {
name?: string; // Human-readable name or description of what this test case validates
description?: string; // Human-readable description of what this test case validates (alternative to name)
input: unknown; // The input data for the test
expected: unknown; // The expected output/result of the test
options?: Record<string, unknown>; // Optional configuration or parsing options for the test
}
The fields every case in the package's JSON test files has: a name or description, the input, what is expected and the options. Exported for tools that read those files.
({ name: "a ZIP code", input: "98101", expected: "WA" }).expected
// "WA"
type UKAddressFields
interface UKAddressFields {
subBuilding?: string; // A part of a building with no number: "Basement Flat", "Stables Flat"
dependentThoroughfare?: string; // A thoroughfare inside another: the "Seastone Cottages" of "1A Seastone Cottages, Station Road"
doubleDependentLocality?: string; // A locality inside the dependent locality, written above it
county?: string; // A county, when one is written; Royal Mail no longer needs it
nation?: UKNationCode; // The nation the postcode delivers to, from the tables
bfpo?: string; // A British Forces Post Office number: the 105 of "BFPO 105"
}
The fields an address in the United Kingdom fills beside the shared ones. The shared fields keep their meaning: number is the building number, street and type the thoroughfare's name and its descriptor in full (Upper and Street, as Royal Mail writes it), secUnitType and secUnitNum a flat or unit (Flat 2) or a PO Box, building the building's name, locality the dependent locality, city the post town and zip the postcode.
parseUKAddress("Flat 2, Rose Court, 14 High Street, Kingsbury, LONDON NW9 0AA")?.locality
// "Kingsbury"
type UKNation
interface UKNation {
code: UKNationCode;
iso: string; // GB-SCT
name: string; // Scotland
nameJa: string; // スコットランド
}
A nation of the United Kingdom in the tables: its code, its ISO 3166-2 code, and its name in English and in Japanese (from kuni, which takes them from Unicode CLDR and Wikidata).
GB_NATIONS.find((nation) => nation.code === "SCT")
// {"code":"SCT","iso":"GB-SCT","name":"Scotland","nameJa":"スコットランド"}
type UKNationCode
type UKNationCode = "ENG" | "NIR" | "SCT" | "WLS";
The code of one of the four nations of the United Kingdom, as ISO 3166-2:GB writes it after GB-.
getNationFromUKPostcode("CF10 1AA")
// "WLS"
type UKPostcode
interface UKPostcode {
postcode: string; // Capitals, one space: EC1A 1BB
outward: string; // EC1A
inward: string; // 1BB
area: string; // EC
district: string; // EC1A, the same as the outward code
sector: string; // EC1A 1
country: "GB" | "GY" | "IM" | "JE";
nation?: UKNationCode; // Absent outside the United Kingdom, and for a non-geographic area (BX, BF)
}
A postcode taken apart: the outward code (area and district) and the inward code (sector and unit), and where it delivers. country is GB for the United Kingdom and JE, GY or IM for Jersey, Guernsey and the Isle of Man, which use Royal Mail's postcodes but are not part of the United Kingdom.
parseUKPostcode("ec1a1bb")
// {"postcode":"EC1A 1BB","outward":"EC1A","inward":"1BB","area":"EC","district":"EC1A","sector":"EC1A 1","country":"GB","nation":"ENG"}
const UNIT_TYPE_KEYWORDS
UNIT_TYPE_KEYWORDS: string
The secondary unit designators that take a value after them, as one alternation for a regular expression, longest first so that apartment is tried before apt: USPS Publication 28 Appendix C2, Canada Post's English and French unit words, and a few spellings people write.
new RegExp("^(?:" + UNIT_TYPE_KEYWORDS + ")$", "i").test("suite")
// true
const UNIT_TYPE_NUMBER_PATTERN
UNIT_TYPE_NUMBER_PATTERN: RegExp
A unit's designator and value: groups 1 and 2 (apt 123, Apt. #4B), groups 3 and 4 for a lot run together (lt42), group 5 for the value after a bare #.
UNIT_TYPE_NUMBER_PATTERN.exec("Apt. #4B")?.slice(1, 3)
// ["Apt","4B"]
const US_REGIONS
US_REGIONS: Region[]
Every name and other spelling of a US state, DC or territory as a Region object, for fuzzy matching by normalizeRegion.
US_REGIONS.find((region) => region.abbr === "WA")?.name
// "washington"
const US_STATE_ALTERNATIVES
US_STATE_ALTERNATIVES: Record<string, string>
Other ways US states are written (shortened forms, old abbreviations, D.C.), in lower case, to their codes.
US_STATE_ALTERNATIVES["calif"]
// "CA"
const US_STATE_EXPANSIONS
US_STATE_EXPANSIONS: Record<string, string>
Each US state's code in lower case, to its name in lower case: the reverse of US_STATE_NAMES.
US_STATE_EXPANSIONS["wa"]
// "washington"
const US_STATE_NAMES
US_STATE_NAMES: Record<string, string>
Every US state, DC and territory by its name in lower case, to its two-letter code.
US_STATE_NAMES["new york"]
// "NY"
const US_STATES
US_STATES: Record<string, string>
Every name and other spelling of a US state in lower case, to its code: US_STATE_NAMES and US_STATE_ALTERNATIVES together.
US_STATES["mass"]
// "MA"
const US_STREET_TYPES
US_STREET_TYPES: Record<string, string>
Every street type USPS Publication 28 lists, and the common spellings of each, in lower case, to its USPS abbreviation in lower case.
US_STREET_TYPES["boulevard"]
// "blvd"
type USPSFormattingOptions
interface USPSFormattingOptions {
includeDeliveryLine?: boolean; // Whether to include delivery line
includeLastLine?: boolean; // Whether to include city/state/ZIP line
includeBarcode?: boolean; // Whether to include postal barcode
standardizeCase?: boolean; // Whether to standardize case per USPS guidelines
}
Options for formatUSPS: which lines to include, and whether to set the letter case the USPS way.
formatUSPS(parseLocation("123 Main St, Anytown, NY 12345"), { includeLastLine: false }).lines
// ["123 MAIN ST"]
function validateAddress
validateAddress(addressString: string, options?: ValidationOptions): AddressValidationResult
Checks an address: whether it has what an address needs, whether its ZIP or postal code is well formed and belongs to the state, province or prefecture named, and how sure the parser is. With country modules in options.countries, an Australian or British address is checked by its module's validator.
validateAddress("123 Main St, Seattle, NY 98101").warnings[0].code
// "POSTAL_REGION_MISMATCH"
function validateJapaneseAddress
validateJapaneseAddress(address: ParsedAddress, options?: ValidationOptions): JapaneseValidation
Checks a parsed Japanese address against the tables: the postal code's shape, whether any code begins with its first three digits, whether it delivers to the prefecture named, and whether the municipality is a real one in that prefecture.
validateJapaneseAddress(parseLocation("〒530-0001 東京都千代田区丸の内1-2-3")).warnings.map((one) => one.code)
// ["POSTAL_REGION_MISMATCH"]
function validatePostalCode
validatePostalCode: (code: string) => PostalValidationResult
Checks the shape of a US ZIP code or a Canadian postal code.
validatePostalCode("k1a0b1")
// {"isValid":true,"type":"postal","formatted":"K1A 0B1","message":"Valid Canadian postal code format"}
const VALIDATION_PATTERNS
VALIDATION_PATTERNS: { readonly HAS_LETTERS: RegExp; readonly ALPHANUMERIC: RegExp; readonly HAS_DIGITS: RegExp; readonly HOUSE_NUMBER_START: RegExp; readonly STARTS_WITH_NUMBER: RegExp; readonly WHITESPACE_SPLIT: RegExp; readonly TITLE_CASE: RegExp; readonly NUMERIC_ONLY: RegExp; readonly NON_WORD: RegExp; readonly REGEX_ESCAPE: RegExp; readonly NORMALIZE_SPACES: RegExp; readonly PO_BOX_NORMALIZE: RegExp; }
Small regular expressions the validators share: letters, digits, a house number at the start, and the like.
VALIDATION_PATTERNS.STARTS_WITH_NUMBER.test("123 Main St")
// true
type ValidationError
interface ValidationError {
field: string; // Field name where error occurred
code: string; // Error code identifier
message: string; // Human-readable error message
severity: "error" | "warning" | "info"; // Severity level of the validation issue
}
One finding of a validator: the field it is about, its code, a message, and how serious it is.
validateAddress("123 Main St, Seattle, NY 98101").warnings[0]
// {"field":"zip","code":"POSTAL_REGION_MISMATCH","message":"ZIP code 98101 belongs to WA, not NY","severity":"warning"}
type ValidationOptions
interface ValidationOptions {
requireStreetNumber?: boolean; // Whether street number is required
requireStreetName?: boolean; // Whether street name is required
requireCity?: boolean; // Whether city is required
requireState?: boolean; // Whether state/province is required
requirePostalCode?: boolean; // Whether postal code is required
allowPOBox?: boolean; // Whether PO Box addresses are allowed
allowRuralRoute?: boolean; // Whether rural route addresses are allowed
allowGeneralDelivery?: boolean; // Whether general delivery addresses are allowed
strictPostalValidation?: boolean; // Whether to use strict postal code validation
country?: ParseOptions["country"]; // Country context for validation rules; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly import("./country-module").CountryModule[]; // Country modules to read beside the US, Canada and Japan
}
Options for the validators: which parts an address must have, which kinds are allowed, and whether a postal code that does not match its region is an error.
validateAddress("123 Main St", { requirePostalCode: true }).errors.map((error) => error.code)
// ["MISSING_POSTAL_CODE"]
const WRITTEN_NUMBERS
WRITTEN_NUMBERS: string
House numbers written as words (One, Twenty), as one alternation for a regular expression, so One Microsoft Way is read as number 1.
new RegExp("^(?:" + WRITTEN_NUMBERS + ")$", "i").test("one")
// true
const ZIP_CODE_PATTERN
ZIP_CODE_PATTERN: RegExp
A US ZIP code with an optional ZIP+4.
ZIP_CODE_PATTERN.test("98101-1234")
// true
const ZIP_CODE_REGEX_PATTERN
ZIP_CODE_REGEX_PATTERN: string
The source of ZIP_CODE_PATTERN, for building larger patterns.
ZIP_CODE_REGEX_PATTERN.length > 0
// true
const ZIP_VALIDATION_PATTERNS
ZIP_VALIDATION_PATTERNS: { readonly POTENTIAL_ZIP: RegExp; }
The shape of something that could be a ZIP code.
ZIP_VALIDATION_PATTERNS.POTENTIAL_ZIP.test("98101")
// true
@johnmorrisdotca/address-plus/jp
findMunicipalitiesByName findMunicipalitiesByRomaji findMunicipalityByCode findPrefecture formatJapanese formatJapaneseEnglish getPostalPrefixesForPrefecture getPrefectureFromJapanesePostalCode JapaneseAddressFields JapaneseEnglishFormattingOptions JapaneseFormattingOptions JapaneseMunicipality JapanesePrefecture JapaneseValidation JP_DESIGNATED_CITIES JP_MUNICIPALITIES JP_POSTAL_EXCEPTIONS JP_POSTAL_PREFIXES JP_PREFECTURES kanjiNumeralsToDigits looksJapanese municipalitiesOf normalizeJapaneseAddressText ParsedAddress parseJapaneseAddress ParseOptions validateJapaneseAddress ValidationError ValidationOptions
function findMunicipalitiesByName
findMunicipalitiesByName(name: string, prefectureCode?: string): JapaneseMunicipality[]
The municipalities a Japanese name could mean: several when the name is shared (府中市 is in Tokyo and in Hiroshima). A town or village may be named without its district (当別町).
findMunicipalitiesByName("府中市").map((one) => one.code + " " + one.romaji)
// ["13206 Fuchu-shi","34208 Fuchu-shi"]
function findMunicipalitiesByRomaji
findMunicipalitiesByRomaji(text: string, prefectureCode?: string): JapaneseMunicipality[]
The municipalities a romaji name could mean, written with or without macrons and designators (Chiyoda-ku, Chiyoda City, Sapporo-shi Chuo-ku, Chuo-ku, Sapporo). A town written without its district, and then a ward written without its city, are looked up when the full name finds nothing.
findMunicipalitiesByRomaji("Chuo-ku, Sapporo").map((one) => one.name)
// ["札幌市中央区"]
function findMunicipalityByCode
findMunicipalityByCode(code: string): JapaneseMunicipality | null
The municipality with a JIS X 0402 code, a designated city's own code included.
findMunicipalityByCode("13101")?.name
// "千代田区"
function findPrefecture
findPrefecture(text: string): JapanesePrefecture | null
The prefecture a name, reading, romaji spelling or JIS code refers to: 東京都, 東京, トウキョウト, Tokyo, Osaka Prefecture, 13.
findPrefecture("Osaka Prefecture")?.name
// "大阪府"
function formatJapanese
formatJapanese(address: ParsedAddress, options?: JapaneseFormattingOptions): string
Writes a Japanese address in Japanese order, as an envelope is addressed: 〒100-0005, then 東京都千代田区丸の内1-2-3, then サンプルビル5階501号室. The prefecture and municipality are always in kanji, from the tables. A town or building parsed from romaji keeps its romaji, set off by spaces so the scripts do not run together. Kyoto's street directions are written before the town.
formatJapanese(parseLocation("〒100-0005 東京都千代田区丸の内1-2-3"), { blockStyle: "markers", multiline: false })
// "〒100-0005 東京都千代田区丸の内1丁目2番3号"
function formatJapaneseEnglish
formatJapaneseEnglish(address: ParsedAddress, options?: JapaneseEnglishFormattingOptions): string
Writes a Japanese address in English order, as a form from abroad expects: building, room, block, town, municipality, prefecture, postal code, Japan. The prefecture and municipality are always romaji, from the tables; the town and building are written as they were parsed, since the tables hold no romaji for towns.
formatJapaneseEnglish(parseLocation("1-2-3 Marunouchi, Chiyoda-ku, Tokyo 100-0005"))
// "1-2-3 Marunouchi, Chiyoda-ku, Tokyo 100-0005, Japan"
function getPostalPrefixesForPrefecture
getPostalPrefixesForPrefecture(prefecture: string): string[]
The three-digit postal prefixes a prefecture's codes begin with, the reverse of getPrefectureFromJapanesePostalCode. A prefix on a border is listed under the prefecture most of its codes belong to.
getPostalPrefixesForPrefecture("沖縄県")
// ["900","901","902","903","904","905","906","907"]
function getPrefectureFromJapanesePostalCode
getPrefectureFromJapanesePostalCode(postalCode: string): string | null
The prefecture a Japanese postal code delivers to, from Japan Post's data: by its first three digits, and for the codes on the far side of a prefix that straddles a border, by the whole code.
getPrefectureFromJapanesePostalCode("530-0001")
// "27"
type JapaneseAddressFields
interface JapaneseAddressFields {
postalCode?: string; // 〒 code as NNN-NNNN
prefecture?: string; // 東京都
prefectureCode?: string; // JIS code: "13"
prefectureRomaji?: string; // Tokyo
municipality?: string; // 千代田区
municipalityCode?: string; // JIS code: "13101"
municipalityRomaji?: string; // Chiyoda-ku
streetDirections?: string; // Kyoto's street directions before the town (通り名): 寺町通御池上る
town?: string; // 丸の内 (大字・町名), without the chome
chome?: string; // 丁目: "1"
ban?: string; // 番 (番地): "2"
go?: string; // 号: "3"
block?: string; // The numbered block as one string: "1-2-3"
building?: string; // サンプルビル
floor?: string; // 階: "5"
room?: string; // 号室: "501"
}
The fields a Japanese address fills on top of the shared ones. Every value is normalised: full-width and kanji numerals become ASCII digits, and the block is split into chome, ban and go whichever way it was written.
parseLocation("〒100-0005 東京都千代田区丸の内1丁目2番3号")?.municipalityCode
// "13101"
type JapaneseEnglishFormattingOptions
interface JapaneseEnglishFormattingOptions {
includeCountry?: boolean; // ", Japan" at the end; default true
includePostalCode?: boolean; // Default true
}
Options for formatJapaneseEnglish.
formatJapaneseEnglish(parseLocation("〒100-0005 東京都千代田区丸の内1-2-3"), { includeCountry: false })
// "1-2-3 丸の内, Chiyoda-ku, Tokyo 100-0005"
type JapaneseFormattingOptions
interface JapaneseFormattingOptions {
blockStyle?: "hyphen" | "markers"; // 1-2-3 (default) or 1丁目2番3号
includePostalCode?: boolean; // 〒100-0005 on its own line; default true
multiline?: boolean; // Lines joined with newlines (default) or one line with spaces
}
Options for formatJapanese.
formatJapanese(parseLocation("〒100-0005 東京都千代田区丸の内1-2-3"), { blockStyle: "markers", includePostalCode: false })
// "東京都千代田区丸の内1丁目2番3号"
type JapaneseMunicipality
interface JapaneseMunicipality {
code: string; // JIS X 0402 code, five digits; the first two are the prefecture's
prefecture: string; // The prefecture's JIS code
name: string; // Official name, with the district for towns and villages in one: 千代田区, 札幌市中央区, 石狩郡当別町
kana: string; // Reading in katakana
romaji: string; // Romaji with designators hyphenated on: Chiyoda-ku, Sapporo-shi Chuo-ku, Ishikari-gun Tobetsu-cho
}
A municipality (市区町村) in the tables: its JIS code, its prefecture's code, its official name with the district for a town or village in one, its reading and its romaji.
findMunicipalityByCode("13101")
// {"code":"13101","prefecture":"13","name":"千代田区","kana":"チヨダク","romaji":"Chiyoda-ku"}
type JapanesePrefecture
interface JapanesePrefecture {
code: string; // JIS X 0401 code, "01" (Hokkaido) to "47" (Okinawa)
name: string; // Official name with its designator: 東京都, 大阪府, 北海道, 愛知県
kana: string; // Reading in katakana: トウキョウト
romaji: string; // Romaji with the designator hyphenated on: Tokyo-to
}
A prefecture (都道府県) in the tables: its JIS code, official name, katakana reading and romaji.
findPrefecture("13")
// {"code":"13","name":"東京都","kana":"トウキョウト","romaji":"Tokyo-to"}
type JapaneseValidation
interface JapaneseValidation {
errors: ValidationError[];
warnings: ValidationError[];
}
What validateJapaneseAddress returns: the errors and the warnings found.
validateJapaneseAddress(parseLocation("東京都大阪市北区梅田1-1")).warnings.map((one) => one.code)
// ["MUNICIPALITY_PREFECTURE_MISMATCH"]
const JP_DESIGNATED_CITIES
JP_DESIGNATED_CITIES: readonly JapaneseMunicipality[]
The twenty designated cities (政令指定都市) as municipalities of their own. Geolonia lists only their wards, but addresses often name the city alone (大阪市, Sapporo), and the city has a JIS code of its own: its wards' codes with the last digit 0 (札幌市 01100).
JP_DESIGNATED_CITIES.length
// 20
const JP_MUNICIPALITIES
JP_MUNICIPALITIES: readonly JapaneseMunicipality[]
Every municipality (市区町村) by JIS X 0402 code, with its prefecture, official name, reading and romaji; a designated city's wards are listed, and the city itself is in JP_DESIGNATED_CITIES. Generated from Geolonia 住所データ (MIT).
JP_MUNICIPALITIES.find((one) => one.code === "13101")?.name
// "千代田区"
const JP_POSTAL_EXCEPTIONS
JP_POSTAL_EXCEPTIONS: Readonly<Record<string, string>>
The postal codes that deliver to another prefecture than the rest of their three-digit prefix, each to that prefecture's JIS code. Generated from Japan Post's KEN_ALL.CSV through jp-postal (MIT).
Object.keys(JP_POSTAL_EXCEPTIONS).length > 0
// true
const JP_POSTAL_PREFIXES
JP_POSTAL_PREFIXES: Readonly<Record<string, string>>
Each three-digit postal prefix, to the JIS code of the prefecture most of its codes deliver to. Generated from Japan Post's KEN_ALL.CSV through jp-postal (MIT).
JP_POSTAL_PREFIXES["530"]
// "27"
const JP_PREFECTURES
JP_PREFECTURES: readonly JapanesePrefecture[]
The 47 prefectures in JIS X 0401 order, each with its code, official name, katakana reading and romaji. Generated from Geolonia 住所データ (MIT).
JP_PREFECTURES.length
// 47
function kanjiNumeralsToDigits
kanjiNumeralsToDigits(text: string): string
Turns the kanji numerals that stand for block, floor or room numbers into digits: 一丁目二番三号 becomes 1丁目2番3号. A numeral that is part of a name stays: 北一条西, 三番町, 麻布十番, 二階堂.
kanjiNumeralsToDigits("二丁目十五番")
// "2丁目15番"
function looksJapanese
looksJapanese(text: string): boolean
Whether a text is a Japanese address: in Japanese script, ending with Japan, or naming a prefecture beside a Japanese postal code, a romaji designator (-ku, -shi) or a municipality written with an English word (Chiyoda City). A US address that only mentions a Japanese name (100 Tokyo Ave) does not count.
looksJapanese("1-2-3 Marunouchi, Chiyoda-ku, Tokyo")
// true
function municipalitiesOf
municipalitiesOf(prefectureCode: string): readonly JapaneseMunicipality[]
Every municipality of a prefecture, the designated cities included.
municipalitiesOf("47").length
// 41
function normalizeJapaneseAddressText
normalizeJapaneseAddressText(text: string): string
Makes the text of a Japanese address uniform, as the parser reads it: widths folded, spaces tidied, numerals as digits, and 1の2の3, 1-2-3 or any other dash written 1-2-3. The postal mark 〒 is kept.
normalizeJapaneseAddressText("〒100-0005 東京都千代田区丸の内一丁目二番三号")
// "〒100-0005 東京都千代田区丸の内1丁目2番3号"
type ParsedAddress
interface ParsedAddress extends JapaneseAddressFields, AustralianAddressFields, FrenchAddressFields, UKAddressFields {
city?: string; // City name, or the municipality in Japan; APO, FPO or DPO in a military address
compartment?: string; // Compartment on a Canadian rural route (the 10 in "SITE 6 COMP 10 RR 8")
country?: "CA" | "US" | "JP" | "AU" | "GB" | "GY" | "IM" | "JE" | FrenchPostalCountry | "DE"; // Detected country; AU, GB (with Jersey, Guernsey and the Isle of Man), FR (with Monaco and the overseas collectivities) and DE only from their modules
fraction?: string; // Fractional address number (e.g., 1/2 in "123 1/2 Main St")
generalDelivery?: boolean; // General delivery indicator
highwayContract?: string; // Highway contract route number (the 68 in "HC 68 BOX 23A"); ruralRoute holds "HC 68"
locality?: string; // Sub-city locality (borough, district, neighborhood), or a Puerto Rico urbanization
military?: string; // Military delivery line ("PSC 802 Box 74", "Unit 2050 Box 4190"); state is AA, AE or AP
number?: string; // Street number
place?: string; // Place name (landmark, POI, building, monument, etc.)
plus4?: string; // Extended ZIP+4 code
postalValid?: boolean; // Postal code validation status
postalType?: "zip" | "postal"; // Postal code type (zip or postal)
prefix?: string; // Directional prefix (N, S, E, W, etc.)
rpo?: string; // Retail Postal Outlet (Canada Post) identifier
rr?: string; // Rural Route number (RR/R.R.)
ruralRoute?: string; // Rural route or similar
secUnitNum?: string; // Secondary unit number
secUnitType?: string; // Secondary unit type (apt, suite, etc.)
secondary?: string; // Legacy properties for backward compatibility
site?: string; // Site number on a Canadian rural route (the 6 in "SITE 6 COMP 10 RR 8")
state?: string; // State/Province code; AA, AE or AP for a military address
station?: string; // Station or Succursale identifier (e.g., Station A, Succ. Centre-ville)
street?: string; // Street name
suffix?: string; // Directional suffix
type?: string; // Street type/suffix (St, Ave, Rd, etc.)
unit?: string; // Legacy unit property for backward compatibility
zip?: string; // ZIP or postal code
zipValid?: boolean; // ZIP/postal code format validation (true if format is valid)
}
What parseLocation returns: every part it found, each absent when the address has none. A Japanese address fills its own fields and the shared ones that stand for them: state the prefecture's JIS code, city the municipality, street the town, number the block, zip the postal code.
parseLocation("123 Main St Apt 4, Anytown, NY 12345")
// {"number":"123","secUnitType":"Apartment","secUnitNum":"4","unit":"Apt 4","street":"Main","type":"St","city":"Anytown","state":"NY","zip":"12345","zipValid":true,"country":"US"}
function parseJapaneseAddress
parseJapaneseAddress(text: string, options?: ParseOptions): ParsedAddress | null
Parses a Japanese address, in Japanese script or in romaji, into the Japanese fields and the shared ones. parseLocation calls it for any address that looks Japanese; call it directly to skip the detection.
parseJapaneseAddress("〒100-0005 東京都千代田区丸の内1丁目2番3号 サンプルビル5階501号室")?.block
// "1-2-3"
type ParseOptions
interface ParseOptions {
country?:
| "CA"
| "US"
| "JP"
| "AU"
| "GB"
| "GY"
| "IM"
| "JE"
| FrenchPostalCountry
| "DE"
| "GP"
| "MQ"
| "GF"
| "RE"
| "YT"
| "auto"; // Country to optimize parsing for; JP skips the detection and parses as Japanese; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly CountryModule[]; // Country modules to read beside the US, Canada and Japan: australia from "/au", unitedKingdom from "/gb", france from "/fr", germany from "/de"
normalize?: boolean; // Whether to normalize street types and directions
validatePostalCode?: boolean; // Whether to validate postal/ZIP codes
language?: "auto" | "en" | "fr"; // Language preference for bilingual parsing (Canada)
extractFacilities?: boolean; // Whether to extract facility names
parseParenthetical?: boolean; // Whether to parse parenthetical information
strict?: boolean; // Whether to only extract valid ZIP/postal codes (strict mode) - true: Only extract codes that pass format validation, false (default): Extract all codes but indicate validity with zipValid field
useSnakeCase?: boolean; // Whether to return field names in snake_case format for backward compatibility - true: Return snake_case field names (sec_unit_type, sec_unit_num, etc.), false (default): Return camelCase field names (secUnitType, secUnitNum, etc.)
}
Options for every parser: the country, strict postal codes, snake_case keys and the rest. Every one is optional.
parseLocation("東京都千代田区丸の内1-2-3", { country: "JP", useSnakeCase: true })?.prefecture_code
// "13"
function validateJapaneseAddress
validateJapaneseAddress(address: ParsedAddress, options?: ValidationOptions): JapaneseValidation
Checks a parsed Japanese address against the tables: the postal code's shape, whether any code begins with its first three digits, whether it delivers to the prefecture named, and whether the municipality is a real one in that prefecture.
validateJapaneseAddress(parseLocation("〒530-0001 東京都千代田区丸の内1-2-3")).warnings.map((one) => one.code)
// ["POSTAL_REGION_MISMATCH"]
type ValidationError
interface ValidationError {
field: string; // Field name where error occurred
code: string; // Error code identifier
message: string; // Human-readable error message
severity: "error" | "warning" | "info"; // Severity level of the validation issue
}
One finding of a validator: the field it is about, its code, a message, and how serious it is.
validateAddress("123 Main St, Seattle, NY 98101").warnings[0]
// {"field":"zip","code":"POSTAL_REGION_MISMATCH","message":"ZIP code 98101 belongs to WA, not NY","severity":"warning"}
type ValidationOptions
interface ValidationOptions {
requireStreetNumber?: boolean; // Whether street number is required
requireStreetName?: boolean; // Whether street name is required
requireCity?: boolean; // Whether city is required
requireState?: boolean; // Whether state/province is required
requirePostalCode?: boolean; // Whether postal code is required
allowPOBox?: boolean; // Whether PO Box addresses are allowed
allowRuralRoute?: boolean; // Whether rural route addresses are allowed
allowGeneralDelivery?: boolean; // Whether general delivery addresses are allowed
strictPostalValidation?: boolean; // Whether to use strict postal code validation
country?: ParseOptions["country"]; // Country context for validation rules; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly import("./country-module").CountryModule[]; // Country modules to read beside the US, Canada and Japan
}
Options for the validators: which parts an address must have, which kinds are allowed, and whether a postal code that does not match its region is an error.
validateAddress("123 Main St", { requirePostalCode: true }).errors.map((error) => error.code)
// ["MISSING_POSTAL_CODE"]
@johnmorrisdotca/address-plus/au
AU_CROSS_BORDER_POSTCODES AU_EXTERNAL_TERRITORY_POSTCODES AU_POSTCODE_RANGES AU_STATES AU_STREET_TYPES australia AustralianAddressFields AustralianPostcodeRange AustralianState AustralianStateCode AustraliaPostFormattingOptions compareAustralianAddresses CountryComparison CountryDifference CountryModule CountryValidation expandAustralianStreetType findAustralianState formatAustraliaPost FormattedAddress getPostcodeRangesForAustralianState getStateFromAustralianPostcode getStatesForAustralianPostcode looksAustralian parseAustralianAddress ParsedAddress ParseOptions validateAustralianAddress ValidationError ValidationOptions
const AU_CROSS_BORDER_POSTCODES
AU_CROSS_BORDER_POSTCODES: Readonly<Record<string, readonly AustralianStateCode[]>>
The postcodes whose area lies in more than one state or territory, each with the states it lies in, the one with most of it first. Read from the Australian Bureau of Statistics' Postal Areas (ASGS Edition 3, CC BY 4.0), which approximate Australia Post's postcodes by mesh blocks, so a state with a sliver of a postcode's area is listed too. An address naming any of these states passes the postcode check.
AU_CROSS_BORDER_POSTCODES["2620"]
// ["NSW","ACT"]
const AU_EXTERNAL_TERRITORY_POSTCODES
AU_EXTERNAL_TERRITORY_POSTCODES: readonly string[]
The postcodes the Australian Bureau of Statistics places, wholly or partly, in the Other Territories: Jervis Bay, Norfolk Island, Christmas Island and the Cocos (Keeling) Islands. Which state's code Australia Post writes beside each is not in the open data, so the validator does not judge the state given with one.
AU_EXTERNAL_TERRITORY_POSTCODES
// ["2540","2899","6798","6799"]
const AU_POSTCODE_RANGES
AU_POSTCODE_RANGES: readonly AustralianPostcodeRange[]
The blocks of postcodes Australia Post allocates to each state and territory. A postcode outside every block is not Australian. A few postcodes near a border also serve towns across it (AU_CROSS_BORDER_POSTCODES), and the external territories have postcodes inside a state's block (AU_EXTERNAL_TERRITORY_POSTCODES).
AU_POSTCODE_RANGES.filter((range) => range.state === "ACT").map((range) => `${range.from}-${range.to}`)
// ["0200-0299","2600-2618","2900-2920"]
const AU_STATES
AU_STATES: readonly AustralianState[]
The six states and two territories of Australia, in order of their codes, each with Australia Post's code, the ISO 3166-2 code and its name in English and in Japanese. Copied from kuni when the tables are made.
AU_STATES.map((state) => state.code)
// ["ACT","NSW","NT","QLD","SA","TAS","VIC","WA"]
const AU_STREET_TYPES
AU_STREET_TYPES: Readonly<Record<string, string>>
Australia's street types: each AS4590 abbreviation, in proper case as the parser reports it, with the word it stands for. The parser also reads the word itself, and a few spellings people use (Boulevarde, Crs, Tce).
AU_STREET_TYPES.Pde
// "Parade"
const australia
australia: CountryModule
Australia's module, for parseLocation and validateAddress: pass it in countries, and an address that ends with a state and its postcode (or Australia) is read as Australian; country: "AU" reads any address as one.
parseLocation("3/12 Smith St, Parramatta NSW 2150", { countries: [australia] })?.secUnitNum
// "3"
type AustralianAddressFields
interface AustralianAddressFields {
floorType?: string; // A level or floor: Level, Floor, Ground Floor, Lower Ground Floor, Upper Ground Floor, Basement, Mezzanine
lot?: string; // A lot number where a street number is not yet given: the 12 in "Lot 12 Smith Rd"
}
The fields an Australian address fills beside the shared ones. The shared fields keep their meaning: number is the street number, street and type the street's name and its type (Australia Post's abbreviation, St, Pde, Cres), secUnitType and secUnitNum the unit (Unit 3) or the postal delivery (PO Box 37, Locked Bag 801), city the suburb or town, state the state's code and zip the postcode.
parseAustralianAddress("Level 6, 51 Jacobson St, Brisbane QLD 4000")?.floorType
// "Level"
type AustralianPostcodeRange
interface AustralianPostcodeRange {
state: AustralianStateCode;
from: string; // First postcode of the block: "2000"
to: string; // Last postcode of the block: "2599"
use: "delivery" | "po-box"; // Street delivery, or PO boxes and large-volume receivers (NSW 1000 to 1999, VIC 8000 to 8999, QLD 9000 to 9999)
}
One block of postcodes Australia Post allocates to a state or territory: every postcode from from to to, inclusive, written as four digits.
AU_POSTCODE_RANGES.find((range) => range.state === "TAS")
// {"state":"TAS","from":"7000","to":"7999","use":"delivery"}
type AustralianState
interface AustralianState {
code: AustralianStateCode; // The code on an envelope: VIC
iso: string; // ISO 3166-2: AU-VIC
name: string; // English: Victoria
nameJa: string; // Japanese: ビクトリア州
kind: "state" | "territory"; // The ACT and the NT are territories
}
An Australian state or territory in the tables: Australia Post's code, the ISO 3166-2 code, and its name in English and in Japanese (from kuni, which takes them from Unicode CLDR and Wikidata).
findAustralianState("Victoria")
// {"code":"VIC","iso":"AU-VIC","name":"Victoria","nameJa":"ビクトリア州","kind":"state"}
type AustralianStateCode
type AustralianStateCode = "ACT" | "NSW" | "NT" | "QLD" | "SA" | "TAS" | "VIC" | "WA";
The code of an Australian state or territory, as Australia Post writes it on the last line of an address.
getStateFromAustralianPostcode("3000")
// "VIC"
type AustraliaPostFormattingOptions
interface AustraliaPostFormattingOptions {
unitStyle?: "words" | "slash"; // "UNIT 3 12 SMITH ST" (the default) or "3/12 SMITH ST"
wideSpacing?: boolean; // Two spaces before the state and before the postcode, as Australia Post prefers on a typed label
includeCountry?: boolean; // AUSTRALIA as the last line, for mail from abroad
}
Options for formatAustraliaPost.
formatAustraliaPost(parseAustralianAddress("Unit 3, 12 Smith St, Parramatta NSW 2150"), { unitStyle: "slash" }).lines
// ["3/12 SMITH ST","PARRAMATTA NSW 2150"]
function compareAustralianAddresses
compareAustralianAddresses(first: ParsedAddress, second: ParsedAddress): CountryComparison
Compares two Australian addresses field by field: the unit, level, lot, number, street, suburb, state and postcode. Letter case, punctuation, a street type written out or abbreviated (Street, St) and a state by name or code are not differences.
compareAustralianAddresses(parseAustralianAddress("12 Smith Street, Parramatta NSW 2150"), parseAustralianAddress("14 Smith St, Parramatta New South Wales 2150"))
// {"isSame":false,"differences":[{"field":"number","first":"12","second":"14"}]}
type CountryComparison
interface CountryComparison {
isSame: boolean;
differences: CountryDifference[];
}
What a country module's comparer returns: whether the two addresses are the same delivery point, and every field that differs once both are in the same form (letter case, punctuation, a street type written out or abbreviated).
compareAustralianAddresses(parseAustralianAddress("3/12 Smith Street, Parramatta NSW 2150"), parseAustralianAddress("Unit 3, 12 Smith St, PARRAMATTA NSW 2150")).isSame
// true
type CountryDifference
interface CountryDifference {
field: string;
first?: string;
second?: string;
}
One way two addresses differ, as a country module's comparer reports it: the field, and its value in each address after both were put in the same form.
compareUKAddresses(parseUKAddress("10 High Street, Bath BA1 1AA"), parseUKAddress("12 High St, Bath BA1 1AA")).differences
// [{"field":"number","first":"10","second":"12"}]
type CountryModule
interface CountryModule {
code: string; // The country's ISO 3166-1 code: AU, GB
codes: readonly string[]; // Every country code the module reads; GB also reads Jersey (JE), Guernsey (GY) and the Isle of Man (IM)
name: string; // The country's name in English
detect(address: string): boolean; // Whether the address is surely this country's, with no hint
parse(address: string, options?: ParseOptions): ParsedAddress | null;
validate(address: ParsedAddress, options?: ValidationOptions): CountryValidation;
format(address: ParsedAddress): FormattedAddress;
compare(first: ParsedAddress, second: ParsedAddress): CountryComparison;
}
A country's address module: its codes, how to tell its addresses apart, and its parser, validator, formatter and comparer. Import one from its entry point (australia from @johnmorrisdotca/address-plus/au, unitedKingdom from @johnmorrisdotca/address-plus/gb) and hand it to parseLocation and validateAddress in countries.
australia.codes
// ["AU"]
type CountryValidation
interface CountryValidation {
errors: ValidationError[];
warnings: ValidationError[];
}
What a country module's validator returns: the errors and the warnings it found.
validateAustralianAddress(parseAustralianAddress("1 Main St, Sydney VIC 2000")).warnings.map((one) => one.code)
// ["POSTAL_REGION_MISMATCH"]
function expandAustralianStreetType
expandAustralianStreetType(type: string): string
The word an AS4590 street type stands for: Pde is Parade. Any other text comes back as it is.
expandAustralianStreetType("CRES")
// "Crescent"
function findAustralianState
findAustralianState(text: string): AustralianState | null
Finds an Australian state or territory by its code (VIC, AU-VIC, Vic.) or its name in English or Japanese (Victoria, ビクトリア州), in any letter case.
findAustralianState("n.s.w.")?.name
// "New South Wales"
function formatAustraliaPost
formatAustraliaPost(address: ParsedAddress, options?: AustraliaPostFormattingOptions): FormattedAddress
Writes an Australian address as Australia Post asks: the building's name, then the delivery line (unit and level before the number, the street type abbreviated), then the suburb, state and postcode, both in capitals with no punctuation. A postal delivery (PO BOX 37) takes the delivery line's place.
formatAustraliaPost(parseAustralianAddress("Level 6, 51 Jacobson Street, Brisbane Qld 4000")).lines
// ["LEVEL 6 51 JACOBSON ST","BRISBANE QLD 4000"]
type FormattedAddress
interface FormattedAddress {
lines: string[]; // Individual address lines
singleLine: string; // Single-line representation
deliveryLine?: string; // Street address line
lastLine?: string; // City/state/postal line
country?: string; // Country designation
format:
| "standard"
| "usps"
| "canada-post"
| "international"
| "australia-post"
| "royal-mail"
| "la-poste"
| "deutsche-post"; // Formatting standard used
}
A formatted address: its lines, and the same on one line.
formatUSPS(parseLocation("123 Main St, Anytown, NY 12345"))
// {"lines":["123 MAIN ST","ANYTOWN NY 12345"],"singleLine":"123 Main St, Anytown NY 12345","deliveryLine":"123 Main St","lastLine":"Anytown NY 12345","country":"US","format":"usps"}
function getPostcodeRangesForAustralianState
getPostcodeRangesForAustralianState(state: string): AustralianPostcodeRange[]
The blocks of postcodes Australia Post allocates to a state or territory.
getPostcodeRangesForAustralianState("Victoria").map((range) => `${range.from}-${range.to}`)
// ["3000-3999","8000-8999"]
function getStateFromAustralianPostcode
getStateFromAustralianPostcode(postcode: string): AustralianStateCode | undefined
The state or territory whose block of postcodes a postcode is in. A postcode that also serves a town across a border still gives the state of its block; getStatesForAustralianPostcode gives them all.
getStateFromAustralianPostcode("2620")
// "NSW"
function getStatesForAustralianPostcode
getStatesForAustralianPostcode(postcode: string): AustralianStateCode[]
Every state or territory a postcode serves: its block's state, and for a postcode that crosses a border, the states across it too, the one with most of the postcode's area first.
getStatesForAustralianPostcode("0872")
// ["NT","SA","WA"]
function looksAustralian
looksAustralian(text: string): boolean
Whether an address is surely Australian, with no hint: it ends with Australia, or with a state and an Australian postcode (NSW 2150, Victoria 3000, VIC 2000, whose postcode is Sydney's). WA needs one of Western Australia's postcodes (WA 6000), since WA 9810 is a Washington ZIP code cut short. A postcode alone is not enough: four digits end addresses in many countries.
[looksAustralian("12 Smith St, Parramatta NSW 2150"), looksAustralian("123 Main St, Seattle, WA 9810")]
// [true,false]
function parseAustralianAddress
parseAustralianAddress(text: string, options?: ParseOptions): ParsedAddress | null
Parses an Australian address into its parts, as Australia Post lays one out: the delivery line, then the suburb or town, the state and the postcode. Reads a unit written 3/12, Unit 3/12, Unit 3, 12 or U3 12; a level (Level 6, L6, Ground Floor); a lot (Lot 12); a range of numbers (12-14); a building's name on a line of its own; and the postal deliveries PO Box, GPO Box, Locked Bag, Private Bag, RMB, RSD, RMS, CMB, CMA, CPA, MS and Care PO. The street type is reported as AS4590's abbreviation (St, Pde, Cres). A state is written by its code or its name; a trailing Australia is dropped.
parseAustralianAddress("Unit 3/12 Smith St, Parramatta NSW 2150")
// {"secUnitType":"Unit","secUnitNum":"3","number":"12","street":"Smith","type":"St","city":"Parramatta","state":"NSW","zip":"2150","zipValid":true,"country":"AU"}
type ParsedAddress
interface ParsedAddress extends JapaneseAddressFields, AustralianAddressFields, FrenchAddressFields, UKAddressFields {
city?: string; // City name, or the municipality in Japan; APO, FPO or DPO in a military address
compartment?: string; // Compartment on a Canadian rural route (the 10 in "SITE 6 COMP 10 RR 8")
country?: "CA" | "US" | "JP" | "AU" | "GB" | "GY" | "IM" | "JE" | FrenchPostalCountry | "DE"; // Detected country; AU, GB (with Jersey, Guernsey and the Isle of Man), FR (with Monaco and the overseas collectivities) and DE only from their modules
fraction?: string; // Fractional address number (e.g., 1/2 in "123 1/2 Main St")
generalDelivery?: boolean; // General delivery indicator
highwayContract?: string; // Highway contract route number (the 68 in "HC 68 BOX 23A"); ruralRoute holds "HC 68"
locality?: string; // Sub-city locality (borough, district, neighborhood), or a Puerto Rico urbanization
military?: string; // Military delivery line ("PSC 802 Box 74", "Unit 2050 Box 4190"); state is AA, AE or AP
number?: string; // Street number
place?: string; // Place name (landmark, POI, building, monument, etc.)
plus4?: string; // Extended ZIP+4 code
postalValid?: boolean; // Postal code validation status
postalType?: "zip" | "postal"; // Postal code type (zip or postal)
prefix?: string; // Directional prefix (N, S, E, W, etc.)
rpo?: string; // Retail Postal Outlet (Canada Post) identifier
rr?: string; // Rural Route number (RR/R.R.)
ruralRoute?: string; // Rural route or similar
secUnitNum?: string; // Secondary unit number
secUnitType?: string; // Secondary unit type (apt, suite, etc.)
secondary?: string; // Legacy properties for backward compatibility
site?: string; // Site number on a Canadian rural route (the 6 in "SITE 6 COMP 10 RR 8")
state?: string; // State/Province code; AA, AE or AP for a military address
station?: string; // Station or Succursale identifier (e.g., Station A, Succ. Centre-ville)
street?: string; // Street name
suffix?: string; // Directional suffix
type?: string; // Street type/suffix (St, Ave, Rd, etc.)
unit?: string; // Legacy unit property for backward compatibility
zip?: string; // ZIP or postal code
zipValid?: boolean; // ZIP/postal code format validation (true if format is valid)
}
What parseLocation returns: every part it found, each absent when the address has none. A Japanese address fills its own fields and the shared ones that stand for them: state the prefecture's JIS code, city the municipality, street the town, number the block, zip the postal code.
parseLocation("123 Main St Apt 4, Anytown, NY 12345")
// {"number":"123","secUnitType":"Apartment","secUnitNum":"4","unit":"Apt 4","street":"Main","type":"St","city":"Anytown","state":"NY","zip":"12345","zipValid":true,"country":"US"}
type ParseOptions
interface ParseOptions {
country?:
| "CA"
| "US"
| "JP"
| "AU"
| "GB"
| "GY"
| "IM"
| "JE"
| FrenchPostalCountry
| "DE"
| "GP"
| "MQ"
| "GF"
| "RE"
| "YT"
| "auto"; // Country to optimize parsing for; JP skips the detection and parses as Japanese; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly CountryModule[]; // Country modules to read beside the US, Canada and Japan: australia from "/au", unitedKingdom from "/gb", france from "/fr", germany from "/de"
normalize?: boolean; // Whether to normalize street types and directions
validatePostalCode?: boolean; // Whether to validate postal/ZIP codes
language?: "auto" | "en" | "fr"; // Language preference for bilingual parsing (Canada)
extractFacilities?: boolean; // Whether to extract facility names
parseParenthetical?: boolean; // Whether to parse parenthetical information
strict?: boolean; // Whether to only extract valid ZIP/postal codes (strict mode) - true: Only extract codes that pass format validation, false (default): Extract all codes but indicate validity with zipValid field
useSnakeCase?: boolean; // Whether to return field names in snake_case format for backward compatibility - true: Return snake_case field names (sec_unit_type, sec_unit_num, etc.), false (default): Return camelCase field names (secUnitType, secUnitNum, etc.)
}
Options for every parser: the country, strict postal codes, snake_case keys and the rest. Every one is optional.
parseLocation("東京都千代田区丸の内1-2-3", { country: "JP", useSnakeCase: true })?.prefecture_code
// "13"
function validateAustralianAddress
validateAustralianAddress(address: ParsedAddress, options?: ValidationOptions): CountryValidation
Checks an Australian address against Australia Post's blocks of postcodes: the postcode is four digits, some state's block holds it, and it is the state named, or one it serves across a border (from the ABS's Postal Areas). Also warns when the state, the postcode or the suburb is missing.
validateAustralianAddress(parseAustralianAddress("1 Main St, Sydney VIC 2000")).warnings[0].message
// "Postcode 2000 belongs to NSW, not VIC"
type ValidationError
interface ValidationError {
field: string; // Field name where error occurred
code: string; // Error code identifier
message: string; // Human-readable error message
severity: "error" | "warning" | "info"; // Severity level of the validation issue
}
One finding of a validator: the field it is about, its code, a message, and how serious it is.
validateAddress("123 Main St, Seattle, NY 98101").warnings[0]
// {"field":"zip","code":"POSTAL_REGION_MISMATCH","message":"ZIP code 98101 belongs to WA, not NY","severity":"warning"}
type ValidationOptions
interface ValidationOptions {
requireStreetNumber?: boolean; // Whether street number is required
requireStreetName?: boolean; // Whether street name is required
requireCity?: boolean; // Whether city is required
requireState?: boolean; // Whether state/province is required
requirePostalCode?: boolean; // Whether postal code is required
allowPOBox?: boolean; // Whether PO Box addresses are allowed
allowRuralRoute?: boolean; // Whether rural route addresses are allowed
allowGeneralDelivery?: boolean; // Whether general delivery addresses are allowed
strictPostalValidation?: boolean; // Whether to use strict postal code validation
country?: ParseOptions["country"]; // Country context for validation rules; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly import("./country-module").CountryModule[]; // Country modules to read beside the US, Canada and Japan
}
Options for the validators: which parts an address must have, which kinds are allowed, and whether a postal code that does not match its region is an error.
validateAddress("123 Main St", { requirePostalCode: true }).errors.map((error) => error.code)
// ["MISSING_POSTAL_CODE"]
@johnmorrisdotca/address-plus/gb
compareUKAddresses CountryComparison CountryDifference CountryModule CountryValidation findUKNation formatRoyalMail FormattedAddress GB_DISTRICT_NATIONS GB_NATIONS GB_POSTCODE_AREAS GB_POSTCODE_DISTRICTS GB_THOROUGHFARE_DESCRIPTORS getNationFromUKPostcode getNationsForUKPostcode isValidUKPostcode looksBritish ParsedAddress ParseOptions parseUKAddress parseUKPostcode RoyalMailFormattingOptions UKAddressFields UKNation UKNationCode UKPostcode UKPostcodeArea unitedKingdom validateUKAddress ValidationError ValidationOptions
function compareUKAddresses
compareUKAddresses(first: ParsedAddress, second: ParsedAddress): CountryComparison
Compares two addresses in the United Kingdom field by field: the flat, the building, the number, the thoroughfare, the post town and the postcode. Letter case, punctuation, a descriptor abbreviated (St, Rd) and the postcode's space are not differences. The localities and the county are left out, since Royal Mail needs neither when the postcode is given.
compareUKAddresses(parseUKAddress("10 Downing Street, London SW1A 2AA"), parseUKAddress("10 DOWNING ST, LONDON, SW1A2AA")).isSame
// true
type CountryComparison
interface CountryComparison {
isSame: boolean;
differences: CountryDifference[];
}
What a country module's comparer returns: whether the two addresses are the same delivery point, and every field that differs once both are in the same form (letter case, punctuation, a street type written out or abbreviated).
compareAustralianAddresses(parseAustralianAddress("3/12 Smith Street, Parramatta NSW 2150"), parseAustralianAddress("Unit 3, 12 Smith St, PARRAMATTA NSW 2150")).isSame
// true
type CountryDifference
interface CountryDifference {
field: string;
first?: string;
second?: string;
}
One way two addresses differ, as a country module's comparer reports it: the field, and its value in each address after both were put in the same form.
compareUKAddresses(parseUKAddress("10 High Street, Bath BA1 1AA"), parseUKAddress("12 High St, Bath BA1 1AA")).differences
// [{"field":"number","first":"10","second":"12"}]
type CountryModule
interface CountryModule {
code: string; // The country's ISO 3166-1 code: AU, GB
codes: readonly string[]; // Every country code the module reads; GB also reads Jersey (JE), Guernsey (GY) and the Isle of Man (IM)
name: string; // The country's name in English
detect(address: string): boolean; // Whether the address is surely this country's, with no hint
parse(address: string, options?: ParseOptions): ParsedAddress | null;
validate(address: ParsedAddress, options?: ValidationOptions): CountryValidation;
format(address: ParsedAddress): FormattedAddress;
compare(first: ParsedAddress, second: ParsedAddress): CountryComparison;
}
A country's address module: its codes, how to tell its addresses apart, and its parser, validator, formatter and comparer. Import one from its entry point (australia from @johnmorrisdotca/address-plus/au, unitedKingdom from @johnmorrisdotca/address-plus/gb) and hand it to parseLocation and validateAddress in countries.
australia.codes
// ["AU"]
type CountryValidation
interface CountryValidation {
errors: ValidationError[];
warnings: ValidationError[];
}
What a country module's validator returns: the errors and the warnings it found.
validateAustralianAddress(parseAustralianAddress("1 Main St, Sydney VIC 2000")).warnings.map((one) => one.code)
// ["POSTAL_REGION_MISMATCH"]
function findUKNation
findUKNation(text: string): (typeof GB_NATIONS)[number] | null
Finds a nation of the United Kingdom by its code (SCT, GB-SCT) or its name in English or Japanese.
findUKNation("wales")?.code
// "WLS"
function formatRoyalMail
formatRoyalMail(address: ParsedAddress, options?: RoyalMailFormattingOptions): FormattedAddress
Writes an address in the United Kingdom as Royal Mail asks: the flat or part of the building, the floor, the building's name, the number with the dependent thoroughfare or the thoroughfare, the localities, then the post town and the postcode in capitals, each on its own line. A forces address ends BFPO 105.
formatRoyalMail(parseUKAddress("Flat 2, Rose Court, 14 High St, Kingsbury, London NW9 0AA")).lines
// ["Flat 2","Rose Court","14 High Street","Kingsbury","LONDON","NW9 0AA"]
type FormattedAddress
interface FormattedAddress {
lines: string[]; // Individual address lines
singleLine: string; // Single-line representation
deliveryLine?: string; // Street address line
lastLine?: string; // City/state/postal line
country?: string; // Country designation
format:
| "standard"
| "usps"
| "canada-post"
| "international"
| "australia-post"
| "royal-mail"
| "la-poste"
| "deutsche-post"; // Formatting standard used
}
A formatted address: its lines, and the same on one line.
formatUSPS(parseLocation("123 Main St, Anytown, NY 12345"))
// {"lines":["123 MAIN ST","ANYTOWN NY 12345"],"singleLine":"123 Main St, Anytown NY 12345","deliveryLine":"123 Main St","lastLine":"Anytown NY 12345","country":"US","format":"usps"}
const GB_DISTRICT_NATIONS
GB_DISTRICT_NATIONS: Readonly<Record<string, readonly UKNationCode[]>>
The postcode districts not wholly in the nation of their area, each with the nations its postcodes lie in, the one with most of them first: the districts that cross the borders of Wales and of Scotland, and those wholly across one (CH5 to CH8 are in Wales, though most of the CH area is in England). From Code-Point Open (OGL v3).
[GB_DISTRICT_NATIONS["TD15"], GB_DISTRICT_NATIONS["CH5"]]
// [["ENG","SCT"],["WLS"]]
const GB_NATIONS
GB_NATIONS: readonly UKNation[]
The four nations of the United Kingdom, in order of their codes, each with its ISO 3166-2 code and its name in English and in Japanese. Copied from kuni when the tables are made.
GB_NATIONS.map((nation) => nation.name)
// ["England","Northern Ireland","Scotland","Wales"]
const GB_POSTCODE_AREAS
GB_POSTCODE_AREAS: Readonly<Record<string, UKPostcodeArea>>
Every postcode area Royal Mail uses: the 121 of the United Kingdom, the three Crown Dependencies (GY, IM, JE), and the two that are not places, BF (the British Forces Post Office) and BX (addresses kept for organisations wherever they are). Each with the town it is named for, its country and its nation.
[GB_POSTCODE_AREAS.CF.nation, GB_POSTCODE_AREAS.JE.country]
// ["WLS","JE"]
const GB_POSTCODE_DISTRICTS
GB_POSTCODE_DISTRICTS: Readonly<Record<string, string>>
Every postcode district in Great Britain, by area: the numbered districts as runs, then those with a letter. Read from Ordnance Survey's Code-Point Open (OGL v3; contains Royal Mail data © Royal Mail copyright and database right). Northern Ireland's BT area is not in it, so its districts are not listed.
GB_POSTCODE_DISTRICTS["EC"]
// "1A,1M,1N,1P,1R,1V,1Y,2A,2M,2N,2P,2R,2V,2Y,3A,3M,3N,3P,3R,3V,4A,4M,4N,4P,4R,4V,4Y"
const GB_THOROUGHFARE_DESCRIPTORS
GB_THOROUGHFARE_DESCRIPTORS: Readonly<Record<string, readonly string[]>>
The thoroughfare descriptors the parser takes off the end of a street's name and reports, in full, as type (High Street is street High, type Street), with the abbreviations it reads for each. A street ending in none of them (Kingsway, The Strand) has no type.
GB_THOROUGHFARE_DESCRIPTORS.Road
// ["RD"]
function getNationFromUKPostcode
getNationFromUKPostcode(postcode: string): UKNationCode | undefined
The nation of the United Kingdom a postcode delivers to, by its area, or by its district where the district crosses the border with Wales or with Scotland (the nation most of its postcodes are in).
["CH5 1AA", "BT1 1AA", "EH1 1YZ", "JE2 3AB"].map(getNationFromUKPostcode)
// ["WLS","NIR","SCT",null]
function getNationsForUKPostcode
getNationsForUKPostcode(postcode: string): UKNationCode[]
Every nation a postcode's district delivers to: one for most, two for the districts along the borders of Wales and of Scotland (from Code-Point Open), the nation with most of the district's postcodes first.
getNationsForUKPostcode("SY10 7AA")
// ["ENG","WLS"]
function isValidUKPostcode
isValidUKPostcode(postcode: string): boolean
Whether a postcode follows Royal Mail's grammar: one of the six shapes (M2 5BQ, M34 4AB, CR0 2YR, DN16 9AA, W1A 4ZZ, EC1A 1HQ) with the letters each place allows, or GIR 0AA. Letter case and the space do not matter. Whether the postcode is in use is a different question; parseUKPostcode and the validator also check its area and district.
["EC1A 1BB", "sw1a1aa", "GIR 0AA", "Q1 1AA", "M5V 1A1"].map(isValidUKPostcode)
// [true,true,true,false,false]
function looksBritish
looksBritish(text: string): boolean
Whether an address is surely British, with no hint: it ends with the United Kingdom or one of its nations (or Jersey, Guernsey or the Isle of Man), or it holds a full postcode in Royal Mail's grammar whose area Royal Mail uses, or BFPO and a number. A Canadian postal code never passes: it ends in a digit (M5V 1A1), a British postcode in two letters (W1A 0AX).
[looksBritish("10 Downing Street, London SW1A 2AA"), looksBritish("100 Queen St W, Toronto, ON M5H 2N2")]
// [true,false]
type ParsedAddress
interface ParsedAddress extends JapaneseAddressFields, AustralianAddressFields, FrenchAddressFields, UKAddressFields {
city?: string; // City name, or the municipality in Japan; APO, FPO or DPO in a military address
compartment?: string; // Compartment on a Canadian rural route (the 10 in "SITE 6 COMP 10 RR 8")
country?: "CA" | "US" | "JP" | "AU" | "GB" | "GY" | "IM" | "JE" | FrenchPostalCountry | "DE"; // Detected country; AU, GB (with Jersey, Guernsey and the Isle of Man), FR (with Monaco and the overseas collectivities) and DE only from their modules
fraction?: string; // Fractional address number (e.g., 1/2 in "123 1/2 Main St")
generalDelivery?: boolean; // General delivery indicator
highwayContract?: string; // Highway contract route number (the 68 in "HC 68 BOX 23A"); ruralRoute holds "HC 68"
locality?: string; // Sub-city locality (borough, district, neighborhood), or a Puerto Rico urbanization
military?: string; // Military delivery line ("PSC 802 Box 74", "Unit 2050 Box 4190"); state is AA, AE or AP
number?: string; // Street number
place?: string; // Place name (landmark, POI, building, monument, etc.)
plus4?: string; // Extended ZIP+4 code
postalValid?: boolean; // Postal code validation status
postalType?: "zip" | "postal"; // Postal code type (zip or postal)
prefix?: string; // Directional prefix (N, S, E, W, etc.)
rpo?: string; // Retail Postal Outlet (Canada Post) identifier
rr?: string; // Rural Route number (RR/R.R.)
ruralRoute?: string; // Rural route or similar
secUnitNum?: string; // Secondary unit number
secUnitType?: string; // Secondary unit type (apt, suite, etc.)
secondary?: string; // Legacy properties for backward compatibility
site?: string; // Site number on a Canadian rural route (the 6 in "SITE 6 COMP 10 RR 8")
state?: string; // State/Province code; AA, AE or AP for a military address
station?: string; // Station or Succursale identifier (e.g., Station A, Succ. Centre-ville)
street?: string; // Street name
suffix?: string; // Directional suffix
type?: string; // Street type/suffix (St, Ave, Rd, etc.)
unit?: string; // Legacy unit property for backward compatibility
zip?: string; // ZIP or postal code
zipValid?: boolean; // ZIP/postal code format validation (true if format is valid)
}
What parseLocation returns: every part it found, each absent when the address has none. A Japanese address fills its own fields and the shared ones that stand for them: state the prefecture's JIS code, city the municipality, street the town, number the block, zip the postal code.
parseLocation("123 Main St Apt 4, Anytown, NY 12345")
// {"number":"123","secUnitType":"Apartment","secUnitNum":"4","unit":"Apt 4","street":"Main","type":"St","city":"Anytown","state":"NY","zip":"12345","zipValid":true,"country":"US"}
type ParseOptions
interface ParseOptions {
country?:
| "CA"
| "US"
| "JP"
| "AU"
| "GB"
| "GY"
| "IM"
| "JE"
| FrenchPostalCountry
| "DE"
| "GP"
| "MQ"
| "GF"
| "RE"
| "YT"
| "auto"; // Country to optimize parsing for; JP skips the detection and parses as Japanese; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly CountryModule[]; // Country modules to read beside the US, Canada and Japan: australia from "/au", unitedKingdom from "/gb", france from "/fr", germany from "/de"
normalize?: boolean; // Whether to normalize street types and directions
validatePostalCode?: boolean; // Whether to validate postal/ZIP codes
language?: "auto" | "en" | "fr"; // Language preference for bilingual parsing (Canada)
extractFacilities?: boolean; // Whether to extract facility names
parseParenthetical?: boolean; // Whether to parse parenthetical information
strict?: boolean; // Whether to only extract valid ZIP/postal codes (strict mode) - true: Only extract codes that pass format validation, false (default): Extract all codes but indicate validity with zipValid field
useSnakeCase?: boolean; // Whether to return field names in snake_case format for backward compatibility - true: Return snake_case field names (sec_unit_type, sec_unit_num, etc.), false (default): Return camelCase field names (secUnitType, secUnitNum, etc.)
}
Options for every parser: the country, strict postal codes, snake_case keys and the rest. Every one is optional.
parseLocation("東京都千代田区丸の内1-2-3", { country: "JP", useSnakeCase: true })?.prefecture_code
// "13"
function parseUKAddress
parseUKAddress(text: string, options?: ParseOptions): ParsedAddress | null
Parses an address in the United Kingdom (or Jersey, Guernsey and the Isle of Man, which share Royal Mail's postcodes) into the parts of Royal Mail's Postcode Address File: a flat or unit (secUnitType, secUnitNum) or a named part of a building (subBuilding), a floor, the building's name, the number, a dependent thoroughfare, the thoroughfare (its name in street, its descriptor in full in type), the dependent localities, the post town in city, a county, and the postcode in zip with the nation it delivers to. The postcode is found wherever it is written and normalised to capitals with one space; BFPO 105 is read as a forces address.
parseUKAddress("Flat 14, Ziggurat Building, 60-66 Saffron Hill, London EC1N 8QX")
// {"secUnitType":"Flat","secUnitNum":"14","building":"Ziggurat Building","number":"60-66","street":"Saffron","type":"Hill","city":"London","zip":"EC1N 8QX","zipValid":true,"nation":"ENG","country":"GB"}
function parseUKPostcode
parseUKPostcode(postcode: string): UKPostcode | null
Takes a postcode apart: outward code, inward code, area, district and sector, and says where it delivers: the country (GB, or JE, GY or IM for the Crown Dependencies) and, in the United Kingdom, the nation. A district that crosses a border gives the nation most of its postcodes are in; getNationsForUKPostcode gives them all.
parseUKPostcode("JE2 3AB")
// {"postcode":"JE2 3AB","outward":"JE2","inward":"3AB","area":"JE","district":"JE2","sector":"JE2 3","country":"JE"}
type RoyalMailFormattingOptions
interface RoyalMailFormattingOptions {
includeCounty?: boolean; // Keep a county that was written, on the line after the post town; Royal Mail does not need it
includeCountry?: boolean; // UNITED KINGDOM as the last line, for mail from abroad (or JERSEY, GUERNSEY, ISLE OF MAN)
}
Options for formatRoyalMail.
formatRoyalMail(parseUKAddress("10 Downing Street, London SW1A 2AA"), { includeCountry: true }).lines
// ["10 Downing Street","LONDON","SW1A 2AA","UNITED KINGDOM"]
type UKAddressFields
interface UKAddressFields {
subBuilding?: string; // A part of a building with no number: "Basement Flat", "Stables Flat"
dependentThoroughfare?: string; // A thoroughfare inside another: the "Seastone Cottages" of "1A Seastone Cottages, Station Road"
doubleDependentLocality?: string; // A locality inside the dependent locality, written above it
county?: string; // A county, when one is written; Royal Mail no longer needs it
nation?: UKNationCode; // The nation the postcode delivers to, from the tables
bfpo?: string; // A British Forces Post Office number: the 105 of "BFPO 105"
}
The fields an address in the United Kingdom fills beside the shared ones. The shared fields keep their meaning: number is the building number, street and type the thoroughfare's name and its descriptor in full (Upper and Street, as Royal Mail writes it), secUnitType and secUnitNum a flat or unit (Flat 2) or a PO Box, building the building's name, locality the dependent locality, city the post town and zip the postcode.
parseUKAddress("Flat 2, Rose Court, 14 High Street, Kingsbury, LONDON NW9 0AA")?.locality
// "Kingsbury"
type UKNation
interface UKNation {
code: UKNationCode;
iso: string; // GB-SCT
name: string; // Scotland
nameJa: string; // スコットランド
}
A nation of the United Kingdom in the tables: its code, its ISO 3166-2 code, and its name in English and in Japanese (from kuni, which takes them from Unicode CLDR and Wikidata).
GB_NATIONS.find((nation) => nation.code === "SCT")
// {"code":"SCT","iso":"GB-SCT","name":"Scotland","nameJa":"スコットランド"}
type UKNationCode
type UKNationCode = "ENG" | "NIR" | "SCT" | "WLS";
The code of one of the four nations of the United Kingdom, as ISO 3166-2:GB writes it after GB-.
getNationFromUKPostcode("CF10 1AA")
// "WLS"
type UKPostcode
interface UKPostcode {
postcode: string; // Capitals, one space: EC1A 1BB
outward: string; // EC1A
inward: string; // 1BB
area: string; // EC
district: string; // EC1A, the same as the outward code
sector: string; // EC1A 1
country: "GB" | "GY" | "IM" | "JE";
nation?: UKNationCode; // Absent outside the United Kingdom, and for a non-geographic area (BX, BF)
}
A postcode taken apart: the outward code (area and district) and the inward code (sector and unit), and where it delivers. country is GB for the United Kingdom and JE, GY or IM for Jersey, Guernsey and the Isle of Man, which use Royal Mail's postcodes but are not part of the United Kingdom.
parseUKPostcode("ec1a1bb")
// {"postcode":"EC1A 1BB","outward":"EC1A","inward":"1BB","area":"EC","district":"EC1A","sector":"EC1A 1","country":"GB","nation":"ENG"}
type UKPostcodeArea
interface UKPostcodeArea {
name: string;
country: "GB" | "GY" | "IM" | "JE";
nation?: UKNationCode;
}
A postcode area: the town Royal Mail names it for, the country it delivers to (GB, or JE, GY and IM for the Crown Dependencies), and for the United Kingdom the nation, absent for an area that is not a place (BF, BX).
GB_POSTCODE_AREAS.BT
// {"name":"Northern Ireland","country":"GB","nation":"NIR"}
const unitedKingdom
unitedKingdom: CountryModule
The United Kingdom's module, for parseLocation and validateAddress: pass it in countries, and an address with a British postcode (or ending with the United Kingdom or a nation) is read as British; country: "GB" reads any address as one. It reads Jersey (JE), Guernsey (GY) and the Isle of Man (IM) too.
parseLocation("221B Baker Street, London NW1 6XE", { countries: [unitedKingdom] })?.number
// "221B"
function validateUKAddress
validateUKAddress(address: ParsedAddress, options?: ValidationOptions): CountryValidation
Checks an address in the United Kingdom against Royal Mail's postcode grammar and the tables: the postcode is well formed, Royal Mail uses its area, and in Great Britain Code-Point Open lists its district. Says when a postcode is Jersey's, Guernsey's or the Isle of Man's, which are not part of the UK, and warns when the postcode or the post town is missing.
validateUKAddress(parseUKAddress("1 High Street, London EC9Z 1AA")).warnings.map((one) => one.code)
// ["INVALID_POSTAL_FORMAT"]
type ValidationError
interface ValidationError {
field: string; // Field name where error occurred
code: string; // Error code identifier
message: string; // Human-readable error message
severity: "error" | "warning" | "info"; // Severity level of the validation issue
}
One finding of a validator: the field it is about, its code, a message, and how serious it is.
validateAddress("123 Main St, Seattle, NY 98101").warnings[0]
// {"field":"zip","code":"POSTAL_REGION_MISMATCH","message":"ZIP code 98101 belongs to WA, not NY","severity":"warning"}
type ValidationOptions
interface ValidationOptions {
requireStreetNumber?: boolean; // Whether street number is required
requireStreetName?: boolean; // Whether street name is required
requireCity?: boolean; // Whether city is required
requireState?: boolean; // Whether state/province is required
requirePostalCode?: boolean; // Whether postal code is required
allowPOBox?: boolean; // Whether PO Box addresses are allowed
allowRuralRoute?: boolean; // Whether rural route addresses are allowed
allowGeneralDelivery?: boolean; // Whether general delivery addresses are allowed
strictPostalValidation?: boolean; // Whether to use strict postal code validation
country?: ParseOptions["country"]; // Country context for validation rules; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly import("./country-module").CountryModule[]; // Country modules to read beside the US, Canada and Japan
}
Options for the validators: which parts an address must have, which kinds are allowed, and whether a postal code that does not match its region is an error.
validateAddress("123 Main St", { requirePostalCode: true }).errors.map((error) => error.code)
// ["MISSING_POSTAL_CODE"]
@johnmorrisdotca/address-plus/de
compareGermanAddresses CountryComparison CountryDifference CountryModule CountryValidation DE_BUILDING_WORDS DE_COUNTRY_NAMES DE_STATES DE_STREET_OPENERS DE_STREET_SUFFIXES DE_UNIT_TYPES DeutschePostFormattingOptions findGermanState formatDeutschePost FormattedAddress GermanAddressFields GermanPostcode GermanState GermanStateCode germany getStateFromGermanPostcode isKnownGermanPostcode isValidGermanPostcode looksGerman ParsedAddress parseGermanAddress parseGermanPostcode ParseOptions validateGermanAddress ValidationError ValidationOptions
function compareGermanAddresses
compareGermanAddresses(first: ParsedAddress, second: ParsedAddress): CountryComparison
Compares two addresses in Germany field by field: who it is care of, the building, the flat and floor, the street, the house number, the place and the postcode. Letter case, ä and ae, ß and ss, a street's suffix written Straße, Strasse or Str., and the postcode's spacing are not differences. The Land and the Ortsteil are left out, since the postcode already says the first.
compareGermanAddresses(parseGermanAddress("Müllerstraße 5, 13353 Berlin"), parseGermanAddress("MUELLERSTR. 5, 13353 BERLIN")).isSame
// true
type CountryComparison
interface CountryComparison {
isSame: boolean;
differences: CountryDifference[];
}
What a country module's comparer returns: whether the two addresses are the same delivery point, and every field that differs once both are in the same form (letter case, punctuation, a street type written out or abbreviated).
compareAustralianAddresses(parseAustralianAddress("3/12 Smith Street, Parramatta NSW 2150"), parseAustralianAddress("Unit 3, 12 Smith St, PARRAMATTA NSW 2150")).isSame
// true
type CountryDifference
interface CountryDifference {
field: string;
first?: string;
second?: string;
}
One way two addresses differ, as a country module's comparer reports it: the field, and its value in each address after both were put in the same form.
compareUKAddresses(parseUKAddress("10 High Street, Bath BA1 1AA"), parseUKAddress("12 High St, Bath BA1 1AA")).differences
// [{"field":"number","first":"10","second":"12"}]
type CountryModule
interface CountryModule {
code: string; // The country's ISO 3166-1 code: AU, GB
codes: readonly string[]; // Every country code the module reads; GB also reads Jersey (JE), Guernsey (GY) and the Isle of Man (IM)
name: string; // The country's name in English
detect(address: string): boolean; // Whether the address is surely this country's, with no hint
parse(address: string, options?: ParseOptions): ParsedAddress | null;
validate(address: ParsedAddress, options?: ValidationOptions): CountryValidation;
format(address: ParsedAddress): FormattedAddress;
compare(first: ParsedAddress, second: ParsedAddress): CountryComparison;
}
A country's address module: its codes, how to tell its addresses apart, and its parser, validator, formatter and comparer. Import one from its entry point (australia from @johnmorrisdotca/address-plus/au, unitedKingdom from @johnmorrisdotca/address-plus/gb) and hand it to parseLocation and validateAddress in countries.
australia.codes
// ["AU"]
type CountryValidation
interface CountryValidation {
errors: ValidationError[];
warnings: ValidationError[];
}
What a country module's validator returns: the errors and the warnings it found.
validateAustralianAddress(parseAustralianAddress("1 Main St, Sydney VIC 2000")).warnings.map((one) => one.code)
// ["POSTAL_REGION_MISMATCH"]
const DE_BUILDING_WORDS
DE_BUILDING_WORDS: readonly string[]
The words for a part of a building, which open the line the parser keeps whole as building (Hinterhaus, Haus B, Gebäude 4, Block C), in lower case.
DE_BUILDING_WORDS.slice(0, 3)
// ["hinterhaus","vorderhaus","seitenflügel"]
const DE_COUNTRY_NAMES
DE_COUNTRY_NAMES: readonly string[]
What a country at the end of an address may be called, in capitals with no umlauts: DEUTSCHLAND, GERMANY, ALLEMAGNE, BRD.
DE_COUNTRY_NAMES.includes("DEUTSCHLAND")
// true
const DE_STATES
DE_STATES: readonly GermanState[]
The sixteen Länder of Germany, in order of their codes, each with its ISO 3166-2 code and its name in English and in Japanese. Copied from kuni when the tables are made.
DE_STATES.map((state) => state.code)
// ["BB","BE","BW","BY","HB","HE","HH","MV","NI","NW","RP","SH","SL","SN","ST","TH"]
const DE_STREET_OPENERS
DE_STREET_OPENERS: readonly string[]
The words that begin a street's name with no suffix to end it (Am Markt, An der Weide, Zum alten Hof, Im Winkel), and the adjectives that begin one (Große Bleiche, Alte Dorfstraße), in lower case.
DE_STREET_OPENERS.slice(0, 5)
// ["am","an","auf","im","in"]
const DE_STREET_SUFFIXES
DE_STREET_SUFFIXES: readonly string[]
What a street's name may end with, which makes the word the name ends in a thoroughfare (Hauptstraße, Berliner Str., Kastanienallee), in lower case with their spellings: strasse for straße, str for the abbreviation. The parser reads the street's whole name as written into street; these tell where it ends.
DE_STREET_SUFFIXES.slice(0, 4)
// ["straße","strasse","str","weg"]
const DE_UNIT_TYPES
DE_UNIT_TYPES: Readonly<Record<string, readonly string[]>>
The words for a flat or a room, which the parser reports as secUnitType in full, with the abbreviations it reads for each: Whg. 12 is Wohnung 12.
DE_UNIT_TYPES.Wohnung
// ["whg","wohn","wo"]
type DeutschePostFormattingOptions
interface DeutschePostFormattingOptions {
includeCountry?: boolean; // DEUTSCHLAND as the last line, for mail from abroad
}
Options for formatDeutschePost.
formatDeutschePost(parseGermanAddress("Hauptstr. 12, 10115 Berlin"), { includeCountry: true }).lines
// ["Hauptstr. 12","10115 Berlin","DEUTSCHLAND"]
function findGermanState
findGermanState(text: string): GermanState | null
Finds a Land of Germany by its code (BY, DE-BY) or its name in English, German or Japanese, without regard to letter case or umlauts written as ae, oe, ue.
[findGermanState("Bayern")?.code, findGermanState("thueringen")?.code, findGermanState("Lower Saxony")?.code]
// ["BY","TH","NI"]
function formatDeutschePost
formatDeutschePost(address: ParsedAddress, options?: DeutschePostFormattingOptions): FormattedAddress
Writes an address in Germany as Deutsche Post asks: who it is care of (c/o), the part of the building, the flat and the floor, the street and its house number, then the postcode and the place, each on its own line, with no punctuation at the end of a line. A Postfach is written with its number in pairs (Postfach 12 34 56) and a Packstation with its number.
formatDeutschePost(parseGermanAddress("Hinterhaus, 2. OG, Kastanienallee 4 b, 10435 Berlin")).lines
// ["Hinterhaus","2. OG","Kastanienallee 4B","10435 Berlin"]
type FormattedAddress
interface FormattedAddress {
lines: string[]; // Individual address lines
singleLine: string; // Single-line representation
deliveryLine?: string; // Street address line
lastLine?: string; // City/state/postal line
country?: string; // Country designation
format:
| "standard"
| "usps"
| "canada-post"
| "international"
| "australia-post"
| "royal-mail"
| "la-poste"
| "deutsche-post"; // Formatting standard used
}
A formatted address: its lines, and the same on one line.
formatUSPS(parseLocation("123 Main St, Anytown, NY 12345"))
// {"lines":["123 MAIN ST","ANYTOWN NY 12345"],"singleLine":"123 Main St, Anytown NY 12345","deliveryLine":"123 Main St","lastLine":"Anytown NY 12345","country":"US","format":"usps"}
type GermanAddressFields
interface GermanAddressFields {
careOf?: string; // The "c/o", "bei" or "z. Hd." line: who the address is care of
}
The fields an address in Germany fills beside the shared ones. The shared fields keep their meaning: street is the whole name of the street as written (Hauptstraße, Berliner Str., Am Markt), number the house number with its letter (12a) or range (12-14), secUnitType and secUnitNum a flat (Wohnung 12), a box (Postfach 12 34 56) or a Packstation, floorType and floor a floor (OG and 2), building a wing, a house or a name (Hinterhaus, Haus B), locality the Ortsteil, city the place, state the Land's code and zip the postcode.
parseGermanAddress("c/o Weber, Hauptstraße 12a, 10115 Berlin")?.careOf
// "Weber"
type GermanPostcode
interface GermanPostcode {
postcode: string; // 10115
state?: GermanStateCode; // BE; absent for a postcode that is not in the list
known: boolean; // Whether GeoNames' list has it
}
A postcode taken apart: the Land it is in, and whether GeoNames' list has it.
parseGermanPostcode("10115")
// {"postcode":"10115","state":"BE","known":true}
type GermanState
interface GermanState {
code: GermanStateCode; // BY
iso: string; // DE-BY
name: string; // Bavaria
nameJa: string; // バイエルン自由州
}
A Land of Germany in the tables: its code, its ISO 3166-2 code, and its name in English and in Japanese (from kuni, which takes them from Unicode CLDR and Wikidata).
DE_STATES.find((state) => state.code === "BY")
// {"code":"BY","iso":"DE-BY","name":"Bavaria","nameJa":"バイエルン自由州"}
type GermanStateCode
type GermanStateCode =
"BB" | "BE" | "BW" | "BY" | "HB" | "HE" | "HH" | "MV" | "NI" | "NW" | "RP" | "SH" | "SL" | "SN" | "ST" | "TH";
The code of one of the sixteen Länder of Germany, as ISO 3166-2:DE writes it after DE-.
getStateFromGermanPostcode("80331")
// "BY"
const germany
germany: CountryModule
Germany's module, for parseLocation and validateAddress: pass it in countries, and an address that ends with Germany or Deutschland, or has a German street and its number and a postcode first on its last line, is read as German; country: "DE" reads any address as one.
parseLocation("Hauptstraße 12a, 10115 Berlin", { countries: [germany] })?.number
// "12A"
function getStateFromGermanPostcode
getStateFromGermanPostcode(postcode: string): GermanStateCode | undefined
The Land a postcode is in, by its code: BY for 80331, BE for 10115.
["80331", "10115", "20095", "00000"].map(getStateFromGermanPostcode)
// ["BY","BE","HH",null]
function isKnownGermanPostcode
isKnownGermanPostcode(postcode: string): boolean
Whether GeoNames' list for Germany has a postcode (its places include those of large firms, whose postcodes are their own). The list is GeoNames' (CC BY 4.0), not Deutsche Post's, so a postcode made since it was copied is not in it: an unknown one is a warning for a validator to give, not proof that it does not exist.
["10115", "80331", "00000", "99999"].map(isKnownGermanPostcode)
// [true,true,false,false]
function isValidGermanPostcode
isValidGermanPostcode(postcode: string): boolean
Whether a postcode follows Deutsche Post's shape: five digits. Whether the postcode is in use is a different question; isKnownGermanPostcode asks it, and the validator checks both.
["10115", "1011", "1011a", " 80331 "].map(isValidGermanPostcode)
// [true,false,false,true]
function looksGerman
looksGerman(text: string): boolean
Whether an address is surely German, with no hint: it ends with Germany, Deutschland or a code such as D-10115 before the place, or its last line begins with a postcode and a place (10115 Berlin) while the address has a street that ends in a German suffix (Hauptstraße, Kastanienallee, Am Markt) followed by its number, or a Postfach or a Packstation. A US ZIP code follows its state, a Canadian one ends in a digit and a French street begins with its number and a type of voie, so none of those has that shape.
[looksGerman("Hauptstraße 12, 10115 Berlin"), looksGerman("123 Main St, Seattle, WA 98101")]
// [true,false]
type ParsedAddress
interface ParsedAddress extends JapaneseAddressFields, AustralianAddressFields, FrenchAddressFields, UKAddressFields {
city?: string; // City name, or the municipality in Japan; APO, FPO or DPO in a military address
compartment?: string; // Compartment on a Canadian rural route (the 10 in "SITE 6 COMP 10 RR 8")
country?: "CA" | "US" | "JP" | "AU" | "GB" | "GY" | "IM" | "JE" | FrenchPostalCountry | "DE"; // Detected country; AU, GB (with Jersey, Guernsey and the Isle of Man), FR (with Monaco and the overseas collectivities) and DE only from their modules
fraction?: string; // Fractional address number (e.g., 1/2 in "123 1/2 Main St")
generalDelivery?: boolean; // General delivery indicator
highwayContract?: string; // Highway contract route number (the 68 in "HC 68 BOX 23A"); ruralRoute holds "HC 68"
locality?: string; // Sub-city locality (borough, district, neighborhood), or a Puerto Rico urbanization
military?: string; // Military delivery line ("PSC 802 Box 74", "Unit 2050 Box 4190"); state is AA, AE or AP
number?: string; // Street number
place?: string; // Place name (landmark, POI, building, monument, etc.)
plus4?: string; // Extended ZIP+4 code
postalValid?: boolean; // Postal code validation status
postalType?: "zip" | "postal"; // Postal code type (zip or postal)
prefix?: string; // Directional prefix (N, S, E, W, etc.)
rpo?: string; // Retail Postal Outlet (Canada Post) identifier
rr?: string; // Rural Route number (RR/R.R.)
ruralRoute?: string; // Rural route or similar
secUnitNum?: string; // Secondary unit number
secUnitType?: string; // Secondary unit type (apt, suite, etc.)
secondary?: string; // Legacy properties for backward compatibility
site?: string; // Site number on a Canadian rural route (the 6 in "SITE 6 COMP 10 RR 8")
state?: string; // State/Province code; AA, AE or AP for a military address
station?: string; // Station or Succursale identifier (e.g., Station A, Succ. Centre-ville)
street?: string; // Street name
suffix?: string; // Directional suffix
type?: string; // Street type/suffix (St, Ave, Rd, etc.)
unit?: string; // Legacy unit property for backward compatibility
zip?: string; // ZIP or postal code
zipValid?: boolean; // ZIP/postal code format validation (true if format is valid)
}
What parseLocation returns: every part it found, each absent when the address has none. A Japanese address fills its own fields and the shared ones that stand for them: state the prefecture's JIS code, city the municipality, street the town, number the block, zip the postal code.
parseLocation("123 Main St Apt 4, Anytown, NY 12345")
// {"number":"123","secUnitType":"Apartment","secUnitNum":"4","unit":"Apt 4","street":"Main","type":"St","city":"Anytown","state":"NY","zip":"12345","zipValid":true,"country":"US"}
function parseGermanAddress
parseGermanAddress(text: string, options?: ParseOptions): ParsedAddress | null
Parses an address in Germany into the parts Deutsche Post and DIN 5008 lay out: who it is care of (careOf, from c/o, z. Hd., bei), the part of a building (building: Hinterhaus, Haus B, or a name above the street), a flat (secUnitType and secUnitNum: Wohnung 12), a floor (floorType and floor: Obergeschoss and 2), the street's whole name as written in street (Hauptstraße, Berliner Str., Am Markt), its house number in number (12a, 12-14), a Postfach or Packstation (secUnitType and secUnitNum), the Ortsteil in locality, the place in city, and the postcode in zip with the Land's code in state. The postcode may stand anywhere after the street; D-10115 is read as 10115.
parseGermanAddress("c/o Weber, Hinterhaus, Hauptstraße 12a, 10115 Berlin")
// {"careOf":"Weber","building":"Hinterhaus","street":"Hauptstraße","number":"12A","city":"Berlin","state":"BE","zip":"10115","zipValid":true,"country":"DE"}
function parseGermanPostcode
parseGermanPostcode(postcode: string): GermanPostcode | null
Takes a postcode apart: the Land it is in, from GeoNames' list (a postcode with places in two Länder takes the one most are in), and whether the list has it.
parseGermanPostcode("80331")
// {"postcode":"80331","state":"BY","known":true}
type ParseOptions
interface ParseOptions {
country?:
| "CA"
| "US"
| "JP"
| "AU"
| "GB"
| "GY"
| "IM"
| "JE"
| FrenchPostalCountry
| "DE"
| "GP"
| "MQ"
| "GF"
| "RE"
| "YT"
| "auto"; // Country to optimize parsing for; JP skips the detection and parses as Japanese; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly CountryModule[]; // Country modules to read beside the US, Canada and Japan: australia from "/au", unitedKingdom from "/gb", france from "/fr", germany from "/de"
normalize?: boolean; // Whether to normalize street types and directions
validatePostalCode?: boolean; // Whether to validate postal/ZIP codes
language?: "auto" | "en" | "fr"; // Language preference for bilingual parsing (Canada)
extractFacilities?: boolean; // Whether to extract facility names
parseParenthetical?: boolean; // Whether to parse parenthetical information
strict?: boolean; // Whether to only extract valid ZIP/postal codes (strict mode) - true: Only extract codes that pass format validation, false (default): Extract all codes but indicate validity with zipValid field
useSnakeCase?: boolean; // Whether to return field names in snake_case format for backward compatibility - true: Return snake_case field names (sec_unit_type, sec_unit_num, etc.), false (default): Return camelCase field names (secUnitType, secUnitNum, etc.)
}
Options for every parser: the country, strict postal codes, snake_case keys and the rest. Every one is optional.
parseLocation("東京都千代田区丸の内1-2-3", { country: "JP", useSnakeCase: true })?.prefecture_code
// "13"
function validateGermanAddress
validateGermanAddress(address: ParsedAddress, options?: ValidationOptions): CountryValidation
Checks an address in Germany against Deutsche Post's postcode shape and GeoNames' list: the postcode is five digits and the list has it, and the place and the house number are given. The list is GeoNames' and not Deutsche Post's, so a postcode made since it was copied is flagged as unrecognised.
validateGermanAddress(parseGermanAddress("Hauptstraße 12, 00000 Berlin")).warnings.map((one) => one.code)
// ["UNRECOGNIZED_POSTAL_CODE"]
type ValidationError
interface ValidationError {
field: string; // Field name where error occurred
code: string; // Error code identifier
message: string; // Human-readable error message
severity: "error" | "warning" | "info"; // Severity level of the validation issue
}
One finding of a validator: the field it is about, its code, a message, and how serious it is.
validateAddress("123 Main St, Seattle, NY 98101").warnings[0]
// {"field":"zip","code":"POSTAL_REGION_MISMATCH","message":"ZIP code 98101 belongs to WA, not NY","severity":"warning"}
type ValidationOptions
interface ValidationOptions {
requireStreetNumber?: boolean; // Whether street number is required
requireStreetName?: boolean; // Whether street name is required
requireCity?: boolean; // Whether city is required
requireState?: boolean; // Whether state/province is required
requirePostalCode?: boolean; // Whether postal code is required
allowPOBox?: boolean; // Whether PO Box addresses are allowed
allowRuralRoute?: boolean; // Whether rural route addresses are allowed
allowGeneralDelivery?: boolean; // Whether general delivery addresses are allowed
strictPostalValidation?: boolean; // Whether to use strict postal code validation
country?: ParseOptions["country"]; // Country context for validation rules; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly import("./country-module").CountryModule[]; // Country modules to read beside the US, Canada and Japan
}
Options for the validators: which parts an address must have, which kinds are allowed, and whether a postal code that does not match its region is an error.
validateAddress("123 Main St", { requirePostalCode: true }).errors.map((error) => error.code)
// ["MISSING_POSTAL_CODE"]
@johnmorrisdotca/address-plus/fr
compareFrenchAddresses CountryComparison CountryDifference CountryModule CountryValidation findFrenchDepartment formatLaPoste FormattedAddress FR_BUILDING_WORDS FR_COLLECTIVITIES FR_DEPARTMENTS FR_NUMBER_EXTENSIONS FR_STREET_TYPES FR_UNIT_TYPES france FrenchAddressFields FrenchCollectivity FrenchDepartment FrenchPostalCountry FrenchPostcode getDepartmentFromFrenchPostcode isKnownFrenchPostcode isValidFrenchPostcode LaPosteFormattingOptions looksFrench ParsedAddress parseFrenchAddress parseFrenchPostcode ParseOptions validateFrenchAddress ValidationError ValidationOptions
function compareFrenchAddresses
compareFrenchAddresses(first: ParsedAddress, second: ParsedAddress): CountryComparison
Compares two addresses in France field by field: the delivery point, the building, the number and its extension, the type and name of the street, the lieu-dit, the box, the commune and the postcode. Letter case, accents, hyphens, a type of voie abbreviated (av., bd) and St for Saint are not differences. The CEDEX and the department are left out: the postcode already says them.
compareFrenchAddresses(parseFrenchAddress("12 rue de l'Église, 38000 Saint-Étienne"), parseFrenchAddress("12 R. DE L EGLISE, 38000 ST ETIENNE")).isSame
// true
type CountryComparison
interface CountryComparison {
isSame: boolean;
differences: CountryDifference[];
}
What a country module's comparer returns: whether the two addresses are the same delivery point, and every field that differs once both are in the same form (letter case, punctuation, a street type written out or abbreviated).
compareAustralianAddresses(parseAustralianAddress("3/12 Smith Street, Parramatta NSW 2150"), parseAustralianAddress("Unit 3, 12 Smith St, PARRAMATTA NSW 2150")).isSame
// true
type CountryDifference
interface CountryDifference {
field: string;
first?: string;
second?: string;
}
One way two addresses differ, as a country module's comparer reports it: the field, and its value in each address after both were put in the same form.
compareUKAddresses(parseUKAddress("10 High Street, Bath BA1 1AA"), parseUKAddress("12 High St, Bath BA1 1AA")).differences
// [{"field":"number","first":"10","second":"12"}]
type CountryModule
interface CountryModule {
code: string; // The country's ISO 3166-1 code: AU, GB
codes: readonly string[]; // Every country code the module reads; GB also reads Jersey (JE), Guernsey (GY) and the Isle of Man (IM)
name: string; // The country's name in English
detect(address: string): boolean; // Whether the address is surely this country's, with no hint
parse(address: string, options?: ParseOptions): ParsedAddress | null;
validate(address: ParsedAddress, options?: ValidationOptions): CountryValidation;
format(address: ParsedAddress): FormattedAddress;
compare(first: ParsedAddress, second: ParsedAddress): CountryComparison;
}
A country's address module: its codes, how to tell its addresses apart, and its parser, validator, formatter and comparer. Import one from its entry point (australia from @johnmorrisdotca/address-plus/au, unitedKingdom from @johnmorrisdotca/address-plus/gb) and hand it to parseLocation and validateAddress in countries.
australia.codes
// ["AU"]
type CountryValidation
interface CountryValidation {
errors: ValidationError[];
warnings: ValidationError[];
}
What a country module's validator returns: the errors and the warnings it found.
validateAustralianAddress(parseAustralianAddress("1 Main St, Sydney VIC 2000")).warnings.map((one) => one.code)
// ["POSTAL_REGION_MISMATCH"]
function findFrenchDepartment
findFrenchDepartment(text: string): FrenchDepartment | null
Finds a department of France by its code (75, 2A, 971) or its name (Paris, Haute-Corse), without regard to letter case or accents.
findFrenchDepartment("haute-corse")?.code
// "2B"
function formatLaPoste
formatLaPoste(address: ParsedAddress, options?: LaPosteFormattingOptions): FormattedAddress
Writes an address in France as La Poste's specification asks (SP 8855): the person it is care of, then the line of the apartment, the floor and the staircase, the line of the entrance and the building, the number and street, the lieu-dit and the box, then the postcode, the commune and its CEDEX, each on its own line, in capitals without accents or punctuation. The arrondissement of Paris, Lyon or Marseille is not written when there is a postcode, which carries it (75008 PARIS, as La Poste's own list has it); without one it is written in two digits (PARIS 08).
formatLaPoste(parseFrenchAddress("Apt 12, Résidence Les Lilas, 4 bis av. des Écoles, 31000 Toulouse")).lines
// ["APPARTEMENT 12","RESIDENCE LES LILAS","4 BIS AVENUE DES ECOLES","31000 TOULOUSE"]
type FormattedAddress
interface FormattedAddress {
lines: string[]; // Individual address lines
singleLine: string; // Single-line representation
deliveryLine?: string; // Street address line
lastLine?: string; // City/state/postal line
country?: string; // Country designation
format:
| "standard"
| "usps"
| "canada-post"
| "international"
| "australia-post"
| "royal-mail"
| "la-poste"
| "deutsche-post"; // Formatting standard used
}
A formatted address: its lines, and the same on one line.
formatUSPS(parseLocation("123 Main St, Anytown, NY 12345"))
// {"lines":["123 MAIN ST","ANYTOWN NY 12345"],"singleLine":"123 Main St, Anytown NY 12345","deliveryLine":"123 Main St","lastLine":"Anytown NY 12345","country":"US","format":"usps"}
const FR_BUILDING_WORDS
FR_BUILDING_WORDS: Readonly<Record<string, readonly string[]>>
The words that open the line naming a building, a residence or a zone (La Poste's third line), which the parser keeps whole as building (Résidence Les Lilas, Bâtiment A, Zone industrielle Nord, ZAC des Prés), with their abbreviations.
FR_BUILDING_WORDS.Bâtiment
// ["BAT","BATIMENT","BÂT"]
const FR_COLLECTIVITIES
FR_COLLECTIVITIES: readonly FrenchCollectivity[]
The overseas collectivities that have postcodes in La Poste's base: each with its code, its ISO 3166-1 country code and its name. They are French, and addressed through La Poste, but each has a country code of its own and the Universal Postal Union lists them apart, so an address in one reads as that country's. From INSEE's Code officiel géographique (Licence Ouverte 2.0).
FR_COLLECTIVITIES.map((one) => one.country)
// ["PM","BL","MF","WF","PF","NC"]
const FR_DEPARTMENTS
FR_DEPARTMENTS: readonly FrenchDepartment[]
The 101 departments of France, in order of their codes, each with its code (75, 2A, 971), its name and its region's. The code is how a postcode begins: its first two digits (three overseas), with Corsica's 20 split at 20200. From INSEE's Code officiel géographique (Licence Ouverte 2.0).
FR_DEPARTMENTS.find((department) => department.code === "2A")
// {"code":"2A","name":"Corse-du-Sud","region":"Corse"}
const FR_NUMBER_EXTENSIONS
FR_NUMBER_EXTENSIONS: readonly string[]
The words that may follow the number as its indice de répétition: bis, ter, quater and the rest of the Latin series. A single letter (the B of 12 B rue Hugo) is read as one too, when a type of voie follows it.
FR_NUMBER_EXTENSIONS.slice(0, 3)
// ["bis","ter","quater"]
const FR_STREET_TYPES
FR_STREET_TYPES: Readonly<Record<string, readonly string[]>>
The types of voie the parser takes off the front of a street's name and reports, in full, as type (rue de la Paix is street de la Paix, type Rue), with the abbreviations it reads for each. In French the type comes first. A street with none of them (Le Vieux Pont) has no type.
FR_STREET_TYPES.Boulevard
// ["BD","BLD","BOUL","BVD","BLVD"]
const FR_UNIT_TYPES
FR_UNIT_TYPES: Readonly<Record<string, readonly string[]>>
The types of the part of a building that an address names below the number: an apartment or a door, which the parser reports as secUnitType and secUnitNum, with the abbreviations it reads for each.
FR_UNIT_TYPES.Appartement
// ["APPT","APT","APP","APPART"]
const france
france: CountryModule
France's module, for parseLocation and validateAddress: pass it in countries, and an address that ends with France (or Monaco or an overseas territory), holds CEDEX, or has a French type of voie and a postcode first on its last line is read as French; country: "FR" reads any address as one, and so does the code of an overseas department or collectivity. It reads Monaco (MC) and the collectivities (PM, BL, MF, WF, PF, NC) too, and reports their own codes.
parseLocation("12 bis rue de la Paix, 75002 Paris, France", { countries: [france] })?.numberExtension
// "bis"
type FrenchAddressFields
interface FrenchAddressFields {
numberExtension?: string; // The indice de répétition after the number: bis, ter, quater, or a letter (the B of 12 B)
staircase?: string; // The staircase: the B of "Escalier B"
entrance?: string; // The entrance: the A of "Entrée A"
lieuDit?: string; // A lieu-dit: a named place (a hamlet, a farm) that goes on a line of its own
postalBoxType?: "BP" | "CS" | "TSA"; // A boîte postale, a course spéciale or a tri service arrivée
postalBoxNum?: string; // The number of the box: 123 in "BP 123"
cedex?: string; // The CEDEX the commune line ends with, as La Poste writes it: "CEDEX 09", or "CEDEX" alone
arrondissement?: string; // The arrondissement of Paris, Lyon or Marseille, as a number: 8 for "Paris 8e"
careOf?: string; // The "chez" line: who the address is care of
}
The fields an address in France fills beside the shared ones. The shared fields keep their meaning: number is the street number, street the name of the street without its type and type the type in full (Rue; the name is de la Paix), secUnitType and secUnitNum an apartment or a door, floorType and floor a floor, building the building's or the residence's name, city the commune, state the department's code and zip the postcode.
parseFrenchAddress("12 bis rue de la Paix, 75002 Paris")?.numberExtension
// "bis"
type FrenchCollectivity
interface FrenchCollectivity {
code: string; // 988
country: FrenchPostalCountry; // NC
name: string; // Nouvelle-Calédonie
}
An overseas collectivity of France in the tables: its code (987), the ISO 3166-1 country code an address in it reads as (PF) and its name.
FR_COLLECTIVITIES.find((one) => one.code === "988")
// {"code":"988","country":"NC","name":"Nouvelle-Calédonie"}
type FrenchDepartment
interface FrenchDepartment {
code: string; // 75, 2A, 971
name: string; // Paris
region: string; // Île-de-France
}
A department of France in the tables: its code (75, 2A, 971), its name and its region's, from INSEE's Code officiel géographique.
FR_DEPARTMENTS.find((department) => department.code === "75")
// {"code":"75","name":"Paris","region":"Île-de-France"}
type FrenchPostalCountry
type FrenchPostalCountry = "BL" | "FR" | "MC" | "MF" | "NC" | "PF" | "PM" | "WF";
The country an address in La Poste's base belongs to: FR for France (the metropolis and the overseas departments), MC for Monaco, and the ISO 3166-1 code of an overseas collectivity (PM, BL, MF, WF, PF, NC), which is French but has a country code of its own and is listed apart by the Universal Postal Union.
parseFrenchAddress("Avenue Pouvanaa a Oopa, 98713 Papeete, Polynésie française")?.country
// "PF"
type FrenchPostcode
interface FrenchPostcode {
postcode: string; // 75008
place: string; // 75, 2B, 971, 987, 99
country: FrenchPostalCountry;
known: boolean; // Whether La Poste's base lists it
department?: string; // The department's code, absent for a collectivity and for Monaco
}
A postcode taken apart: the code of the department or territory its number belongs to, the country it delivers to, and whether La Poste's base has it. place is a department's code (75, 2A, 971), a collectivity's (987) or 99 for Monaco.
parseFrenchPostcode("20200")
// {"postcode":"20200","place":"2B","country":"FR","known":true,"department":"2B"}
function getDepartmentFromFrenchPostcode
getDepartmentFromFrenchPostcode(postcode: string): string | undefined
The department a postcode's number belongs to, by its code: 75 for 75008, 2B for 20200, 971 for 97100.
["75008", "20190", "20200", "97400", "98714", "98000"].map(getDepartmentFromFrenchPostcode)
// ["75","2A","2B","974",null,null]
function isKnownFrenchPostcode
isKnownFrenchPostcode(postcode: string): boolean
Whether La Poste's base officielle des codes postaux lists a postcode: France, the overseas departments and collectivities and Monaco. The base is La Poste's (Licence Ouverte 2.0); a postcode created since it was copied is not in it, so an unknown one is a warning for a validator to give, not proof that it does not exist.
["75008", "75099", "98000", "00000"].map(isKnownFrenchPostcode)
// [true,false,true,false]
function isValidFrenchPostcode
isValidFrenchPostcode(postcode: string): boolean
Whether a postcode follows La Poste's shape: five digits. Whether the postcode is in use is a different question; isKnownFrenchPostcode asks it, and the validator checks both.
["75008", "2000", "7500a", " 13001 "].map(isValidFrenchPostcode)
// [true,false,false,true]
type LaPosteFormattingOptions
interface LaPosteFormattingOptions {
includeCountry?: boolean; // FRANCE as the last line, for mail from abroad (or MONACO, or the collectivity's name)
keepAccents?: boolean; // Keep the accents and the hyphens: La Poste reads them, though the norm asks for none
}
Options for formatLaPoste.
formatLaPoste(parseFrenchAddress("12 rue de la Paix, 75002 Paris"), { includeCountry: true }).lines
// ["12 RUE DE LA PAIX","75002 PARIS","FRANCE"]
function looksFrench
looksFrench(text: string): boolean
Whether an address is surely French, with no hint: it ends with France, an overseas department, Monaco or one of the collectivities, or holds CEDEX beside a postcode, or its last line begins with a postcode whose number names a department, a collectivity or Monaco, followed by a commune (75008 Paris), while the address has a French type of voie (rue, avenue, chemin) at the start of a street, a BP, a TSA or a lieu-dit. A US ZIP code follows its state and a Canadian one ends in a digit, so neither is the shape of that last line; a German one has its postcode first too, but its streets end in straße or weg and begin with no French type.
[looksFrench("12 rue de la Paix, 75002 Paris"), looksFrench("123 Main St, Seattle, WA 98101")]
// [true,false]
type ParsedAddress
interface ParsedAddress extends JapaneseAddressFields, AustralianAddressFields, FrenchAddressFields, UKAddressFields {
city?: string; // City name, or the municipality in Japan; APO, FPO or DPO in a military address
compartment?: string; // Compartment on a Canadian rural route (the 10 in "SITE 6 COMP 10 RR 8")
country?: "CA" | "US" | "JP" | "AU" | "GB" | "GY" | "IM" | "JE" | FrenchPostalCountry | "DE"; // Detected country; AU, GB (with Jersey, Guernsey and the Isle of Man), FR (with Monaco and the overseas collectivities) and DE only from their modules
fraction?: string; // Fractional address number (e.g., 1/2 in "123 1/2 Main St")
generalDelivery?: boolean; // General delivery indicator
highwayContract?: string; // Highway contract route number (the 68 in "HC 68 BOX 23A"); ruralRoute holds "HC 68"
locality?: string; // Sub-city locality (borough, district, neighborhood), or a Puerto Rico urbanization
military?: string; // Military delivery line ("PSC 802 Box 74", "Unit 2050 Box 4190"); state is AA, AE or AP
number?: string; // Street number
place?: string; // Place name (landmark, POI, building, monument, etc.)
plus4?: string; // Extended ZIP+4 code
postalValid?: boolean; // Postal code validation status
postalType?: "zip" | "postal"; // Postal code type (zip or postal)
prefix?: string; // Directional prefix (N, S, E, W, etc.)
rpo?: string; // Retail Postal Outlet (Canada Post) identifier
rr?: string; // Rural Route number (RR/R.R.)
ruralRoute?: string; // Rural route or similar
secUnitNum?: string; // Secondary unit number
secUnitType?: string; // Secondary unit type (apt, suite, etc.)
secondary?: string; // Legacy properties for backward compatibility
site?: string; // Site number on a Canadian rural route (the 6 in "SITE 6 COMP 10 RR 8")
state?: string; // State/Province code; AA, AE or AP for a military address
station?: string; // Station or Succursale identifier (e.g., Station A, Succ. Centre-ville)
street?: string; // Street name
suffix?: string; // Directional suffix
type?: string; // Street type/suffix (St, Ave, Rd, etc.)
unit?: string; // Legacy unit property for backward compatibility
zip?: string; // ZIP or postal code
zipValid?: boolean; // ZIP/postal code format validation (true if format is valid)
}
What parseLocation returns: every part it found, each absent when the address has none. A Japanese address fills its own fields and the shared ones that stand for them: state the prefecture's JIS code, city the municipality, street the town, number the block, zip the postal code.
parseLocation("123 Main St Apt 4, Anytown, NY 12345")
// {"number":"123","secUnitType":"Apartment","secUnitNum":"4","unit":"Apt 4","street":"Main","type":"St","city":"Anytown","state":"NY","zip":"12345","zipValid":true,"country":"US"}
function parseFrenchAddress
parseFrenchAddress(text: string, options?: ParseOptions): ParsedAddress | null
Parses an address in France (or Monaco, or an overseas department or collectivity, which are written the same way) into the parts La Poste's norm names: who it is care of (careOf), an apartment or a door (secUnitType, secUnitNum), a floor, a staircase, an entrance, the building or residence (building), the number with its extension (numberExtension: bis, ter, a letter), the type of voie in full in type and its name in street (rue de la Paix is Rue and de la Paix), a lieu-dit, a box (BP 12, CS 30001), the commune in city with its arrondissement and CEDEX, and the postcode in zip with the department's code in state. The postcode may stand anywhere after the street; F-75008 is read as 75008.
parseFrenchAddress("Résidence Les Lilas, 12 bis rue de la Paix, 75002 Paris")
// {"building":"Résidence Les Lilas","number":"12","numberExtension":"bis","type":"Rue","street":"de la Paix","city":"Paris","state":"75","zip":"75002","zipValid":true,"country":"FR"}
function parseFrenchPostcode
parseFrenchPostcode(postcode: string): FrenchPostcode | null
Takes a postcode apart: the code of the department or territory its number belongs to, and the country it delivers to (FR for France and its overseas departments, MC for Monaco, PF, NC and the other collectivities' own codes), and whether La Poste's base lists it. A few postcodes serve a commune across a department's border; the department reported is the one the number names.
parseFrenchPostcode("98714")
// {"postcode":"98714","place":"987","country":"PF","known":true}
type ParseOptions
interface ParseOptions {
country?:
| "CA"
| "US"
| "JP"
| "AU"
| "GB"
| "GY"
| "IM"
| "JE"
| FrenchPostalCountry
| "DE"
| "GP"
| "MQ"
| "GF"
| "RE"
| "YT"
| "auto"; // Country to optimize parsing for; JP skips the detection and parses as Japanese; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly CountryModule[]; // Country modules to read beside the US, Canada and Japan: australia from "/au", unitedKingdom from "/gb", france from "/fr", germany from "/de"
normalize?: boolean; // Whether to normalize street types and directions
validatePostalCode?: boolean; // Whether to validate postal/ZIP codes
language?: "auto" | "en" | "fr"; // Language preference for bilingual parsing (Canada)
extractFacilities?: boolean; // Whether to extract facility names
parseParenthetical?: boolean; // Whether to parse parenthetical information
strict?: boolean; // Whether to only extract valid ZIP/postal codes (strict mode) - true: Only extract codes that pass format validation, false (default): Extract all codes but indicate validity with zipValid field
useSnakeCase?: boolean; // Whether to return field names in snake_case format for backward compatibility - true: Return snake_case field names (sec_unit_type, sec_unit_num, etc.), false (default): Return camelCase field names (secUnitType, secUnitNum, etc.)
}
Options for every parser: the country, strict postal codes, snake_case keys and the rest. Every one is optional.
parseLocation("東京都千代田区丸の内1-2-3", { country: "JP", useSnakeCase: true })?.prefecture_code
// "13"
function validateFrenchAddress
validateFrenchAddress(address: ParsedAddress, options?: ValidationOptions): CountryValidation
Checks an address in France against La Poste's postcode shape and the tables: the postcode is five digits, its number names a department, a collectivity or Monaco, and La Poste's base officielle lists it. Says when a postcode is Monaco's or an overseas collectivity's, which have country codes of their own, and warns when the postcode or the commune is missing.
validateFrenchAddress(parseFrenchAddress("12 rue de la Paix, 75099 Paris")).warnings.map((one) => one.code)
// ["UNRECOGNIZED_POSTAL_CODE"]
type ValidationError
interface ValidationError {
field: string; // Field name where error occurred
code: string; // Error code identifier
message: string; // Human-readable error message
severity: "error" | "warning" | "info"; // Severity level of the validation issue
}
One finding of a validator: the field it is about, its code, a message, and how serious it is.
validateAddress("123 Main St, Seattle, NY 98101").warnings[0]
// {"field":"zip","code":"POSTAL_REGION_MISMATCH","message":"ZIP code 98101 belongs to WA, not NY","severity":"warning"}
type ValidationOptions
interface ValidationOptions {
requireStreetNumber?: boolean; // Whether street number is required
requireStreetName?: boolean; // Whether street name is required
requireCity?: boolean; // Whether city is required
requireState?: boolean; // Whether state/province is required
requirePostalCode?: boolean; // Whether postal code is required
allowPOBox?: boolean; // Whether PO Box addresses are allowed
allowRuralRoute?: boolean; // Whether rural route addresses are allowed
allowGeneralDelivery?: boolean; // Whether general delivery addresses are allowed
strictPostalValidation?: boolean; // Whether to use strict postal code validation
country?: ParseOptions["country"]; // Country context for validation rules; AU, GB, FR, DE and the rest need their module in countries
countries?: readonly import("./country-module").CountryModule[]; // Country modules to read beside the US, Canada and Japan
}
Options for the validators: which parts an address must have, which kinds are allowed, and whether a postal code that does not match its region is an error.
validateAddress("123 Main St", { requirePostalCode: true }).errors.map((error) => error.code)
// ["MISSING_POSTAL_CODE"]