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, такий же результат.

Відповіді:


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 і вказуємо тип вмісту, тому рейки знатимуть, як проаналізувати наш рядок):

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


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

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 опублікує, виглядатимуть приблизно так (якщо ви подивитесь на Запит у вибраній вами Firebug), ви отримаєте дані форми, які виглядають так:

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 є іменами елементів форми, і що він повинен розглядати їх так, ніби у вас є поле з ім'ям "користувач [ім'я]".

Тож вони потрапляють у ваш додаток 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 (де він неправильно здогадується, що ви хочете з ним зробити.


Зверніть увагу на краще рішення - нижче від @swajak.
event_jr

7

Я щойно натрапив на цю проблему з Rails 4. Щоб чітко відповісти на ваше запитання ("Чому Rails змінює масив на той дивний хеш?"), Див. Розділ 4.1 посібника Rails на контролерах дій :

Щоб надіслати масив значень, додайте до імені ключа порожню пару квадратних дужок "[]".

Проблема в тому, що jQuery форматує запит із явними індексами масивів, а не з порожніми квадратними дужками. Так, наприклад, замість надсилання shared_items[]=1&shared_items[]=2, воно надсилає shared_items[0]=1&shared_items[1]=2. Рейки бачать індекси масиву і інтерпретують їх як хеш - ключі , а не індекси масиву, перетворюючи запит в дивний рубінового хеш: { 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 (застереження: я автор). Це виправляє вашу проблему, коли масиви стають хешами з цілочисельними клавішами.


Краще надати якийсь фрагмент коду замість розміщення посилань
Vikasdeep Singh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.