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

Каждый раз, когда я хочу POST массива объектов 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 

Спасибо!

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

Ответ 1

В случае, если кто-то наткнется на это и хочет получить лучшее решение, вы можете указать опцию "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 и указываем тип содержимого, поэтому рельсы будут знать, как разбирать нашу строку):

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

Ответ 2

Немного старый вопрос, но я воевал с этим сам сегодня, и вот ответ, который я придумал: я считаю, что это слегка ошибка 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 опубликует, будут выглядеть примерно так (если вы посмотрите на запрос в своем 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 в параметрах [: data].

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

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

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

Ответ 3

Я просто столкнулся с этой проблемой с 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
}

Ответ 4

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

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

Ответ 5

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

Ответ 6

Вы просто пытаетесь получить строку 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 сделает интеллектуальное предположение о типе dataType, хотя обычно полезно указать его явно.

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

params[:shared_items]

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

def reply_in_json
  @shared = params[:shared_items]

  render :json => @shared
end

Ответ 7

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