DrakaSAN
DrakaSAN

Reputation: 7853

Ensure the POST data is valid JSON

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.

Upvotes: 6

Views: 7435

Answers (3)

Martijn Pieters
Martijn Pieters

Reputation: 1123440

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, e):
    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.

Upvotes: 10

DrakaSAN
DrakaSAN

Reputation: 7853

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.

Upvotes: 1

Andrii Rusanov
Andrii Rusanov

Reputation: 4606

You can try to decode JSON object using python json library. The main idea is to take plain request body and try to convert to JSON.E.g:

import json
...
# somewhere in view
def view():
    try:
        json.loads(request.get_data())
    except ValueError:
        # not a JSON! return error
        return {'error': '...'}
    # do plain stuff

Upvotes: 0

Related Questions