Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails not decoding JSON from jQuery correctly (array becoming a hash with integer keys)

Every time I want to POST an array of JSON objects with jQuery to Rails, I have this problem. If I stringify the array I can see that jQuery is doing its work correctly:

"shared_items"=>"[{\"entity_id\":\"253\",\"position\":1},{\"entity_id\":\"823\",\"position\":2}]" 

But if I just send the array it as the data of the AJAX call I get:

"shared_items"=>{"0"=>{"entity_id"=>"253", "position"=>"1"}, "1"=>{"entity_id"=>"823", "position"=>"2"}} 

Whereas if I just send a plain array it works:

"shared_items"=>["entity_253"] 

Why is Rails changing the array to that strange hash? The only reason that comes to mind is that Rails can't correctly understand the contents because there is no type here (is there a way to set it in the jQuery call?):

Processing by SharedListsController#create as  

Thank you!

Update: I'm sending the data as an array, not a string and the array is created dynamically using the .push() function. Tried with $.post and $.ajax, same result.

like image 764
aledalgrande Avatar asked Jun 20 '11 12:06

aledalgrande


2 Answers

In case someone stumbles upon this and wants a better solution, you can specify the "contentType: 'application/json'" option in the .ajax call and have Rails properly parse the JSON object without garbling it into integer-keyed hashes with all-string values.

So, to summarize, my problem was that this:

$.ajax({   type : "POST",   url :  'http://localhost:3001/plugin/bulk_import/',   dataType: 'json',   data : {"shared_items": [{"entity_id":"253","position":1}, {"entity_id":"823","position":2}]} }); 

resulted in Rails parsing things as:

Parameters: {"shared_items"=>{"0"=>{"entity_id"=>"253", "position"=>"1"}, "1"=>{"entity_id"=>"823", "position"=>"2"}}} 

whereas this (NOTE: we're now stringifying the javascript object and specifying a content type, so rails will know how to parse our string):

$.ajax({   type : "POST",   url :  'http://localhost:3001/plugin/bulk_import/',   dataType: 'json',   contentType: 'application/json',   data : JSON.stringify({"shared_items": [{"entity_id":"253","position":1}, {"entity_id":"823","position":2}]}) }); 

results in a nice object in Rails:

Parameters: {"shared_items"=>[{"entity_id"=>"253", "position"=>1}, {"entity_id"=>"823", "position"=>2}]} 

This works for me in Rails 3, on Ruby 1.9.3.

like image 160
swajak Avatar answered Sep 18 '22 17:09

swajak


Slightly old question, but I fought with this myself today, and here's the answer I came up with: I believe this is slightly jQuery's fault, but that it's only doing what is natural to it. I do, however, have a workaround.

Given the following jQuery ajax call:

$.ajax({    type : "POST",    url :  'http://localhost:3001/plugin/bulk_import/',    dataType: 'json',    data : {"shared_items": [{"entity_id":"253","position":1},{"entity_id":"823","position":2}]}  }); 

The values jQuery will post will look something like this (if you look at the Request in your Firebug-of-choice) will give you form data that looks like:

shared_items%5B0%5D%5Bentity_id%5D:1 shared_items%5B0%5D%5Bposition%5D:1 

If you CGI.unencode that you'll get

shared_items[0][entity_id]:1 shared_items[0][position]:1 

I believe this is because jQuery thinks that those keys in your JSON are form element names, and that it should treat them as if you had a field named "user[name]".

So they come into your Rails app, Rails sees the brackets, and constructs a hash to hold the innermost key of the field name (the "1" that jQuery "helpfully" added).

Anyway, I got around this behavior by constructing my ajax call the following way;

$.ajax({    type : "POST",    url :  'http://localhost:3001/plugin/bulk_import/',    dataType: 'json',    data : {"data": JSON.stringify({"shared_items": [{"entity_id":"253","position":1},{"entity_id":"823","position":2}])},   } }); 

Which forces jQuery to think that this JSON is a value that you want to pass, entirely, and not a Javascript object it must take and turn all the keys into form field names.

However, that means things are a little different on the Rails side, because you need to explicitly decode the JSON in params[:data].

But that's OK:

ActiveSupport::JSON.decode( params[:data] ) 

TL;DR: So, the solution is: in the data parameter to your jQuery.ajax() call, do {"data": JSON.stringify(my_object) } explicitly, instead of feeding the JSON array into jQuery (where it guesses wrongly what you want to do with it.

like image 44
RyanWilcox Avatar answered Sep 21 '22 17:09

RyanWilcox