Rails không giải mã JSON từ jQuery một cách chính xác (mảng trở thành một băm với các khóa số nguyên)


89

Mỗi khi tôi muốn ĐĂNG một mảng các đối tượng JSON với jQuery thành Rails, tôi gặp sự cố này. Nếu tôi xâu chuỗi mảng, tôi có thể thấy rằng jQuery đang hoạt động chính xác:

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

Nhưng nếu tôi chỉ gửi mảng nó dưới dạng dữ liệu của lệnh gọi AJAX, tôi nhận được:

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

Trong khi nếu tôi chỉ gửi một mảng đơn giản thì nó hoạt động:

"shared_items"=>["entity_253"]

Tại sao Rails lại thay đổi mảng thành hàm băm kỳ lạ đó? Lý do duy nhất mà Rails không thể hiểu chính xác nội dung bởi vì không có kiểu ở đây (có cách nào để đặt nó trong lệnh gọi jQuery không?):

Processing by SharedListsController#create as 

Cảm ơn bạn!

Cập nhật: Tôi đang gửi dữ liệu dưới dạng một mảng, không phải một chuỗi và mảng được tạo động bằng cách sử dụng .push()hàm. Đã thử $.post$.ajax, cùng một kết quả.

Câu trả lời:


169

Trong trường hợp ai đó tình cờ gặp phải điều này và muốn có giải pháp tốt hơn, bạn có thể chỉ định tùy chọn "contentType: 'application / json'" trong lệnh gọi .ajax và yêu cầu Rails phân tích cú pháp đối tượng JSON một cách chính xác mà không cần biến nó thành các hàm băm khóa nguyên với all- giá trị chuỗi.

Vì vậy, tóm lại, vấn đề của tôi là:

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

dẫn đến Rails phân tích cú pháp những thứ như:

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

trong khi điều này (LƯU Ý: chúng tôi hiện đang chuỗi đối tượng javascript và chỉ định loại nội dung, vì vậy, rails sẽ biết cách phân tích chuỗi của chúng tôi):

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

kết quả là một đối tượng đẹp trong Rails:

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

Điều này phù hợp với tôi trong Rails 3, trên Ruby 1.9.3.


1
Điều này có vẻ không phải là hành vi phù hợp với Rails, vì nhận JSON từ JQuery là một trường hợp khá phổ biến đối với các ứng dụng Rails. Có lý do nào có thể bào chữa được tại sao Rails lại hành xử theo cách này không? Có vẻ như mã JS phía máy khách đang phải nhảy qua các vòng lặp một cách không cần thiết.
Jeremy Burton

1
Tôi nghĩ trong trường hợp trên, thủ phạm là jQuery. iirc jQuery đã không đặt Content-Typeyêu cầu thành application/json, mà thay vào đó là gửi dữ liệu dưới dạng một biểu mẫu html? Thật khó để nghĩ lại điều này. Rails hoạt động tốt, sau khi Content-Typeđược đặt thành giá trị chính xác và dữ liệu được gửi là JSON hợp lệ.
swajak

3
Bạn không chỉ muốn ghi chú @swajak đã xâu chuỗi đối tượng mà còn chỉ địnhcontentType: 'application/json'
Roger Lam

1
Cảm ơn @RogerLam, tôi đã cập nhật giải pháp để mô tả điều đó tốt hơn mà tôi hy vọng
swajak

1
@swajak: cảm ơn bạn rất nhiều! Bạn đã cứu ngày của tôi, thực sự! :)
Kulgar

12

Câu hỏi hơi cũ, nhưng hôm nay tôi đã tự mình đấu tranh với điều này và đây là câu trả lời tôi nghĩ ra: Tôi tin rằng đây là lỗi của jQuery, nhưng nó chỉ làm những gì tự nhiên với nó. Tuy nhiên, tôi có một cách giải quyết.

Đưa ra lệnh gọi ajax jQuery sau:

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

});

Các giá trị mà jQuery sẽ đăng sẽ trông giống như thế này (nếu bạn nhìn vào Yêu cầu trong Firebug-lựa chọn của bạn) sẽ cung cấp cho bạn dữ liệu biểu mẫu giống như sau:

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

Nếu bạn CGI.unencode mà bạn sẽ nhận được

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

Tôi tin rằng điều này là do jQuery nghĩ rằng các khóa đó trong JSON của bạn là tên phần tử biểu mẫu và nó sẽ coi chúng như thể bạn có một trường có tên "user [name]".

Vì vậy, chúng đi vào ứng dụng Rails của bạn, Rails nhìn thấy các dấu ngoặc và tạo một hàm băm để giữ khóa trong cùng của tên trường ("1" mà jQuery đã "bổ sung một cách hữu ích").

Dù sao, tôi đã giải quyết được hành vi này bằng cách xây dựng lệnh gọi ajax của mình theo cách sau;

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

Điều này buộc jQuery nghĩ rằng JSON này là một giá trị mà bạn muốn chuyển, hoàn toàn chứ không phải là một đối tượng Javascript mà nó phải sử dụng và biến tất cả các khóa thành tên trường biểu mẫu.

Tuy nhiên, điều đó có nghĩa là mọi thứ có một chút khác biệt ở phía Rails, vì bạn cần giải mã rõ ràng JSON trong params [: data].

Nhưng không sao cả:

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

TL; DR: Vì vậy, giải pháp là: trong tham số dữ liệu cho lệnh gọi jQuery.ajax () của bạn, hãy thực hiện {"data": JSON.stringify(my_object) }một cách rõ ràng, thay vì đưa mảng JSON vào jQuery (nơi nó đoán sai những gì bạn muốn làm với nó.


Cảnh báo giải pháp tốt hơn bên dưới từ @swajak.
event_jr

7

Tôi vừa gặp sự cố này với Rails 4. Để trả lời rõ ràng câu hỏi của bạn ("Tại sao Rails lại thay đổi mảng thành hàm băm kỳ lạ đó?"), Hãy xem phần 4.1 của hướng dẫn Rails về Bộ điều khiển hành động :

Để gửi một mảng giá trị, hãy thêm một cặp dấu ngoặc vuông "[]" trống vào tên khóa.

Vấn đề là, jQuery định dạng yêu cầu bằng các chỉ số mảng rõ ràng, thay vì bằng dấu ngoặc vuông trống. Vì vậy, ví dụ, thay vì gửi shared_items[]=1&shared_items[]=2, nó sẽ gửi shared_items[0]=1&shared_items[1]=2. Rails xem các chỉ số mảng và diễn giải chúng dưới dạng các khóa băm chứ không phải chỉ số mảng, biến yêu cầu thành một hàm băm kỳ lạ của Ruby : { shared_items: { '0' => '1', '1' => '2' } }.

Nếu bạn không có quyền kiểm soát máy khách, bạn có thể khắc phục sự cố này ở phía máy chủ bằng cách chuyển đổi băm thành một mảng. Đây là cách tôi đã làm điều đó:

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

1

phương pháp sau có thể hữu ích nếu bạn sử dụng các tham số mạnh

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

Bạn đã xem xét việc làm parsed_json = ActiveSupport::JSON.decode(your_json_string)chưa? Nếu bạn đang gửi nội dung theo cách khác, bạn có thể sử dụng .to_jsonđể tuần tự hóa dữ liệu.


1
Có được xem xét, nhưng muốn biết liệu có "cách đúng" để làm điều này trong Rails mà không cần phải mã hóa / giải mã theo cách thủ công hay không.
aledalgrande

0

Bạn chỉ đang cố gắng đưa chuỗi JSON vào một hành động của bộ điều khiển Rails?

Tôi không chắc Rails đang làm gì với hàm băm, nhưng bạn có thể giải quyết vấn đề và gặp nhiều may mắn hơn bằng cách tạo một đối tượng Javascript / JSON (trái ngược với chuỗi JSON) và gửi nó qua dưới dạng tham số dữ liệu cho Ajax của bạn gọi.

myData = {
  "shared_items":
    [
        {
            "entity_id": "253",
            "position": 1
        }, 
        {
            "entity_id": "823",
            "position": 2
        }
    ]
  };

Nếu bạn muốn gửi cái này qua ajax thì bạn sẽ làm như sau:

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

Lưu ý với đoạn mã ajax ở trên, jQuery sẽ đưa ra một phỏng đoán thông minh trên dataType, mặc dù thường thì tốt khi chỉ định nó một cách rõ ràng.

Dù bằng cách nào, trong hành động trình điều khiển của bạn, bạn có thể nhận được đối tượng JSON mà bạn đã chuyển với hàm băm params, tức là

params[:shared_items]

Ví dụ: hành động này sẽ nhổ đối tượng json của bạn về phía bạn:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end

Cảm ơn, nhưng đó là những gì tôi đang làm ngay bây giờ (xem bản cập nhật) và nó không hoạt động.
aledalgrande

0

Sử dụng gem rack-jquery-params (tuyên bố từ chối trách nhiệm: Tôi là tác giả). Nó khắc phục sự cố của bạn về việc mảng trở thành hàm băm với các khóa số nguyên.


Tốt hơn để cung cấp đoạn mã số mã thay vì liên kết đặt
Vikasdeep Singh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.