Pydantic V2中@field_validator未触发,自定义编码解析失败?
Pydantic V2自定义编解码:解码报错"Input should be an object"及Validator未执行的问题解决
核心问题原因
你遇到的问题本质是Pydantic的类型检查优先级高于字段验证器:
- 当你把
ref字段的类型声明为自定义类时,Pydantic默认会期望输入是一个符合该类结构的JSON对象(比如包含类属性的键值对)。 - 而你传入的是纯ID值(字符串/数字),这会在
mode="before"的字段验证器执行前就触发类型匹配错误,直接抛出"Input should be an object",导致验证器根本没机会运行。
正确实现方案
下面提供三种可行的解决方式,你可以根据场景选择:
方案1:字段类型声明为Union,结合BeforeValidator
通过允许字段接受ID(标量)或自定义类两种类型,让Pydantic先接受输入,再通过验证器把ID转换为类实例:
from pydantic import BaseModel, BeforeValidator from typing import Union # 自定义类及模拟数据存储 class Item: def __init__(self, item_id: str, name: str): self.id = item_id self.name = name item_db = { "item1": Item("item1", "Apple"), "item2": Item("item2", "Banana") } # 转换函数:ID转Item实例 def ref_from_id(v): if isinstance(v, str): return item_db.get(v) return v class MyModel(BaseModel): ref: Union[Item, str] = BeforeValidator(ref_from_id) # 保持原有的编码逻辑 model_config = { "json_encoders": { Item: lambda v: v.id } } # 测试解码 json_str = '{"ref": "item1"}' model = MyModel.model_validate_json(json_str) print(model.ref.name) # 输出: Apple
方案2:使用模型级别的model_validator(mode="before")
在模型层面提前处理输入数据,把ID转换为类实例后,再交给Pydantic做后续验证:
from pydantic import BaseModel, model_validator class Item: def __init__(self, item_id: str, name: str): self.id = item_id self.name = name item_db = { "item1": Item("item1", "Apple"), "item2": Item("item2", "Banana") } class MyModel(BaseModel): ref: Item model_config = { "json_encoders": { Item: lambda v: v.id } } @model_validator(mode='before') def process_ref_id(cls, values): # 提前处理ref字段的ID值 ref_value = values.get('ref') if isinstance(ref_value, str): values['ref'] = item_db.get(ref_value) return values # 测试解码 json_str = '{"ref": "item1"}' model = MyModel.model_validate_json(json_str) print(model.ref.name) # 输出: Apple
方案3:自定义TypeAdapter完全接管编解码
通过自定义TypeAdapter,直接控制该类型的编码(ID输出)和解码(ID转实例)逻辑,更灵活:
from pydantic import BaseModel, TypeAdapter from typing import Any class Item: def __init__(self, item_id: str, name: str): self.id = item_id self.name = name item_db = { "item1": Item("item1", "Apple"), "item2": Item("item2", "Banana") } class ItemAdapter(TypeAdapter[Item]): def validate_python(self, value: Any, context=None) -> Item: # 解码逻辑:ID转实例 if isinstance(value, str): return item_db.get(value) return super().validate_python(value, context) def dump_python(self, value: Item, context=None) -> str: # 编码逻辑:实例转ID return value.id class MyModel(BaseModel): ref: Item = ItemAdapter() # 测试编码 model = MyModel(ref=item_db["item1"]) print(model.model_dump_json()) # 输出: {"ref": "item1"} # 测试解码 json_str = '{"ref": "item1"}' model = MyModel.model_validate_json(json_str) print(model.ref.name) # 输出: Apple
关键总结
- 避免直接把自定义类作为字段类型后,仅用字段验证器处理标量输入,因为Pydantic的类型检查会先拦截。
- 优先选择模型级验证器或TypeAdapter来处理这种跨类型的编解码场景,逻辑更清晰且不易触发优先级问题。
内容的提问来源于stack exchange,提问作者DirkR
相关产品推荐
相关产品推荐

