An address parser converts one string into structured fields: line 1, line 2, city, region, postal code, and country. That sounds like splitting on commas until you meet a German address where the postal code precedes the city, a Japanese address written largest-unit-first, or a US line where "St" is part of a city name rather than a street suffix.
This guide describes a parser that is honest about ambiguity. The design rule is simple: parse only what the grammar supports, score what you parsed, and return the original string untouched when confidence is low. A parser that silently returns wrong fields is worse than one that refuses, because the wrong fields get stored and the original is thrown away.
What parsing can and cannot recover
Parsing recovers structure that was present in the text: the postal code, the region code, the city, and the street line, in that order of reliability. It cannot recover information that was never written down. A string with no unit designator does not contain a unit number, and no amount of inference should invent one.
It also cannot confirm that the fields are true. Parsing "123 Main St, Springfield, IL 62701" produces coherent fields, and the address may still be fictional or the city may not match the ZIP. Parsing is a text operation. Verification is a data operation, and the two should never share a function name or a success message.
- Recoverable: postal code, region, city, street line, unit designator
- Not recoverable: a missing unit, a missing country, a wrong city
- Never infer a country from a phone prefix or a language
- Never fill an empty field with a plausible default
A four-stage pipeline
Stage one is normalization: trim, collapse internal whitespace, replace non-breaking spaces, normalize punctuation variants, and decide whether to uppercase. Keep the original untouched for the fallback. Stage two is segmentation: split on commas when they exist, otherwise split on the country grammar. Stage three is classification: label each segment as postal, region, city, street, or unit using the country rule set. Stage four is assignment: map labels to fields, resolve conflicts, and score the result.
Keeping the stages separate makes the failure modes separable. A segmentation bug looks different from a classification bug, and each stage can be unit tested with its own corpus. It also lets you stop early: if the country is unknown, skip classification entirely and return the segments with per-field confidence marked as unknown.
- Normalize without destroying the original
- Segment by comma first, grammar second
- Classify with a country-specific rule set
- Assign, then score, then decide whether to accept
Country-aware field order
Field order is the single biggest source of wrong parses, because the same tokens move around. The United States, Canada, the United Kingdom, and Australia put the postal code after the city. Germany, France, the Netherlands, and Belgium put it before the city. Japan puts it first, before the prefecture. Brazil places the postal code after the state with a separator.
That is why the parser must know the country before it classifies anything. Inferring the country from the string itself is circular: the postal pattern can suggest one, but many patterns overlap. Ask for the country from the form, the locale, or an explicit selector, and mark the parse as low confidence when it is missing rather than guessing from a two-letter token that might be a state or a country.
| Country | Order | Example |
|---|---|---|
| United States | Street, city, ST ZIP | 1420 SW Alder St, Portland, OR 97205 |
| Canada | Street, city, ST A1A 1A1 | 88 Queen St W, Toronto, ON M5H 2M5 |
| Germany | Street, ZIP city | Unter den Linden 77, 10117 Berlin |
| France | Number street, ZIP city | 12 Rue de Rivoli, 75004 Paris |
| Netherlands | Street number, ZIP city | Damrak 1, 1012 LG Amsterdam |
| Japan | ZIP, prefecture, city, street | 100-0001 Tokyo, Chiyoda, Chiyoda 1-1 |
| Brazil | Street, city - ST, ZIP | Av. Paulista 1000, Sao Paulo - SP, 01310-100 |
The ambiguous cases worth handling
Secondary unit designators are the most common ambiguity. "Apt", "Ste", "Unit", "Fl", and "Rm" are reliable when followed by an identifier, but "Unit 5" can also be a street name, and "Building 3" can be the primary address of a campus. A parser should extract a unit only when the designator is followed by a number or a short alphanumeric token and appears after the street number.
Street suffixes are the second trap. A token like "Way", "Court", or "Park" can be a suffix, a street name, or a city. Directionals have the same problem: "N" can be a predirectional, a street name abbreviation, or a region. Use position relative to the house number and the comma structure, and when two readings are both plausible, keep the original segment and lower the confidence instead of choosing.
| Input | Plausible readings | Safe handling |
|---|---|---|
| 12 Court St | Street suffix or part of name | Keep full street line |
| Unit 5, 100 Main St | Unit before street | Assign unit, keep street |
| 100 N Park Ave | Directional plus suffix | Keep the street tokens in order |
| Springfield Rd, Springfield | Suffix in city name | Prefer the comma boundary |
| Tokyo, Chiyoda 1-1 | City and block | Use the country grammar |
| Apt 4B 12 Main | Unit without a comma | Extract then re-score |
Confidence scoring and thresholds
Score each field, then the record. A field is high confidence when it matched an anchored country pattern and its position agreed with the expected order. It is medium when the pattern matched but the position was inferred, and low when it was assigned by elimination. A record is only high confidence when every required field is high and there are no unplaced segments.
Set a threshold and enforce it. Above the threshold, accept and store. Below it, keep the original string and flag the record for review rather than writing a guess into the structured columns. Expose the score and the reasons in the API response, because the reviewer needs to know which field failed, not just that something did.
| Field | High confidence signal | Low confidence signal |
|---|---|---|
| Postal | Anchored match, correct position | Pattern matched but moved |
| Region | Known code for the country | Two-letter token with no match |
| City | Left of the postal or right of the street | Assigned by elimination |
| Street | Contains a house number and a name | Bare name with no number |
| Unit | Designator plus identifier after street | Designator inside the street name |
Error states you should return
A parse result should have explicit outcomes, not a boolean. Missing country means the grammar cannot run. Missing postal code matters in countries that have one and is normal in Hong Kong and the UAE. An unplaceable segment means the comma structure and the grammar disagree, and the record needs human eyes.
Give each outcome a stable code so callers can branch without parsing prose. Return the normalized input, the original input, the field values with confidence, and the list of unplaced segments. That payload lets a UI highlight the exact token that failed instead of showing a generic error at the top of the form.
- MISSING_COUNTRY: no grammar available, do not guess
- MISSING_POSTAL: an error where the country has codes, normal where it does not
- UNPLACED_SEGMENT: a comma-delimited part matched no field
- AMBIGUOUS_UNIT: a unit designator with more than one reading
- REGION_MISMATCH: region code not valid for the supplied country
Building a golden corpus and mutating it
Start with a small hand-verified corpus, one to three addresses per country, each with the expected field values written by someone who reads that country's mail. Twenty to forty cases is enough to catch structural regressions and small enough to review in a pull request. Store it as data, not as assertions buried in test code.
Then mutate each case mechanically: remove commas, change case, collapse double spaces, insert a non-breaking space, swap two lines, abbreviate a suffix, and append a unit. The expected output for a mutation is not always identical, but it should stay within a defined tolerance, and the confidence should fall rather than the fields silently changing. Track precision and recall per field so a fix for city does not quietly break postal.
| Mutation | Example change | Expected behaviour |
|---|---|---|
| Drop commas | 1420 SW Alder St Portland OR 97205 | Same fields, lower confidence |
| Lowercase | portland, or 97205 | Same fields after normalization |
| Double space | Portland, OR 97205 | Same fields, whitespace collapsed |
| NBSP between city and state | Portland, OR with U+00A0 | Same fields, NBSP normalized |
| Swap city and region | OR Portland 97205 | Flagged, not silently swapped |
| Truncate the street | 1420 SW Alder | Street kept, city still found |
When not to parse at all
Do not parse freeform notes, delivery instructions, or anything a human wrote as prose. "Leave at the side gate, second house after the blue one" has no fields, and extracting tokens from it produces confident garbage. Keep it as a text blob attached to the order.
Do not parse scripts your rule set does not cover. A Japanese address in Kanji needs a Japanese grammar; running a Latin-script tokenizer over it will split on the wrong boundaries and lose the prefecture. Do not parse addresses that have no street number or that use a building name as the primary locator, which is common in parts of Asia and in campus addresses. In all of these cases the correct output is the original string plus a clear "not parsed" status.
- Delivery instructions: store as text, never as fields
- Unsupported scripts: return the original with an explicit status
- Building-name addresses: do not force a house number
- PO boxes and rural routes: parse with a country-specific rule, not a generic one
- Anything already structured: validate it instead of re-parsing it
Questions
- No. It recovers structure that is present in the text and scores its own confidence. Fields that were never written down cannot be recovered, and parsing never proves an address is real.
- Field order differs: Germany and Japan put the postal code before the city, Japan puts it first, and the US puts it last. Without a country rule set, the same tokens get assigned to the wrong fields.
- Keep the original string, return the partial fields with their scores, and flag the record for review. Writing a guess into structured columns loses the only reliable copy of the input.
- Twenty to forty hand-verified cases, one to three per country, plus mechanical mutations. That is enough to catch structural regressions without creating a corpus nobody reviews.
- The address parser tool exposes the field model and the format rules. It does not verify deliverability and does not claim that parsed fields describe a real location.
Can an address parser guarantee correct fields?+
Why does country matter before parsing?+
What should a parser do with low confidence?+
How many test addresses do I need?+
Does the site parse addresses for me?+
Examples are synthetic. Do not use a generated address for identity checks, account registration, or delivery.