0

The post and get functions for my Flask server look as follows:

from flask import Flask, request
import json
app = Flask(__name__)

tasks=[]

#Create a new task
@app.route('/v1/tasks', methods=['POST'])
def post():
    data=request.get_json()
    if "title" not in data:
        return bulkadd(data)
    title=data["title"]
    tasks.append(json.dumps({"id": len(tasks)+1, "title": title, "is_completed": "false"}))
    index=len(tasks)
    return json.dumps({"id": index}), 201

#List all tasks created
@app.route('/v1/tasks', methods=['GET'])
def getall():
   app.logger.info({"tasks": tasks})
   return json.dumps({"tasks": tasks})

After calling post twice, this is the output from get:

{"tasks": ["{\"is_completed\": \"false\", \"id\": 1, \"title\":\"Test Task 2\"}", "{\"is_completed\": \"false\", \"id\": 2, \"t:03] "POST /v1/tasks HTTP/1.1" 201 -itle\": \"Test Task 2\"}"]}

Instead, this is how I would like the format of the output:

{
   tasks: [
     {id: 1, title: "Test Task 1", is_completed: true},
     {id: 2, title: "Test Task 2", is_completed: false}
   ]
}

Why is the order different and what do the \" mean?

Thank you for your help!

Edit 1: Could you also help me why the following test to post function throws this error?:

Does this not generate a valid json?

return json.dumps({"id": index}), 201

_____________________________________ test_create_task _____________________________________
    def test_create_task():
        r = requests.post('http://localhost:5000/v1/tasks', json={"title": "My First Task"})>       assert isinstance(r.json()["id"], int)

project1-test3.py:6:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/home/alexanderfarr/.local/lib/python3.6/site-packages/requests/models.py:898: in json      
    return complexjson.loads(self.text, **kwargs)
/usr/lib/python3/dist-packages/simplejson/__init__.py:518: in loads
    return _default_decoder.decode(s)
/usr/lib/python3/dist-packages/simplejson/decoder.py:370: in decode
    obj, end = self.raw_decode(s)

1 Answers1

1

The reason why the output inside the tasks list has the \" is because the individual elements are actually a string, and given that JSON use the " character to quote strings, a literal " inside that must be escaped by prefixing it with the escape character, \, thus making \". Since you obviously don't want a string but the actual object, you simply need to not encode that object into a string before adding it into the list.

So instead of

def post():
    ...
    tasks.append(json.dumps({"id": len(tasks)+1, "title": title, "is_completed": "false"}))

Simply do

    tasks.append({"id": len(tasks)+1, "title": title, "is_completed": "false"})

Now, if you want a specific output order for the keys in a specific (non-alphabetic) way, this SO thread goes into the details behind that. In brief, you might want to do this for the post function:

import json
from collections import OrderedDict
from flask import Flask, request

app = Flask(__name__)
tasks = []

# Create a new task
@app.route('/v1/tasks', methods=['POST'])
def post():
    data = request.get_json()
    if "title" not in data:
        return bulkadd(data)
    title = data["title"]
    tasks.append(OrderedDict((
        ('id', len(tasks) + 1),
        ('title', title),
        ('is_completed', False),
    )))
    index = len(tasks)
    return json.dumps({"id": index}), 201


# List all tasks created
@app.route('/v1/tasks', methods=['GET'])
def getall():
   app.logger.info({"tasks": tasks})
   return json.dumps({"tasks": tasks}, indent=4)

Example run:

$ curl http://127.0.0.1:5000/v1/tasks --data '{"title": "hi"}' -H 'Content-Type: application/json'
{"id": 1}
$ curl http://127.0.0.1:5000/v1/tasks
{
    "tasks": [
        {
            "id": 1,
            "title": "hi",
            "is_completed": false
        }
    ]
}

Alternative, with the use of requests (with a recent enough version that supports the json keyword argument):

>>> import requests
>>> r = requests.post('http://localhost:5000/v1/tasks', json={"title": "My First Task"})
>>> r.json()["id"]
1
>>> print(requests.get('http://localhost:5000/v1/tasks').text)
{
    "tasks": [
        {
            "id": 1,
            "title": "My First Task",
            "is_completed": false
        }
    ]
}

You may want to consider not quoting false for is_completed (which I did here), as that turns the value into a string, rather than a boolean value which you may be intending to convey. Also note that for the GET version of this end point I added the indent=4 argument for json.dumps so that the pretty formatting is done, but note that this increases the size of the payload.

metatoaster
  • 17,419
  • 5
  • 55
  • 66
  • Thank you @metatoaster! Could you also help me with the new edit? Thank you! – Python-Data-Science-Learner Feb 28 '20 at 02:31
  • See this [thread](https://stackoverflow.com/questions/9733638/post-json-using-python-requests) on why that might not have worked, but in short you need at least `requests>=2.4.2` to use the `json` argument. I can't reproduce your issue with the appropriate version of `requests`. – metatoaster Feb 28 '20 at 02:48
  • I am quite sure I have requests>=2.4.2 since I can pass JSON as an argument. How could I validate this? – Python-Data-Science-Learner Feb 28 '20 at 02:51
  • One way is to step through the code with a debugger and verify all the values being passed are what you expected. Since you have asked a different question it looks like that isn't your actual problem and something else is hiding it. In any case answer is edited to include an example with requests. – metatoaster Feb 28 '20 at 02:58