160

What is the difference between the data and json parameters in the Python Requests package?

It is unclear from the documentation.

Does this code:

import requests
import json
d = {'a': 1}
response = requests.post(url, data=json.dumps(d))

( note that we convert the dict to JSON here ☝️ ! )

... do anything different than:

import requests
import json
d = {'a': 1}
response = requests.post(url, json=d)

If so, what?

Does the latter automatically set the Content-Type HTTP header to application/json?

4 Answers 4

134

To answer my own question, it appears my two examples above do the same thing and that using the json parameter does indeed set the content-type in the headers to application/json. In my first example above using the data parameter, the content-type in the headers would need to be set manually.

5
  • 2
    This behaves like an API should behave. You can look that up here github.com/kennethreitz/requests/blob/master/requests/… as well.
    – wenzul
    Commented Nov 1, 2014 at 2:54
  • 7
    Agreed, just wasn't documented anywhere without actually reading the code. Commented Nov 1, 2014 at 6:23
  • 34
    This is indeed sane behavior, but it would be helpful to properly document it. json=data with data being a dict is not necessarily obvious. My first instinct was json=json.dumps(data) because it felt more accurate. Then I got unrelated errors on the remote API's end, because it was receiving the result of a json string further encoded in json (i.e. double dumped). Difficult to detect since it's a simple string and thus still valid json. To add to the confusion, when logging what the receiving end obtained, the output is indistinguishable whether in string or dict form. Commented Apr 24, 2018 at 11:10
  • Updating the earlier requests code link to this.
    – jarmod
    Commented Apr 15, 2022 at 16:34
  • Data in the body payload is not serialised to a string in the same way for the two cases, irrespective to the header differences. Other answers explain the difference and provide a more accurate answer.
    – hi2meuk
    Commented Aug 4, 2022 at 8:19
89

As of Dec 2021 the requests documentation about using data vs json is now pretty clear about the difference.

(With our small contribution to it - my PR and your upvotes that confirmed that this used to be an issue. Thank you!).


P.S.

This does NOT answer the OP question but if the FIRST piece of code would be a bit different:

import requests
import json
d = {'a': 1}
response = requests.post(url, data=d)

(note that the dict d is NOT converted to JSON string here!)

And if the second code would be the same (copying it for completeness):

import requests
import json
d = {'a': 1}
response = requests.post(url, json=d)

...then the result would be quite a lot different.

First code would generate a request with content type set to application/x-www-form-urlencoded and the data in this format, so: "a=1"

The second code would generate a request with content type set to application/json and in fact the data in this format, so {"a": 1} - a JSON string.

4
  • 1
    I came across this situation in a test scenario, the json param was passing tests - stripping it made sure we tested the payload as it would if it was passed like the data param. loads(request.data.strip(b'"')) Thanks for the contribution!
    – Mike
    Commented May 20, 2021 at 19:13
  • 2
    To add on to what Greg said, if the data param is supplied a string instead of a dict, like the OP did in their first code above (response = requests.post(url, data=json.dumps(d))) then, instead of defaulting the content type header to application/x-www-form-urlencoded, it doesn't appear to generate any default content type header, as per this code.
    – Seth
    Commented Feb 9, 2022 at 17:42
  • 1
    As of this moment, python-requests.org is not working. The documentation is also available at requests.readthedocs.io/en/latest/user/quickstart/…
    – Jimothy
    Commented Aug 11, 2022 at 14:11
  • Thanks @Jimothy, I updated the URL in my answer. Commented Aug 12, 2022 at 9:21
4

Talking only from my experience here, but please note that it should be preferred to use the json field with the dict, rather than dumping the dict in the data field.

Again, talking only from experience, I did not study the code itself, but it seems that the requests library does some more clever json serialization than just json.dumps. When using json.dumps in data field, I have encountered several instances where this resulted in an "value is not a valid dict" error response from the (FastAPI) server. Using the json field instead fixed these issues.

EDIT: I went through the code today. If you use the json parameter, it seems the requests library really only sets Content-Type and dumps it:

from .compat import json as complexjson
content_type = 'application/json'
body = complexjson.dumps(json)
if not isinstance(body, bytes):
    body = body.encode('utf-8')

where in the requests.compat, the json is just:

try:
    import simplejson as json
except ImportError:
    import json

... therefore I really cannot figure out why doing this manually using the data parameter just sometimes fails to work. ¯\_(ツ)_/¯

0

I agree with all the answer above, but there is a big difference in the interpretation of 'null' value :

  • with python, the way to express the equivalent of a 'null' json value is to use the object None
  • with parameter data=, this value is interpreted like a "None" value, that is like the string 'None'
  • with pamemeter json=, None is interpreted as 'null' json value, what is expected generally

That's the only difference I found

Not the answer you're looking for? Browse other questions tagged or ask your own question.