pythonjsonfastapi

FastAPI JSON List in body raises 'There was an error parsing the body' exception


Using the FastAPI documentation, I'm attempting to send a POST request to an endpoint that takes a JSON object with a list as input:

{
   "urls":[
      "https://www.website.com/",
      "https://www.anotherwebsite.com"
   ]
}

Simplified reproducible example app code:

from fastapi import FastAPI

app = FastAPI()


@app.post("/checkUrls")
def checkUrls(urls: list[str]):
    return urls


#alternatively (given that I expect each url to be unique)
@app.post("/checkUrlsSet")
def checkUrlsSet(urls: set[str]):
    return urls

When I test the endpoint using the Python requests library like this:

import requests

rListTest = requests.post("http://127.0.0.1:8000/checkUrls", json = {"urls" : ["https://www.website.com/", "https://www.anotherwebsite.com"]})

print(rListTest.status_code)
print(rListTest.json())

rSetTest = requests.post("http://127.0.0.1:8000/checkUrlsSet", json = {"urls" : ["https://www.website.com/", "https://www.anotherwebsite.com"]})

print(rSetTest.status_code)
print(rSetTest.json())

in both cases, I get the a 400 status code error and the response is:

{'detail': 'There was an error parsing the body'}

I'm using python 3.10 on a linux ubuntu 22.04 vm with an ampere ARM processor if it matters...

I've also tried the following app code:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class UrlsList(BaseModel):
    urls: list[str]

class UrlsSet(BaseModel):
    urls: set[str]

@app.post("/checkUrls")
async def checkUrls(urls: UrlsList):
    return urls


@app.post("/checkUrlsSet")
async def checkUrlsSet(urls: UrlsSet):
    return urls

which returns a 422 Unprocessable Entity error:

{'detail': [{'loc': ['body'], 'msg': 'value is not a valid list', 'type': 'type_error.list'}]}

{'detail': [{'loc': ['body'], 'msg': 'value is not a valid set', 'type': 'type_error.set'}]}

Solution

  • Option 1

    The reason of the error you get is explained here and here (see this answer as well). In brief, when defining a single body parameter like urls: list[str] or urls: list[str] = Body(), FastAPI will expect a request body like this:

    [
      "string1", "string2"
    ]
    

    You can confirm the above by using the Swagger UI autodocs at /docs. Hence, your Python requests client should look like this:

    url = 'http://127.0.0.1:8000/checkUrls'
    payload = ['https://www.website.com/', 'https://www.anotherwebsite.com']
    r = requests.post(url, json=payload)
    print(r.status_code)
    print(r.json())
    

    Option 2

    If you wanted to have the user adding the urls key in the JSON string, then you would need to explicitly declare your parameter with Body() and use the special Body parameter embed (as described in the linked answers provided above). Example:

    from fastapi import Body
        
    @app.post('/checkUrls')
    def checkUrls(urls: list[str] = Body(..., embed=True)):
        return urls
    

    or, use a Pydantic model (just like in the last code snippet of your question, and as demonstrated in the linked answers earlier):

    class UrlsList(BaseModel):
        urls: list[str]
    
    @app.post('/checkUrls')
    async def checkUrls(urls: UrlsList):
        return urls
    

    The API endpoint would then expect a body like this:

    {
       "urls":[
          "string1",
          "string2"
       ]
    }
    

    Hence, the client side should then look like this:

    url = 'http://127.0.0.1:8000/checkUrls'
    payload = {'urls': ['https://www.website.com/', 'https://www.anotherwebsite.com']}
    r = requests.post(url, json=payload)
    print(r.status_code)
    print(r.json())
    

    For posting files together with JSON list, please have a look here, as well as here and here.