Rails, die JSON nicht korrekt aus jQuery dekodieren (Array wird zu einem Hash mit Ganzzahlschlüsseln)


89

Jedes Mal, wenn ich ein Array von JSON-Objekten mit jQuery to Rails POSTEN möchte, tritt dieses Problem auf. Wenn ich das Array stringiere, kann ich sehen, dass jQuery seine Arbeit korrekt macht:

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

Aber wenn ich das Array nur als Daten des AJAX-Aufrufs sende, erhalte ich:

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

Wenn ich nur ein einfaches Array sende, funktioniert es:

"shared_items"=>["entity_253"]

Warum ändert Rails das Array in diesen seltsamen Hash? Der einzige Grund, der mir in den Sinn kommt, ist, dass Rails den Inhalt nicht richtig verstehen kann, weil hier kein Typ vorhanden ist (gibt es eine Möglichkeit, ihn im jQuery-Aufruf festzulegen?):

Processing by SharedListsController#create as 

Danke dir!

Update: Ich sende die Daten als Array, nicht als Zeichenfolge, und das Array wird mithilfe der .push()Funktion dynamisch erstellt . Versucht mit $.postund $.ajax, gleiches Ergebnis.

Antworten:


169

Falls jemand darauf stößt und eine bessere Lösung wünscht, können Sie die Option "contentType: 'application / json'" im .ajax-Aufruf angeben und Rails das JSON-Objekt ordnungsgemäß analysieren lassen, ohne es mit all- in ganzzahlige Hashes zu verwandeln. Zeichenfolgenwerte.

Zusammenfassend war mein Problem, dass dies:

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

führte dazu, dass Rails Dinge wie folgt analysierte:

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

während dies (HINWEIS: Wir stringifizieren jetzt das Javascript-Objekt und geben einen Inhaltstyp an, damit Rails wissen, wie unser String analysiert wird):

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

ergibt ein schönes Objekt in Rails:

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

Dies funktioniert für mich in Rails 3 unter Ruby 1.9.3.


1
Dies scheint nicht das richtige Verhalten für Rails zu sein, da das Empfangen von JSON von JQuery ein ziemlich häufiger Fall für Rails-Apps ist. Gibt es eine vertretbare Begründung dafür, warum sich Rails so verhält? Es scheint, dass der clientseitige JS-Code unnötig durch Reifen springen muss.
Jeremy Burton

1
Ich denke, im obigen Fall ist der Schuldige jQuery. iirc jQuery stellte die Content-TypeAnforderung nicht auf ein application/json, sondern sendete die Daten stattdessen wie ein HTML-Formular? Schwer so weit zurück zu denken. Rails funktionierte Content-Typeeinwandfrei , nachdem der Wert auf den richtigen Wert eingestellt wurde und die gesendeten Daten gültiges JSON waren.
Swajak

3
Ich möchte darauf hinweisen, dass @swajak das Objekt nicht nur stringifiziert, sondern auch spezifiziert hatcontentType: 'application/json'
Roger Lam

1
Danke @RogerLam, ich habe die Lösung aktualisiert, um das besser zu beschreiben, hoffe ich
Swajak

1
@ Swajak: Vielen Dank! Du hast mir wirklich den Tag gerettet! :)
Kulgar

12

Eine etwas alte Frage, aber ich habe heute selbst damit gekämpft, und hier ist die Antwort, die ich gefunden habe: Ich glaube, das ist etwas jQuerys Schuld, aber es tut nur das, was für sie natürlich ist. Ich habe jedoch eine Problemumgehung.

Bei folgendem jQuery Ajax-Aufruf:

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

});

Die Werte, die jQuery veröffentlicht, sehen ungefähr so ​​aus (wenn Sie sich die Anfrage in Ihrem Firebug Ihrer Wahl ansehen) und geben Ihnen Formulardaten, die wie folgt aussehen:

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

Wenn Sie CGI.unencode, den Sie erhalten

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

Ich glaube, das liegt daran, dass jQuery der Meinung ist, dass diese Schlüssel in Ihrem JSON Formularelementnamen sind und dass sie so behandelt werden sollten, als hätten Sie ein Feld mit dem Namen "user [name]".

Sie kommen also in Ihre Rails-App, Rails sieht die Klammern und erstellt einen Hash, der den innersten Schlüssel des Feldnamens enthält (die "1", die jQuery "hilfreich" hinzugefügt hat).

Wie auch immer, ich habe dieses Verhalten umgangen, indem ich meinen Ajax-Aufruf folgendermaßen konstruiert habe:

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

Dies zwingt jQuery zu der Annahme, dass dieser JSON ein Wert ist , den Sie vollständig übergeben möchten, und kein Javascript-Objekt, das alle Schlüssel in Formularfeldnamen umwandeln muss.

Dies bedeutet jedoch, dass die Dinge auf der Rails-Seite etwas anders sind, da Sie den JSON explizit in params [: data] dekodieren müssen.

Aber das ist in Ordnung:

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

TL; DR: Die Lösung lautet also: Führen Sie im Datenparameter Ihres jQuery.ajax () -Aufrufs {"data": JSON.stringify(my_object) }explizit aus, anstatt das JSON-Array in jQuery einzuspeisen (wo es falsch errät, was Sie damit tun möchten.


Heads-up bessere Lösung ist unten von @swajak.
event_jr

7

Ich bin gerade auf dieses Problem mit Rails 4 gestoßen. Um Ihre Frage explizit zu beantworten ("Warum ändert Rails das Array in diesen seltsamen Hash?"), Siehe Abschnitt 4.1 des Rails-Handbuchs zu Action Controllern :

Um ein Array von Werten zu senden, fügen Sie dem Schlüsselnamen ein leeres Paar eckiger Klammern "[]" hinzu.

Das Problem ist, dass jQuery die Anforderung mit expliziten Array-Indizes formatiert und nicht mit leeren eckigen Klammern. Anstatt zu senden shared_items[]=1&shared_items[]=2, sendet es beispielsweise shared_items[0]=1&shared_items[1]=2. Rails sieht die Array-Indizes und interpretiert sie als Hash-Schlüssel und nicht als Array-Indizes. Dadurch wird die Anforderung zu einem seltsamen Ruby-Hash : { shared_items: { '0' => '1', '1' => '2' } }.

Wenn Sie keine Kontrolle über den Client haben, können Sie dieses Problem auf der Serverseite beheben, indem Sie den Hash in ein Array konvertieren. So habe ich es gemacht:

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

1

Die folgende Methode kann hilfreich sein, wenn Sie starke Parameter verwenden

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

Hast du darüber nachgedacht parsed_json = ActiveSupport::JSON.decode(your_json_string)? Wenn Sie Inhalte in die andere Richtung senden, können Sie .to_jsondie Daten serialisieren.


1
Ja, aber ich wollte wissen, ob es einen "richtigen Weg" gibt, dies in Rails zu tun, ohne manuell codieren / decodieren zu müssen.
Aledalgrande

0

Versuchen Sie nur, die JSON-Zeichenfolge in eine Rails-Controller-Aktion zu integrieren?

Ich bin nicht sicher, was Rails mit dem Hash macht, aber Sie könnten das Problem umgehen und mehr Glück haben, indem Sie ein Javascript / JSON-Objekt (im Gegensatz zu einer JSON-Zeichenfolge) erstellen und dieses als Datenparameter für Ihr Ajax senden Anruf.

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

Wenn Sie dies über Ajax senden möchten, würden Sie Folgendes tun:

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

Beachten Sie, dass jQuery mit dem obigen Ajax-Snippet den Datentyp intelligent errät, obwohl es normalerweise gut ist, ihn explizit anzugeben.

In beiden Fällen können Sie in Ihrer Controller-Aktion das JSON-Objekt abrufen, das Sie mit dem Parameter-Hash übergeben haben, d. H.

params[:shared_items]

Zum Beispiel spuckt diese Aktion Ihr JSON-Objekt zurück auf Sie:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end

Danke, aber genau das mache ich gerade (siehe Update) und es funktioniert nicht.
Aledalgrande

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.