> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Pydantic Nested Models: Composing Complex Data Structures

> Compose Pydantic models inside other models, work with lists of models, and parse complex nested JSON into fully validated Python objects.

Real-world data is rarely flat. An order contains line items. A user has an address. A company has departments, and departments have employees. Pydantic handles this naturally by letting you use one model as a field type inside another. When you validate an outer model, Pydantic automatically validates every nested model too — all the way down the hierarchy. This means a single `model_validate()` call can safely parse an entire deeply nested JSON payload from an external API, raising precise errors for any field that doesn't conform, no matter how deep it sits.

## Using one model inside another

Declare a model as the type annotation for a field, just like you would with `str` or `int`:

```python theme={null}
from pydantic import BaseModel

class OrderItem(BaseModel):
    product_id: str
    name: str
    quantity: int
    price: float

class Order(BaseModel):
    order_id: str
    item: OrderItem   # Nested model

order = Order(
    order_id="ORD-001",
    item=OrderItem(
        product_id="P1",
        name="Widget",
        quantity=2,
        price=29.99,
    ),
)

print(order.item.name)   # Widget
print(order.item.price)  # 29.99
```

## Parsing nested dictionaries

The real power of nested models becomes clear when you parse dictionaries — for example, a JSON payload from an HTTP request:

```python theme={null}
from pydantic import BaseModel

class OrderItem(BaseModel):
    product_id: str
    name: str
    quantity: int
    price: float

class Order(BaseModel):
    order_id: str
    item: OrderItem

data = {
    "order_id": "ORD-001",
    "item": {
        "product_id": "P1",
        "name": "Widget",
        "quantity": 2,
        "price": 29.99,
    },
}

# Pydantic validates the outer AND inner data in one call
order = Order.model_validate(data)

print(order.order_id)     # ORD-001
print(order.item.name)    # Widget
```

Pydantic automatically builds the `OrderItem` from the inner dictionary. If any nested field has the wrong type, you get a clear error pointing to the exact path — e.g. `item.price`.

## Lists of nested models

Use `list[YourModel]` to declare a field that holds multiple nested objects:

```python theme={null}
from pydantic import BaseModel

class OrderItem(BaseModel):
    product_id: str
    name: str
    quantity: int
    price: float

class Order(BaseModel):
    order_id: str
    customer_email: str
    items: list[OrderItem]   # Many nested models

order_data = {
    "order_id": "ORD-001",
    "customer_email": "customer@example.com",
    "items": [
        {"product_id": "P1", "name": "Widget", "quantity": 2, "price": 29.99},
        {"product_id": "P2", "name": "Gadget", "quantity": 1, "price": 49.99},
    ],
}

order = Order(**order_data)

print(f"Order {order.order_id}")
for item in order.items:
    print(f"  - {item.name}: {item.quantity} x ${item.price}")
```

Output:

```text theme={null}
Order ORD-001
  - Widget: 2 x $29.99
  - Gadget: 1 x $49.99
```

## Optional nested models

Make a nested model optional by using `| None` with a default of `None`:

```python theme={null}
from pydantic import BaseModel

class Discount(BaseModel):
    code: str
    percent: float

class Order(BaseModel):
    order_id: str
    total: float
    discount: Discount | None = None   # Optional nested model

# Works without a discount
order = Order(order_id="ORD-001", total=99.99)
print(order.discount)   # None

# Works with a discount — dict is coerced automatically
order = Order(
    order_id="ORD-002",
    total=99.99,
    discount={"code": "SAVE20", "percent": 20.0},
)
print(order.discount.code)   # SAVE20
```

<Note>
  Pydantic coerces nested dictionaries into the declared model type for you. You don't need to manually construct `Discount(...)` before passing it in — a plain dict works.
</Note>

## Deep nesting

There's no limit to how deep you can nest models. Pydantic validates the entire tree:

```python theme={null}
from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    country: str

class Customer(BaseModel):
    name: str
    email: str
    billing_address: Address
    shipping_address: Address | None = None

class OrderItem(BaseModel):
    product_id: str
    name: str
    quantity: int
    price: float

class Order(BaseModel):
    order_id: str
    customer: Customer
    items: list[OrderItem]
    notes: str | None = None

data = {
    "order_id": "ORD-123",
    "customer": {
        "name": "Alice Smith",
        "email": "alice@example.com",
        "billing_address": {
            "street": "123 Main St",
            "city": "Amsterdam",
            "country": "Netherlands",
        },
    },
    "items": [
        {"product_id": "SKU-001", "name": "Widget", "quantity": 3, "price": 19.99},
    ],
}

order = Order.model_validate(data)
print(order.customer.billing_address.city)  # Amsterdam
print(order.items[0].name)                  # Widget
```

<Tip>
  Keep each model focused on a single concept. Prefer `Customer` + `Address` over one giant flat model — it makes models reusable, readable, and easier to test in isolation.
</Tip>

## Serialising nested models

`model_dump()` converts the entire model tree to a plain dictionary, and `model_dump_json()` converts it to a JSON string — nested models are handled automatically:

```python theme={null}
from pydantic import BaseModel

class OrderItem(BaseModel):
    name: str
    price: float

class Order(BaseModel):
    order_id: str
    item: OrderItem

order = Order(
    order_id="ORD-001",
    item=OrderItem(name="Widget", price=29.99),
)

print(order.model_dump())
# {'order_id': 'ORD-001', 'item': {'name': 'Widget', 'price': 29.99}}

print(order.model_dump_json())
# {"order_id":"ORD-001","item":{"name":"Widget","price":29.99}}
```

## Common patterns

<Accordion title="Reusing a model across multiple contexts">
  Define a general-purpose model once and reference it wherever it's needed:

  ```python theme={null}
  from pydantic import BaseModel

  class Money(BaseModel):
      amount: float
      currency: str = "USD"

  class Product(BaseModel):
      name: str
      price: Money

  class Invoice(BaseModel):
      items: list[Product]
      subtotal: Money
      tax: Money
      total: Money

  invoice = Invoice(
      items=[{"name": "Widget", "price": {"amount": 29.99}}],
      subtotal={"amount": 29.99},
      tax={"amount": 2.40},
      total={"amount": 32.39},
  )
  print(invoice.total.amount)  # 32.39
  ```
</Accordion>

<Accordion title="Self-referencing models (tree structures)">
  A model can reference itself, which is useful for comments, categories, or any tree-shaped data:

  ```python theme={null}
  from __future__ import annotations
  from pydantic import BaseModel

  class Comment(BaseModel):
      text: str
      author: str
      replies: list[Comment] = []

  comment = Comment(
      text="Great article!",
      author="Alice",
      replies=[
          Comment(text="Thanks!", author="Bob"),
      ],
  )

  print(comment.replies[0].text)   # Thanks!
  ```

  The `from __future__ import annotations` import is required to allow `Comment` to reference itself before the class definition is complete.
</Accordion>

## Summary

<Steps>
  <Step title="Nest a model as a field">
    Use any Pydantic model as the type annotation for a field in another model.
  </Step>

  <Step title="Parse from nested dicts">
    Call `Model.model_validate(data)` or `Model(**data)` — Pydantic builds nested models from inner dictionaries automatically.
  </Step>

  <Step title="Use list[Model] for collections">
    Declare `items: list[OrderItem]` to validate a list of nested objects in one shot.
  </Step>

  <Step title="Serialise the whole tree">
    `model_dump()` and `model_dump_json()` recursively convert all nested models to dicts/JSON.
  </Step>
</Steps>

## Learn more

* [Nested models documentation](https://docs.pydantic.dev/latest/concepts/models/#nested-models)
* [Serialisation documentation](https://docs.pydantic.dev/latest/concepts/serialization/)

***

## What's next?

You can now handle complex, deeply nested data. Next, learn how to manage your application's configuration — API keys, ports, debug flags — with full type safety using Pydantic Settings.

<Card title="Pydantic Settings" icon="arrow-right" href="/pydantic/pydantic-settings">
  Learn how to manage environment-based application configuration with Pydantic Settings.
</Card>
