告别千篇一律:Pydantic高级序列化的艺术与实战

告别千篇一律:Pydantic高级序列化的艺术与实战

【免费下载链接】pydantic Data validation using Python type hints 【免费下载链接】pydantic 项目地址: https://gitcode.com/GitHub_Trending/py/pydantic

你是否还在为API返回的JSON格式呆板而烦恼?是否遇到过日期格式不统一、敏感字段泄露或复杂数据类型序列化失败的问题?本文将带你掌握Pydantic序列化的高级技巧,通过5个实战案例,让你的JSON输出既安全又优雅。读完本文,你将学会如何定制字段序列化、处理嵌套模型、动态过滤敏感数据,以及优化复杂数据结构的输出格式。

序列化基础与挑战

Pydantic作为数据验证领域的事实标准,其序列化功能同样强大。默认情况下,Pydantic模型通过model_dump()model_dump_json()方法提供基础的序列化能力,但在实际开发中,我们常常需要更灵活的控制。

Pydantic序列化流程

官方文档中详细介绍了默认序列化行为,包括Python模式和JSON模式的区别。Python模式会递归转换模型为字典,但可能包含非JSON兼容类型;JSON模式则确保输出符合JSON规范。例如:

from pydantic import BaseModel

class Event(BaseModel):
    timestamp: datetime
    participants: set[str]

event = Event(timestamp=datetime(2023, 1, 1), participants={'alice', 'bob'})
print(event.model_dump())           # Python模式:包含datetime和set
print(event.model_dump(mode='json')) # JSON模式:datetime转为ISO字符串,set转为列表

完整序列化基础说明

字段级序列化:精细控制输出

Pydantic提供两种字段序列化方式:Plain Serializer(直接替换默认序列化)和Wrap Serializer(包装默认序列化),分别适用于不同场景。

Plain Serializer:完全自定义

当需要完全控制字段输出格式时,可使用PlainSerializer。例如将列表转换为逗号分隔的字符串:

from typing import Annotated
from pydantic import BaseModel, PlainSerializer

# 定义可复用的序列化类型
CommaSeparatedStr = Annotated[
    list[str], 
    PlainSerializer(lambda x: ','.join(x), return_type=str)
]

class Book(BaseModel):
    title: str
    tags: CommaSeparatedStr

book = Book(title='Python 101', tags=['programming', 'python'])
print(book.model_dump())  # {'title': 'Python 101', 'tags': 'programming,python'}

PlainSerializer实现源码

Wrap Serializer:增强默认行为

当需要在默认序列化基础上添加额外处理时,WrapSerializer是更好的选择。例如为数值字段添加单位:

from typing import Annotated
from pydantic import BaseModel, WrapSerializer, SerializerFunctionWrapHandler

def add_currency(value: float, handler: SerializerFunctionWrapHandler) -> str:
    return f"${handler(value):.2f}"

Price = Annotated[float, WrapSerializer(add_currency)]

class Product(BaseModel):
    name: str
    price: Price

product = Product(name='Laptop', price=999.99)
print(product.model_dump())  # {'name': 'Laptop', 'price': '$999.99'}

WrapSerializer实现源码

装饰器语法:模型内定义

对于模型特定的序列化逻辑,推荐使用@field_serializer装饰器:

from pydantic import BaseModel, field_serializer

class User(BaseModel):
    join_date: datetime
    roles: set[str]
    
    @field_serializer('join_date')
    def serialize_date(self, value: datetime) -> str:
        return value.strftime('%Y-%m-%d')
        
    @field_serializer('roles', mode='wrap')
    def serialize_roles(self, value: set[str], handler: SerializerFunctionWrapHandler) -> list[str]:
        return sorted(handler(value))  # 先调用默认序列化(set转list)再排序

field_serializer装饰器实现

模型级序列化:整体结构重塑

当需要控制整个模型的输出结构时,@model_serializer装饰器允许我们完全重定义序列化行为。

场景1:数据脱敏输出

from pydantic import BaseModel, model_serializer

class User(BaseModel):
    username: str
    email: str
    password: str  # 敏感字段
    
    @model_serializer
    def serialize_model(self) -> dict:
        return {
            'username': self.username,
            'email': self.email.split('@')[0] + '***'  # 隐藏邮箱域名
        }

user = User(username='alice', email='alice@example.com', password='secret')
print(user.model_dump())  # {'username': 'alice', 'email': 'alice***'}

场景2:嵌套结构扁平化

class Address(BaseModel):
    street: str
    city: str
    zipcode: str

class Customer(BaseModel):
    name: str
    address: Address
    
    @model_serializer(mode='wrap')
    def serialize_model(self, handler: SerializerFunctionWrapHandler) -> dict:
        data = handler(self)  # 获取默认序列化结果
        # 扁平化地址字段
        data.update(data.pop('address'))
        return data

customer = Customer(name='Bob', address=Address(street='Main St', city='NY', zipcode='10001'))
print(customer.model_dump())
# {'name': 'Bob', 'street': 'Main St', 'city': 'NY', 'zipcode': '10001'}

模型序列化完整指南

高级技巧:上下文感知与动态调整

Pydantic支持通过context参数传递动态信息,实现同一模型根据不同场景输出不同格式。

示例:多版本API兼容

from pydantic import BaseModel, FieldSerializationInfo, field_serializer

class Product(BaseModel):
    id: int
    name: str
    price: float
    
    @field_serializer('price')
    def serialize_price(self, value: float, info: FieldSerializationInfo) -> float | str:
        # 根据上下文决定是否添加货币符号
        if info.context and info.context.get('api_version') == 'v2':
            return f"${value:.2f}"
        return value

product = Product(id=1, name='Phone', price=999.99)
print(product.model_dump(context={'api_version': 'v1'}))  # {'id': 1, 'name': 'Phone', 'price': 999.99}
print(product.model_dump(context={'api_version': 'v2'}))  # {'id': 1, 'name': 'Phone', 'price': '$999.99'}

序列化上下文使用说明

实战案例:电商订单API优化

让我们综合运用上述技巧,优化一个电商订单的API响应:

from datetime import datetime
from typing import Annotated, set
from pydantic import BaseModel, PlainSerializer, field_serializer, model_serializer

# 1. 定义可复用的序列化类型
Price = Annotated[float, PlainSerializer(lambda x: f"${x:.2f}", return_type=str)]
CommaList = Annotated[set[str], PlainSerializer(lambda x: ','.join(sorted(x)), return_type=str)]

# 2. 子模型定义
class OrderItem(BaseModel):
    product_id: int
    name: str
    quantity: int
    unit_price: Price  # 使用自定义价格类型
    
    @field_serializer('quantity', 'unit_price')
    def add_labels(self, value, info: FieldSerializationInfo):
        if info.field_name == 'quantity':
            return f"{value}件"
        return value  # 价格已由Price类型处理

# 3. 主模型定义
class Order(BaseModel):
    order_id: str
    items: list[OrderItem]
    created_at: datetime
    status: str
    tags: CommaList  # 使用自定义列表类型
    internal_notes: str = ''  # 内部字段,默认不输出
    
    @model_serializer(mode='wrap')
    def serialize_order(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo):
        data = handler(self)
        
        # 4. 根据上下文决定是否包含内部字段
        if info.context and info.context.get('include_internal'):
            data['internal_notes'] = self.internal_notes
        else:
            data.pop('internal_notes', None)
            
        # 5. 添加计算字段
        data['total_amount'] = sum(float(item['unit_price'][1:]) * int(item['quantity'][:-1]) 
                                  for item in data['items'])
        return data

# 使用示例
order = Order(
    order_id='ORD-001',
    items=[
        OrderItem(product_id=1, name='键盘', quantity=2, unit_price=99.99),
        OrderItem(product_id=2, name='鼠标', quantity=1, unit_price=49.99)
    ],
    created_at=datetime(2023, 10, 1),
    status='paid',
    tags={'electronics', 'promotion'},
    internal_notes='客户要求加急发货'
)

# 普通用户视角
print(order.model_dump(mode='json', indent=2))
# 管理员视角(包含内部信息)
print(order.model_dump(context={'include_internal': True}, indent=2))

性能与最佳实践

1.** 优先使用字段级序列化 :相比模型级序列化,字段级序列化性能更好且更易维护 2. 复用序列化逻辑 :通过Annotated创建可复用的序列化类型,如本文的PriceCommaList 3. 注意类型安全 :始终指定return_type,帮助Pydantic进行类型检查 4. 谨慎使用动态排除 **:频繁使用exclude/include参数可能导致API契约不明确,优先通过字段定义控制

性能优化指南

通过本文介绍的Pydantic高级序列化技巧,你可以轻松实现灵活、安全且易于维护的API输出。无论是简单的格式转换还是复杂的动态输出,Pydantic都能提供优雅的解决方案。更多高级用法请参考官方序列化文档

如果你觉得本文对你有帮助,请点赞收藏,关注获取更多Pydantic实战技巧!

【免费下载链接】pydantic Data validation using Python type hints 【免费下载链接】pydantic 项目地址: https://gitcode.com/GitHub_Trending/py/pydantic

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值