TL;DR: Japanese and Korean addresses run largest unit first, use block-and-lot numbering rather than street numbering, and exist in two scripts and two competing systems respectively. Store components in a country-neutral record and render per country and per audience; never bake the Anglophone order into the schema. The parent workflow is international address format standardization.
Block-and-Lot, Not Street-and-Number
The structural difference that defeats a Western parser is that most Japanese addresses do not name a street at all. A location is identified by a district, a block within it, and a building within the block — written as three numbers separated by hyphens.
The consequence for a pipeline is that a “street name” field is the wrong shape for this data. Forcing the district into it and the block triple into a house-number field produces something that renders acceptably and matches nothing, because the components no longer mean what their column names claim.
Korea Has Two Systems in Parallel
South Korea introduced a road-name address system that formally replaced the older lot-number system, and both remain in circulation: official documents use road names, while many residents and older records still use lot numbers. They are not interchangeable, and the same location has a different address in each.
| System | Shape | Status | Handling |
|---|---|---|---|
| road name (도로명) | road name + building number | official since 2014 | preferred for new records and delivery |
| lot number (지번) | district + lot number | legacy, still widely used | accept, store, and map where possible |
Storing which system a record uses is not optional. A pipeline that mixes them produces duplicate customers — the same person under two addresses that no string comparison will ever reconcile — and a deduplication key built from either alone cannot catch it. Where a mapping service is available, resolving both forms and keying on the road-name form is the approach that converges.
Korea’s Two Systems, Same Building
A Renderer, Not a Formatter
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class EastAsianAddress:
country: str # "JP" or "KR"
postcode: str
region: str # prefecture / do
city: str # shi / si
district: str # chō / dong or road name
block: str # "1-2-3" for JP; building number for KR
building: str | None = None
unit: str | None = None
romanised: bool = False # True when the fields hold Latin text
def render_domestic(a: EastAsianAddress) -> str:
"""Local-script line, largest unit first — for domestic delivery."""
parts = [a.postcode, a.region, a.city, a.district, a.block]
parts += [p for p in (a.building, a.unit) if p]
return " ".join(p for p in parts if p)
def render_international(a: EastAsianAddress) -> str:
"""Romanised line, smallest unit first — for an international carrier."""
parts = [a.unit, a.building, a.block, a.district, a.city, a.region, a.postcode]
body = ", ".join(p for p in parts if p)
return f"{body}, {'JAPAN' if a.country == 'JP' else 'SOUTH KOREA'}"
Two renderers over one record is the entire pattern. The record is country-neutral, the order lives in the renderer, and adding a third audience — a label printer with a line-length limit, say — is a new function rather than a schema migration.
Normalisation That These Markets Specifically Need
Full-width digits are the most common issue: 1-9 are distinct code points from 1-9
and appear routinely in text entered with a Japanese input method. NFKC folding converts
them, which is one more reason it is the right form for a match key, as covered in
Unicode and character normalization in Python.
The hyphen in a block triple is the second. At least four characters appear in practice —
the ASCII hyphen, the full-width hyphen-minus, the Japanese katakana-hiragana prolonged
sound mark used incorrectly, and the en dash — and unifying them to ASCII before comparison
is what makes 1-2-3 match 1-2-3.
The third is the optional trailing marker. Japanese addresses sometimes spell out
1丁目2番3号 rather than using the numeric triple, and the two forms are equivalent. A small
substitution pass that converts the spelled form to the numeric one is far simpler than
teaching every downstream comparison about both.
One Record, Two Audiences
Matching Across Scripts
The hardest practical problem in these markets is not rendering but matching: the same address arrives in local script from one channel and romanised from another, and both are correct.
Build the match key from the local-script form wherever it is present, and treat the
romanisation as an alias rather than as a second address. Romanisation is not
deterministic — Tokyo appears as Tokyo, Tôkyô and Toukyou depending on the scheme —
so a key built from it inherits that variation and splits records that a local-script key
would have merged.
For records that arrive romanised only, the pragmatic approach is a secondary key built from the romanised form with macrons stripped and long vowels collapsed, plus the postcode. The postcode does most of the work: it is script-independent, it is present far more often than any other component, and in both markets it narrows the candidate set to a handful of blocks.
Where a mapping service is available, resolving a romanised record back to its local-script form at ingestion is better than either key. It costs one lookup per new address, produces a single canonical representation, and removes the alias handling entirely — which is worth more than the lookup costs on any corpus with a meaningful share of these markets.
Edge Cases and Failure Modes
Romanised and local forms treated as different records. They describe the same place, and a customer who orders once in each will appear twice. Key on the local form where present, and store the romanisation as an alias rather than as a separate address.
Building names as street names. A Japanese building name is a proper noun that behaves
nothing like a street, and putting it in a street field breaks both matching and rendering.
It belongs in its own field, exactly as a BLDG designator does elsewhere.
Korean addresses without a system marker. When it is unclear which system a record uses,
the presence of a road-name suffix such as -ro or -gil is a strong signal; a bare district
plus a number is usually a lot number. Record the inference rather than silently assuming.
Postcode length changes. Korea moved from six-digit to five-digit postcodes in 2015, and historical records still carry the old form. Store the length alongside the value or map old codes forward — comparing a five and a six-digit code as strings will never match.
One more note on storage: keep the country code on the record even when the corpus is single-market today. Every rule on this page is conditional on it, and retrofitting a country column onto a table that assumed one is far more disruptive than carrying an apparently redundant field from the start.
Integration Note
This renderer sits at the output boundary, after parsing and after the country has been
determined. The country determination itself is the harder problem when a record arrives
without one, and the postcode-shape signals in
international address format standardization
are what resolve it: both the Japanese NNN-NNNN form and the Korean five-digit form are
distinctive enough to infer from, which is fortunate given how differently the two are
handled from here on.
The country code is also what selects the normalisation rules, the postcode grammar and the renderer, so it earns its column three times over.
Related
- International Address Format Standardization — the country-neutral record and the ordering problem in general.
- Normalizing International Addresses With libpostal — a parser that already knows these conventions.
- Unicode and Character Normalization in Python — full-width digit folding and script handling.