繁体   English   中英

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

[英]How to return data in JSON format using FastAPI?

我在FastAPIFlask中编写了具有相同功能的相同 API 应用程序。 但是,在返回 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() ,如您的示例所示),该对象最终将被序列化两次,因为 FastAPI 将自动序列化返回值。 因此,您最终得到输出字符串的原因。 请参阅下面的可用选项。

选项1

您通常可以返回dictlist等数据,并且 FastAPI 会在首先使用jsonable_encoder将数据转换为 JSON 兼容数据(例如dict )后自动将该返回值转换为 JSON 。 jsonable_encoder确保不可序列化的对象(例如datetime对象)被转换为str 然后,在幕后,FastAPI 会将与 JSON 兼容的数据放入JSONResponse中,这将向客户端返回application/json编码的响应。 JSONResponse的源代码中可以看到JSONResponse将使用 Python 标准json.dumps()来序列化dict (对于 alternatvie/faster JSON 编码器,请参见此答案)。

数据:

from datetime import date

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

API端点:

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

以上相当于:

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

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

输出:

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

选项 2

如果出于任何原因(例如,试图强制使用某种自定义 JSON 格式),您必须在返回对象之前对其进行序列化,然后您可以直接返回自定义Response ,如本答案中所述。 根据 文档

当您直接返回Response时,其数据不会自动验证、转换(序列化)或记录。

此外,如此所述:

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

因此,您还可以将media_type设置为您期望数据的任何类型; 在这种情况下,即application/json 示例如下。

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

注意 2 :在json.dumps()中将default参数设置为str可以序列化date对象,否则如果未设置,您将得到: TypeError: Object of type date is not JSON serializable default是为无法序列化的对象调用的函数。 它应该返回对象的 JSON 编码版本。 在这种情况下它是str ,这意味着每个不可序列化的对象都将转换为字符串。 如果您想以自定义方式序列化对象,也可以使用自定义函数或JSONEncoder子类,如此所示。

注 3 :FastAPI/Starlette 的Response接受strbytes对象作为content参数。 如此处的实现所示,如果您不传递bytes对象,Starlette 将尝试使用content.encode(self.charset)对其进行编码。 因此,例如,如果您传递了一个dict ,您将得到: AttributeError: 'dict' object has no attribute 'encode' 在下面的示例中,传递了一个 JSON str ,稍后会将其编码为bytes (您也可以在将其传递给Response对象之前自己对其进行编码)。

API端点:

from fastapi import Response
import json

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

输出:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM