Как выводить JSON в Rails без экранирования обратных косых черт
Мне нужно вывести некоторый JSON для клиента в несколько необычном формате. Мое приложение написано с помощью Rails 5.
Желаемый JSON:
{
"key": "\/Date(0000000000000)\/"
}
Значение метки времени должно иметь \/
как в начале, так и в конце строки. Насколько я могу судить, это формат, обычно используемый в сервисах .NET. Я застрял, пытаясь правильно вывести косые черты.
Я уменьшил проблему до приложения vanilla Rails 5 с одним действием контроллера. Все перестановки эскизов, о которых я могу думать, до сих пор не удалось.
def index
render json: {
a: '\/Date(0000000000000)\/',
b: "\/Date(0000000000000)\/",
c: '\\/Date(0000000000000)\\/',
d: "\\/Date(0000000000000)\\/"
}
end
Что выводит следующее:
{
"a": "\\/Date(0000000000000)\\/",
"b": "/Date(0000000000000)/",
"c": "\\/Date(0000000000000)\\/",
"d": "\\/Date(0000000000000)\\/"
}
В целях обсуждения предположим, что формат не может быть изменен, так как он контролируется третьей стороной.
Я загрузил тестовое приложение в Github, чтобы продемонстрировать проблему. https://github.com/gregawoods/test_app_ignore_me
Ответы
Ответ 1
После некоторого мозгового штурма с коллегами (спасибо @TheZanke), мы столкнулись с решением, которое работает с исходным выходом Rails JSON.
ПРЕДУПРЕЖДЕНИЕ: Этот код переопределяет некоторые основные действия в ActiveSupport. Используйте на свой страх и риск и применяйте разумное модульное тестирование!
Мы отследили это до кодировки JSON в ActiveSupport. Все строки в конечном итоге кодируются через ActiveSupport::JSON.encode
. Нам нужно было найти способ короткого замыкания этой логики и просто вернуть незакодированную строку.
Сначала мы расширили метод EscapedString#to_json
, найденный здесь.
module EscapedStringExtension
def to_json(*)
if starts_with?('noencode:')
"\"#{self}\"".gsub('noencode:', '')
else
super
end
end
end
module ActiveSupport::JSON::Encoding
class JSONGemEncoder
class EscapedString
prepend EscapedStringExtension
end
end
end
Затем в контроллере мы добавляем флаг noencode:
в хэш-код json. Это говорит нашей версии to_json
не делать никакой дополнительной кодировки.
def index
render json: {
a: '\/Date(0000000000000)\/',
b: 'noencode:\/Date(0000000000000)\/',
}
end
Полученный вывод показывает, что b
дает нам то, что мы хотим, а a
сохраняет стандартное поведение.
$ curl http://localhost:3000/sales/index.json
{"a":"\\/Date(0000000000000)\\/","b":"\/Date(0000000000000)\/"}
Ответ 2
Размышляйте над этим:
Ruby рассматривает forward-slashes одно и то же в строках с двойными кавычками и одинарными кавычками.
"/" # => "/"
'/' # => "/"
В строке с двойными кавычками "\/"
означает, что \
выполняет следующий символ. Поскольку /
не имеет экранированного эквивалента, он приводит к одной прямой косой чертой:
"\/" # => "/"
В одной кавычки во всех случаях, кроме одного, это означает, что обратный слэш, за которым следует буквальное значение символа. Это единственный случай, когда вы хотите представить обратную косую черту:
'\/' # => "\\/"
"\\/" # => "\\/"
'\\/' # => "\\/"
Изучение этого является одной из самых запутанных частей, связанных со строками в языках, и это не ограничивается Ruby, это что-то с первых дней программирования.
Зная это:
require 'json'
puts JSON[{ "key": "\/value\/" }]
puts JSON[{ "key": '/value/' }]
puts JSON[{ "key": '\/value\/' }]
# >> {"key":"/value/"}
# >> {"key":"/value/"}
# >> {"key":"\\/value\\/"}
вы должны уметь больше понимать, что вы видите в своих результатах и в выводе JSON выше.
Я думаю, что правила для этого были первоначально созданы для C, поэтому "" Escape sequence in C" может помочь.
Ответ 3
Привет, я думаю, что это самый простой способ
.gsub("/",'//').gsub('\/','')
для ввода {:key=>"\\/Date(0000000000000)\\/"}
(напечатан)
первый gsub выполнит {"key":"\\//Date(0000000000000)\\//"}
секунд вы получите
{"key":"\/Date(0000000000000)\/"}
по мере необходимости