Reading Any Valid JSON Body in FastAPI
Learn how to handle and validate arbitrary JSON payloads in your FastAPI endpoints while maintaining type safety and proper validation.
Problem Overview
When working with FastAPI, you may need to accept any valid JSON payload - not just objects and arrays, but also primitive values like strings, numbers, booleans, and null. The challenge is to:
- Accept any valid JSON structure
- Validate that the input is proper JSON
- Maintain FastAPI's automatic documentation and validation features
- Avoid forcing a specific structure using Pydantic models
Recommended Solution
The most effective approach uses FastAPI's built-in Body
parameter with Any
type annotation:
from typing import Any
from fastapi import Body, FastAPI
app = FastAPI()
@app.post('/test')
async def process_json(payload: Any = Body(...)):
return payload
Why This Works
Any
type accepts any valid JSON value (objects, arrays, strings, numbers, booleans, null)Body(...)
marks the parameter as required and tells FastAPI to parse the entire request body- Automatic validation ensures only valid JSON is accepted
- FastAPI's OpenAPI documentation correctly reflects the endpoint
INFO
Use Body(None)
instead of Body(...)
if you want the parameter to be optional rather than required.
Alternative Approaches
Using Annotated Type (Python 3.9+)
For newer Python versions, you can use typing.Annotated
for better clarity:
from typing import Any, Annotated
from fastapi import Body, FastAPI
app = FastAPI()
@app.post("/test")
async def get_body(payload: Annotated[Any, Body()]):
return payload
Dictionary Approach (Less Recommended)
While you can use a dictionary type, this limits you to JSON objects only:
from typing import Dict, Any
from fastapi import FastAPI
app = FastAPI()
@app.post("/dict/")
async def process_dict(data: Dict[str, Any]):
return data
WARNING
The dictionary approach only accepts JSON objects, not primitive values like strings, numbers, or arrays.
Complete Working Example
Here's a comprehensive example with test cases showing the solution in action:
from typing import Any
from fastapi import Body, FastAPI
from fastapi.testclient import TestClient
import json
app = FastAPI()
@app.post('/test')
async def process_json(payload: Any = Body(...)):
return payload
# Test client and test cases
client = TestClient(app)
# Test various JSON types
test_cases = [
('{"key": 1}', {"key": 1}), # Object
(json.dumps({"key": None}), {"key": None}), # Object with null
(json.dumps([1, 2, 3]), [1, 2, 3]), # Array
(json.dumps(6), 6), # Number
(json.dumps("hello"), "hello"), # String
(json.dumps(True), True), # Boolean
(json.dumps(None), None), # Null
("{}", {}), # Empty object
("[]", []), # Empty array
]
for payload, expected in test_cases:
response = client.post("/test", data=payload)
assert response.status_code == 200
assert response.json() == expected
Error Handling
Invalid JSON will automatically return a 422 Unprocessable Entity response with detailed error information:
{
"detail": [
{
"loc": ["body"],
"msg": "Expecting value: line 1 column 1 (char 0)",
"type": "value_error.jsondecode"
}
]
}
When to Use Direct Request Access
For advanced use cases where you need to inspect the raw request, you can access it directly:
from fastapi import Request
@app.post("/raw")
async def raw_request(request: Request):
try:
return await request.json()
except:
return await request.body()
DANGER
Direct request access bypasses FastAPI's automatic validation and documentation. Use this approach only when you need to handle non-JSON content or require low-level access to the request.
Common Pitfalls
- Using Pydantic models with strict schemas: These enforce specific structures rather than accepting any JSON
- Dictionary types:
Dict[str, Any]
only accepts JSON objects, not primitives - Missing Body parameter: Forgetting
= Body(...)
will cause the parameter to be treated as a query parameter
Conclusion
The optimal solution for reading any valid JSON in FastAPI is to use Any = Body(...)
as your parameter type. This approach:
- Validates JSON syntax automatically
- Accepts all JSON value types (objects, arrays, primitives)
- Maintains proper OpenAPI documentation
- Provides clear error messages for invalid JSON
- Works with FastAPI's dependency injection system
Choose the direct Request
access approach only when you need to handle non-JSON content or require low-level request inspection capabilities.