Rails ไม่ได้ถอดรหัส JSON จาก jQuery อย่างถูกต้อง (อาร์เรย์กลายเป็นแฮชที่มีคีย์จำนวนเต็ม)


89

ทุกครั้งที่ฉันต้องการโพสต์อาร์เรย์ของออบเจ็กต์ JSON ด้วย jQuery to Rails ฉันมีปัญหานี้ ถ้าฉันสตริงอาร์เรย์ฉันจะเห็นว่า jQuery ทำงานได้อย่างถูกต้อง:

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

แต่ถ้าฉันส่งอาร์เรย์เป็นข้อมูลของการโทร AJAX ฉันจะได้รับ:

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

ในขณะที่ถ้าฉันส่งอาร์เรย์ธรรมดามันใช้งานได้:

"shared_items"=>["entity_253"]

เหตุใด Rails จึงเปลี่ยนอาร์เรย์เป็นแฮชแปลก ๆ เหตุผลเดียวที่อยู่ในใจคือ Rails ไม่สามารถเข้าใจเนื้อหาได้อย่างถูกต้องเนื่องจากไม่มีประเภทที่นี่ (มีวิธีตั้งค่าในการเรียก jQuery หรือไม่):

Processing by SharedListsController#create as 

ขอขอบคุณ!

อัปเดต: ฉันกำลังส่งข้อมูลเป็นอาร์เรย์ไม่ใช่สตริงและอาร์เรย์ถูกสร้างขึ้นแบบไดนามิกโดยใช้.push()ฟังก์ชัน พยายาม$.postและ$.ajaxผลลัพธ์เดียวกัน

คำตอบ:


169

ในกรณีที่มีคนสะดุดและต้องการวิธีแก้ปัญหาที่ดีกว่าคุณสามารถระบุตัวเลือก "contentType: 'application / json'" ในการเรียก. jax และให้ Rails แยกวิเคราะห์ออบเจ็กต์ JSON อย่างถูกต้องโดยไม่ต้องพูดให้เข้าใจผิดเป็นแฮชที่มีคีย์จำนวนเต็มด้วย all- ค่าสตริง

ดังนั้นเพื่อสรุปปัญหาของฉันคือสิ่งนี้:

$.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}]}
});

ส่งผลให้ Rails แยกวิเคราะห์สิ่งต่างๆเป็น:

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

ในขณะที่สิ่งนี้ (หมายเหตุ: ตอนนี้เรากำลังสตริงวัตถุ javascript และระบุประเภทเนื้อหาดังนั้นทางรถไฟจะรู้วิธีแยกวิเคราะห์สตริงของเรา):

$.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.


1
This does not seem like the right behavior for Rails, given that receiving JSON from JQuery is a pretty common case for Rails apps. Is there a defensible rationale as to why Rails behaves in this way? It seems that the client-side JS code is having to unnecessarily jump through hoops.
Jeremy Burton

1
I think in the case above the culprit is jQuery. iirc jQuery wasn't setting the Content-Type of the request to application/json, but was instead sending the data as an html form would? Hard to think back this far. Rails was working just fine, after the Content-Type was set to the correct value, and the data being sent was valid JSON.
swajak

3
Want to note not only did @swajak stringify the object but also specify contentType: 'application/json'
Roger Lam

1
Thanks @RogerLam, I've updated the solution to describe that better I hope
swajak

1
@swajak : thank you soooo much! You saved my day, really! :)
Kulgar

12

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.


Heads up better solution is below from @swajak.
event_jr

7

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
}

1

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

0

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.


1
Yes considered, but wanted to know if there is a "right way" to do this in Rails, without having to manually encode/decode.
aledalgrande

0

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

Thanks, but that's what I'm doing right now (see update) and it doesn't work.
aledalgrande

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.