122

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

11 Answers 11

229

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)
Sign up to request clarification or add additional context in comments.

6 Comments

Is there a function to do reverse, i.e given List[User] converts List[dict]
@ShivKrishnaJaiswal, if you're in FastAPI, there's fastapi.encoders.jsonable_encoder, as in jsonable_encoder(my_user_list).
I got the unhappy __root__ value is not a valid list (type=type_error.list)
There is pydantic.json.pydantic_encoder, for those who do not want to install FastAPI for that. Cf pydantic-docs.helpmanual.io/usage/dataclasses/#json-dumping
parse_obj_as is already deprecated.
|
67

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.

5 Comments

If you use .json() for a model with a custom root type, it'll root the root object (without the '__root__': ).
Except when you use .dict() it will include __root__ keys :)
To remove __root__ key, I defined dict method which does return super().dict()['__root__']
@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())
In the meantime RootModel was added to pydantic v2 (in may), which works very similar to this example: class UserList(RootModel): root: list[User]. The only difference in the use is, that you can leave out the dunders. Also you get some custom behaviour of theRootModel class compared to the BaseModel, e.g. throwing an exception when a non-root field is added.
23

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

1 Comment

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!
23

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

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

5 Comments

I like it, pretty clean
simplest and most obvious. should be accepted answer.
FYI this method is about 2x faster than the accepted answer. I have no idea why the other one was accepted instead lol.
@rv.kvetch can you show how you test the speed?
yes, cleanest & fastest solution. However pydantic throws 'model_attributes_type' validation error, seems pydantic expects 'users' property that contains the list. Any suggestion to make it work in pydantic as well ?
12

Pydantic V2

For adapter approach see david-asaf's answers.

Another solution is to use RootModel:

from pydantic import BaseModel, RootModel

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

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

data = [{"name": "user1", "age": 15}, {"name": "user2", "age": 28}]
persons = PersonList(data)

print(persons)
print(persons.model_dump())

2 Comments

What is the purpose of defining the users list in this example? Agree with where I think this answer is going, but it is missing the final step.
@user2263572: added final step
10

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

Comments

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]

Comments

1

this worked for me using list comprehension:

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

Comments

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]

Comments

0

here is the pydantic model model.py

from pydantic import BaseModel

class Post_Response(BaseModel):
    name: str

class Post_Mother(BaseModel):
    status: int
    users: List[Post_Response] = []

let import it into our app.py

from . import Post_Response, Post_Mother

response = Post_Mother(
        status=200,
        users=[Post_Response(name='user_name')],
 )

Comments

0

While David's answer is perfect, you can also use the model_validate method in V2:

# list operator
[User.model_validate(x) for x in users]

# map, nice because it returns a generator
list(map(User.model_validate, users))

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.