How to read the request body using orjson library in FastAPI?
Asked Answered
G

1

4

I am writing code to receive a JSON payload in FastAPI.

Here is my code:

from fastapi import FastAPI, status, Request
from fastapi.responses import ORJSONResponse
import uvicorn
import asyncio
import orjson

app = FastAPI()

@app.post("/", status_code = status.HTTP_200_OK)
async def get_data(request: Request):
    param = await request.json()
    return param

However, what I want is request.json() to be used with orjson instead of the default json library of Python. Any idea how to address this problem? Please help me, thanks.

Gath answered 23/10, 2022 at 4:7 Comment(2)
Does this answer your question? FastAPI is very slow in returning a large amount of JSON dataVagabondage
it seems to be reading and then dumping a JSON file while mine is getting a payload in JSON format. In request.json, it was used json.dumps(), I wanna replace it with orgjson package.Gath
V
5

Reading request data using orjson

When calling await request.json(), FastAPI (actually Starlette) first reads the body (using the .body() method of the Request object), and then calls json.loads() (using the standard json library of Python) to return a dict/list object to you inside the endpoint (see the implementation here)—it doesn't use .dumps(), as you mentioned in the comments section, as that method is used to serialise a Python object into JSON.

Hence, to read/convert the request body using orjson instead, you can use the below (if you would like to retrieve the raw body within a def instead of async def endpoint, please have a look at this answer):

from fastapi import FastAPI, Request
import orjson

app = FastAPI()

@app.post('/')
async def submit(request: Request):
    body = await request.body()
    data = orjson.loads(body)
    return 'success'

Returning response data using orjson

When returning data such as dict, list, etc, FastAPI will automatically convert that return value into JSON, using the Python standard json.dumps(), after inspecting every item inside and making sure it is serializable with JSON, using the JSON Compatible Encoder (see this answer for more details). Hence, if you would like to use the orjson library instead, you would need to send a custom Response directly, as described in this answer. Example:

from fastapi import FastAPI, Request
import orjson

app = FastAPI()

@app.post('/')
async def submit(request: Request):
    body = await request.body()
    data = orjson.loads(body)
    return Response(orjson.dumps(data), media_type='application/json')

Returning response data using FastAPI's ORJSONResponse

Alternatively, you could use the ORJSONResponse provided by FastAPI (still make sure you have the orjson libray installed, as well as the content that you are returning is serializable with JSON). Have a look at futher documentation here and here on how to customise and/or set ORJSONResponse as the default response class (the implementation of the ORJSONResponse could be found here).

Note that when using the response_class parameter in the endpoint's decorator to set the Response class, one does not necessarily need to use that response class when returning the data from the endpoint as well. This is because FastAPI, behind the scenes, will encode/serialise the data based on the response_class you set, as well as use it to define the "media type" of the response. Hence, one could either set response_class=ORJSONResponse or return ORJSONResponse(...). Having said that, it wouldn't hurt using both, as shown in the example below and in some examples provided in the official FastAPI documentation, not only for clarity purposes, but also for proper Swagger UI documentation purposes. The response_class will inform Swagger UI/OpenAPI docs for the expected media type of a successful response—one could confirm that by looking at the expected "Responses" and their media type under an endpoint in /docs (see FastAPI's documentation for declaring additional responses in OpenAPI).

from fastapi import FastAPI, Request
from fastapi.responses import ORJSONResponse

app = FastAPI()

@app.post('/', response_class=ORJSONResponse)
async def submit(request: Request):
    body = await request.body()
    return ORJSONResponse(body.decode('utf-8'))

In the case of the example above, one wouldn't notice any difference in Swagger UI autodocs whether or not using the response_class=ORJSONResponse, as application/json is the default media type for FastAPI endpoints, regardless. However, in a follwoing example in this answer, where HTMLResponse is returned from a specific endpoint, if one didn't use response_class=HTMLResponse, Swagger UI/OpenAPI docs would incorrectly indicate that an application/json response is expected from that endpoint in case of a successful response.

Setting ORJSONResponse as the default_response_class

As explained in the documentation, one can define the default response class for their FastAPI application, as follows. In that way, every response from FastAPI will be encoded using ORJSONResponse in the example below; thus, there would be no need for you to either set response_class=ORJSONResponse or use return ORJSONResponse(...).

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)


@app.get("/items")
async def read_items():
    return [{"item_id": "Foo"}]

You will still be able to override the default response class in an endpoint if needed, as shown in the examples earlier, by either setting the response_class parameter in the endpoint's decorator or returning that Response class directly, or using both methods, as explained earlier, so that Swagger UI/OpenAPI documentation is informed for the expected response type of that endpoint. For instance:

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse, HTMLResponse

app = FastAPI(default_response_class=ORJSONResponse)


@app.get("/items")
async def read_items():
    return [{"item_id": "Foo"}]


@app.get("/html", response_class=HTMLResponse)
async def get_html():
    html_content = """
    <html>
        <body>
            <h1>HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)

Please make sure to have a look here, here, as well as here and here to learn about the various approaches of sending JSON data to a FastAPI backend, and how to define an endpoint to expect and validate JSON data, instead of relying on using await request.json() (which is useful when the app requires passing arbitrary JSON data, but does not perform any validation on the data).

Vagabondage answered 23/10, 2022 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.