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

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.


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.