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

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:1shared_items%5B0%5D%5Bposition%5D:1

If you CGI.unencode that you'll get

shared_items[0][entity_id]:1shared_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.


I just ran into this issue with Rails 4. To explicitly answer your question ("Why is Rails changing the array to that strange hash?"), see section 4.1 of the Rails guide on Action Controllers:

To send an array of values, append an empty pair of square brackets "[]" to the key name.

The problem is, jQuery formats the request with explicit array indices, rather than with empty square brackets. So, for example, instead of sending shared_items[]=1&shared_items[]=2, it sends shared_items[0]=1&shared_items[1]=2. Rails sees the array indices and interprets them as hash keys rather than array indices, turning the request into a weird Ruby hash: { shared_items: { '0' => '1', '1' => '2' } }.

If you don't have control of the client, you can fix this problem on the server side by converting the hash to an array. Here's how I did it:

shared_items = []params[:shared_items].each { |k, v|  shared_items << v}