Skip to content

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

The most effective approach uses FastAPI's built-in Body parameter with Any type annotation:

python
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:

python
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

While you can use a dictionary type, this limits you to JSON objects only:

python
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:

python
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:

json
{
  "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:

python
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 ​

  1. Using Pydantic models with strict schemas: These enforce specific structures rather than accepting any JSON
  2. Dictionary types: Dict[str, Any] only accepts JSON objects, not primitives
  3. 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.