Body - Nested Models
Master complex nested data structures with FastAPI and Pydantic. Learn to use lists, sets, nested models, and deeply nested structures for powerful APIs.
🎯 What You'll Learn
- •Create models with list and set fields using proper type annotations
- •Build nested models by embedding one Pydantic model inside another
- •Handle lists of nested models for complex data structures
- •Use Dict fields for key-value mappings with type safety
- •Understand deeply nested models and their validation benefits
Body - Nested Models
🎯 What You'll Learn
This lesson covers the complete official FastAPI "Body - Nested Models" tutorial. You'll master creating arbitrarily deeply nested models using FastAPI and Pydantic, enabling you to handle complex data structures with full validation and documentation.
By the end of this lesson, you'll understand how to:
- Create models with list and set fields using proper type annotations
- Build nested models by embedding one Pydantic model inside another
- Handle lists of nested models for complex data structures
- Use Dict fields for key-value mappings with type safety
- Understand deeply nested models and their validation benefits
📚 1. List Fields
You can define an attribute to be a Python list:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: list = [] # Basic list without type specification
This makes tags a list, but doesn't specify the type of elements inside the list.
🎯 2. List Fields with Type Parameters
Python has a specific way to declare lists with internal types using "type parameters":
Import typing's List
For Python versions before 3.9, import List from the typing module:
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = [] # List of strings
Modern Python (3.9+)
In Python 3.9 and above, you can use the standard list:
class Item(BaseModel):
name: str
tags: list[str] = [] # Modern syntax
Request body example:
{
"name": "Laptop",
"description": "Gaming laptop",
"price": 999.99,
"tags": ["electronics", "computers", "gaming"]
}
🔄 3. Set Fields
You can use Set types for collections of unique items:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set() # Set of unique strings
Benefits of Sets:
- Automatically removes duplicates
- Validates uniqueness
- More efficient for membership testing
Request body example:
{
"name": "Laptop",
"price": 999.99,
"tags": ["electronics", "computers", "electronics"]
}
Result: tags will be {"electronics", "computers"} (duplicate removed)
🏗️ 4. Nested Models
You can define a Pydantic model as the type of an attribute:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
class Image(BaseModel):
url: HttpUrl # Special Pydantic type for URLs
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None # Nested model
Request body example:
{
"name": "Laptop",
"price": 999.99,
"tags": ["electronics", "computers"],
"image": {
"url": "https://example.com/laptop.jpg",
"name": "Laptop Image"
}
}
Special Pydantic Types
HttpUrl: Validates that the string is a valid HTTP URLEmailStr: Validates email addresses (requiresemail-validator)UUID: Validates UUID stringsdatetime: Handles datetime objects
📋 5. Lists of Nested Models
You can have lists containing nested models:
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: List[Image] = [] # List of nested models
Request body example:
{
"name": "Gaming Setup",
"price": 1999.99,
"images": [
{
"url": "https://example.com/setup1.jpg",
"name": "Main Setup"
},
{
"url": "https://example.com/setup2.jpg",
"name": "Side View"
}
]
}
🗂️ 6. Deeply Nested Models
You can create arbitrarily deep nesting:
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: List[Image] = []
class Offer(BaseModel):
name: str
description: Union[str, None] = None
price: float
items: List[Item] # Nested list of items with their own nested images
🗃️ 7. Dict Fields
You can use Dict for key-value mappings:
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
return weights
Request body example:
{
"1": 0.5,
"2": 1.0,
"3": 0.75
}
Response:
{
"1": 0.5,
"2": 1.0,
"3": 0.75
}
🌟 Key Concepts
Type Safety Benefits
Nested models provide:
- Automatic validation at every level
- Type conversion for compatible types
- Clear error messages showing exactly which nested field failed
- IDE support with autocomplete for nested attributes
- Automatic documentation in OpenAPI schema
Validation Flow
- Top-level validation: Main model fields are validated
- Nested validation: Each nested model validates its own fields
- Collection validation: Lists/Sets validate each element
- Type conversion: Automatic conversion where possible
- Error aggregation: All validation errors collected and returned
Performance Considerations
- Validation overhead: Deeper nesting = more validation work
- Memory usage: Nested objects use more memory
- Serialization cost: Complex structures take longer to serialize/deserialize
- Network payload: Larger JSON payloads
💡 Best Practices
Model Organization
# Organize from simple to complex
class Address(BaseModel):
street: str
city: str
country: str
class User(BaseModel):
name: str
email: EmailStr
address: Address # Simple nesting
class Order(BaseModel):
id: int
user: User # Nested user
items: List[Item] # List of nested items
total: float
Validation Constraints
from pydantic import Field
class Item(BaseModel):
name: str = Field(min_length=1, max_length=100)
tags: Set[str] = Field(max_items=10) # Limit set size
images: List[Image] = Field(max_items=5) # Limit list size
Optional vs Required Nesting
class Item(BaseModel):
name: str
# Optional nested model
image: Union[Image, None] = None
# Required nested model
category: Category
# Optional list (can be empty)
tags: List[str] = []
🎯 Complete Example
from typing import Dict, List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
class ItemWithImages(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
images: List[Image] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
@app.put("/items/{item_id}/images")
async def update_item_with_images(item_id: int, item: ItemWithImages):
results = {"item_id": item_id, "item": item}
return results
@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
return weights
🔗 What's Next?
In the next lesson, you'll learn about Extra Data Types, where you'll discover FastAPI's support for additional data types like UUID, datetime, timedelta, frozenset, bytes, and Decimal.
📖 Additional Resources
💡 Hint
Use List[str] for typed lists, Set[str] for unique collections, and embed Pydantic models inside other models for complex nested structures.
Ready to Practice?
Now that you understand the theory, let's put it into practice with hands-on coding!
Start Interactive Lesson