Rails não decodificando JSON de jQuery corretamente (array se tornando um hash com chaves inteiras)

Toda vez que eu quero publicar uma matriz de objects JSON com jQuery to Rails, eu tenho esse problema. Se eu stringify a matriz, posso ver que o jQuery está fazendo o seu trabalho corretamente:

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

Mas se eu apenas enviar a matriz como os dados da chamada AJAX eu recebo:

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

Considerando que, se eu apenas enviar um array simples, ele funciona:

 "shared_items"=>["entity_253"] 

Por que o Rails está alterando o array para esse estranho hash? A única razão que vem à mente é que o Rails não pode entender corretamente o conteúdo porque não há nenhum tipo aqui (existe uma maneira de configurá-lo na chamada do jQuery?):

 Processing by SharedListsController#create as 

Obrigado!

Update: Estou enviando os dados como uma matriz, não uma string e a matriz é criada dinamicamente usando a function .push() . Tentei com $.post e $.ajax , mesmo resultado.

No caso de alguém se deparar com isso e quiser uma solução melhor, você pode especificar a opção “contentType: ‘application / json'” na chamada .ajax e fazer com que o Rails analise corretamente o object JSON sem passar para hashes com chaves inteiras com all-in-one. valores de string.

Então, para resumir, meu problema era que isso:

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

resultou no Rails analisando coisas como:

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

enquanto isso (OBSERVAÇÃO: agora estamos restringindo o object javascript e especificando um tipo de conteúdo, assim os rails saberão como analisar nossa string):

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

resulta em um bom object no Rails:

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

Isso funciona para mim no Rails 3, no Ruby 1.9.3.

Questão ligeiramente antiga, mas eu lutei com isso eu mesmo hoje, e aqui está a resposta que eu criei: Eu acredito que isso é um pouco culpa de jQuery, mas isso é apenas fazer o que é natural para ele. Eu tenho, no entanto, uma solução alternativa.

Dada a seguinte chamada ajax jQuery:

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

Os valores que o jQuery postará serão parecidos com isto (se você olhar para o Pedido no seu Firebug-of-choice) lhe fornecerão dados de formulário que se parecem com:

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

Se você CGI.unencode que você vai conseguir

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

Eu acredito que isso é porque o jQuery acha que essas chaves no seu JSON são nomes de elementos de formulário, e que deve tratá-las como se você tivesse um campo chamado “user [name]”.

Então eles entram em seu aplicativo Rails, o Rails vê os colchetes e constrói um hash para manter a chave mais interna do nome do campo (o “1” que o jQuery “utilmente” adicionou).

De qualquer forma, eu contornei esse comportamento construindo minha chamada ajax da seguinte maneira;

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

O que força o jQuery a pensar que esse JSON é um valor que você deseja passar, inteiramente, e não um object Javascript, que ele deve executar e transformar todas as chaves em nomes de campos de formulário.

No entanto, isso significa que as coisas são um pouco diferentes no lado do Rails, porque você precisa decodificar explicitamente o JSON em params [: data].

Mas tudo bem

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

TL; DR: Então, a solução é: no parâmetro data para sua chamada jQuery.ajax (), fazer {"data": JSON.stringify(my_object) } explicitamente, em vez de alimentar o array JSON em jQuery (onde ele adivinha erroneamente o que você quer fazer com isso.

Acabei de me deparar com este problema com o Rails 4. Para responder explicitamente à sua pergunta (“Por que o Rails está alterando a matriz para esse estranho hash?”), Consulte a seção 4.1 do guia Rails em Action Controllers :

Para enviar uma matriz de valores, anexe um par vazio de colchetes “[]” ao nome da chave.

O problema é que o jQuery formata o pedido com índices de array explícitos, ao invés de colchetes vazios. Assim, por exemplo, em vez de enviar shared_items[]=1&shared_items[]=2 , ele envia shared_items[0]=1&shared_items[1]=2 . Rails vê os índices da matriz e interpreta-os como chaves hash em vez de índices de array, transformando a requisição em um estranho hash Ruby: { shared_items: { '0' => '1', '1' => '2' } } .

Se você não tiver controle do cliente, poderá corrigir esse problema no lado do servidor convertendo o hash em uma matriz. Veja como eu fiz:

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

O seguinte método pode ser útil se você usar parâmetros fortes

 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 

Você já pensou em fazer parsed_json = ActiveSupport::JSON.decode(your_json_string) ? Se você está enviando coisas de outra maneira, você pode usar .to_json para serializar os dados.

Você está apenas tentando obter a string JSON em uma ação do controlador Rails?

Não tenho certeza do que o Rails está fazendo com o hash, mas você pode contornar o problema e ter mais sorte criando um object Javascript / JSON (em oposição a uma string JSON) e enviando isso como o parâmetro de dados para seu Ajax. ligar.

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

Se você quisesse enviar isso via ajax, você faria algo assim:

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

Observe com o snippet ajax acima, o jQuery fará um palpite inteligente no dataType, embora geralmente seja bom especificá-lo explicitamente.

De qualquer forma, na sua ação de controlador, você pode obter o object JSON passado com o hash params, ou seja,

 params[:shared_items] 

Por exemplo, esta ação irá cuspir seu object json em você:

 def reply_in_json @shared = params[:shared_items] render :json => @shared end 

Use o gem -jquery-params (disclaimer: Eu sou o autor). Ele corrige seu problema de matrizes se tornando hashes com chaves inteiras.