87

I use Pydantic to model the requests and responses to an API.

I defined a User class:

from pydantic import BaseModel

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

My API returns a list of users which I retrieve with requests and convert into a dict:

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

How can I convert this dict to a list of User instances?

My solution for now is

user_list = []
for user in users:
  user_list.append(User(**user))
Nymous
  • 971
  • 1
  • 7
  • 8

8 Answers8

165

Pydantic V1:

This is now possible using 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:

Use Type Adapter.

from pydantic import TypeAdapter

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

ta = TypeAdapter(List[User])
m = ta.validate_python(users)
Yaakov Bressler
  • 9,056
  • 2
  • 45
  • 69
David Asaf
  • 1,666
  • 1
  • 6
  • 6
  • 3
    Is there a function to do reverse, i.e given List[User] converts List[dict] – Shiv Krishna Jaiswal Aug 16 '21 at 06:41
  • 6
    @ShivKrishnaJaiswal, if you're in FastAPI, there's [`fastapi.encoders.jsonable_encoder`](https://fastapi.tiangolo.com/tutorial/encoder/), as in `jsonable_encoder(my_user_list)`. – LeoRochael Jan 21 '22 at 17:45
  • 1
    I got the unhappy `__root__ value is not a valid list (type=type_error.list)` – Jonathan Mugan May 18 '22 at 23:19
  • 2
    There is pydantic.json.pydantic_encoder, for those who do not want to install FastAPI for that. Cf https://pydantic-docs.helpmanual.io/usage/dataclasses/#json-dumping – cbenz Nov 10 '22 at 15:07
  • Pydantic 2.0 update: use `TypeAdapter(List[User]).validate_python(users)`. Moreover, if you have access to the raw JSON `bytes`/`str`, you might also want to delegate the parsing step to Pydantic (skipping the intermediate `dict` representation). e.g. `TypeAdapter(List[User]).validate_json(json_bytes)` – Danilo Gómez Jul 17 '23 at 05:21
  • parse_obj_as is already deprecated. – Wang Jul 17 '23 at 17:33
58

To confirm and expand the previous answer, here is an "official" answer at pydantic-github - All credits to "dmontagu":

The "right" way to do this in pydantic is to make use of "Custom Root Types". You still need to make use of a container model:

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

but then the following will work:

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']},
])

(and will put the values inside the root property).

Unfortunately, I think there is not good serialization support for this yet, so I think when you go to return the results, if you want to return just a list you'll still need to return UserList.root.

I don't think there is currently a unified interface that gets you a serialized/unstructured version of the model that respects the root_model, but if this is what you are looking for, it could be worth building.

Timusan
  • 3,255
  • 1
  • 22
  • 27
Jiri
  • 691
  • 5
  • 7
  • 1
    If you use `.json()` for a model with a custom root type, it'll root the root object (without the `'__root__': `). – SColvin Nov 18 '19 at 13:57
  • Except when you use `.dict()` it will include `__root__` keys :) – Jeremy Sep 29 '21 at 15:13
  • To remove `__root__` key, I defined `dict` method which does `return super().dict()['__root__']` – hshib Mar 09 '22 at 22:18
  • @hshib can u share the snippet of how did it? Thanks in advance. The ugly way I am working with is json.loads(SomeSchema.json()) – Prashant Nair Jun 02 '22 at 18:49
19

You can try this

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())
ozcanyarimdunya
  • 2,324
  • 1
  • 18
  • 21
  • 3
    Thanks, but that returns an object with a `users` property that contains the list. I will keep it in mind if there is no way to do this, that's nicer! – Nymous Apr 19 '19 at 15:08
18

You could consider using a list comprehension along with dict unpacking to the User constructor

user_list = [
  User(**user) for user in users
]
svrist
  • 7,042
  • 7
  • 44
  • 67
9

You can use the __root__ Pydantic keyword:

from typing import List
from pydantic import BaseModel

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

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

To build the JSON response:

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))

Your API web framework can jsonify user_list to be returned as a JSON array (within the response body).

oHo
  • 51,447
  • 27
  • 165
  • 200
1

I just set in my models.py list of dict like this:

from django.db import models
from pydantic import BaseModel

class CustomList(BaseModel):
    data: list[dict]
reza_khalafi
  • 6,230
  • 7
  • 56
  • 82
1

this worked for me using list comprehension:

user_list = [User(**user) for user in users]
Aymen Azoui
  • 369
  • 2
  • 4
0

I have another idea to simple this code if pydantic version below 1.2 that doesn't support parse_obj_as method.

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

simple way

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