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

89

Toda vez que desejo POSTAR uma matriz de objetos JSON com jQuery to Rails, tenho esse problema. Se eu stringificar o array, posso ver que o jQuery está fazendo seu trabalho corretamente:

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

Mas se eu apenas enviar o array como os dados da chamada AJAX, 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 Rails está mudando o array para aquele hash estranho? A única razão que vem à mente é que o Rails não consegue 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!

Atualização: estou enviando os dados como um array, não uma string e o array é criado dinamicamente usando a .push()função. Tentei com $.poste $.ajax, mesmo resultado.

Aledalgrande
fonte

Respostas:

169

Caso alguém se depare com isso e queira uma solução melhor, você pode especificar a opção "contentType: 'application / json'" na chamada .ajax e fazer com que Rails analise corretamente o objeto JSON sem distorcê-lo em hashes de chave inteira com todos os valores de string.

Então, para resumir, meu problema era este:

$.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 em Rails analisando coisas como:

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

enquanto isto (OBSERVAÇÃO: agora estamos definindo o objeto javascript e especificando um tipo de conteúdo, então o rails saberá como analisar nossa sequência):

$.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 objeto 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.

Swajak
fonte
1
Este não parece ser o comportamento correto para Rails, visto que receber JSON de JQuery é um caso bastante comum para aplicativos Rails. Existe uma justificativa defensável de porque Rails se comporta dessa maneira? Parece que o código JS do lado do cliente está tendo que pular obstáculos desnecessariamente.
Jeremy Burton
1
Acho que no caso acima o culpado é jQuery. iirc jQuery não estava definindo o Content-Typeda solicitação como application/json, mas, em vez disso, estava enviando os dados como um formulário html faria? É difícil pensar tão longe. O Rails estava funcionando bem, depois que o Content-Typefoi configurado com o valor correto e os dados enviados eram JSON válido.
swajak
3
Quero observar que @swajak não apenas restringiu o objeto, mas também especificoucontentType: 'application/json'
Roger Lam
1
Obrigado @RogerLam, eu atualizei a solução para descrever isso melhor, espero
swajak 01 de
1
@swajak: muuuuito obrigado! Você salvou meu dia, de verdade! :)
Kulgar
12

Uma pergunta um pouco antiga, mas eu mesma lutei contra isso hoje, e aqui está a resposta que eu encontrei: Eu acredito que isso é um pouco a culpa do jQuery, mas ele está apenas fazendo o que é natural. Eu, no entanto, tenho uma solução alternativa.

Dada a seguinte chamada 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}]}

});

Os valores que o jQuery postará se parecerão com isto (se você olhar para a Solicitação em seu Firebug-of-choice) fornecerá dados de formulário semelhantes a:

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

Se você CGI.unencode que você obterá

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

Acredito que isso seja porque jQuery pensa que essas chaves em seu JSON são nomes de elementos de formulário e que deve tratá-las como se você tivesse um campo chamado "usuário [nome]".

Então, eles entram em seu aplicativo Rails, 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, 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 objeto Javascript que ele deve pegar 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 de dados para sua chamada jQuery.ajax (), faça {"data": JSON.stringify(my_object) }explicitamente, em vez de alimentar o array JSON no jQuery (onde ele adivinha erroneamente o que você deseja fazer com ele.

RyanWilcox
fonte
Fique atento para uma melhor solução abaixo de @swajak.
event_jr
7

Acabei de encontrar esse problema com o Rails 4. Para responder explicitamente à sua pergunta ("Por que o Rails está mudando o array para aquele hash estranho?"), Consulte a seção 4.1 do guia Rails sobre controladores de ação :

Para enviar uma matriz de valores, acrescente um par vazio de colchetes "[]" ao nome da chave.

O problema é que o jQuery formata a solicitação com índices de array explícitos, em vez de colchetes vazios. Então, por exemplo, em vez de enviar shared_items[]=1&shared_items[]=2, ele envia shared_items[0]=1&shared_items[1]=2. Trilhos vê os índices de array e interpreta como chaves de hash em vez de índices de array, transformando o pedido em um hash de Ruby estranho: { 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 um array. Veja como eu fiz:

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

o método a seguir 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
Eugene
fonte
0

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

Michael De Silva
fonte
1
Sim considerou, mas queria saber se existe uma "maneira certa" de fazer isso no Rails, sem ter que codificar / decodificar manualmente.
aledalgrande de
0

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

Não tenho certeza do que Rails está fazendo com o hash, mas você pode contornar o problema e ter mais sorte criando um objeto Javascript / JSON (em oposição a uma string JSON) e enviando-o 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 via ajax, 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, jQuery fará uma suposição inteligente no dataType, embora geralmente seja bom especificá-lo explicitamente.

De qualquer forma, em sua ação do controlador, você pode obter o objeto JSON que você passou com o hash de parâmetros, ou seja,

params[:shared_items]

Por exemplo, esta ação vai cuspir seu objeto json de volta para você:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end
australis
fonte
Obrigado, mas é o que estou fazendo agora (veja a atualização) e não funciona.
aledalgrande de
0

Use a gem rack-jquery-params (isenção de responsabilidade: eu sou o autor). Ele corrige o problema de matrizes se tornarem hashes com chaves inteiras.

Caleb Clark
fonte
Melhor fornecer algum snippet de código em vez de colocar links
Vikasdeep Singh