Los rieles no decodifican JSON de jQuery correctamente (la matriz se convierte en un hash con claves enteras)

89

Cada vez que quiero PUBLICAR una matriz de objetos JSON con jQuery to Rails, tengo este problema. Si clasifico la matriz, puedo ver que jQuery está haciendo su trabajo correctamente:

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

Pero si solo envío la matriz como los datos de la llamada AJAX, obtengo:

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

Mientras que si solo envío una matriz simple, funciona:

"shared_items"=>["entity_253"]

¿Por qué Rails está cambiando la matriz a ese extraño hash? La única razón por la que me viene a la mente es que Rails no puede entender correctamente el contenido porque no hay ningún tipo aquí (¿hay alguna forma de configurarlo en la llamada de jQuery?):

Processing by SharedListsController#create as 

¡Gracias!

Actualización: estoy enviando los datos como una matriz, no una cadena, y la matriz se crea dinámicamente usando la .push()función. Probado con $.posty $.ajax, mismo resultado.

aledalgrande
fuente

Respuestas:

169

En caso de que alguien se tope con esto y quiera una mejor solución, puede especificar la opción "contentType: 'application / json'" en la llamada .ajax y hacer que Rails analice correctamente el objeto JSON sin distorsionarlo en hashes de clave entera con todo- valores de cadena.

Entonces, para resumir, mi problema fue que esto:

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

resultó en Rails analizando cosas como:

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

mientras que esto (NOTA: ahora estamos encadenando el objeto javascript y especificando un tipo de contenido, por lo que rails sabrá cómo analizar nuestra cadena):

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

da como resultado un objeto agradable en Rails:

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

Esto me funciona en Rails 3, en Ruby 1.9.3.

Swajak
fuente
1
Esto no parece el comportamiento correcto para Rails, dado que recibir JSON de JQuery es un caso bastante común para las aplicaciones Rails. ¿Existe una justificación defendible de por qué Rails se comporta de esta manera? Parece que el código JS del lado del cliente tiene que saltar innecesariamente a través de aros.
Jeremy Burton
1
Creo que en el caso anterior, el culpable es jQuery. iirc jQuery no estaba configurando el Content-Typede la solicitud en application/json, sino que estaba enviando los datos como lo haría un formulario html. Es difícil pensar tan lejos. Rails funcionaba bien, después de que Content-Typese estableció el valor correcto y los datos que se enviaban eran JSON válidos.
Swajak
3
Quiero tener en cuenta que @swajak no solo codificó el objeto, sino que también especificócontentType: 'application/json'
Roger Lam
1
Gracias @RogerLam, he actualizado la solución para describirlo mejor, espero
swajak
1
@swajak: ¡muchas gracias! ¡Me salvaste el día, de verdad! :)
Kulgar
12

Pregunta un poco vieja, pero luché con esto yo mismo hoy, y aquí está la respuesta que se me ocurrió: creo que esto es un poco culpa de jQuery, pero que solo está haciendo lo que es natural para él. Sin embargo, tengo una solución.

Dada la siguiente llamada ajax de 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}]}

});

Los valores que publicará jQuery se verán así (si miras la Solicitud en tu Firebug-of-choice) te darán datos de formulario que se parecen a:

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

Si CGI.unencode obtendrá

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

Creo que esto se debe a que jQuery piensa que esas claves en su JSON son nombres de elementos de formulario, y que debería tratarlas como si tuviera un campo llamado "usuario [nombre]".

Entonces entran en su aplicación Rails, Rails ve los corchetes y construye un hash para contener la clave más interna del nombre del campo (el "1" que jQuery agregó "amablemente").

De todos modos, evité este comportamiento construyendo mi llamada ajax de la siguiente manera;

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

Lo que obliga a jQuery a pensar que este JSON es un valor que desea pasar, por completo, y no un objeto Javascript que debe tomar y convertir todas las claves en nombres de campo de formulario.

Sin embargo, eso significa que las cosas son un poco diferentes en el lado de Rails, porque necesitas decodificar explícitamente el JSON en params [: data].

Pero eso esta bien:

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

TL; DR: Entonces, la solución es: en el parámetro de datos de su llamada jQuery.ajax (), hágalo {"data": JSON.stringify(my_object) }explícitamente, en lugar de alimentar la matriz JSON en jQuery (donde adivina erróneamente lo que quiere hacer con ella.

RyanWilcox
fuente
La mejor solución de atención se encuentra a continuación de @swajak.
event_jr
7

Me acabo de encontrar con este problema con Rails 4. Para responder explícitamente a su pregunta ("¿Por qué Rails cambia la matriz a ese hash extraño?"), Consulte la sección 4.1 de la guía Rails sobre controladores de acción :

Para enviar una matriz de valores, agregue un par de corchetes vacíos "[]" al nombre de la clave.

El problema es que jQuery formatea la solicitud con índices de matriz explícitos, en lugar de corchetes vacíos. Entonces, por ejemplo, en lugar de enviar shared_items[]=1&shared_items[]=2, envía shared_items[0]=1&shared_items[1]=2. Carriles ve los índices de matriz e interpreta como claves hash en lugar de índices de matriz, convirtiendo la solicitud en un hash Rubí raro: { shared_items: { '0' => '1', '1' => '2' } }.

Si no tiene el control del cliente, puede solucionar este problema en el lado del servidor convirtiendo el hash en una matriz. Así es como lo hice:

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

El siguiente método podría ser útil si usa parámetros fuertes

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
Eugenio
fuente
0

¿Ha considerado hacerlo parsed_json = ActiveSupport::JSON.decode(your_json_string)? Si está enviando cosas al revés, puede usarlo .to_jsonpara serializar los datos.

Michael De Silva
fuente
1
Sí, considerado, pero quería saber si hay una "forma correcta" de hacer esto en Rails, sin tener que codificar / decodificar manualmente.
aledalgrande
0

¿Está tratando de convertir la cadena JSON en una acción de controlador Rails?

No estoy seguro de qué está haciendo Rails con el hash, pero puede solucionar el problema y tener más suerte creando un objeto Javascript / JSON (en lugar de una cadena JSON) y enviándolo como parámetro de datos para su Ajax. llamada.

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

Si quisiera enviar esto a través de ajax, entonces haría algo como esto:

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

Tenga en cuenta que con el fragmento de ajax anterior, jQuery hará una suposición inteligente sobre el tipo de datos, aunque generalmente es bueno especificarlo explícitamente.

De cualquier manera, en la acción de su controlador, puede obtener el objeto JSON que pasó con el hash params, es decir

params[:shared_items]

Por ejemplo, esta acción le escupirá su objeto json:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end
australis
fuente
Gracias, pero eso es lo que estoy haciendo ahora mismo (ver actualización) y no funciona.
aledalgrande
0

Utilice la gema rack-jquery-params (descargo de responsabilidad: soy el autor). Soluciona el problema de que las matrices se conviertan en hashes con claves enteras.

Caleb Clark
fuente
Es mejor proporcionar un fragmento de código en lugar de poner enlaces
Vikasdeep Singh