Parse JSON-encoded query strings in FastAPI
React ↔︎ FastAPI dashboard
I work on a dashboard that shows charts with filters.
- React serializes filter parameters into JSON and sends them to FastAPI in a query string.
- The FastAPI server returns the data to display the chart.

My dashboard
FastAPI parses your request and maps it to controller parameters. Can I turn complex parameters in the query string into Pydantic models?
class Quarter(BaseModel):
q: int
year: int
@app.get("/api/v1/chart")
def get_chart(
start_quarter: Quarter = parse_querystring_and_create_quarter_instance_somehow(),
end_quarter: Quarter = parse_querystring_and_create_quarter_instance_somehow(),
):
...
FastAPI request mapping
FastAPI automatically maps query strings to scalar function parameters. We get strings inside the functions.
@app.get("/api/v1/chart")
def get_chart(start_quarter: str, end_quarter: str):
...
Ref: Query Parameters in the FastAPI documentation.
Below is the same example, with slightly more verbose mapping. There is no difference for FastAPI, but it sends a clearer message to readers of our code: “Hey, reader, the start_quarter and end_quarter parameters are taken from the query string.”
from fastapi import Query
@app.get("/api/v1/chart")
def get_chart(start_quarter: str = Query(), end_quarter: str = Query()):
...
It’s a bit less well-known, but FastAPI can accept JSON-encoded values and turn them into dicts.
from fastapi import Query, Json
@app.get("/api/v1/chart")
def get_chart(start_quarter: Json = Query(), end_quarter: Json = Query()):
...
In the code above, both start_quarter and end_quarter will be passed to the function as dicts. Inside the function, we can use these dicts as-is or call BaseModel.parse_obj, or even better, pydantic.parse_obj_as to convert dicts to model instances.
@app.get("/api/v1/chart")
def get_chart(start_quarter: Json = Query(), end_quarter: Json = Query()):
start_quarter_obj = Quarter.parse_obj(start_quarter)
end_quarter_obj = Quarter.parse_obj(end_quarter)
...
I prefer pydantic.parse_obj_as() over BaseModel.parse_obj() because the former can accept more complex types for parsing. For example, you can use parse_obj_as(Quarter | None, start_quarter) with optional arguments.
Models in query string parameters?
I thought defining a model and passing it as a query would be enough. Will the following example work?
from fastapi import Query, Json
from pydantic import BaseModel
class Quarter(BaseModel):
q: int
year: int
@app.get("/api/v1/chart")
def get_chart(start_quarter: Quarter = Query(), end_quarter: Quarter = Query()):
...
Well, it doesn’t. The FastAPI server doesn’t accept models in query strings, and an attempt to run this code throws an assertion error.
AssertionError: Param: start_quarter can only be a request body, using Body()
Workarounds
There is a feature request #884 asking for support of this feature and enumerating workarounds. Sebastián Ramírez, the author of FastAPI, closed the issue, clearly stating that the server will not support this. Therefore, a workaround is our only solution.
The simplest workaround is to use dependencies and create functions that turn raw JSON objects into models.
from fastapi import Depends
from pydantic import parse_obj_as
def get_start_quarter(start_quarter: Json = Query()) -> Quarter:
return parse_obj_as(Quarter, start_quarter)
def get_end_quarter(end_quarter: Json = Query()) -> Quarter:
return parse_obj_as(Quarter, end_quarter)
@app.get("/api/v1/chart")
def get_chart(
start_quarter: Quarter = Depends(get_start_quarter),
end_quarter: Quarter = Depends(get_end_quarter),
):
...
This code works well, but we must create a separate function for every query string parameter.
Generalized solution
I created a json_param() function that can be used in place of Depends() to let FastAPI know that it should convert the parameter from a JSON-encoded query string to a model.
from typing import Type
from fastapi import Depends, HTTPException, Query
from pydantic import BaseModel, Json, ValidationError
def json_param(param_name: str, model: Type[BaseModel], **query_kwargs):
"""Parse JSON-encoded query parameters as pydantic models.
The function returns a `Depends()` instance that takes the JSON-encoded value from
the query parameter `param_name` and converts it to a Pydantic model, defined
by the `model` attribute.
"""
def get_parsed_object(value: Json = Query(alias=param_name, **query_kwargs)):
try:
return pydantic.parse_obj_as(model, value)
except ValidationError as err:
raise HTTPException(400, detail=err.errors())
return Depends(get_parsed_object)
Usage example.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
@app.get("/")
def root(user: User = json_param("user", User, description="User object")):
return {"message": f"Hello, {user!r}"}
Request and response examples (with httpie)
Success:
$ http localhost:7000 user=='{"name": "Foo"}'
HTTP/1.1 200 OK
{
"message": "Hello, User(name='Foo')"
}
Validation error:
$ http localhost:7000 user=='{"name": null}'
HTTP/1.1 400 Bad Request
{
"detail": [
{
"loc": [
"name"
],
"msg": "none is not an allowed value",
"type": "type_error.none.not_allowed"
}
]
}
The gist snippet is available at https://gist.github.com/imankulov/cef71dd5a01f9a27caeb66f7bedaf241.