You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FastAPI中让phonenumbers.PhoneNumber作为Pydantic模型字段支持JSON转换?

解决FastAPI中phonenumbers.PhoneNumber的类型安全序列化问题

问题根源

直接将phonenumbers.PhoneNumber作为Pydantic模型字段类型时,即使开启arbitrary_types_allowed,FastAPI在处理响应模型时仍会判定该类型不是合法的Pydantic字段类型,导致序列化报错。

解决方案(无需修改PhoneNumber类)

方案一:Pydantic v2 推荐写法

通过Annotated整合输入验证、输出序列化逻辑,创建可被Pydantic和FastAPI识别的自定义类型:

import phonenumbers
from pydantic import BaseModel, BeforeValidator, PlainSerializer, WithJsonSchema
from typing import Annotated
from fastapi import FastAPI

# 输入验证:将字符串解析为PhoneNumber对象
def parse_phone_number(value: str) -> phonenumbers.PhoneNumber:
    try:
        return phonenumbers.parse(value, None)
    except phonenumbers.NumberParseException as e:
        raise ValueError(f"无效手机号:{e}") from e

# 输出序列化:将PhoneNumber对象转为E164格式字符串
def serialize_phone_number(phone: phonenumbers.PhoneNumber) -> str:
    return phonenumbers.format_number(phone, phonenumbers.PhoneNumberFormat.E164)

# 自定义类型:整合验证、序列化和JSON Schema定义
PhoneNumber = Annotated[
    phonenumbers.PhoneNumber,
    BeforeValidator(parse_phone_number),
    PlainSerializer(serialize_phone_number, return_type=str),
    WithJsonSchema({"type": "string"}, mode="serialization"),
    WithJsonSchema({"type": "string"}, mode="validation")
]

# 定义响应/请求模型
class MyModel(BaseModel):
    phone_number: PhoneNumber

# FastAPI接口示例
app = FastAPI()

@app.post("/submit-phone", response_model=MyModel)
def submit_phone(data: MyModel):
    return data

方案二:Pydantic v1 兼容写法

通过自定义字段类让Pydantic识别PhoneNumber,配合json_encoders处理序列化:

import phonenumbers
from pydantic import BaseModel, ModelField
from typing import Any
from fastapi import FastAPI

# 自定义Pydantic字段类
class PhoneNumberField(str):
    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, value: Any, field: ModelField) -> phonenumbers.PhoneNumber:
        if isinstance(value, phonenumbers.PhoneNumber):
            return value
        if not isinstance(value, str):
            raise ValueError("手机号必须为字符串格式")
        try:
            return phonenumbers.parse(value, None)
        except phonenumbers.NumberParseException as e:
            raise ValueError(f"无效手机号:{e}") from e

    @classmethod
    def __modify_schema__(cls, field_schema):
        field_schema.update(type="string")

# 定义模型
class MyModel(BaseModel):
    phone_number: PhoneNumberField

    class Config:
        json_encoders = {
            phonenumbers.PhoneNumber: lambda p: phonenumbers.format_number(
                p, phonenumbers.PhoneNumberFormat.E164
            )
        }

# FastAPI接口示例
app = FastAPI()

@app.post("/submit-phone", response_model=MyModel)
def submit_phone(data: MyModel):
    return data

核心原理

通过将phonenumbers.PhoneNumber包装为Pydantic可识别的自定义类型,既保留了类型安全的校验,又让FastAPI能正确处理输入解析和输出序列化,避免了直接使用原生类型导致的兼容性问题。

内容的提问来源于stack exchange,提问作者tibbe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 16:15:32