Question: I have an endpoint that takes a Pydantic model, Foo, as a query parameter. from typing import Annotated import uvicorn from fastapi import FastAPI, Query from pydantic import BaseModel app = FastAPI() class Foo(BaseModel): bar: str baz: dict[str, str] @app.get("/") def root(foo: Annotated[Foo, Query()]): return foo if __name__ == "__main__": uvicorn.run("test:app")  I'm defining my query params using Swagger, so the encoding should be correct. I know the baz param syntax looks redundant because I've nested a dictionary, but parsing fails even without nesting.  But when I call the endpoint... curl -X 'GET' \ 'http://127.0.0.1:8000/?bar=sda&baz=%7B%22abc%22%3A%22def%22%7D' \ -H 'accept: application/json'  FastAPI does not seem to read in Foo.baz correctly, returning { "detail": [ { "type": "dict_type", "loc": [ "query", "baz" ], "msg": "Input should be a valid dictionary", "input": "{\"abc\":\"def\"}" } ] }  I've read similar questions and I know I can ingest the dictionary by accessing dict(request.query_params), but this bypasses FastAPI's validation and I'd prefer to keep the endpoint simple and consistent with the rest of my codebase by keeping the param as a Pydantic model. How can I get FastAPI to parse Foo as a query param?

Answer: It is indeed a complex subjects for which I see mainly 2 paths forward. Parse your dictionary from the query It is actually possible to get the dictionary back from the query parameters: from pydantic import BaseModel, Json class Foo(BaseModel): bar: str baz: Json  When baz will receive the Json string, it will be parsed into a dictionary. This method changes the openapi schema, so swagger will render baz as a file input. openapi schema: "parameters": [ {"name": "bar",...}, { "name": "baz", "in": "query", "required": true, "schema": { "type": "string", "contentMediaType": "application/json", "contentSchema": {}, "title": "Baz" } } ]  swagger rendering:  If swagger is important to you, you can use your custom Json loader type that will render as a textarea: from typing import Annotated import json from pydantic import BaseModel, Json, BeforeValidator CustomJsonValidator = Annotated[dict[str, str], BeforeValidator(json.loads)] class Foo(BaseModel): bar: str baz: CustomJsonValidator  The associated openapi schema will be: "parameters": [ {"name": "bar", ...}, { "name": "baz", "in": "query", "required": true, "schema": { "type": "object", "additionalProperties": { "type": "string" }, "title": "Baz" } } ]  and swagger will render like the following:  Explicit all of the filters If possible, you can explicit all of your filters in a pydantic schema (a bit like flattening out baz dictionary). It can avoid potential issue since it's way harder to validate a dictionary than fields. Of course, you will have to use None by defaults a bit everywhere, but you can always exclude them from your model dumping. from typing import Literal from pydantic import BaseModel class Foo(BaseModel): bar: str abc: Literal["def"] | None = None ghi: str | None = None jkl: int | None = None  If you have filters that can't be used together, you can use pydantic unions for that. Let's say "abc" and "ghi" filters must always coexist, but can't be defined along with "jkl". You would have: from typing import Literal from pydantic import BaseModel class GoodLuckNamingThat(BaseModel): bar: str abc: Literal["def"] ghi: str class AnotherNamingNightmare(BaseModel): bar: str jkl: int @app.get("/") def root(foo: Annotated[GoodLuckNamingThat | AnotherNamingNightmare, Query()]): return foo