web-dev-qa-db-fra.com

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

Chaque fois que je veux POST un tableau d'objets JSON avec jQuery to Rails, j'ai ce problème. Si je stringifie 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 j'obtiens:

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

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

"shared_items"=>["entity_253"]

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

Processing by SharedListsController#create as 

Merci!

pdate: J'envoie les données sous forme de tableau, pas une chaîne et le tableau est créé dynamiquement à l'aide de la fonction .Push(). J'ai essayé avec $.post Et $.ajax, Même résultat.

88
aledalgrande

Dans le cas où quelqu'un tombe sur cela et souhaite une meilleure solution, vous pouvez spécifier l'option "contentType: 'application/json'" dans l'appel .ajax et avoir Rails correctement analyser l'objet JSON sans se gargariser il dans des hachages à clé entière avec des valeurs toutes chaînes.

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 entraîné Rails analyse les choses comme:

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

alors que ceci (REMARQUE: nous allons maintenant stringifier l'objet javascript et spécifier un type de contenu, donc Rails saura 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.

165
swajak

Question un peu ancienne, mais j'ai combattu avec cela moi-même aujourd'hui, et voici la réponse que j'ai trouvée: je crois que c'est légèrement la faute de jQuery, mais qu'il ne fait que ce qui lui est naturel. J'ai cependant une solution de contournement.

Étant donné 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 affichera ressembleront à ceci (si vous regardez la demande dans votre Firebug-of-choice) vous donnera 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éments de formulaire, et qu'il devrait les traiter comme si vous aviez un champ nommé "utilisateur [nom]".

Donc, ils entrent dans votre Rails app, Rails voit les crochets et construit un hachage pour contenir la clé la plus profonde du nom du champ (le "1" qui jQuery "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 voulez passer, entièrement, et pas 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é 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 la suivante: dans le paramètre de données de votre appel jQuery.ajax (), faites explicitement {"data": JSON.stringify(my_object) }, au lieu d'alimenter le tableau JSON dans jQuery (où il devine à tort ce que vous voulez en faire.

11
RyanWilcox

Je viens de rencontrer ce problème avec Rails 4. Pour répondre explicitement à votre question ("Pourquoi Rails change-t-il le tableau en cet étrange hachage?")), Voir 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 demande avec des indices 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 des clés de hachage plutôt que des indices de tableau, transformant la demande en un étrange Ruby hachage: { shared_items: { '0' => '1', '1' => '2' } }.

Si vous ne contrôlez pas le 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
}
6
jessepinho

la méthode suivante pourrait ê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
1
Eugene

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

Je ne sais pas ce que Rails fait 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 envoyant que travers comme paramètre de données pour votre appel Ajax.

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

Si vous vouliez l'envoyer 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 supposition intelligente sur le 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 params, 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
0
australis

Utilisez le rack-jquery-params gem (clause de non-responsabilité: je suis l'auteur). Il corrige votre problème de tableaux devenant des hachages avec des clés entières.

0
Caleb Clark

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

0
Michael De Silva