Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensure the POST data is valid JSON

Tags:

python

json

flask

I am developping a JSON API with Python Flask.
What I want is to always return JSON, with a error message indicating any error that occured.

That API also only accept JSON data in the POST body, but Flask by default return a HTML error 400 if it can't read the data as JSON.

Preferably, I d also like to not force the user to send the Content-Type header, and if raw or text content-type, try to parse the body as JSON nonetheless.

In short, I need a way to validate that the POST body's is JSON, and handle the error myself.

I've read about adding decorator to request to do that, but no comprehensive example.

like image 882
DrakaSAN Avatar asked Sep 12 '16 13:09

DrakaSAN


2 Answers

You have three options:

  • Register a custom error handler for 400 errors on the API views. Have this error return JSON instead of HTML.

  • Set the Request.on_json_loading_failed method to something that raises a BadRequest exception subclass with a JSON payload. See Custom Errors in the Werkzeug exceptions documentation to see how you can create one.

  • Put a try: except around the request.get_json() call, catch the BadRequest exception and raise a new exception with a JSON payload.

Personally, I'd probably go with the second option:

from werkzeug.exceptions import BadRequest
from flask import json, Request, _request_ctx_stack


class JSONBadRequest(BadRequest):
    def get_body(self, environ=None):
        """Get the JSON body."""
        return json.dumps({
            'code':         self.code,
            'name':         self.name,
            'description':  self.description,
        })

    def get_headers(self, environ=None):
        """Get a list of headers."""
        return [('Content-Type', 'application/json')]


def on_json_loading_failed(self):
    ctx = _request_ctx_stack.top
    if ctx is not None and ctx.app.config.get('DEBUG', False):
        raise JSONBadRequest('Failed to decode JSON object: {0}'.format(e))
    raise JSONBadRequest()


Request.on_json_loading_failed = on_json_loading_failed

Now, every time request.get_json() fails, it'll call your custom on_json_loading_failed method and raise an exception with a JSON payload rather than a HTML payload.

like image 65
Martijn Pieters Avatar answered Oct 10 '22 15:10

Martijn Pieters


Combining the options force=True and silent=True make the result of request.get_json be None if the data is not parsable, then a simple if allow you to check the parsing.

from flask import Flask
from flask import request

@app.route('/foo', methods=['POST'])
def function(function = None):
    print "Data: ", request.get_json(force = True, silent = True);
    if request.get_json() is not None:
        return "Is JSON";
    else:
        return "Nope";

if __name__ == "__main__":
    app.run()

Credits to lapinkoira and Martijn Pieters.

like image 42
DrakaSAN Avatar answered Oct 10 '22 15:10

DrakaSAN