在Pydantic中,类
class Foo(BaseModel):
bar : str
baz : int
可以通过执行以下操作从元组["aaa", 3]
导入:
[**{key: tr[i] for i, key in enumerate(fields.__TraceItem__.keys())})]
它也可以使用parse_obj
从{bar: "aaa", baz: 3}
转换。但是,您如何导入将两者结合起来的东西呢?换句话说,给定类
class Bar(BaseModel):
f1: str
f2: float
f3: Boolean
class Qux(BaseModel):
field1: str
field2: float
field3: list[Bar]
如何将以下 JSON 转换为上面的Qux
对象?
{
field1: "bar",
field2: 3.14,
field3: [
["aaa", 2.71, True],
["bbb", -1, False]
]
}
您可以使用validator
.
正如评论中指出的那样,">在pydantic接受数据之前,总会有一些情况下可能需要进行转换",这对于与模型不完全匹配的JSON数据尤其如此。
这
[["aaa", 2.71, True], ["bbb", -1, False]]
是一个基元值列表,它绝对不是与
[
{'f1': 'aaa', 'f2': 2.71, 'f3': True},
{'f1': 'bbb', 'f2': -1.0, 'f3': False}
]
它更接近于实际Bar
对象的列表。
对于此类情况,您可能需要自己实现解析。
选项 1
Bar
有一个validator
,它将列表解析为Bar
的列表。
class Bar(BaseModel):
f1: str
f2: float
f3: bool
class Qux(BaseModel):
field1: str
field2: float
field3: list[Bar]
@validator("field3", pre=True)
def parse_field3_as_bar(cls, value):
# If value is already a list[Bar], then return as-is
if isinstance(value, list) and isinstance(value[0], Bar):
return value
# If not, try coercing into a list[Bar]
# Expect value to be ex. [["aaa", 2.71, True], ["bbb", -1, False]]
try:
bar_fields = Bar.__fields__.keys()
return [
Bar(**dict(zip(bar_fields, triplet)))
for triplet in value
]
except Exception:
raise ValueError(f"Cannot convert to list[Bar]: {value!r}")
>>> from main import Bar, Qux
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> q = Qux(**d)
>>> q
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])
如果传递的value
的数据格式错误:
>>> dx = {"field1": "bar", "field2": 3.14, "field3": [["not", "a"], ["list", "of", "Bar"], "values"]}
>>> q = Qux(**dx)
...
File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Qux
field3
Cannot convert to list[Bar]: [['not', 'a'], ['list', 'of', 'Bar'], 'values'] (type=value_error)
您在标题中提到了parse_obj
,我假设您的意思是parse_obj
辅助函数.
它与此配合得很好:
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> Qux.parse_obj(d)
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])
选项 2
让Bar
具有与选项 1相同的validator
,但具有each_item=True
,以便您可以一次(一次一个triplet
)将一个列表项解析为Bar
对象:
class Bar(BaseModel):
f1: str
f2: float
f3: bool
class Qux(BaseModel):
field1: str
field2: float
field3: list[Bar]
@validator("field3", pre=True, each_item=True)
def parse_field3_as_bar(cls, value):
# If value is already a Bar, then return as-is
if isinstance(value, Bar):
return value
# Try coercing value into a Bar
# Expect value to be ex. ['aaa', 2.71, True]
try:
bar_fields = Bar.__fields__.keys()
return Bar(**dict(zip(bar_fields, value)))
except Exception:
raise ValueError("Cannot convert to Bar:", value)
>>> from main import Bar, Qux
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> q = Qux(**d)
>>> q
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])
如果传递的value
的数据格式错误:
>>> from main import Bar, Qux
>>> dx = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], 9999]}
>>> q = Qux(**dx)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Qux
field3 -> 1
Cannot convert to Bar: 9999 (type=value_error)
您在标题中提到了parse_obj
,我假设您指的是parse_obj
辅助函数.
它与此同样有效:
>>> d = {"field1": "bar", "field2": 3.14, "field3": [["aaa", 2.71, True], ['bbb', -1, False]]}
>>> Qux.parse_obj(d)
Qux(field1='bar', field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False)])
作为旁注,我已经在评论中提到过,您在问题中提到了">元组">
可以从元组
["aaa", 3]
导入
但这不是元组,我在您的任何示例代码和输入/输出数据中都没有看到任何元组。我不确定你是否知道,但在 Python 中,元组指的是特定的数据类型:https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences。
您拥有的基本上是一个列表列表,每个子列表只是其他数据类型的列表。(这就是为什么在我的回答中我将其称为triplet
)。
我会根据 Gino 的答案采取更简单的方法——在Bar
上添加一个类方法以允许从数组实例化,然后从Qux
的验证器调用该方法:
from typing import List, Any
from pydantic import BaseModel, validator
class Bar(BaseModel):
f1: str
f2: float
f3: bool
@classmethod
def from_array(cls, values: List[Any]):
"""
Returns instance of class from an array of values.
NOTE: This method assumes all fields are present
ALSO: A hackier implementation of this method:
return cls(
f1=values[0],
f2=values[1],
f3=values[2]
)
"""
model_data = {
key: values[i]
for i, key in enumerate(cls.__fields__.keys())
}
return cls.parse_obj(model_data)
class Qux(BaseModel):
field1: str
field2: float
field3: List[Bar]
@validator('field3', each_item=True, pre=True)
def field3_from_array(cls, v: List[Any]):
"""
Validator for field3 which can be represented as
a list containing:
- `Bar` objects
- arrays (of Bar values)
- dicts (of Bar objects)
"""
if isinstance(v, Bar):
return v
elif isinstance(v, list):
return Bar.from_array(v)
elif isinstance(v, dict):
return Bar.parse_obj(v)
以下是它的实际效果:
>>> data = {
... "field1": "bar",
... "field2": 3.14,
... "field3": [
... ["aaa", 2.71, True],
... ["bbb", -1, False],
... {"f1": "ccc", "f2": 3.0, "f3": True}
... ]
... }
>>> Qux.parse_obj(data)
Qux(field1='bar',field2=3.14, field3=[Bar(f1='aaa', f2=2.71, f3=True), Bar(f1='bbb', f2=-1.0, f3=False), Bar(f1='ccc', f2=3.0, f3=True)])