如何使用Pydantic解析模型列表

94

我使用Pydantic来对API的请求和响应进行建模。

我定义了一个User类:


from pydantic import BaseModel

class User(BaseModel):
  name: str
  age: int

我的API返回一个用户列表,我使用requests获取并将其转换为字典:

users = [{"name": "user1", "age": 15}, {"name": "user2", "age": 28}]

我该如何将这个字典转换为一个User实例列表?

我目前的解决方案是:

user_list = []
for user in users:
  user_list.append(User(**user))
9个回答

174

Pydantic V1:

现在可以使用parse_obj_as来实现。

from pydantic import parse_obj_as

users = [
    {"name": "user1", "age": 15}, 
    {"name": "user2", "age": 28}
]

m = parse_obj_as(List[User], users)

Pydantic V2:

使用类型适配器

from pydantic import TypeAdapter

users = [
    {"name": "user1", "age": 15}, 
    {"name": "user2", "age": 28}
]

ta = TypeAdapter(List[User])
m = ta.validate_python(users)

3
有没有一个函数可以实现反向操作,即将List[User]转换为List[dict]? - Shiv Krishna Jaiswal
6
如果你正在使用 FastAPI,可以使用 fastapi.encoders.jsonable_encoder 来对数据进行编码,例如 jsonable_encoder(my_user_list) - LeoRochael
1
我收到了不幸的消息:__root__ 值不是有效的列表(类型为 type_error.list)。 - Jonathan Mugan
2
对于那些不想安装FastAPI的人,可以使用pydantic.json.pydantic_encoder。请参考https://pydantic-docs.helpmanual.io/usage/dataclasses/#json-dumping。 - cbenz
Pydantic 2.0 更新:使用 TypeAdapter(List[User]).validate_python(users)。此外,如果您可以访问原始的 JSON bytes/str,您可能还希望将解析步骤委托给 Pydantic(跳过中间的 dict 表示)。 例如:TypeAdapter(List[User]).validate_json(json_bytes) - Danilo Gómez
parse_obj_as已经被弃用。 - Wang

60

为了确认和扩展之前的答案,这里有一个“官方”答案在pydantic-github - 所有荣誉归于“dmontagu”:

在pydantic中实现这一点的“正确”方法是利用“自定义根类型”。您仍需要使用容器模型:

class UserList(BaseModel):
    __root__: List[User]

但接下来的内容将起作用:

UserList.parse_obj([
    {'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']},
    {'id': '456', 'signup_ts': '2017-06-02 12:22', 'friends': ['you']},
])
(并将这些值放入 root 属性中。)不幸的是,我认为目前对此没有良好的序列化支持,因此我认为,当您返回结果时,如果您只想返回一个列表,仍然需要返回 UserList.root。我不认为目前有一个统一的接口可以让您获得一个序列化/非结构化版本的模型,该版本尊重 root_model,但如果这正是您要寻找的内容,那么构建这个接口可能是值得的。

1
如果您在具有自定义根类型的模型上使用.json(),它将使根对象成为根(不带'__root__':)。 - SColvin
除非您使用.dict(),否则它将包括__root__键 :) - Jeremy
为了删除__root__键,我定义了一个dict方法,它执行return super().dict()['__root__'] - hshib
@hshib,你能分享一下它是如何实现的代码片段吗?提前感谢。 我正在使用的丑陋方法是json.loads(SomeSchema.json())。 - Prashant Nair
1
与此同时,RootModel已经在pydantic v2中添加(在五月份),它的工作方式与这个例子非常相似:class UserList(RootModel): root: list[User]。唯一的区别在于,你可以省略dunders的使用。此外,与BaseModel相比,RootModel类还具有一些自定义行为,例如在添加非根字段时抛出异常。 - undefined

20

你可以尝试这个

from typing import List
from pydantic import BaseModel

class User(BaseModel):
  name: str
  age: int

class Users(BaseModel):
    users: List[User]

users = [{"name": "user1", "age": 15}, {"name": "user2", "age": 28}]
m = Users(users=users)
print(m.dict())

3
谢谢,但是那会返回一个带有 users 属性的对象,其中包含列表。如果没有其他方法可以实现,我会记住这个方法的,它很好! - Nymous

19
您可以考虑使用列表推导式以及字典解包到构造函数中。
user_list = [
  User(**user) for user in users
]

我喜欢它,非常简洁。 - lowercase00
最简单和最明显的答案应该被接受。 - rv.kvetch
FYI,这个方法比被接受的答案快2倍。我不知道为什么其他的那个被接受了哈哈。 - rv.kvetch
@rv.kvetch,你能展示一下如何测试速度吗? - Zaffer

9
您可以使用 Pydantic 的关键字 __root__
from typing import List
from pydantic import BaseModel

class User(BaseModel):
  name: str
  age: int

class UserList(BaseModel):
  __root__: List[User]     # ⯇-- __root__

构建JSON响应:
user1 = {"name": "user1", "age": 15}
user2 = {"name": "user2", "age": 28}

user_list = UserList(__root__=[])
user_list.__root__.append(User(**user1))
user_list.__root__.append(User(**user2))

您的API Web框架可以将user_list转换成JSON数组(在响应体中返回)。


1

我刚在我的models.py中设置了一个字典列表,如下所示:

from django.db import models
from pydantic import BaseModel

class CustomList(BaseModel):
    data: list[dict]

1

使用列表推导式对我有用:

user_list = [User(**user) for user in users]

0

如果 pydantic 版本低于 1.2,不支持 parse_obj_as 方法,我有另一个简化此代码的想法。

user_list = []
for user in users:
  user_list.append(User(**user))

简单的方式

user_list = [User(**user) for user in users]

0

Pydantic V2

要了解适配器方法,请参考david-asaf的答案

另一种解决方案是使用RootModel

from pydantic import BaseModel, RootModel

class User(BaseModel):
  name: str
  age: int

class PersonList(RootModel):
    root: list[User]

users = [{"name": "user1", "age": 15}, {"name": "user2", "age": 28}]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接