Rails non decodifica correttamente JSON da jQuery (l'array diventa un hash con chiavi intere)


89

Ogni volta che voglio POST un array di oggetti JSON con jQuery su Rails, ho questo problema. Se stringo l'array posso vedere che jQuery sta facendo il suo lavoro correttamente:

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

Ma se invio semplicemente l'array come i dati della chiamata AJAX ottengo:

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

Considerando che se mando solo un semplice array funziona:

"shared_items"=>["entity_253"]

Perché Rails sta cambiando l'array in quello strano hash? L'unico motivo che mi viene in mente è che Rails non riesce a capire correttamente i contenuti perché non c'è un tipo qui (c'è un modo per impostarlo nella chiamata jQuery?):

Processing by SharedListsController#create as 

Grazie!

Aggiornamento: invio i dati come array, non come stringa e l'array viene creato dinamicamente utilizzando la .push()funzione. Ho provato con $.poste $.ajax, stesso risultato.

Risposte:


169

Nel caso in cui qualcuno si imbatta in questo e desidera una soluzione migliore, puoi specificare l'opzione "contentType: 'application / json'" nella chiamata .ajax e fare in modo che Rails analizzi correttamente l'oggetto JSON senza confonderlo in hash con chiave intera con all- valori di stringa.

Quindi, per riassumere, il mio problema era che questo:

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

ha portato Rails ad analizzare cose come:

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

mentre questo (NOTA: ora stiamo stringendo l'oggetto javascript e specificando un tipo di contenuto, quindi rails saprà come analizzare la nostra stringa):

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

si traduce in un bell'oggetto in Rails:

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

Questo funziona per me in Rails 3, su Ruby 1.9.3.


1
Questo non sembra il comportamento giusto per Rails, dato che ricevere JSON da JQuery è un caso abbastanza comune per le app Rails. C'è una motivazione giustificabile sul perché Rails si comporta in questo modo? Sembra che il codice JS lato client debba saltare inutilmente attraverso i cerchi.
Jeremy Burton

1
Penso che nel caso sopra il colpevole sia jQuery. iirc jQuery non impostava la Content-Typerichiesta su application/json, ma inviava i dati come farebbe un modulo html? Difficile ripensare a questo punto. Rails funzionava perfettamente, dopo che è Content-Typestato impostato il valore corretto e i dati inviati erano JSON validi.
swajak

3
Voglio notare che non solo @swajak ha stringa l'oggetto, ma ha anche specificatocontentType: 'application/json'
Roger Lam

1
Grazie @RogerLam, ho aggiornato la soluzione per descriverla meglio, spero
swajak

1
@swajak: grazie mille! Mi hai salvato la giornata, davvero! :)
Kulgar

12

Domanda un po 'vecchia, ma oggi ho combattuto con questo, ed ecco la risposta che ho trovato: credo che questo sia leggermente colpa di jQuery, ma che stia solo facendo ciò che è naturale per esso. Tuttavia, ho una soluzione alternativa.

Data la seguente chiamata 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}]}

});

I valori che jQuery pubblicherà saranno simili a questo (se guardi la richiesta nel tuo Firebug-of-choice) ti daranno dati del modulo che assomigliano a:

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

Se usi CGI.unencode che otterrai

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

Credo che ciò sia dovuto al fatto che jQuery pensa che quelle chiavi nel tuo JSON siano nomi di elementi del modulo e che dovrebbe trattarle come se avessi un campo denominato "utente [nome]".

Quindi entrano nella tua app Rails, Rails vede le parentesi e costruisce un hash per contenere la chiave più interna del nome del campo (l '"1" che jQuery ha "utilmente" aggiunto).

Ad ogni modo, ho aggirato questo comportamento costruendo la mia chiamata ajax nel modo seguente;

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

Il che costringe jQuery a pensare che questo JSON sia un valore che vuoi passare, interamente, e non un oggetto Javascript che deve prendere e trasformare tutte le chiavi in ​​nomi di campo del modulo.

Tuttavia, ciò significa che le cose sono un po 'diverse sul lato di Rails, perché è necessario decodificare esplicitamente il JSON in params [: data].

Ma va bene:

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

TL; DR: Quindi, la soluzione è: nel parametro data alla tua chiamata jQuery.ajax (), fallo {"data": JSON.stringify(my_object) }esplicitamente, invece di alimentare l'array JSON in jQuery (dove indovina erroneamente cosa vuoi fare con esso.


Attenzione la soluzione migliore è di seguito da @swajak.
event_jr

7

Mi sono appena imbattuto in questo problema con Rails 4. Per rispondere esplicitamente alla tua domanda ("Perché Rails sta cambiando l'array in quello strano hash?"), Vedi la sezione 4.1 della guida Rails sui controller di azione :

Per inviare un array di valori, aggiungi una coppia vuota di parentesi quadre "[]" al nome della chiave.

Il problema è che jQuery formatta la richiesta con indici di array espliciti, piuttosto che con parentesi quadre vuote. Quindi, ad esempio, invece di inviare shared_items[]=1&shared_items[]=2, invia shared_items[0]=1&shared_items[1]=2. Rotaie vede gli indici degli array e li interpreta come chiavi di hash, piuttosto che gli indici degli array, trasformando la richiesta in una strana hash di Ruby: { shared_items: { '0' => '1', '1' => '2' } }.

Se non hai il controllo del client, puoi risolvere questo problema sul lato server convertendo l'hash in un array. Ecco come l'ho fatto:

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

1

il seguente metodo potrebbe essere utile se si utilizzano parametri forti

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

Hai considerato di fare parsed_json = ActiveSupport::JSON.decode(your_json_string)? Se stai inviando materiale in un altro modo, puoi usarlo .to_jsonper serializzare i dati.


1
Sì considerato, ma volevo sapere se esiste un "modo giusto" per farlo in Rails, senza dover codificare / decodificare manualmente.
aledalgrande

0

Stai solo cercando di ottenere la stringa JSON in un'azione del controller Rails?

Non sono sicuro di cosa stia facendo Rails con l'hash, ma potresti aggirare il problema e avere più fortuna creando un oggetto Javascript / JSON (al contrario di una stringa JSON) e inviandolo come parametro di dati per il tuo Ajax chiamata.

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

Se volessi inviarlo tramite ajax, faresti qualcosa del genere:

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

Nota con lo snippet ajax sopra, jQuery farà un'ipotesi intelligente sul dataType, anche se di solito è bene specificarlo esplicitamente.

In ogni caso, nell'azione del controller, puoi ottenere l'oggetto JSON che hai passato con l'hash params, ad es

params[:shared_items]

Ad esempio, questa azione ti restituirà il tuo oggetto json:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end

Grazie, ma è quello che sto facendo in questo momento (vedi aggiornamento) e non funziona.
aledalgrande

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.