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

如何从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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:36:02