Address Plusアドレスプラス

@johnmorrisdotca/address-plus 1.6.0 · 6 entry points · 335 exports

@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.

  • dict The dictionary whose keys are matched.
  • capture Whether to wrap the alternatives in a capturing group; default true.
  • Returns The expression, case-insensitive.
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).

  • text The street name.
  • Returns The name capitalized.
capitalizeStreetName("o'brien")
// "O'Brien"

function capitalizeWords

capitalizeWords(text: string): string

Capitalizes the first letter of each word.

  • text The text.
  • Returns The text with each word's first letter in capitals and the rest as written.
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.

  • addressString The address as typed.
  • options What to tidy and the letter case to set (see CleanAddressOptions).
  • Returns The tidied address; the input, trimmed, when it cannot be parsed.
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.

  • addressString The address as typed.
  • options What to tidy and the letter case to set (see CleanAddressOptions).
  • Returns The tidied address, the input, whether anything changed, and a line for each change.
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.

  • address1 The first address, as parseLocation returns it.
  • address2 The second address.
  • options How to compare: which parts to ignore and how close a fuzzy match must be (see AddressComparisonOptions).
  • Returns Whether they are the same place, how strong the match is (exact to none), the similarity score from 0 to 1 and every difference found.
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.

  • address The parsed address, or any object with its zip and state.
  • Returns US or CA, or undefined when nothing says.
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 (当別町).

  • name The municipality's name in Japanese.
  • prefectureCode A prefecture's JIS code, to look in that prefecture only.
  • Returns Every match, an empty array when there is none.
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.

  • text The name in romaji.
  • prefectureCode A prefecture's JIS code, to look in that prefecture only.
  • Returns Every match, an empty array when there is none.
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.

  • code The five-digit code.
  • Returns The municipality, or null for a code the tables do not have.
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.

  • text The name, reading or code; letter case and macrons do not matter.
  • Returns The prefecture, or null when the text names none.
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.

  • address The address, as parseLocation returns it.
  • options How to write it: abbreviated or in full, the letter case, the country (see AddressFormattingOptions).
  • Returns The address as an array of lines and as one line.
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.

  • address The address, as parseLocation returns it.
  • options Canada Post settings (see CanadaPostFormattingOptions).
  • Returns The address as lines and as one line.
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.

  • address The address, as parseLocation returns it for a Japanese address.
  • options The block's style (1-2-3 or 1丁目2番3号), the postal code, and lines or one line (see JapaneseFormattingOptions).
  • Returns The address as text, its lines joined with newlines unless options.multiline is false; an empty string when the address has none of the parts.
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.

  • address The address, as parseLocation returns it for a Japanese address.
  • options Whether to end with the postal code and with Japan (see JapaneseEnglishFormattingOptions).
  • Returns The address as one line, its parts joined by commas.
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.

  • address The address, as parseLocation returns it.
  • options USPS settings (see USPSFormattingOptions).
  • Returns The address as lines and as one line.
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.

  • Returns One map per kind, each from the full word to its abbreviation.
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.

  • address1 The first address, as parseLocation returns it.
  • address2 The second address.
  • options How to compare (see AddressComparisonOptions).
  • Returns The overall score from 0 to 1, the street, city, state and postal scores, and each difference with its kind.
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.

  • prefecture The prefecture, by JIS code, name or romaji, as findPrefecture reads it.
  • Returns The prefixes in ascending order, an empty array for an unknown prefecture.
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.

  • province The province or territory's two-letter code.
  • Returns The prefixes, a letter or, for the territories, three characters; an empty array for an unknown code.
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.

  • postalCode The seven-digit code, with or without its hyphen, in either width.
  • Returns The prefecture's JIS code, or null for a malformed code or one no Japanese code begins like.
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.

  • postalCode The postal code, with or without its space.
  • Returns The two-letter code, or null for a code that is not Canadian.
getProvinceFromPostalCode("H3G 1P1")
// "QC"

function getStateFromZip

getStateFromZip(zip: string | number): StateCode | undefined

The state or territory a US ZIP code is in.

  • zip A five-digit ZIP or a ZIP+4, as a string or a number (a number loses its leading zeros, which are put back).
  • Returns The two-letter code, or undefined for a ZIP no state uses or a malformed one.
getStateFromZip("98101")
// "WA"

function getValidationErrors

getValidationErrors(addressString: string, options?: ValidationOptions): ValidationError[]

The errors validateAddress finds, without the rest of its result.

  • addressString The address as one string.
  • options What to require and how strict to be (see ValidationOptions).
  • Returns The errors, each with its field, code and message; an empty array when there are none.
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.

  • state The state or territory's two-letter code.
  • Returns The prefixes in ascending order, or an empty array for an unknown code.
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.

  • address The text to look at.
  • Returns true when it holds something an address is made of.
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.

  • address1 The first address, as parseLocation returns it.
  • address2 The second address.
  • options How to compare (see AddressComparisonOptions).
  • Returns true when they match, false otherwise.
isSameAddress(parseLocation("東京都千代田区丸の内1丁目2番3号"), parseLocation("東京都千代田区丸の内1-2-3"))
// true

function isValidAddress

isValidAddress(addressString: string, options?: ValidationOptions): boolean

Whether an address passes validateAddress.

  • addressString The address as one string.
  • options What to require and how strict to be (see ValidationOptions).
  • Returns true when it has no errors, false otherwise. Warnings do not make it invalid unless strictPostalValidation turns them into errors.
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: 北一条西, 三番町, 麻布十番, 二階堂.

  • text Japanese text.
  • Returns The text with those numerals as digits.
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.

  • text The text.
  • Returns true when the text should be read as Japanese.
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.

  • prefectureCode The prefecture's JIS code.
  • Returns The municipalities, an empty array for an unknown code.
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.

  • text Japanese text.
  • Returns The text made uniform.
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.

  • input A state or province name, code or abbreviation, in English or French.
  • Returns Its code and country, or null when nothing is close enough.
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.

  • stateName The name in full, in English or French, any letter case.
  • Returns The code in lower case, or undefined for a name that is not a state or province.
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.

  • text The text.
  • Returns The text in lower case, without that punctuation, with single spaces and none at either end.
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.

  • address The address as one string.
  • options How to parse (see ParseOptions).
  • Returns The parts found, or null when nothing can be read as an address.
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.

  • addresses The addresses, one string each.
  • options How to parse every one of them (see ParseOptions).
  • Returns One result per address, in the same order, null where an address could not be read.
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.

  • addresses The addresses, one string each.
  • options How to parse, plus the batch settings (see BatchParseOptions).
  • Returns The results in order (null for a failure), the errors, and the counts and timing.
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.

  • text The street text.
  • Returns The directional (undefined when there is none) and the text that remains.
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.

  • text The address text.
  • Returns The facility (undefined when there is none) and the text that remains.
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.

  • address The address as one string.
  • options How to parse (see ParseOptions).
  • Returns The street and the ZIP code it could find, or null when the text is empty.
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.

  • addresses The addresses, one string each.
  • options How to parse every one of them (see ParseOptions).
  • Returns One result per address, in the same order, null where nothing could be read.
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.

  • addresses The addresses, one string each.
  • options How to parse, plus the batch settings (see BatchParseOptions).
  • Returns The results in order (null for a failure), the errors, and the counts and timing.
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.

  • address The intersection as one string, such as Main St & Pine Ave, Tacoma, WA.
  • options How to parse (see ParseOptions).
  • Returns Both streets with their types and directionals, and the place, or null when the text does not name two streets. A street with no type has type1 or type2 set to an empty string, as in parse-address.
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.

  • addresses The intersections, one string each.
  • options How to parse every one of them (see ParseOptions).
  • Returns One result per intersection, in the same order, null where one could not be read.
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.

  • addresses The intersections, one string each.
  • options How to parse, plus the batch settings (see BatchParseOptions).
  • Returns The results in order (null for a failure), the errors, and the counts and timing.
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.

  • text The address as one string, in either script, with or without 〒 and the postal code.
  • options useSnakeCase gives snake_case keys; the other options are ignored.
  • Returns The parts found, or null when nothing in the text names a place in Japan (no prefecture, municipality or postal code).
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.

  • address The address as one string; commas, line breaks and full-width characters are all read.
  • options How to parse: the country, strict postal codes, snake_case keys and the rest (see ParseOptions).
  • Returns The parts found, or null when the text is empty or holds nothing that can be read as an address. A part that is not in the address is absent from the result, never an empty string.
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.

  • addresses The addresses, one string each.
  • options How to parse every one of them (see ParseOptions).
  • Returns One result per address, in the same order: the parts, or null where an address could not be read.
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.

  • addresses The addresses, one string each.
  • options How to parse, plus the batch settings (see BatchParseOptions).
  • Returns The results in order (null for a failure), the errors with their index and input, and the counts and timing.
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).

  • text The address text.
  • Returns The words in the parentheses (undefined when there are none) and the text without them.
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.

  • text The text, such as Toronto ON M5H 2N2.
  • Returns The code and its ZIP+4 (undefined when there is none), the text that remains, and the country and province the code points to.
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.

  • text The street line.
  • Returns The unit as written, its designator in full and its number (each undefined when there is none), and the text that remains.
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.

  • text The text, such as Anytown NY.
  • Returns The two-letter code (undefined when there is none), the text that remains, and the country it points to.
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.

  • text The street line.
  • Returns The number (undefined when there is none) and the text that remains.
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.

  • text The street text.
  • country US or CA, for the types only one country uses.
  • Returns The type's USPS abbreviation in lower case (undefined when there is none) and the text that remains.
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.

  • result The parsed address or intersection to set the code on.
  • zipCode The code as written.
  • options strict keeps a malformed code out (see ParseOptions).
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.

  • addressString The address as one string.
  • options What to require and how strict to be (see ValidationOptions).
  • Returns Whether it is valid, the confidence and completeness from 0 to 1, every error and warning with its code, suggestions, and the parsed address (null when it could not be parsed).
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.

  • address The address, as parseLocation returns it for a Japanese address.
  • options strictPostalValidation makes the postal findings errors rather than warnings; the municipality findings stay warnings.
  • Returns The errors and the warnings, each with its field, code and message.
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.

  • code The code, with or without the space or the ZIP+4.
  • Returns Whether it is well formed, its type (zip or postal), and the code written the standard way.
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 (当別町).

  • name The municipality's name in Japanese.
  • prefectureCode A prefecture's JIS code, to look in that prefecture only.
  • Returns Every match, an empty array when there is none.
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.

  • text The name in romaji.
  • prefectureCode A prefecture's JIS code, to look in that prefecture only.
  • Returns Every match, an empty array when there is none.
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.

  • code The five-digit code.
  • Returns The municipality, or null for a code the tables do not have.
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.

  • text The name, reading or code; letter case and macrons do not matter.
  • Returns The prefecture, or null when the text names none.
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.

  • address The address, as parseLocation returns it for a Japanese address.
  • options The block's style (1-2-3 or 1丁目2番3号), the postal code, and lines or one line (see JapaneseFormattingOptions).
  • Returns The address as text, its lines joined with newlines unless options.multiline is false; an empty string when the address has none of the parts.
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.

  • address The address, as parseLocation returns it for a Japanese address.
  • options Whether to end with the postal code and with Japan (see JapaneseEnglishFormattingOptions).
  • Returns The address as one line, its parts joined by commas.
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.

  • prefecture The prefecture, by JIS code, name or romaji, as findPrefecture reads it.
  • Returns The prefixes in ascending order, an empty array for an unknown prefecture.
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.

  • postalCode The seven-digit code, with or without its hyphen, in either width.
  • Returns The prefecture's JIS code, or null for a malformed code or one no Japanese code begins like.
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: 北一条西, 三番町, 麻布十番, 二階堂.

  • text Japanese text.
  • Returns The text with those numerals as digits.
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.

  • text The text.
  • Returns true when the text should be read as Japanese.
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.

  • prefectureCode The prefecture's JIS code.
  • Returns The municipalities, an empty array for an unknown code.
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.

  • text Japanese text.
  • Returns The text made uniform.
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.

  • text The address as one string, in either script, with or without 〒 and the postal code.
  • options useSnakeCase gives snake_case keys; the other options are ignored.
  • Returns The parts found, or null when nothing in the text names a place in Japan (no prefecture, municipality or postal code).
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.

  • address The address, as parseLocation returns it for a Japanese address.
  • options strictPostalValidation makes the postal findings errors rather than warnings; the municipality findings stay warnings.
  • Returns The errors and the warnings, each with its field, code and message.
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.

  • first The first address, as parseAustralianAddress returns it.
  • second The second address.
  • Returns Whether they are the same delivery point, and each field that differs.
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.

  • type The abbreviation, in any letter case.
  • Returns The word.
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.

  • text The code or the name.
  • Returns The state, or null when nothing matches.
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.

  • address The address as parseAustralianAddress returns it.
  • options The unit's style, the spacing of the last line, and whether to add AUSTRALIA (see AustraliaPostFormattingOptions).
  • Returns The lines, the same on one line, the delivery line and the last line.
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.

  • state The state's code or name.
  • Returns The blocks, in order; an empty array for something that is not a state.
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.

  • postcode Four digits, with or without spaces around them.
  • Returns The state's code, or undefined when the text is not four digits or no block holds it.
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.

  • postcode Four digits.
  • Returns The states' codes; an empty array when the postcode is not in any block.
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.

  • text The address as one string.
  • Returns true when the address is Australian beyond doubt, false otherwise.
[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.

  • text The address as one string; commas and line breaks both separate its parts.
  • options useSnakeCase gives snake_case keys; the other options are not used.
  • Returns The parts found, with country: "AU", or null when the text is empty or has nothing but a state.
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.

  • address The address as parseAustralianAddress (or parseLocation with the module) returns it.
  • options strictPostalValidation: true makes the postcode findings errors; the rest are not used.
  • Returns The errors and warnings, each with its field, code and message.
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.

  • first The first address, as parseUKAddress returns it.
  • second The second address.
  • Returns Whether they are the same delivery point, and each field that differs.
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.

  • text The code or the name.
  • Returns The nation, or null when nothing matches.
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.

  • address The address as parseUKAddress returns it.
  • options Whether to keep a county and to add the country (see RoyalMailFormattingOptions).
  • Returns The lines, the same on one line, the delivery line (the number and thoroughfare) and the last line.
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).

  • postcode The postcode.
  • Returns ENG, NIR, SCT or WLS; undefined for a postcode outside the United Kingdom, one that is not a place (BFPO), or text that is not a postcode.
["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.

  • postcode The postcode.
  • Returns The nations; an empty array outside the United Kingdom or for text that is not a postcode.
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.

  • postcode The postcode.
  • Returns true when it has a postcode's shape and letters.
["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).

  • text The address as one string.
  • Returns true when the address is British beyond doubt, false otherwise.
[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.

  • text The address as one string; commas and line breaks both separate its parts.
  • options useSnakeCase gives snake_case keys; the other options are not used.
  • Returns The parts found, with country GB (or JE, GY, IM), or null when the text is empty.
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.

  • postcode The postcode, in any letter case, with or without its space.
  • Returns The parts, or null when it does not follow the grammar or its area is not one Royal Mail uses.
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.

  • address The address as parseUKAddress (or parseLocation with the module) returns it.
  • options strictPostalValidation: true makes the postcode findings errors; the rest are not used.
  • Returns The errors and warnings, each with its field, code and message.
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.

  • first The first address, as parseGermanAddress returns it.
  • second The second address.
  • Returns Whether they are the same delivery point, and each field that differs.
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.

  • text The code or the name.
  • Returns The Land, or null when nothing matches.
[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.

  • address The address as parseGermanAddress returns it.
  • options Whether to add the country (see DeutschePostFormattingOptions).
  • Returns The lines, the same on one line, the delivery line (the street and its number) and the last line.
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.

  • postcode The postcode.
  • Returns The Land's code; undefined for a postcode GeoNames' list does not have, or text that is not a postcode.
["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.

  • postcode The postcode.
  • Returns true when the list has it.
["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.

  • postcode The postcode.
  • Returns true when it is five digits.
["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.

  • text The address as one string.
  • Returns true when the address is German beyond doubt, false otherwise.
[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.

  • text The address as one string; commas and line breaks both separate its parts.
  • options useSnakeCase gives snake_case keys; the other options are not used.
  • Returns The parts found, with country DE, or null when the text is empty or holds nothing that makes it an address.
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.

  • postcode The postcode, with or without spaces.
  • Returns The parts, or null when it is not five digits.
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.

  • address The address as parseGermanAddress (or parseLocation with the module) returns it.
  • options strictPostalValidation: true makes the postcode findings errors; the rest are not used.
  • Returns The errors and warnings, each with its field, code and message.
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.

  • first The first address, as parseFrenchAddress returns it.
  • second The second address.
  • Returns Whether they are the same delivery point, and each field that differs.
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.

  • text The code or the name.
  • Returns The department, or null when nothing matches.
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).

  • address The address as parseFrenchAddress returns it.
  • options Whether to add the country, and whether to keep accents (see LaPosteFormattingOptions).
  • Returns The lines, the same on one line, the delivery line (the number and street) and the last line.
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.

  • postcode The postcode.
  • Returns The department's code; undefined for a postcode in a collectivity or Monaco, one that no department has, or text that is not a postcode.
["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.

  • postcode The postcode.
  • Returns true when La Poste's base lists it.
["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.

  • postcode The postcode.
  • Returns true when it is five digits.
["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.

  • text The address as one string.
  • Returns true when the address is French beyond doubt, false otherwise.
[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.

  • text The address as one string; commas and line breaks both separate its parts.
  • options useSnakeCase gives snake_case keys; the other options are not used.
  • Returns The parts found, with country FR (or MC or a collectivity's code), or null when the text is empty or holds nothing that makes it an address.
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.

  • postcode The postcode, with or without spaces.
  • Returns The parts, or null when it is not five digits.
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.

  • address The address as parseFrenchAddress (or parseLocation with the module) returns it.
  • options strictPostalValidation: true makes the postcode findings errors; the rest are not used.
  • Returns The errors and warnings, each with its field, code and message.
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"]