Rails ne décodant pas correctement JSON de jQuery (tableau devenant un hachage avec des clés entières)

89

Chaque fois que je veux POSTER un tableau d'objets JSON avec jQuery to Rails, j'ai ce problème. Si je stringify le tableau, je peux voir que jQuery fait son travail correctement:

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

Mais si j'envoie simplement le tableau en tant que données de l'appel AJAX, je reçois:

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

Alors que si j'envoie juste un tableau simple, cela fonctionne:

"shared_items"=>["entity_253"]

Pourquoi Rails change-t-il le tableau en ce hachage étrange? La seule raison qui me vient à l'esprit est que Rails ne peut pas comprendre correctement le contenu car il n'y a pas de type ici (existe-t-il un moyen de le définir dans l'appel jQuery?):

Processing by SharedListsController#create as 

Je vous remercie!

Mise à jour: j'envoie les données sous forme de tableau, pas de chaîne et le tableau est créé dynamiquement à l'aide de la .push()fonction. Essayé avec $.postet $.ajax, même résultat.

Aledalgrande
la source

Réponses:

169

Au cas où quelqu'un trébucherait sur cela et souhaiterait une meilleure solution, vous pouvez spécifier l'option "contentType: 'application / json'" dans l'appel .ajax et demander à Rails d'analyser correctement l'objet JSON sans le brouiller en hachages entiers avec tous valeurs de chaîne.

Donc, pour résumer, mon problème était que ceci:

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

a abouti à l'analyse de Rails comme:

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

alors que ceci (NOTE: nous allons maintenant stringifier l'objet javascript et spécifier un type de contenu, donc les rails sauront comment analyser notre chaîne):

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

donne un bel objet dans Rails:

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

Cela fonctionne pour moi dans Rails 3, sur Ruby 1.9.3.

swajak
la source
1
Cela ne semble pas être le bon comportement pour Rails, étant donné que la réception de JSON à partir de JQuery est un cas assez courant pour les applications Rails. Existe-t-il une justification défendable quant à la raison pour laquelle Rails se comporte de cette manière? Il semble que le code JS côté client doive sauter inutilement à travers des cerceaux.
Jeremy Burton
1
Je pense que dans le cas ci-dessus, le coupable est jQuery. iirc jQuery ne définissait pas Content-Typela requête sur application/json, mais envoyait plutôt les données comme le ferait un formulaire html? Difficile de repenser aussi loin. Rails fonctionnait très bien, après avoir Content-Typedéfini la valeur correcte, et les données envoyées étaient du JSON valide.
swajak
3
Vous voulez noter non seulement @swajak a stringifié l'objet, mais également spécifiercontentType: 'application/json'
Roger Lam
1
Merci @RogerLam, j'ai mis à jour la solution pour mieux décrire cela, j'espère
swajak
1
@swajak: merci beaucoup! Tu as sauvé ma journée, vraiment! :)
Kulgar
12

Question un peu ancienne, mais je me suis battu avec cela moi-même aujourd'hui, et voici la réponse que j'ai trouvée: je crois que c'est un peu la faute de jQuery, mais que ce n'est que ce qui lui est naturel. J'ai cependant une solution de contournement.

Compte tenu de l'appel jQuery ajax suivant:

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

});

Les valeurs que jQuery publiera ressembleront à ceci (si vous regardez la requête dans votre Firebug-of-choice) vous donneront des données de formulaire qui ressemblent à:

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

Si vous CGI.unencode que vous obtiendrez

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

Je crois que c'est parce que jQuery pense que ces clés dans votre JSON sont des noms d'élément de formulaire, et qu'il devrait les traiter comme si vous aviez un champ nommé "utilisateur [nom]".

Alors ils entrent dans votre application Rails, Rails voit les crochets et construit un hachage pour contenir la clé la plus interne du nom de champ (le "1" que jQuery a "utilement" ajouté).

Quoi qu'il en soit, j'ai contourné ce comportement en construisant mon appel ajax de la manière suivante;

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

Ce qui oblige jQuery à penser que ce JSON est une valeur que vous souhaitez transmettre, entièrement, et non un objet Javascript qu'il doit prendre et transformer toutes les clés en noms de champs de formulaire.

Cependant, cela signifie que les choses sont un peu différentes du côté de Rails, car vous devez décoder explicitement le JSON dans les paramètres [: data].

Mais ça va:

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

TL; DR: Donc, la solution est: dans le paramètre data de votre appel jQuery.ajax (), faites-le {"data": JSON.stringify(my_object) }explicitement, au lieu d'alimenter le tableau JSON dans jQuery (où il devine mal ce que vous voulez en faire.

RyanWilcox
la source
Heads up meilleure solution est ci-dessous de @swajak.
event_jr
7

Je viens de rencontrer ce problème avec Rails 4. Pour répondre explicitement à votre question ("Pourquoi Rails change-t-il le tableau en ce hachage étrange?"), Voir la section 4.1 du guide Rails sur les contrôleurs d'action :

Pour envoyer un tableau de valeurs, ajoutez une paire vide de crochets "[]" au nom de la clé.

Le problème est que jQuery formate la requête avec des index de tableau explicites, plutôt qu'avec des crochets vides. Ainsi, par exemple, au lieu d'envoyer shared_items[]=1&shared_items[]=2, il envoie shared_items[0]=1&shared_items[1]=2. Rails voit les indices de tableau et les interprète comme clés de hachage plutôt que les indices de tableau, transformant la demande dans un étrange hachage Ruby: { shared_items: { '0' => '1', '1' => '2' } }.

Si vous n'avez pas le contrôle du client, vous pouvez résoudre ce problème côté serveur en convertissant le hachage en tableau. Voici comment je l'ai fait:

shared_items = []
params[:shared_items].each { |k, v|
  shared_items << v
}
Jessépinho
la source
1

La méthode suivante peut être utile si vous utilisez des paramètres forts

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
Eugène
la source
0

Avez-vous envisagé de le faire parsed_json = ActiveSupport::JSON.decode(your_json_string)? Si vous envoyez des éléments dans l'autre sens, vous pouvez les utiliser .to_jsonpour sérialiser les données.

Michael De Silva
la source
1
Oui considéré, mais je voulais savoir s'il existe une «bonne façon» de faire cela dans Rails, sans avoir à encoder / décoder manuellement.
aledalgrande
0

Essayez-vous simplement d'obtenir la chaîne JSON dans une action de contrôleur Rails?

Je ne suis pas sûr de ce que fait Rails avec le hachage, mais vous pourriez contourner le problème et avoir plus de chance en créant un objet Javascript / JSON (par opposition à une chaîne JSON) et en l'envoyant comme paramètre de données pour votre Ajax appel.

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

Si vous vouliez envoyer ceci via ajax, vous feriez quelque chose comme ceci:

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

Notez qu'avec l'extrait ajax ci-dessus, jQuery fera une estimation intelligente du type de données, bien qu'il soit généralement bon de le spécifier explicitement.

Quoi qu'il en soit, dans votre action de contrôleur, vous pouvez obtenir l'objet JSON que vous avez passé avec le hachage de paramètres, c'est-à-dire

params[:shared_items]

Par exemple, cette action vous crachera votre objet json:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end
australis
la source
Merci, mais c'est ce que je fais en ce moment (voir la mise à jour) et cela ne fonctionne pas.
aledalgrande
0

Utilisez le gem rack-jquery-params (avertissement: je suis l'auteur). Il résout le problème des tableaux qui deviennent des hachages avec des clés entières.

Caleb Clark
la source
Mieux vaut fournir un extrait de code au lieu de mettre des liens
Vikasdeep Singh