Rails не декодирует JSON из jQuery правильно (массив становится хешем с целочисленными ключами)

89

Каждый раз, когда я хочу отправить массив объектов JSON с помощью jQuery в Rails, у меня возникает эта проблема. Если я структурирую массив, я вижу, что jQuery выполняет свою работу правильно:

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

Но если я просто отправлю массив как данные вызова AJAX, я получу:

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

Если я просто отправлю простой массив, он работает:

"shared_items"=>["entity_253"]

Почему Rails меняет массив на этот странный хеш? Единственная причина, которая приходит на ум, заключается в том, что Rails не может правильно понять содержимое, потому что здесь нет типа (есть ли способ установить его в вызове jQuery?):

Processing by SharedListsController#create as 

Спасибо!

Обновление: я отправляю данные в виде массива, а не строки, и массив создается динамически с помощью .push()функции. Пробовал с $.postи $.ajax, результат тот же.

Aledalgrande
источник

Ответы:

169

Если кто-то наткнется на это и захочет найти лучшее решение, вы можете указать параметр contentType: 'application / json' в вызове .ajax и заставить Rails правильно проанализировать объект JSON, не превращая его в хэши с целочисленными ключами со всеми- строковые значения.

Итак, чтобы подвести итог, моя проблема заключалась в следующем:

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

привело к тому, что Rails разобрал вещи как:

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

тогда как это (ПРИМЕЧАНИЕ: теперь мы привязываем объект javascript к строкам и указываем тип содержимого, поэтому rails будут знать, как анализировать нашу строку):

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

приводит к красивому объекту в Rails:

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

У меня это работает в Rails 3 на Ruby 1.9.3.

Swajak
источник
1
Это не кажется правильным поведением для Rails, учитывая, что получение JSON из JQuery - довольно распространенный случай для приложений Rails. Есть ли разумное объяснение тому, почему Rails ведет себя подобным образом? Кажется, что клиентскому JS-коду приходится без надобности прыгать через обручи.
Джереми Бертон
1
Я думаю, что в приведенном выше случае виноват jQuery. iirc jQuery не устанавливал Content-Typeзапрос application/json, а вместо этого отправлял данные в виде html-формы? Трудно вспомнить так далеко. Rails работал нормально, после того, как Content-Typeбыло установлено правильное значение, и отправляемые данные были действительным JSON.
Swajak
3
Хочу отметить, что @swajak не только структурировал объект, но и указывал на негоcontentType: 'application/json'
Роджер Лам
1
Спасибо @RogerLam, я обновил решение, чтобы лучше описать это, надеюсь,
swajak 01
1
@swajak: огромное спасибо! Ты действительно спас мне день! :)
Кульгар
12

Немного старый вопрос, но я сам боролся с этим сегодня, и вот ответ, который я придумал: я считаю, что это небольшая ошибка jQuery, но он делает только то, что для него естественно. Однако у меня есть обходной путь.

Учитывая следующий вызов 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}]}

});

Значения, которые публикует jQuery, будут выглядеть примерно так (если вы посмотрите на Request в вашем Firebug-of-choice), вы получите данные формы, которые выглядят так:

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

Если вы CGI.unencode, вы получите

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

Я считаю, что это связано с тем, что jQuery считает, что эти ключи в вашем JSON являются именами элементов формы и что он должен обрабатывать их, как если бы у вас было поле с именем «user [name]».

Итак, они входят в ваше приложение Rails, Rails видит скобки и создает хеш для хранения самого внутреннего ключа имени поля («1», которую jQuery «услужливо» добавил).

Во всяком случае, я обошел это поведение, построив свой вызов ajax следующим образом;

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

Это заставляет jQuery думать, что этот JSON - это значение, которое вы хотите передать целиком, а не объект Javascript, который он должен принять, и превратить все ключи в имена полей формы.

Однако это означает, что на стороне Rails все немного иначе, потому что вам нужно явно декодировать JSON в params [: data].

Но это нормально:

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

TL; DR: Итак, решение: в параметре данных для вашего вызова jQuery.ajax () сделайте {"data": JSON.stringify(my_object) }явно, вместо того, чтобы загружать массив JSON в jQuery (где он неправильно угадывает, что вы хотите с ним делать.

Райан Уилкокс
источник
Лучшее решение - ниже от @swajak.
event_jr 05
7

Я только что столкнулся с этой проблемой с Rails 4. Чтобы явно ответить на ваш вопрос («Почему Rails меняет массив на этот странный хеш?»), См. Раздел 4.1 руководства Rails по контроллерам действий :

Чтобы отправить массив значений, добавьте пустую пару квадратных скобок «[]» к имени ключа.

Проблема в том, что jQuery форматирует запрос с помощью явных индексов массива, а не с помощью пустых квадратных скобок. Так, например, вместо отправки shared_items[]=1&shared_items[]=2он отправляет shared_items[0]=1&shared_items[1]=2. Rails видит индексы массива и интерпретирует их как хеш-ключи, а не индексы массива, превращая запрос в странный хеш Ruby:{ shared_items: { '0' => '1', '1' => '2' } } .

Если у вас нет контроля над клиентом, вы можете решить эту проблему на стороне сервера, преобразовав хэш в массив. Вот как я это сделал:

shared_items = []
params[:shared_items].each { |k, v|
  shared_items << v
}
Джессепино
источник
1

следующий метод может быть полезен, если вы используете строгие параметры

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
Евгений
источник
0

Вы думали сделать parsed_json = ActiveSupport::JSON.decode(your_json_string)? Если вы отправляете материал другим способом, вы можете использовать его .to_jsonдля сериализации данных.

Майкл Де Сильва
источник
1
Да, рассматривался, но хотел знать, есть ли «правильный способ» сделать это в Rails, без необходимости вручную кодировать / декодировать.
aledalgrande
0

Вы просто пытаетесь вставить строку JSON в действие контроллера Rails?

Я не уверен, что Rails делает с хешем, но вы можете обойти проблему и получить больше удачи, создав объект Javascript / JSON (в отличие от строки JSON) и отправив его как параметр данных для вашего Ajax вызов.

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

Если вы хотите отправить это через ajax, сделайте что-то вроде этого:

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

Обратите внимание на приведенный выше фрагмент ajax, jQuery сделает разумное предположение о типе данных, хотя обычно лучше указать его явно.

В любом случае, в действии вашего контроллера вы можете получить объект JSON, который вы передали с хешем params, т.е.

params[:shared_items]

Например, это действие вернет вам ваш json-объект:

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end
австралис
источник
Спасибо, но это то, что я сейчас делаю (см. Обновление), и это не работает.
aledalgrande
0

Используйте гем rack-jquery-params (отказ от ответственности: я являюсь автором). Он устраняет проблему превращения массивов в хэши с целочисленными ключами.

Калеб Кларк
источник
Лучше предоставить фрагмент кода вместо того, чтобы помещать ссылки
Викасдип Сингх