I discovered recently that I could create a custom response class for use with FastAPI and that I could use this to create a pretty printed json-like reponse for my API. I didn’t want to force all my responses to go through this custom class though, just when a specific response_format=pretty parameter was selected. I therefore spent quite a bit of time trying to find a way to add a conditional response class. Turns out it was pretty simple to do what I wanted.

The custom response class shown here was shamefully nicked from @dmontagu

import json
from typing import Any

from starlette.responses import Response

class PrettyJSONResponse(Response):
    media_type = "application/json"

    def render(self, content: Any) -> bytes:
        return json.dumps(
            content,
            ensure_ascii=False,
            allow_nan=False,
            indent=4,
            separators=(", ", ": "),
        ).encode("utf-8")

@dmontagu in the above link is then showing it used like:

@app.get("/", response_class=PrettyJSONResponse)
    def get_some_json():

But that forces everything to be output using the PrettyJsonResponse instruction.

I already have a response_format parameter that accepts ‘json’ and ‘csv’ and would like to add ‘prettyprint’ as an option.

Here’s how I achieved that and implemented a ‘conditional response class’:

import json
from enum import Enum
from pydantic import BaseModel, Field
from typing import Any, List
from starlette.responses import Response

class PrettyJSONResponse(Response):
    media_type = "application/json"

    def render(self, content: Any) -> bytes:
        return json.dumps(
            content,
            ensure_ascii=False,
            allow_nan=False,
            indent=4,
            separators=(", ", ": "),
        ).encode("utf-8")

# Enum for restricting parameter values
class Format(str, Enum):
    json = 'json'
    csv = 'csv'
    prettyprint = 'prettyprint'

# Response model for Events
class Event(BaseModel):
    event_id: str
    name: str
    place: List[str] = []
    organisation: List[str] = []

@app.get("/event/{event_id}", response_model=Event, tags=["unique_id"])
  def get_stuff(event_id: str = Path(..., title="The unique id of the event"),
                response_format: Format = 'json'):
      elastic_index = 'events'
      result = EMGet(client=es_client,
                    index=elastic_index,
                    doc_id=event_id)
      event = result['_source']
      if response_format == 'prettyprint':
          return PrettyJSONResponse(event)
      elif response_format == 'csv':
        ....
      else:
          return event

/event/event_1 would give me the standard json output from fastapi while, /event/event_1?response_format=prettyprint would return the data with indentation and formatting that makes the output easy to read even without the use of parsing tools such as chrome extensions and the like.