Railsy nie dekodują poprawnie JSON z jQuery (tablica staje się hashem z kluczami całkowitymi)

89

Za każdym razem, gdy chcę OPUBLIKOWAĆ tablicę obiektów JSON z jQuery do Railsów, mam ten problem. Jeśli zdefiniuję tablicę, widzę, że jQuery działa poprawnie:

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

Ale jeśli po prostu wyślę tablicę jako dane wywołania AJAX, otrzymam:

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

Natomiast jeśli wyślę zwykłą tablicę, to działa:

"shared_items"=>["entity_253"]

Dlaczego Railsy zmieniają tablicę na ten dziwny skrót? Jedynym powodem, który przychodzi na myśl, jest to, że Railsy nie mogą poprawnie zrozumieć zawartości, ponieważ nie ma tutaj typu (czy jest sposób, aby ustawić go w wywołaniu jQuery?):

Processing by SharedListsController#create as 

Dziękuję Ci!

Aktualizacja: wysyłam dane jako tablicę, a nie ciąg, a tablica jest tworzona dynamicznie za pomocą .push()funkcji. Próbowałem $.posti ten $.ajaxsam wynik.

aledalgrande
źródło

Odpowiedzi:

169

W przypadku, gdy ktoś natknie się na to i będzie chciał lepszego rozwiązania, możesz określić opcję "contentType: 'application / json'" w wywołaniu .ajax i sprawić, by Railsy poprawnie przeanalizowały obiekt JSON bez przekształcania go w skróty z kluczem całkowitym za wartości ciągów.

Podsumowując, mój problem polegał na tym, że:

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

spowodowało, że Railsy analizowały rzeczy jako:

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

mając na uwadze, że to (UWAGA: teraz określamy obiekt javascript i określamy typ zawartości, aby railsy wiedziały, jak przeanalizować nasz ciąg):

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

daje ładny obiekt w Railsach:

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

U mnie to działa w Railsach 3, na Rubim 1.9.3.

swajak
źródło
1
Wydaje się, że nie jest to właściwe zachowanie dla Railsów, biorąc pod uwagę, że odbieranie JSON z JQuery jest dość częstym przypadkiem dla aplikacji Railsowych. Czy istnieje uzasadnione uzasadnienie, dlaczego Railsy zachowują się w ten sposób? Wygląda na to, że kod JS po stronie klienta musi niepotrzebnie przeskakiwać przez obręcze.
Jeremy Burton
1
Myślę, że w powyższym przypadku winowajcą jest jQuery. iirc jQuery nie ustawił Content-Typeżądania na application/json, ale zamiast tego wysyłał dane tak, jak zrobiłby to formularz html? Trudno myśleć tak daleko. Railsy działały dobrze, po Content-Typeustawieniu poprawnej wartości, a wysyłane dane były w poprawnym formacie JSON.
swajak
3
Chcesz zauważyć, że @swajak nie tylko zdefiniował obiekt, ale także określiłcontentType: 'application/json'
Roger Lam
1
Dzięki @RogerLam, zaktualizowałem rozwiązanie, aby opisać to lepiej mam nadzieję
swajak
1
@swajak: bardzo dziękuję! Naprawdę uratowałeś mi dzień! :)
Kulgar
12

Trochę stare pytanie, ale dzisiaj sam z tym walczyłem, a oto odpowiedź, na którą wpadłem: uważam, że to trochę wina jQuery, ale robi to tylko to, co jest dla niego naturalne. Mam jednak obejście.

Biorąc pod uwagę następujące wywołanie jQuery ajax:

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

});

Wartości, które opublikuje jQuery, będą wyglądać mniej więcej tak (jeśli spojrzysz na żądanie w swoim Firebug-of-choice), dadzą ci dane formularza, które wyglądają następująco:

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

Jeśli masz CGI.uncode, który otrzymasz

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

Wydaje mi się, że dzieje się tak, ponieważ jQuery uważa, że ​​te klucze w twoim JSON są nazwami elementów formularza i że powinno traktować je tak, jakbyś miał pole o nazwie „user [nazwa]”.

Więc przychodzą do twojej aplikacji Railsów, Railsy widzą nawiasy i konstruują hash do przechowywania najbardziej wewnętrznego klucza nazwy pola („1”, które jQuery „pomocnie” dodało).

W każdym razie obejrzałem to zachowanie, konstruując moje wywołanie ajax w następujący sposób;

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

Co zmusza jQuery do myślenia, że ​​ten kod JSON jest wartością , którą chcesz przekazać w całości, a nie obiektem JavaScript, który musi przyjąć i zamienić wszystkie klucze w nazwy pól formularza.

Jednak oznacza to, że po stronie Railsów sprawy wyglądają trochę inaczej, ponieważ musisz jawnie zdekodować JSON w params [: data].

Ale jest dobrze:

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

TL; DR: Więc rozwiązanie jest takie: w parametrze data w wywołaniu jQuery.ajax () rób to {"data": JSON.stringify(my_object) }jawnie, zamiast podawać tablicę JSON do jQuery (gdzie błędnie zgadnie, co chcesz z nią zrobić.

RyanWilcox
źródło
Uważam, że lepsze rozwiązanie jest poniżej z @swajak.
event_jr
7

Właśnie natknąłem się na ten problem z Railsami 4. Aby jednoznacznie odpowiedzieć na twoje pytanie ("Dlaczego Railsy zmieniają tablicę na ten dziwny hash?"), Zobacz sekcję 4.1 przewodnika Rails na kontrolerach akcji :

Aby wysłać tablicę wartości, dodaj pustą parę nawiasów kwadratowych „[]” do nazwy klucza.

Problem polega na tym, że jQuery formatuje żądanie za pomocą jawnych indeksów tablicowych zamiast pustych nawiasów kwadratowych. Na przykład zamiast wysyłać shared_items[]=1&shared_items[]=2, wysyła shared_items[0]=1&shared_items[1]=2. Szyny widzi indeksów tablicy i interpretuje je jako klawiszy skrótu zamiast indeksów tablicy, zwracając wniosek do dziwnej Ruby hash: { shared_items: { '0' => '1', '1' => '2' } }.

Jeśli nie masz kontroli nad klientem, możesz rozwiązać ten problem po stronie serwera, konwertując hash na tablicę. Oto jak to zrobiłem:

shared_items = []
params[:shared_items].each { |k, v|
  shared_items << v
}
jessepinho
źródło
1

Poniższa metoda może być pomocna, jeśli używasz silnych parametrów

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
Eugene
źródło
0

Czy rozważałeś zrobienie tego parsed_json = ActiveSupport::JSON.decode(your_json_string)? Jeśli wysyłasz rzeczy w inny sposób, możesz użyć .to_jsondo serializacji danych.

Michael De Silva
źródło
1
Tak, rozważałem, ale chciałem wiedzieć, czy istnieje "właściwy sposób" na zrobienie tego w Railsach, bez konieczności ręcznego kodowania / dekodowania.
aledalgrande
0

Czy po prostu próbujesz pobrać ciąg JSON do akcji kontrolera Rails?

Nie jestem pewien, co Railsy robią z hashem, ale możesz obejść problem i mieć więcej szczęścia, tworząc obiekt Javascript / JSON (w przeciwieństwie do ciągu JSON) i wysyłając go jako parametr danych dla swojego Ajax połączenie.

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

Jeśli chcesz wysłać to przez Ajax, zrób coś takiego:

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

Zauważ, że z powyższym fragmentem Ajaxa, jQuery dokona inteligentnego odgadnięcia typu dataType, chociaż zwykle dobrze jest go wyraźnie określić.

Tak czy inaczej, w akcji kontrolera możesz pobrać obiekt JSON, który przekazałeś z hashem params, tj

params[:shared_items]

Np. Ta czynność wypluje z powrotem twój obiekt json:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end
australis
źródło
Dzięki, ale właśnie to teraz robię (patrz aktualizacja) i to nie działa.
aledalgrande
0

Użyj klejnotu rack-jquery-params (zastrzeżenie: jestem autorem). Rozwiązuje problem z tablicami, które stają się skrótami z kluczami całkowitymi.

Caleb Clark
źródło
Lepiej podać fragment kodu zamiast
wstawiać