Root Models
Using RootModel to validate lists and dicts as top-level data structures.
Sometimes my data isn't a dictionary with fields — it's just a list. Like an API that returns a JSON array of users. How do I validate that with Pydantic?
With RootModel. While BaseModel always expects key-value data, RootModel lets you validate any top-level type — lists, dicts, even simple values:
from pydantic import BaseModel, RootModel
class User(BaseModel):
name: str
age: int
class UserList(RootModel[list[User]]):
pass
users = UserList.model_validate([
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
])
print(len(users.root)) # 2
print(users.root[0].name) # "Alice"
The validated data lives in the root attribute. Each element in the list is a fully validated User instance.
Why not just use list[User] directly? Why do I need a whole class?
You can use TypeAdapter for simple cases. But RootModel gives you a proper class with all the model methods — model_dump(), model_dump_json(), model_json_schema(), and custom validators:
class UserList(RootModel[list[User]]):
def get_names(self) -> list[str]:
return [u.name for u in self.root]
users = UserList.model_validate([
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
])
print(users.get_names()) # ["Alice", "Bob"]
print(users.model_dump()) # [{"name": "Alice", "age": 30}, ...]
Can I use RootModel for a dictionary too? Like a mapping of string keys to objects?
Absolutely. A dict-based RootModel validates both keys and values:
class ScoreMap(RootModel[dict[str, int]]):
pass
scores = ScoreMap.model_validate({"alice": 95, "bob": 87})
print(scores.root["alice"]) # 95
Or with model values:
class Config(BaseModel):
enabled: bool
value: str
class ConfigMap(RootModel[dict[str, Config]]):
pass
configs = ConfigMap.model_validate({
"feature_a": {"enabled": True, "value": "on"},
"feature_b": {"enabled": False, "value": "off"}
})
print(configs.root["feature_a"].enabled) # True
Does RootModel work with iteration? Can I loop over a list-based RootModel?
Yes. You can add __iter__ and __len__ to make it behave like a collection:
class UserList(RootModel[list[User]]):
def __iter__(self):
return iter(self.root)
def __len__(self):
return len(self.root)
users = UserList.model_validate([{"name": "Alice", "age": 30}])
for user in users:
print(user.name)
RootModel is the bridge between Pydantic's model system and the reality that not all data comes as a flat dictionary. APIs, config files, and data feeds often use arrays and mappings at the top level.
Practice your skills
Sign up to write and run code in this lesson.
Root Models
Using RootModel to validate lists and dicts as top-level data structures.
Sometimes my data isn't a dictionary with fields — it's just a list. Like an API that returns a JSON array of users. How do I validate that with Pydantic?
With RootModel. While BaseModel always expects key-value data, RootModel lets you validate any top-level type — lists, dicts, even simple values:
from pydantic import BaseModel, RootModel
class User(BaseModel):
name: str
age: int
class UserList(RootModel[list[User]]):
pass
users = UserList.model_validate([
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
])
print(len(users.root)) # 2
print(users.root[0].name) # "Alice"
The validated data lives in the root attribute. Each element in the list is a fully validated User instance.
Why not just use list[User] directly? Why do I need a whole class?
You can use TypeAdapter for simple cases. But RootModel gives you a proper class with all the model methods — model_dump(), model_dump_json(), model_json_schema(), and custom validators:
class UserList(RootModel[list[User]]):
def get_names(self) -> list[str]:
return [u.name for u in self.root]
users = UserList.model_validate([
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
])
print(users.get_names()) # ["Alice", "Bob"]
print(users.model_dump()) # [{"name": "Alice", "age": 30}, ...]
Can I use RootModel for a dictionary too? Like a mapping of string keys to objects?
Absolutely. A dict-based RootModel validates both keys and values:
class ScoreMap(RootModel[dict[str, int]]):
pass
scores = ScoreMap.model_validate({"alice": 95, "bob": 87})
print(scores.root["alice"]) # 95
Or with model values:
class Config(BaseModel):
enabled: bool
value: str
class ConfigMap(RootModel[dict[str, Config]]):
pass
configs = ConfigMap.model_validate({
"feature_a": {"enabled": True, "value": "on"},
"feature_b": {"enabled": False, "value": "off"}
})
print(configs.root["feature_a"].enabled) # True
Does RootModel work with iteration? Can I loop over a list-based RootModel?
Yes. You can add __iter__ and __len__ to make it behave like a collection:
class UserList(RootModel[list[User]]):
def __iter__(self):
return iter(self.root)
def __len__(self):
return len(self.root)
users = UserList.model_validate([{"name": "Alice", "age": 30}])
for user in users:
print(user.name)
RootModel is the bridge between Pydantic's model system and the reality that not all data comes as a flat dictionary. APIs, config files, and data feeds often use arrays and mappings at the top level.
Practice your skills
Sign up to write and run code in this lesson.