266 words, 2 min read

When working with APIs or configuration files, it’s common to receive raw JSON data that needs to be parsed into structured Pydantic models. However, Pydantic’s model_validate_json() can behave unexpectedly when your data is already a Python list or dictionary.

Let’s take a simple example.

from pydantic import BaseModel, Field
class UserDTO(BaseModel):
"""Model for user information."""
name: str = Field(..., description="Full name of the user")
email: str = Field(..., description="Email address of the user")

Suppose you receive this data from an API:

users = [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"},
]

If you try this:

UserDTO.model_validate_json(users)

it won’t work, because model_validate_json() expects a JSON string, not a Python list or dict.

Option 1: Parsing from Python objects

If your JSON is already parsed into Python objects (for example, using json.loads()), use model_validate() or TypeAdapter.validate_python():

from pydantic import TypeAdapter
# Parse each item individually
models = [UserDTO.model_validate(item) for item in users]
# Or parse the entire list at once
adapter = TypeAdapter(list[UserDTO])
models = adapter.validate_python(users)

Option 2: Parsing directly from JSON

If you have the raw JSON string instead:

json_data = '[{"name": "Alice", "email": "alice@example.com"}]'

you can parse it in one step:

from pydantic import TypeAdapter
adapter = TypeAdapter(list[UserDTO])
models = adapter.validate_json(json_data)

Optional: Using a wrapper model

If you want a cleaner interface, you can define a wrapper model that represents a list of items:

class UserList(BaseModel):
__root__: list[UserDTO]

Now you can parse JSON directly:

UserList.model_validate_json(json_data)

and get structured, typed models ready for use.

Conclusion

  • Use model_validate() for Python dicts/lists
  • Use model_validate_json() for raw JSON strings
  • Use TypeAdapter or wrapper models for lists of objects