FastAPI Request Body Fields and Nested Models
In Pydantic models, you can use Field to declare validation rules and metadata for fields. You can also nest models to handle complex JSON data structures.
Using Field to Declare Field Validation
Similar to using Query and Path for validation in path operation functions, you use Field inside Pydantic models to declare field validation:
Example
from typing import Annotated
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(min_length=1, max_length=100, description="Product name") # Required, 1-100 characters
description: str | None = Field(default=None, max_length=300, description="Product description") # Optional
price: float = Field(gt=0, description="Product price") # Required, must be greater than 0
tax: float | None = Field(default=None, ge=0, description="Tax") # Optional, >= 0
@app.post("/items/")
async def create_item(item: Item):
return item
Validation parameters supported by Field:
| Parameter Type | Parameter | Description |
|---|---|---|
| String Validation | min_length |
Minimum length |
max_length |
Maximum length | |
pattern |
Regular expression match | |
| Numeric Validation | gt |
Greater than |
ge |
Greater than or equal | |
lt |
Less than | |
le |
Less than or equal | |
| Metadata | title |
Field title |
description |
Field description |
Note:
Fieldis imported frompydantic, not fromfastapi. This is different fromQuery,Path, etc., which are imported fromfastapi.
List Type Fields
You can declare fields as lists and specify the element type:
Example
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list = [] # String list, defaults to empty list
@app.post("/items/")
async def create_item(item: Item):
return item
Request body example:
{
"name": "Foo",
"price": 42.0,
"tags": ["rock", "metal", "bar"]
}
Using Set for Deduplication
If tags should not be duplicated, use the set type:
Example
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set = set() # Automatically removes duplicate tags
Even if the request contains duplicate tags, the response will automatically deduplicate them.
Nested Models
The field type of a Pydantic model can be another Pydantic model, supporting arbitrary nesting depth:
Example
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
# Sub-model: Image
class Image(BaseModel):
url: HttpUrl # Automatically validates if it's a valid URL
name: str
# Main model: Product, containing Image sub-model
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set = set()
image: Image | None = None # Optional image information
@app.post("/items/")
async def create_item(item: Item):
return item
Corresponding request body structure:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
Sub-models in Lists
You can place sub-models in lists:
Example
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set = set()
images: list | None = None # Image list
@app.post("/items/")
async def create_item(item: Item):
return item
Corresponding request body:
{
"name": "Foo",
"price": 42.0,
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
Deeply Nested Models
You can define nested structures of arbitrary depth:
Example
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set = set()
images: list | None = None
class Offer(BaseModel):
name: str
description: str | None = None
price: float
items: list # Offer contains multiple Items, and each Item contains multiple Images
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
Pure List Request Body
If the outermost layer of the request body is a JSON array, you can directly declare the list type in the function parameter:
Example
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
# Request body is directly an Image list
@app.post("/images/multiple/")
async def create_multiple_images(images: list):
return images
Arbitrary dict Request Body
When you need to receive dictionary data with uncertain key names, you can declare a dict type request body:
Example
from fastapi import FastAPI
app = FastAPI()
# Receives a dictionary with int keys and float values
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights
Request body example:
{
"1": 0.5,
"2": 1.5,
"3": 2.0
}
JSON only supports string-type keys, but Pydantic will automatically convert string keys to the declared type (like
intin this example).
Summary
- Use Pydantic's
Fieldto add validation and metadata to model fields - Collection types like
list,setcan declare list/set fields - Models can be nested, supporting JSON structures of arbitrary depth
- Special types like
HttpUrlautomatically validate formats - Pure lists and arbitrary dictionaries can also be used as request body types
YouTip