错误的做法
如果在返回对象之前序列化该对象,请使用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
}
]