[英]How to return a .csv file/Pandas DataFrame in JSON format using FastAPI?
[英]How to return data in JSON format using FastAPI?
我在FastAPI和Flask中编写了具有相同功能的相同 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 将自动序列化返回值。 因此,您最终得到输出字符串的原因。 请参阅下面的可用选项。
您通常可以返回dict
、 list
等数据,并且 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}]
如果出于任何原因(例如,试图强制使用某种自定义 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
接受str
或bytes
对象作为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.