2

I am building a web app on the Google App Engine platform, which uses webapp2, which uses WebOb. I would like to POST some data in JSON format, including nested arrays and dictionaries. E.g.:

$.post('/addvendor', {'vendor': {'name': 'test', 'description': 'a good company', 'tags':['foo', 'bar']}}, function(data){console.log(data)}, 'application/json')

However, on the server side, the data comes in as a flat "MultiDict" object, not anything like the original nested JSON object that I POSTed. E.g.:

>>> print self.request.params.items()
[(u'vendor[name]', u'test'), (u'vendor[description]', u'a good company'), (u'vendor[tags][]', u'foo'), (u'vendor[tags][]', u'bar')]

This object is very difficult to parse. In my server code, is there a way to get the same data in standard JSON format, or at least a Python equivalent using nested dictionaries and arrays, on the server so that I can manipulate and examine the data easily?

jayhendren
  • 4,286
  • 2
  • 35
  • 59
  • I found a similar question: http://stackoverflow.com/questions/9058233/how-to-convert-a-multidict-to-nested-dictionary Unfortunately the answer to that question is unhelpful to me because I can't include the formencode package in a google app engine environment. I think I may switch over to Heroku instead and use node.js. – jayhendren Oct 06 '13 at 05:42
  • $.post is jquery? you are using python 2.7.*? Can you then not do something like: `vendor = json.loads(self.request.get('vendor'))` You have to import json with `import json` – HMR Oct 06 '13 at 07:20

2 Answers2

2

(updated with jayhendren's help) You should use $.ajax and manually set contentType='application/json; charset=utf-8' instead because $.post uses default "application/x-www-form-urlencoded;" content type. Also you need to manually encode data to JSON-string with JSON.stringify:

$.ajax({url:'/addvendor', 
        type: 'post', 
        data:JSON.stringify({'vendor': {'name': 'test', 'description': 'a good company', 'tags':['foo', 'bar']}}), 
        contentType:'application/json; charset=utf-8',
        dataType: "json",
        success:function(data){console.log(data)}})

...

print json.loads(self.request.body)
Denisigo
  • 640
  • 4
  • 13
  • This answer, I think, shows a lack of understanding of the webob interface. Here is the value of `self.request.body`: `vendor%5Bname%5D=test&vendor%5Bdescription%5D=a+good+company&vendor%5Btags%5D%5B%5D=foo&vendor%5Btags%5D%5B%5D=bar` And here is what happens if I try to decode that as a json oject: `ValueError: No JSON object could be decoded` – jayhendren Oct 06 '13 at 16:33
  • 1
    Sorry, forgot about one important thing: jquery.ajax's data param must be plain object or string (http://api.jquery.com/jQuery.ajax/). If you pass just object, jquery converts it to query string which you are seeing. To send JSON-encoded data you should manually build JSON-string by using JSON.stringify() – Denisigo Oct 06 '13 at 17:57
  • Huh, thanks, this is starting to get there. I still get a `no JSON object could be decoded` error, but at least I have an encoded string that represents a JSON object rather than a multi-dict to work with. That's much better than passing a straight JSON object to `$.post`. However, I've used other APIs which didn't require me to `stringify` the JSON object before `POST`ing it to the server. For instance, when using backbone.js, I could interpret the `POST`ed data on the as a JSON object on the server without requiring that the client `stringify` it or that the server decode it. – jayhendren Oct 06 '13 at 18:44
  • You still get this error using self.request.body? I suppose jquery doesn't encode to JSON automatically because it is wide-purpose library and it is developer who decides how to send data to server. If my answer were useful please accept it) – Denisigo Oct 06 '13 at 18:58
  • Here is the value of `self.request.body`: `%7B%22vendor%22%3A%7B%22name%22%3A%22test%22%2C%22description%22%3A%22a+good+company%22%2C%22tags%22%3A%5B%22foo%22%2C%22bar%22%5D%7D%7D=`. As you can see, it needs to be decoded. ;) I think the urllib library will help with that. However, the data being passed in is, inexplicably, being interpreted as a key (with no associated value), hence the `=` at the end. – jayhendren Oct 06 '13 at 19:01
  • 1
    Finally, I got it. On the client side: `$.ajax({url:'/addvendor', type: 'post', data:JSON.stringify({'vendor': {'name': 'test', 'description': 'a good company', 'tags':['foo', 'bar']}}), success:function(data){console.log(data)}, contentType:'application/json', processData: false})` On the server side: `json.loads(self.request.body)`. This works, though I'm mildly irritated that I have to `JSON.stringify` the object and make sure that the request contains `processData: false`. I guess I'll just wrap the ajax call in a function on the client side and call the wrapper instead :) – jayhendren Oct 06 '13 at 19:17
  • 2
    Sorry again, you should use $.ajax instead and set contentType = 'application/json; charset=utf-8' manually ($.post is just wrapper for $.ajax which uses default "application/x-www-form-urlencoded;" content type). Didn't mention before because $.post works fine for me too and doesn't urlencodes post data ) – Denisigo Oct 06 '13 at 19:31
  • OOps, you've already got it) If you don't mind, I'll correct my answer anyway. – Denisigo Oct 06 '13 at 19:33
  • Yeah, I don't know what's going on exactly - `$.post` in a different environment (another project I am working on) seems to behave more like the `$.ajax` call I mentioned earlier. If you edit your answer so it matches what I have found to work, then I'll accept it. :) Thanks for the help! – jayhendren Oct 06 '13 at 19:34
  • Aren't sure, but maybe issue is in different jquery versions? Very strange that you have to set processData: false, because jquery should process anything but strings o_O. Updated answer, and thank you too! – Denisigo Oct 06 '13 at 19:55
  • Possibly. I don't know what jQuery version the other project is using. A few other developers have worked on it as well, so it is possible that the jQuery library may have been modified or that the $.post function may have been overloaded somewhere upstream. – jayhendren Oct 06 '13 at 19:57
1

use json.dumps(yourdata) don't forgot to change the header Content-Type to application/json

headers = {
"Content-Type": "application/json"
}
session.post(<url>, data=json.dumps(yourdata))
MONTYHS
  • 926
  • 1
  • 7
  • 30
  • Thank you for your answer, but I don't think you read carefully the question or the other answer. I don't need to send a POST request from the server, I need to send it using a javascript AJAX call from the client. Furthermore, this question has already been solved, as you can see from the other answer, which I accepted, and the thread of comments. – jayhendren Oct 07 '13 at 14:50
  • @jayhendren +1 because it's important to understand, for webapp2/GAE, that `request.body` only works for JSON when `request.content_type == 'application/json'`. I made a change today in my tests and all of a sudden, inside the handler, my `request.body` was URL-encoded. I just had to add `content-type = application/json` on the POST and everything was back to normal. – Zach Young Aug 03 '16 at 05:35