正则表达式校验美国地址:邮编、州缩写与街道格式全解析
正则表达式校验美国地址
在开发表单系统时,前端即时校验能显著提升用户体验。正则表达式是成本最低、响应最快的校验方案。本文提供一套完整的美国地址字段正则校验规则,覆盖邮编、州缩写、街道地址等核心字段。
邮编 (ZIP Code) 校验
基础 5 位邮编
```javascript
const ZIP_CODE_REGEX = /^d{5}$/;
// 测试
console.log(ZIP_CODE_REGEX.test("90210")); // true
console.log(ZIP_CODE_REGEX.test("9021")); // false
console.log(ZIP_CODE_REGEX.test("902101")); // false
console.log(ZIP_CODE_REGEX.test("90a10")); // false
```
ZIP+4 扩展格式
```javascript
const ZIP_PLUS_4_REGEX = /^d{5}-d{4}$/;
// 允许两种格式
const ZIP_FLEXIBLE_REGEX = /^d{5}(-d{4})?$/;
console.log(ZIP_FLEXIBLE_REGEX.test("90210")); // true
console.log(ZIP_FLEXIBLE_REGEX.test("90210-1234")); // true
console.log(ZIP_FLEXIBLE_REGEX.test("90210-12")); // false
```
Python 实现
```python
import re
ZIP_PATTERNS = {
'strict_5': re.compile(r'^d{5}$'),
'zip_plus_4': re.compile(r'^d{5}-d{4}$'),
'flexible': re.compile(r'^d{5}(-d{4})?$')
}
def validate_zip(zip_code, allow_plus_4=True):
pattern = ZIP_PATTERNS['flexible'] if allow_plus_4 else ZIP_PATTERNS['strict_5']
return bool(pattern.match(zip_code))
批量测试
test_zips = ["90210", "90210-1234", "1234", "90210-12", "9021O"]
for z in test_zips:
print(f"{z}: {validate_zip(z)}")
```
州缩写 (State Code) 校验
美国有 50 个州 + DC + 5 个海外领地,共 56 个有效州代码。
完整州代码列表
```javascript
const VALID_STATES = new Set([
'AL','AK','AZ','AR','CA','CO','CT','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','PA','RI','SC',
'SD','TN','TX','UT','VT','VA','WA','WV','WI','WY',
'DC', // 华盛顿特区
'PR','VI','GU','AS','MP' // 海外领地
]);
function validateState(state) {
return VALID_STATES.has(state.toUpperCase());
}
```
带自动补全的州代码校验
```javascript
const STATE_NAMES = {
'CA': 'California', 'NY': 'New York', 'TX': 'Texas',
'FL': 'Florida', 'IL': 'Illinois', 'PA': 'Pennsylvania'
// ... 完整列表
};
function normalizeState(input) {
const upper = input.trim().toUpperCase();
// 直接匹配缩写
if (VALID_STATES.has(upper)) {
return { valid: true, code: upper, name: STATE_NAMES[upper] };
}
// 尝试匹配全称
const stateEntry = Object.entries(STATE_NAMES)
.find(([code, name]) => name.toLowerCase() === input.toLowerCase());
if (stateEntry) {
return { valid: true, code: stateEntry[0], name: stateEntry[1] };
}
return { valid: false, code: null, name: null };
}
```
街道地址 (Street Address) 校验
基础街道格式
```javascript
// 匹配 "123 Main St" 或 "4567 Oak Avenue"
const STREET_REGEX = /^d+s+[A-Za-z0-9s.-]+(?:s+(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Boulevard|Dr|Drive|Ln|Lane|Ct|Court|Way|Pl|Place|Terrace|Circle|Highway|Hwy|Route|Rte))?$/i;
// 更严格的版本(要求包含街道类型)
const STREET_STRICT_REGEX = /^d+s+[A-Za-z0-9s.-]+s+(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Boulevard|Dr|Drive|Ln|Lane|Ct|Court|Way|Pl|Place|Circle|Hwy|Highway)$/i;
```
公寓/套房号 (Address Line 2)
```javascript
// 匹配 Apt 5B, Suite 100, Unit 2A, #123
const APT_REGEX = /^(?:Apt|Apartment|Suite|Ste|Unit|#|Fl|Floor)s*[A-Za-z0-9-]+$/i;
// 更宽松的版本,允许 PO Box
const ADDRESS_LINE2_REGEX = /^(?:(?:Apt|Apartment|Suite|Ste|Unit|#|Fl|Floor)s*[A-Za-z0-9-]+|P.?O.?s*Boxs*d+)$/i;
```
完整街道校验函数
```python
import re
STREET_TYPES = {
'st': 'Street', 'street': 'Street',
'ave': 'Avenue', 'avenue': 'Avenue',
'blvd': 'Boulevard', 'boulevard': 'Boulevard',
'rd': 'Road', 'road': 'Road',
'dr': 'Drive', 'drive': 'Drive',
'ln': 'Lane', 'lane': 'Lane',
'ct': 'Court', 'court': 'Court',
'way': 'Way', 'pl': 'Place', 'place': 'Place',
'cir': 'Circle', 'circle': 'Circle',
'hwy': 'Highway', 'highway': 'Highway'
}
class AddressValidator:
def __init__(self):
self.zip_pattern = re.compile(r'^d{5}(-d{4})?$')
self.state_pattern = re.compile(r'^[A-Z]{2}$')
self.street_pattern = re.compile(
r'^d+s+[A-Za-z0-9s.-]+(?:s+(?:' +
'|'.join(STREET_TYPES.keys()) +
r'))?$',
re.IGNORECASE
)
def validate_zip(self, zip_code):
return bool(self.zip_pattern.match(zip_code))
def validate_state(self, state):
if not self.state_pattern.match(state):
return False
return state.upper() in self._get_all_state_codes()
def validate_street(self, street):
return bool(self.street_pattern.match(street))
def validate_full_address(self, address):
"""验证完整地址对象"""
errors = []
if not self.validate_street(address.get('street', '')):
errors.append("街道格式不正确")
if not address.get('city'):
errors.append("城市不能为空")
if not self.validate_state(address.get('state', '')):
errors.append("州缩写格式不正确")
if not self.validate_zip(address.get('zip', '')):
errors.append("邮编格式不正确")
return {
"valid": len(errors) == 0,
"errors": errors
}
def _get_all_state_codes(self):
return {
'AL','AK','AZ','AR','CA','CO','CT','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','PA','RI','SC',
'SD','TN','TX','UT','VT','VA','WA','WV','WI','WY','DC'
}
```
城市 (City) 校验
城市名称通常不需要严格的正则校验,但需要一些基本规则:
```javascript
const CITY_REGEX = /^[A-Za-zs.-']{2,50}$/;
function validateCity(city) {
if (!city || city.length < 2) return { valid: false, error: "城市名称太短" };
if (city.length > 50) return { valid: false, error: "城市名称太长" };
if (!CITY_REGEX.test(city)) return { valid: false, error: "城市名称包含非法字符" };
return { valid: true };
}
```
完整表单校验方案
React Hook 实现
```typescript
import { useState, useCallback } from 'react';
interface AddressForm {
street: string;
apartment: string;
city: string;
state: string;
zip: string;
}
interface ValidationResult {
valid: boolean;
errors: Record<string, string>;
}
export function useAddressValidation() {
const [errors, setErrors] = useState<Record<string, string>>({});
const validate = useCallback((form: AddressForm): ValidationResult => {
const newErrors: Record<string, string> = {};
// 街道校验
if (!form.street || form.street.length < 3) {
newErrors.street = "请输入有效的街道地址";
}
// 城市校验
if (!form.city || !/^[A-Za-zs.-']{2,50}$/.test(form.city)) {
newErrors.city = "请输入有效的城市名称";
}
// 州校验
const validStates = new Set(['AL','AK','AZ','AR','CA','CO','CT','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','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY','DC']);
if (!validStates.has(form.state.toUpperCase())) {
newErrors.state = "请输入有效的州缩写(如 CA, NY, TX)";
}
// 邮编校验
if (!/^d{5}(-d{4})?$/.test(form.zip)) {
newErrors.zip = "请输入有效的 ZIP Code(如 90210 或 90210-1234)";
}
setErrors(newErrors);
return { valid: Object.keys(newErrors).length === 0, errors: newErrors };
}, []);
return { validate, errors };
}
```
校验规则速查表
| 字段 | 正则表达式 | 示例 | 说明 | |||
|---|---|---|---|---|---|---|
| 邮编 | `^\d{5}(-\d{4})?$` | 90210, 90210-1234 | 5位或5+4位 | |||
| 州缩写 | `^[A-Z]{2}$` | CA, NY, TX | 两字母大写 | |||
| 街道 | `^\d+\s+[A-Za-z0-9\s\.\-]+` | 123 Main St | 门牌号+街道 | |||
| 公寓号 | `^(Apt | Suite | Unit | #)\s*[A-Za-z0-9\-]+$` | Apt 5B | 可选字段 |
| 城市 | `^[A-Za-z\s\.\-']{2,50}$` | Los Angeles | 2-50字符 |
总结
正则表达式是美国地址前端校验最轻量、最高效的方案。通过本文提供的完整正则集合,你可以快速构建一个可靠的地址表单校验系统。对于生产环境,建议将正则校验作为第一层过滤,再结合 USPS API 进行第二层精确验证。