如何在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
相关产品推荐
相关产品推荐

