Nested dict data get silently ignored in POST request

I just spent the last several hours trying to figure out why I was able to successfully send an API POST request through `curl` but not through requests.

After much searching on SO, I finally figured out the solution, but I'm still confused why this should be a problem at all. Furthermore, it's very strange that requests does such a good job of simplifying HTTP requests but then reveals intricacies that an end user likely won't know how to deal with.

It turns out that `requests.post()` already has a `json` argument which handles non-trivial data dictionaries, but I don't understand why a user should need to know when to use `json` instead of `data`. To a simple user like me (and others: #2885), `data` simply takes a dictionary and parses it into a request. Using `json` in some cases but not others adds confusion and is not obvious at all. I also don't understand why I wouldn't want to *always* use `json` instead of `data`.

At the very least, if there's a strong reason why it makes sense to have both `data` and `json` arguments, a warning should be raised in these cases telling the user that they might not be sending what they think they're sending.

## Expected Result

Here's an example request of what I was sending through `curl`:

```bash
$ curl -X POST 'https://httpbin.org/post' -H 'Content-Type: application/json' -d '{"first": [{"second": {"third": "data"}}]}'

{
  "args": {}, 
  "data": "{\"first\": [{\"second\": {\"third\": \"data\"}}]}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "42", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.54.0"
  }, 
  "json": {
    "first": [
      {
        "second": {
          "third": "data"
        }
      }
    ]
  }, 
  "origin": "123.56.7.890, 123.56.7.890", 
  "url": "https://httpbin.org/post"
}
```

Sending (what I thought was) an equivalent request through requests always returned an invalid json error:

```python
import requests

headers = {"Content-Type": "application/json"}

data = {
    "first": [
        {
            "second": {
                "third": "data"
            }
        }
    ]
}

r = requests.post("https://httpbin.org/post", headers=headers, data=data)

print(r.json())
```

I expected the entire `data` dictionary to be sent in the request. However, when looking at the response from httpbin, it becomes clear that all the data from the second level is missing.

## Actual Result

This is the response from httpbin, with almost all of the sent data missing:

```bash
{'args': {},
 'data': 'first=second',
 'files': {},
 'form': {},
 'headers': {'Accept': '*/*',
             'Accept-Encoding': 'gzip, deflate',
             'Content-Length': '12',
             'Content-Type': 'application/json',
             'Host': 'httpbin.org',
             'User-Agent': 'python-requests/2.19.1'},
 'json': None,
 'origin': '123.56.7.890, 123.56.7.890',
 'url': 'https://httpbin.org/post'}
```

## Reproduction Steps

```python
import requests

headers = {"Content-Type": "application/json"}

data = {
    "first": [
        {
            "second": {
                "third": "data"
            }
        }
    ]
}

r = requests.post("https://httpbin.org/post", headers=headers, data=data)

print(r.json())
```

Changing the request fixes the issue, but the solution is (to me) non-obvious:
```python
r = requests.post("https://httpbin.org/post", headers=headers, json=data)
```

## System Information

    $ python -m requests.help

```
{
  "chardet": {
    "version": "3.0.4"
  },
  "cryptography": {
    "version": ""
  },
  "idna": {
    "version": ""
  },
  "implementation": {
    "name": "CPython",
    "version": "3.6.6"
  },
  "platform": {
    "release": "18.5.0",
    "system": "Darwin"
  },
  "pyOpenSSL": {
    "openssl_version": "",
    "version": null
  },
  "requests": {
    "version": "2.19.1"
  },
  "system_ssl": {
    "version": "1000212f"
  },
  "urllib3": {
    "version": "1.23"
  },
  "using_pyopenssl": false
}

```

Is there a recommendation for working around this?

I ran into the same (or closely related) problem today.  My nested json got turned into a list, using the keys as entries in the list and dropping the actual values.

I had some issues doing it like this:
r = requests.post(endpoint, data=params, headers=headers)

@martimlobao  had the answer, just change data to json
r = requests.post(endpoint, json=params, headers=headers)

Thanks @martimlobao 

It does seem ridiculous that using the standard `data` parameter as described in the docs should choose to silently discard data, just because some nesting was involved! So users are left having to browse Github to find issues like this one to learn about a workaround. At the very least, the limitation should be highlighted in the docs.

I realise that similar issues have been closed in the past with the suggestion that users use [Requests Toolbelt](https://toolbelt.readthedocs.io/en/latest/formdata.html), but as nested form data is such a common part of web applications and APIs it really does feel like Requests should support this type of encoding directly.

are there any other workaround then to change the req from **data** to **json**?

I can'r change the request type and need some way to handle it and send the response back. I have tried most of the solutions but Nested dict data still gets ignored. 

@sahilnyati9 Another workaround is to use `json.dumps`. So, e.g. this should work as intended:

```python
r = requests.post("https://httpbin.org/post", headers=headers, data=json.dumps(data))
```

One thing to note is that using `json` instead of `data` has the side-effect of automatically setting `'Content-Type'` to `'application/json'` in the headers; using `data` you have to set the content type manually.

I think the issue here is not so much that you have to use one or the other in a given situation, but that an exception (or at least a warning) should be raised when misusing the `data` argument.

@martimlobao Thanks for the suggestion. The issue here I don't have control over the request that I receive. 
The request that I receive is in this format - **auth.session.post(self.__path + self._url, data=data)**

In that case, all I could do is something on the receiving end to handle the request data in a way that I could formulate it correctly and send the response back. But, I have been unsuccessful as whatever I do to read it, it still skips the nested values.

I have tried things like below to read it properly but no success so far:
#print(request.__dict__)
#print(request.headers)
#data = request.get_data()
#data = request['data']
#print(data)
#print(request.data)
#print("BODY", request.body)
#json.dumps(parse_qs(request.get_data()))
#payload_dict = parse_qs(request.get_data())
#print(payload_dict, type(payload_dict))
#payload_dict_value_arr = payload_dict[b'creditTransferTransactionInformation']
#print(payload_dict_value_arr, type(payload_dict_value_arr))
# Get data from the index[0] of the array payload_dict_value_arr
#data = payload_dict_value_arr[0]
#print(data, type(data))
#payload = request.form
#print(payload['paymentTypeInformation'], type(payload['paymentTypeInformation']))
#print(payload['serviceLevelCode'], type(payload['serviceLevelCode']))

@sahilnyati9 I don't understand what you mean. If you want to send a nested dict `payload` in a request, you either have to use `json=payload` or `data=json.dumps(payload)`.

@martimlobao So basically, this is a request that I don't send. I receive it. We get this request from our banking partner and we send response body back to it based on the request body. Since, I was not able to send the correct response back because of missing data, I asked them how they are sending me the request and was told by the support team that they send it as data=data. 
I hope I could explain better but the issue here is I don't have any control over the request sending format and based on your comment it seems like, they need to send it properly. There is nothing that could be done on response side to handle it better.

@sahilnyati9 Ah, I see. With the way it is now, then there's no way for you to get the data since they're not actually sending it when doing `data=data`. You'll have to tell them to use `json=data` or `data=json.dumps(data)`, otherwise there's literally nothing you can do.

I think the user should be at least warned when the original dictionary gets modified.
Ideally, this should raise an exception, but such a change will break a lot of existing projects.