Day 19 · ~12m

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.

Already have an account? Sign in