如何从JSON反序列化含计算字段列表嵌套模型的Pydantic类?
问题描述
我定义了代表银行账户的Pydantic模型,交易记录存储在隐藏字段_transactions的列表中,通过带校验逻辑的计算字段transactions进行访问。序列化时可正常将账户及交易序列化为JSON,但使用model_validate_json反序列化时,transactions字段为空,嵌套的Customer模型无异常。如何实现计算字段序列化的可逆性?
简化模型:
class Account(BaseModel): id: UUID = Field(default_factory=uuid4) owner: Customer _transactions: List[Transaction] = list() @fields.computed_field() @property def transactions(self) -> List[Transaction]: return self._transactions @transactions.setter def transactions(self, list_: List[Transaction]) -> None: # DO SOME CHECKS self._transactions = sorted_list class Transaction(BaseModel): id: UUID = Field(default_factory=uuid4) date: datetime.date = Field(frozen=False) amount: PositiveFloat = Field(frozen=False) class Customer(BaseModel): id: UUID = Field(default_factory=uuid4) name: str = Field(frozen=False)
序列化结果示例:
{ "id":"1eeed00b-3bd7-48fa-ab6a-6979618ef723", "owner":{ "id":"0cad242d-ad03-492b-9c6b-86f0b75f9c00", "name":"Bob" }, "transactions":[ {"id":"42738f1d-e998-4add-94b8-713afe25b525","date":"2012-01-01","amount":1.0} ] }
反序列化异常结果示例:
Account( id=UUID('1eeed00b-3bd7-48fa-ab6a-6979618ef723'), owner=Customer( id=UUID('0cad242d-ad03-492b-9c6b-86f0b75f9c00'), name='Bob' ), transactions=[] )
解决方案
问题核心是:Pydantic的计算字段默认仅参与序列化流程,反序列化时不会自动将JSON中的字段值映射到计算字段的setter方法,因此_transactions始终保持默认的空列表。以下是两种可行的解决方式:
方案一:配置计算字段参与反序列化
在@fields.computed_field()装饰器中明确指定类型、序列化/反序列化别名,让Pydantic将JSON中的transactions字段绑定到计算字段的setter。
修改后的完整模型代码:
from pydantic import BaseModel, Field, fields import uuid from uuid import UUID from datetime import datetime from typing import List class Customer(BaseModel): id: UUID = Field(default_factory=uuid4) name: str = Field(frozen=False) class Transaction(BaseModel): id: UUID = Field(default_factory=uuid4) date: datetime.date = Field(frozen=False) amount: float = Field(frozen=False, gt=0) # 替代PositiveFloat,兼容更广泛 class Account(BaseModel): id: UUID = Field(default_factory=uuid4) owner: Customer _transactions: List[Transaction] = Field(default_factory=list, exclude=True) # 明确排除序列化 @fields.computed_field( return_type=List[Transaction], serialization_alias="transactions", validation_alias="transactions" ) @property def transactions(self) -> List[Transaction]: return self._transactions @transactions.setter def transactions(self, list_: List[Transaction]) -> None: # 自定义校验逻辑示例:按日期排序 sorted_list = sorted(list_, key=lambda t: t.date) # 可添加其他校验,比如金额非负、交易日期合法等 self._transactions = sorted_list
关键说明:
return_type必须显式指定,否则Pydantic无法推断反序列化时的字段类型serialization_alias和validation_alias统一设为transactions,保证序列化和反序列化的字段名一致_transactions添加exclude=True,避免序列化时出现重复字段
方案二:手动触发计算字段赋值(临时方案)
如果无法修改模型定义,可在反序列化后手动解析JSON数据,调用计算字段的setter完成赋值:
import json # 待反序列化的JSON字符串 json_str = ''' { "id":"1eeed00b-3bd7-48fa-ab6a-6979618ef723", "owner":{ "id":"0cad242d-ad03-492b-9c6b-86f0b75f9c00", "name":"Bob" }, "transactions":[ {"id":"42738f1d-e998-4add-94b8-713afe25b525","date":"2012-01-01","amount":1.0} ] } ''' # 1. 先完成基础反序列化 account = Account.model_validate_json(json_str) # 2. 解析JSON数据并手动赋值给计算字段 data = json.loads(json_str) account.transactions = [Transaction.model_validate(tx) for tx in data["transactions"]]
说明:这种方式需要手动处理嵌套模型的校验,灵活性较差,仅适合临时场景。
内容的提问来源于stack exchange,提问作者Richard Riley
相关产品推荐
相关产品推荐

