Rails tidak mendekode JSON dari jQuery dengan benar (array menjadi hash dengan kunci integer)


89

Setiap kali saya ingin POST array objek JSON dengan jQuery to Rails, saya mengalami masalah ini. Jika saya merangkai array, saya dapat melihat bahwa jQuery melakukan tugasnya dengan benar:

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

Tetapi jika saya hanya mengirim array itu sebagai data panggilan AJAX saya mendapatkan:

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

Sedangkan jika saya hanya mengirim array biasa itu berfungsi:

"shared_items"=>["entity_253"]

Mengapa Rails mengubah array menjadi hash aneh itu? Satu-satunya alasan yang terlintas dalam pikiran adalah bahwa Rails tidak dapat memahami konten dengan benar karena tidak ada tipe di sini (apakah ada cara untuk mengaturnya dalam panggilan jQuery?):

Processing by SharedListsController#create as 

Terima kasih!

Pembaruan: Saya mengirim data sebagai array, bukan string dan array dibuat secara dinamis menggunakan .push()fungsi tersebut. Mencoba dengan $.postdan $.ajax, hasil yang sama.

Jawaban:


169

Jika seseorang tersandung pada hal ini dan menginginkan solusi yang lebih baik, Anda dapat menentukan opsi "contentType: 'application / json'" dalam panggilan .ajax dan minta Rails mengurai dengan benar objek JSON tanpa membuatnya kacau menjadi hash dengan kunci integer dengan all- nilai string.

Jadi, untuk meringkas, masalah saya adalah ini:

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

mengakibatkan Rails mengurai hal-hal sebagai:

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

sedangkan ini (CATATAN: kami sekarang merangkai objek javascript dan menentukan tipe konten, jadi rails akan tahu cara mengurai string kami):

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

menghasilkan objek yang bagus di Rails:

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

Ini berfungsi untuk saya di Rails 3, di Ruby 1.9.3.


1
Ini sepertinya bukan perilaku yang tepat untuk Rails, mengingat menerima JSON dari JQuery adalah kasus yang cukup umum untuk aplikasi Rails. Apakah ada alasan yang dapat dipertahankan tentang mengapa Rails berperilaku seperti ini? Tampaknya kode JS sisi klien harus melewati rintangan yang tidak perlu.
Jeremy Burton

1
Saya pikir dalam kasus di atas pelakunya adalah jQuery. iirc jQuery tidak menyetel Content-Typepermintaan ke application/json, tetapi mengirim data sebagai bentuk html? Sulit untuk memikirkan kembali sejauh ini. Rails berfungsi dengan baik, setelah Content-Typedisetel ke nilai yang benar, dan data yang dikirim adalah JSON yang valid.
swajak

3
Ingin diperhatikan bahwa @swajak tidak hanya merangkai objek tetapi juga menentukancontentType: 'application/json'
Roger Lam

1
Terima kasih @RogerLam, saya telah memperbarui solusi untuk mendeskripsikan yang lebih baik yang saya harap
swajak

1
@swajak: terima kasih banyak! Kamu benar-benar menyelamatkan hariku! :)
Kulgar

12

Pertanyaan yang agak lama, tetapi saya berjuang dengan ini sendiri hari ini, dan inilah jawaban yang saya dapatkan: Saya percaya ini sedikit kesalahan jQuery, tetapi itu hanya melakukan apa yang wajar untuk itu. Saya, bagaimanapun, punya solusi.

Diberikan panggilan ajax jQuery berikut:

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

});

Nilai-nilai yang akan dikirim jQuery akan terlihat seperti ini (jika Anda melihat Permintaan di Firebug-of-choice Anda) akan memberi Anda data formulir yang terlihat seperti:

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

Jika Anda CGI.unencode itu Anda akan mendapatkan

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

Saya percaya ini karena jQuery berpikir bahwa kunci tersebut di JSON Anda adalah nama elemen formulir, dan harus memperlakukannya seolah-olah Anda memiliki bidang bernama "pengguna [nama]".

Jadi mereka datang ke aplikasi Rails Anda, Rails melihat tanda kurung, dan membuat hash untuk menahan kunci terdalam dari nama field ("1" yang ditambahkan jQuery "membantu").

Bagaimanapun, saya mengatasi perilaku ini dengan membangun panggilan ajax saya dengan cara berikut;

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

Yang memaksa jQuery untuk berpikir bahwa JSON ini adalah nilai yang ingin Anda teruskan, seluruhnya, dan bukan objek Javascript yang harus diambil dan mengubah semua kunci menjadi nama bidang formulir.

Namun, itu berarti ada sedikit perbedaan di sisi Rails, karena Anda perlu secara eksplisit mendekode JSON di params [: data].

Tapi tidak apa-apa:

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

TL; DR: Jadi, solusinya adalah: di parameter data ke panggilan jQuery.ajax () Anda, lakukan {"data": JSON.stringify(my_object) }secara eksplisit, alih-alih memberi makan array JSON ke jQuery (di mana ia salah menebak apa yang ingin Anda lakukan dengannya.


Solusi yang lebih baik ada di bawah dari @swajak.
event_jr

7

Saya baru saja mengalami masalah ini dengan Rails 4. Untuk menjawab pertanyaan Anda secara eksplisit ("Mengapa Rails mengubah array menjadi hash aneh itu?"), Lihat bagian 4.1 dari panduan Rails di Pengontrol Tindakan :

Untuk mengirim larik nilai, tambahkan pasangan kosong tanda kurung siku "[]" ke nama kunci.

Masalahnya adalah, jQuery memformat permintaan dengan indeks array eksplisit, bukan dengan tanda kurung siku kosong. Jadi, misalnya, alih-alih mengirim shared_items[]=1&shared_items[]=2, ia mengirim shared_items[0]=1&shared_items[1]=2. Rails melihat indeks array dan menafsirkannya sebagai kunci hash daripada indeks array, mengubah permintaan menjadi hash Ruby yang aneh:{ shared_items: { '0' => '1', '1' => '2' } } .

Jika Anda tidak memiliki kendali atas klien, Anda dapat memperbaiki masalah ini di sisi server dengan mengonversi hash menjadi sebuah array. Begini cara saya melakukannya:

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

1

metode berikut dapat membantu jika Anda menggunakan parameter yang kuat

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

Sudahkah Anda mempertimbangkan untuk melakukan parsed_json = ActiveSupport::JSON.decode(your_json_string)? Jika Anda mengirim barang dengan cara lain, Anda dapat menggunakan .to_jsonuntuk membuat serial data.


1
Ya dipertimbangkan, tetapi ingin tahu apakah ada "cara yang benar" untuk melakukan ini di Rails, tanpa harus menyandikan / mendekode secara manual.
aledalgrande

0

Apakah Anda hanya mencoba memasukkan string JSON menjadi tindakan pengontrol Rails?

Saya tidak yakin apa yang dilakukan Rails dengan hash tersebut, tetapi Anda mungkin dapat mengatasi masalah dan lebih beruntung dengan membuat objek Javascript / JSON (sebagai lawan dari string JSON) dan mengirimkannya sebagai parameter data untuk Ajax Anda. panggilan.

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

Jika Anda ingin mengirim ini melalui ajax maka Anda akan melakukan sesuatu seperti ini:

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

Perhatikan dengan cuplikan ajax di atas, jQuery akan membuat tebakan cerdas pada dataType, meskipun biasanya baik untuk menetapkannya secara eksplisit.

Apa pun itu, dalam aksi pengontrol Anda, Anda bisa mendapatkan objek JSON yang diteruskan dengan hash params, yaitu

params[:shared_items]

Misalnya tindakan ini akan meludahkan objek json Anda kembali kepada Anda:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end

Terima kasih, tapi itulah yang saya lakukan sekarang (lihat pembaruan) dan tidak berhasil.
aledalgrande

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.