How to obtain the request JSON data in Django
One of the most infuriating things about Django’s request.POST attribute is that it only contains data if the request body is encoded with…
One of the most infuriating things about Django’s request.POST
attribute is that it only contains data if the request body is encoded with application/x-www-form-urlencoded
or multipart. If another application makes a request with application/json
, request.POST
will be an empty QueryDict. In this article, I’ll show you how to handle both formats.
Granted, Django is made to handle HTML form POST requests, which every browser encodes as application/x-www-form-urlencoded
. Additionally, a valid JSON body isn’t necessarily a key-value object. It could be a list or it could be a single object, like a string or a number. How would you make a QueryDict out of that?
But when we are presented with a valid dictionary, we can replace request.POST
with a QueryDict that contains all the data in it:
import json
from django.http import JsonResponse, QueryDict
class JsonMiddleware:
"""Middleware that parses JSON request body and sets it as a QueryDict
in the request object.
"""
invalid_json_http_status = 400
invalid_json_response = {'error': 'Invalid JSON request'}
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.method in ('GET', 'POST') \
and (content_type := request.META.get('CONTENT_TYPE')) \
and 'application/json' in content_type.lower():
try:
data = json.loads(request.body)
except ValueError:
return JsonResponse(
self.invalid_json_response,
status=self.invalid_json_http_status)
if isinstance(data, dict):
querydict = QueryDict(mutable=True)
querydict.update(data)
# set either request.GET or request.POST
setattr(request, request.method, querydict)
return self.get_response(request)
Then, in your settings file, just include JsonMiddleware:
MIDDLEWARE = [
...,
'path.to.JsonMiddleware',
...,
]
There are a few things that could be improved in this snippet (which weren’t done for simplicity’s sake):
Other HTTP methods (like PUT and DELETE) may also contain a request body, and that body could be JSON-encoded. But Django’s request object has no PUT or DELETE attributes, and although in theory you could set new attributes with those names, they would be non-standard.
Invalid JSON bodies are rejected with a JsonResponse (which normally goes together with a JSON request). The caller may have set a different format in the
Accept
header and we may choose to honor that.QueryDict respects Django’s setting
DATA_UPLOAD_MAX_NUMBER_FIELDS
. This solution doesn’t.