如何将任何有效的JSON格式转换为可读取的正文?

88

我还没有找到针对那个使用案例的文档。 我该如何获取请求主体,确保它是有效的JSON(任何有效的JSON,包括数字,字符串,布尔值和null,不仅限于对象和数组),并获取实际的JSON。 使用Pydantic强制JSON具有特定的结构。


请查看这个答案,以及这个答案 - Chris
8个回答

125

您可以在Request对象中找到几乎所有的东西。

您可以使用request.json()获取请求正文,这将以字典形式给您解析后的JSON。

from fastapi import Request, FastAPI

@app.post("/dummypath")
async def get_body(request: Request):
    return await request.json()
如果您想将请求的主体作为字符串获取,可以使用 request.body()

1
在此答案的“编辑”历史记录中,似乎仅在代码块内request.body()被替换为request.json() :-) - tgogos
5
我用表单数据在请求体中发送了一个请求,但是收到了这个错误信息 "ValueError: [TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have dict attribute')]"。 - Parashuram
我正在尝试访问错误日志的正文。当我尝试从记录器中使用request.body()访问正文时,我得到的是"<coroutine object Request.body at 0xram-address>"而不是字符串形式的正文。 - Naveen Reddy Marthala
1
@ChristianSicari 请查看这个答案,如果您想在使用普通的def定义的端点中获取原始的body或JSON(使用.json(),实际上返回一个dict对象,如此处所示)。 - Chris
1
@parashuram 另一个得票最高的答案使用了 Body(...) 来解决了我的问题。 - Matt
显示剩余6条评论

62
接受的答案也是有效的,但是FastAPI提供了一种内置的方法来实现这一点-在文档中查看body中的单值部分。 具有默认Body的参数会获取所有与传递的Pydantic类型参数不匹配的有效负载(在我们的情况下是整个有效负载),并将其转换为适当的Python类型。如果JSON无效,则会产生标准的验证错误。
from typing import Any
from fastapi import Body, FastAPI

app = FastAPI()


@app.post('/test')
async def update_item(
        payload: Any = Body(None)
):
    return payload
更新:关于第一个Body位置参数(默认值)的说明 - 这里的None表示请求体是可选的,...(省略号) - 表示它是必需的(不传递任何内容实际上会保持它是必需的)。在带有省略号的必需参数文档部分中了解更多信息。 此外,这个解决方案适用于只包含nulltruefalse、任意字符串和任意数字的JSON。

1
dict = Body() 赢了。这个简单的语句让我苦苦追寻了很久。 - zelusp
1
@zelusp 我不知道为什么request.json()经常与常规的参数类型一起推荐和使用。在我看来,除非你需要处理复杂的请求体,否则Body()是FastAPI最自然的方式。 - Oleh Rybalchenko
3
这无疑是正确的做法,应该被接受为答案。 - brandonscript
抱歉,但我怀疑这个不接受nulltruefalse、任何字符串、任何数字和数组。 - undefined
@caeus它实际上可以处理数组。谢谢提醒,我稍微修改了我的答案以反映在类型提示中,但无论如何它都可以工作。nulltruefalse,任何字符串,任何数字 - 这些单独不能构成有效的JSON,所以它不应该接受它们。 - undefined
显示剩余3条评论

22
如果您有信心确认输入数据是“有效的JSON”,那么您可以创建一个简单的类型注释结构来接收任意的JSON数据。
from fastapi import FastAPI
from typing import Any, Dict, AnyStr, List, Union

app = FastAPI()

JSONObject = Dict[AnyStr, Any]
JSONArray = List[Any]
JSONStructure = Union[JSONArray, JSONObject]


@app.post("/")
async def root(arbitrary_json: JSONStructure = None):
    return {"received_data": arbitrary_json}

示例

1. JSON对象

curl -X POST "http://0.0.0.0:6022/" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\"test_key\":\"test_val\"}"

响应:

{
  "received_data": {
    "test_key": "test_val"
  }
}

2. JSON数组

curl -X POST "http://0.0.0.0:6022/" -H  "accept: application/json" -H  "Content-Type: application/json" -d "[\"foo\",\"bar\"]"

响应:

{
  "received_data": [
    "foo",
    "bar"
  ]
}

如果您不确定传入数据的内容类型,最好解析请求正文。

可以这样做:

from fastapi import FastAPI, Request

app = FastAPI()


@app.post("/")
async def root(request: Request):
    return {"received_request_body": await request.body()}

这种方法的优点在于请求主体可以包含任何类型的数据,如JSON、form-data、multipart-form-data等。


2
这不对,如果它不是有效的JSON,Pydantic会抛出一个错误。除此之外,Python没有所谓的“JSON数组”,它将JSON解析为字典。因此,OP的问题非常清楚,他想要获取未解析的实际JSON,而获取实际JSON的唯一方法是从request.body()中获取。 - Yagiz Degirmenci
@YagizcanDegirmenci 是的,有一种叫做JSON数组的东西,它不能被解析成Python的dict,但可以被解析成Python的list - JPG
是的,我非常确定 OP 在问题中并不是指 JSON 数组。 - Yagiz Degirmenci
OP 没有提到 JSON 对象。此答案适用于那些想要从请求中获取一个 任意的 JSON(数组或对象),而不是作为字符串的人,因为我已经提到过。 - JPG
3
实际上,我希望任何有效的JSON都可以。包括数字、布尔值、null和字符串。而不仅仅是对象和数组。 - caeus

5
from fastapi import Request

async def synonyms__select(request: Request):
    return await request.json()

将返回一个JSON对象。


3
这是一个示例,用于打印请求内容,它将打印JSON主体(如果可以解析为JSON),否则只打印主体的原始字节。
async def print_request(request):
        print(f'request header       : {dict(request.headers.items())}' )
        print(f'request query params : {dict(request.query_params.items())}')  
        try : 
            print(f'request json         : {await request.json()}')
        except Exception as err:
            # could not parse json
            print(f'request body         : {await request.body()}')
    
    
    @app.post("/printREQUEST")
    async def create_file(request: Request):
        try:
            await print_request(request)
            return {"status": "OK"}
        except Exception as err:
            logging.error(f'could not print REQUEST: {err}')
            return {"status": "ERR"}

0

如果你正在使用BaseModel并且想要一个JSON字段,你可以从pydantic导入Json。

from fastapi import FastAPI
from pydantic import BaseModel, Json, Field


app = FastAPI()


class MockEndpoint(BaseModel):
    endpoint: str = Field(description="API endpoint to mock")
    response: Json = Field(description="Example response of the endpoint")


@app.get("/")
async def root():
    return {"message": "Hello World"}


@app.post("/mock")
async def mock_request(mock_endpoint: MockEndpoint):
    return mock_endpoint

0
FastAPI有一个JSON编码器

在某些情况下,您可能需要将数据类型(如Pydantic模型)转换为与JSON兼容的格式。

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
import simplejson as json

class SubmitGeneral(BaseModel):
    controllerIPaddress: str
    readerIPaddress: str
    ntpServer: str

@app.post("/submitGeneral")
async def submitGeneral(data: SubmitGeneral):
    data = jsonable_encoder(data)
    #data = json.loads(data.json()) # same as above line
    
    print(f"data = {json.dumps(data)}")

    # you have to access the properties with brackets, not by dot notation
    query = f"update LocalPLC set ControllerIpAddress = '{data['controllerIPaddress']}', ReaderIPAddress = '{data['readerIPaddress']}'"

    return {"status": "OK"}

0

这也是另一种方法,可以将任何形式的 Json 作为输入

@app.post("/dict/")
async def post_dict(data: Dict[str, Any]):
    return data
但我猜这不是做事情最干净的方式

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,