如何使用FastAPI返回JSON格式的数据?

2024-03-10

我在两者中编写了具有相同功能的相同 API 应用程序FastAPI and Flask。但是,当返回 JSON 时,两个框架之间的数据格式不同。两者使用相同的json库,甚至相同的代码:

import json
from google.cloud import bigquery
bigquery_client = bigquery.Client()

@router.get('/report')
async def report(request: Request):
    response = get_clicks_impression(bigquery_client, source_id)
    return response

def get_user(client, source_id):
    try:
        query = """ SELECT * FROM ....."""
        job_config = bigquery.QueryJobConfig(
            query_parameters=[
                bigquery.ScalarQueryParameter("source_id", "STRING", source_id),
            ]
        )
        query_job = client.query(query, job_config=job_config)  # Wait for the job to complete.
        result = []
        for row in query_job:
            result.append(dict(row))
        json_obj = json.dumps(result, indent=4, sort_keys=True, default=str)

    except Exception as e:
        return str(e)

    return json_obj

返回的数据在Flask是字典:


  {
    "User": "fasdf",
    "date": "2022-09-21",
    "count": 205
  },
  {
    "User": "abd",
    "date": "2022-09-27",
    "count": 100
  }
]

而在FastAPI是字符串:

"[\n    {\n        \"User\": \"aaa\",\n        \"date\": \"2022-09-26\",\n        \"count\": 840,\n]"

我使用的原因json.dumps()就是它date不能是可迭代的。


错误的做法

如果在返回对象之前序列化该对象,请使用json.dumps()(如您的示例所示),例如:

import json

@app.get('/user')
async def get_user():
    return json.dumps(some_dict, indent=4, default=str)

返回的 JSON 对象最终将被连载两次,正如 FastAPI 将自动地在幕后序列化返回值。因此,您最终得到输出字符串的原因是:

"[\n    {\n        \"User\": \"aaa\",\n        \"date\": \"2022-09-26\",\n ... 

解决方案

查看可用的解决方案,以及下面关于 FastAPI/Starlette 如何在后台工作的说明。

Option 1

第一个选项是返回数据(例如dict, list等)照常 - 即使用,例如,return some_dict——还有 FastAPI,在幕后,会自动将该返回值转换为 JSON https://fastapi.tiangolo.com/advanced/response-directly/,首先将数据转换为 JSON 兼容的数据后,使用jsonable_encoder https://fastapi.tiangolo.com/tutorial/encoder/. The jsonable_encoder ensures不可序列化的对象,例如datetime https://docs.python.org/3/library/datetime.html对象,被转换为str。然后,FastAPI 会将 JSON 兼容的数据放入JSONResponse https://fastapi.tiangolo.com/advanced/custom-response/?h=jsonresp#jsonresponse,这将返回一个application/json对客户端的编码响应(这也在选项 1 中进行了解释)这个答案 https://stackoverflow.com/a/71205127/17865804). The JSONResponse,从Starlette的源代码中可以看出here https://github.com/encode/starlette/blob/858629f5188bc79d452600b1eb90eaa0045f6454/starlette/responses.py#L181,将使用Python标准json.dumps()序列化dict(对于替代/更快的 JSON 编码器,请参阅这个答案 https://stackoverflow.com/a/73580096/17865804 and 这个答案 https://stackoverflow.com/a/74173023/17865804).

Example

from datetime import date


d = [
    {"User": "a", "date": date.today(), "count": 1},
    {"User": "b", "date": date.today(), "count": 2},
]


@app.get('/')
def main():
    return d

以上是相等的 to:

from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder

@app.get('/')
def main():
    return JSONResponse(content=jsonable_encoder(d))

Output:

[{"User":"a","date":"2022-10-21","count":1},{"User":"b","date":"2022-10-21","count":2}]


返回一个JSONResponse或自定义Response直接(在下面的选项 2 中演示),以及继承自的任何其他响应类Response(参见FastAPI的文档here https://fastapi.tiangolo.com/advanced/custom-response/,以及 Starlette 的文档here https://www.starlette.io/responses/和响应的实施here https://github.com/encode/starlette/blob/master/starlette/responses.py),还允许指定一个custom status_code,如果他们愿意的话。 FastAPI/Starlette的实现JSONResponse可以找到类here https://github.com/encode/starlette/blob/da7adf246de5495b154b45e32d6fa95e181993d8/starlette/responses.py#L185,以及可以使用的 HTTP 代码列表(而不是传递HTTP 响应状态码 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status as an int直接)可见here https://github.com/encode/starlette/blob/da7adf246de5495b154b45e32d6fa95e181993d8/starlette/status.py#L11。例子:

from fastapi import status
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder

@app.get('/')
def main():
    return JSONResponse(content=jsonable_encoder(d), status_code=status.HTTP_201_CREATED)

Option 2

如果出于任何原因(例如,尝试强制使用某些自定义 JSON 格式),您必须在返回对象之前序列化该对象,然后您可以返回自定义Response直接地 https://fastapi.tiangolo.com/advanced/response-directly/#returning-a-custom-response,如中所述这个答案 https://stackoverflow.com/a/72246260/17865804。根据文档 https://fastapi.tiangolo.com/advanced/response-directly/#notes:

当您返回一个Response直接它的数据是not已验证, 转换(序列化),也不会自动记录。

另外,如上所述here https://fastapi.tiangolo.com/advanced/custom-response/?h=jsonresp#response:

FastAPI(实际上是 Starlette)会自动包含一个 内容长度标头。它还将包括一个 Content-Type 标头, 基于media_type并附加文本类型的字符集。

因此,您还可以设置media_type无论您期望数据是什么类型;在这种情况下,即application/json。下面给出示例。

Note 1:此答案中发布的 JSON 输出(在选项 1 和选项 2 中)是直接通过浏览器访问 API 端点的结果(即,通过在浏览器的地址栏中键入 URL,然后按 Enter 键)。如果您通过 Swagger UI 测试了端点/docs相反,您会看到缩进不同(在两个选项中)。这是由于 Swagger UI 格式的原因application/json回应。如果您还需要在 Swagger UI 上强制自定义缩进,则可以避免指定media_type为了Response在下面的例子中。这将导致内容显示为text,作为Content-Type响应中会缺少标头,因此 Swagger UI 无法识别数据的类型,以便自定义它们的格式(如果是application/json反应)。

Note 2:设置default论证str in json.dumps() https://docs.python.org/3/library/json.html#json.dumps是什么使得序列化成为可能date对象,否则如果未设置,您将得到:TypeError: Object of type date is not JSON serializable. The default是一个为无法序列化的对象调用的函数。它应该返回对象的 JSON 编码版本。在这种情况下是str,这意味着每个不可序列化的对象都会转换为字符串。您还可以使用自定义函数或JSONEncoder子类,如图所示here https://stackoverflow.com/questions/11875770/how-to-overcome-datetime-datetime-not-json-serializable,如果您想以自定义方式序列化对象。此外,正如前面选项 1 中提到的,可以使用替代 JSON 编码器,例如orjson,与标准相比,这可能会提高应用程序的性能json图书馆(参见这个答案 https://stackoverflow.com/a/73580096/17865804 and 这个答案 https://stackoverflow.com/a/74173023/17865804).

Note 3:FastAPI/Starlette 的Response https://github.com/encode/starlette/blob/858629f5188bc79d452600b1eb90eaa0045f6454/starlette/responses.py#L38接受作为content论点要么str or bytes目的。如实现所示here https://github.com/encode/starlette/blob/858629f5188bc79d452600b1eb90eaa0045f6454/starlette/responses.py#L60,如果你没有通过bytes对象,Starlette 将尝试使用它进行编码content.encode(self.charset)。因此,例如,如果您通过了dict,你会得到:AttributeError: 'dict' object has no attribute 'encode'。在下面的示例中,一个 JSONstr被传递,稍后将被编码为bytes(您也可以在将其传递给Response目的)。

Example

from fastapi import Response
from datetime import date
import json


d = [
    {"User": "a", "date": date.today(), "count": 1},
    {"User": "b", "date": date.today(), "count": 2},
]


@app.get('/')
def main():
    json_str = json.dumps(d, indent=4, default=str)
    return Response(content=json_str, media_type='application/json')

Output:

[
    {
        "User": "a",
        "date": "2022-10-21",
        "count": 1
    },
    {
        "User": "b",
        "date": "2022-10-21",
        "count": 2
    }
]
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用FastAPI返回JSON格式的数据? 的相关文章

随机推荐