Is there a way to pretty print / prettify a JSON response in FastAPI?
Asked Answered
C

3

6

I'm looking for something that is similar to Flask's app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True.

Civil answered 1/6, 2021 at 6:13 Comment(0)
P
14

This is taken from David Montague.

You can annotate any endpoint with a custom response class, for example

@app.get("/config", response_class=PrettyJSONResponse)
def get_config() -> MyConfigClass:
    return app.state.config

An example for PrettyJSONResponse could be (indent=4 is what you were asking)

import json, typing
from starlette.responses import Response

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

    def render(self, content: typing.Any) -> bytes:
        return json.dumps(
            content,
            ensure_ascii=False,
            allow_nan=False,
            indent=4,
            separators=(", ", ": "),
        ).encode("utf-8")
Perversity answered 17/9, 2021 at 10:38 Comment(3)
Keep in mind, PrettyJSONResponse BLOCKS async functions on large data sets, so this needs to be used carefully or will cause performance issues in certain cases.School
@EricLongstreet nice catch. How does one fix that? Simply adding async to the def render() won't work. Mypy won't like it.Chandra
@BrandonStivers, added an answer example.School
S
2

In order to avoid blocking the event loop for large lists of models, I ended up with a solution like the one below.

This should:

  1. Not block the event loop
  2. Stream results (much faster)
  3. Release event loop in between model dict actions
async def streaming_output_json(content: List[BaseModel]) -> StreamingResponse:
    """
    Convert a list of Pydantic models to a JSON StreamingResponse.
    """
    async def jsonify_async(models: List[BaseModel]) -> AsyncIterable[str]:
        yield '['
        for i, model in enumerate(models):
            await asyncio.sleep(0)
            if i > 0:
                yield ','
            result_dict = jsonable_encoder(model)
            serialized_dict = json.dumps(result_dict)
            yield serialized_dict
        yield ']'

    return StreamingResponse(jsonify_async(models=content), media_type='application/json')

@router.get("")
async def get_huge_result():
    results = [model1, model2, ...]
    return await streaming_output_json(results)
School answered 14/3, 2023 at 15:58 Comment(3)
Thanks for the example. I am developing a FastAPI version of httpbin (with a few extra features), and this should come in handy. Hopefully I can adapt it to a ORJSONResponse. It should work by just tossing the output into ORJSONResponse(streaming_output_json(results)). Hopefully ORJSONResponse doesn't reformat it back into a single line. lolChandra
keep in mind if orjsonresponse isn't async, you will have the same issue. if you want to use it, you'd be better off switching it out in the for loop.School
trolling the FastAPI source code, it looks like the underlying orjson calls aren't async (like most of its other code). However, orjson is 5 to 6 times faster than regular json. So I don't think it'll bottleneck most things in the given dev environment this will be used for.Chandra
L
-3

I'm not sure what exactly your problem is, can you tell what the background of your need?

however, because FASTAPI is based on open standards(OpenAPI, JSONSchema) it has automatic docs. --> FastAPI Auto Docs.

you have the Swagger UI under host/docs. or the ReDoc under host/redoc. Both will give you pretty representation of the JSON response in ease.

Laszlo answered 1/6, 2021 at 6:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.