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.