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 th开发者_开发知识库e 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.
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.
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
}
following method could be helpful if you use strong parameters
def safe_params
values = params.require(:shared_items)
values = items.values if items.keys.first == '0'
ActionController::Parameters.new(shared_items: values).permit(shared_items: [:entity_id, :position]).require(:shared_items)
end
Have you considered doing parsed_json = ActiveSupport::JSON.decode(your_json_string)
? If you're sending stuff the other way about you can use .to_json
to serialise the data.
Are you just trying to get the JSON string into a Rails controller action?
I'm not sure what Rails is doing with the hash, but you might get around the problem and have more luck by creating a Javascript/JSON object (as opposed to a JSON string) and sending that through as the data parameter for your Ajax call.
myData = {
"shared_items":
[
{
"entity_id": "253",
"position": 1
},
{
"entity_id": "823",
"position": 2
}
]
};
If you wanted to send this via ajax then you would do something like this:
$.ajax({
type: "POST",
url: "my_url", // be sure to set this in your routes.rb
data: myData,
success: function(data) {
console.log("success. data:");
console.log(data);
}
});
Note with the ajax snippet above, jQuery will make an intelligent guess on the dataType, although it's usually good to specify it explicitly.
Either way, in your controller action, you can get the JSON object you passed with the params hash, i.e.
params[:shared_items]
E.g. this action will spit your json object back at you:
def reply_in_json
@shared = params[:shared_items]
render :json => @shared
end
Use the rack-jquery-params gem (disclaimer: I'm the author). It fixes your issue of arrays becoming hashes with integer keys.
精彩评论