Returning JSON from Flask

Philip Jones
NOVA Wealth
Published in
2 min readMar 20, 2019

When Flask was first released in 2010 most servers written returned HTML in their responses. What made the Flask API great is that it optimised for this usage by allowing a view to return HTML directly as a string, for example,

@app.route("/")
def hello_world():
return "<h1>Hello World</h1>"

In addition Flask adds syntactic sugar for the next two most common use cases, changing the status code and headers,

@app.route("/")
def hello_world():
return "<h1>Error</h1>", 400, {"X-Special": "Value"}

This can be compared to other frameworks, which lacking this sugar require something like this,

@app.route("/")
def hello_world():
return response.html("<h1>Error</h1>", status=400)

It is my view that details like this are what have made Flask the success it is today. However, web development has progressed and now most servers written return JSON, not HTML. Sadly in this case Flask version 1.0 has no sugar and requires,

@app.route("/")
def hello_world():
return jsonify({"error": "Invalid email"}), 400

instead I think Flask should allow,

@app.route("/")
def hello_world():
return {"error": "Invalid email"}, 400

which would be another simple detail making the Flask API better.

Proposed solution

It is possible to support the better syntax via this simple extension of Flask,

class JSONSyntaxFlask(Flask):

def make_response(self, rv):
"""Turn the rv into a full response.

This allows for the rv to be a dictionary which
indicates that the response should be JSON.
"""
if isinstance(rv, dict):
new_rv = jsonify(rv)
elif isinstance(rv, tuple) and isinstance(rv[0], dict):
new_rv = (jsonify(rv[0]), *rv[1:])
else:
new_rv = rv

return super().make_response(new_rv)

Which can then be used as you would use Flask, i.e.,

app = JSONSyntaxFlask(__name__)@app.route("/")
def hello_world():
return {"error": "Invalid email"}, 400

Aside on JSON types

This solution only allows objects to be returned at the top level, as used to be the case with Flask before the 0.11 release. This is to prevent confusion with existing return values, i.e. should (“a”, 200) represent a HTML response with a status code of 200 or be jsonified to ["a", 200]?

In addition I think that returning top level objects is good practice, as these objects are open for extension i.e. I can happily change the above view to,

@app.route("/")
def hello_world():
return {"error": "Invalid email", "code": "INVALID_EMAIL"}, 400

without breaking any existing client code.

Conclusion

I believe Flask’s API is what has made it the leading web framework in Python, yet it shouldn’t stand still. I’ve hopefully convinced you it can be improved and I hope to convince the current Flask maintainers in pull request #3111.
Update: As of the 24th May 2019 #3111 has been merged.

P.S. Quart Solution

Quart is a re-implementation of the Flask API using the async/await keywords. As such the same arguments for Flask apply to Quart, as does the solution, (note the bold changes),

class JSONSyntaxQuart(Quart):

async def make_response(self, rv):
"""Turn the rv into a full response.

This allows for the rv to be a dictionary which
indicates that the response should be JSON.
"""
if isinstance(rv, dict):
new_rv = jsonify(rv)
elif isinstance(rv, tuple) and isinstance(rv[0], dict):
new_rv = (jsonify(rv[0]), *rv[1:])
else:
new_rv = rv

return await super().make_response(new_rv)

--

--

Philip Jones
NOVA Wealth

Maintainer of Quart, Hypercorn and various other Python HTTP projects.