У меня есть хэш, который выглядит так:
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
}
}
Мне нужно преобразовать каждое значение, которое представляет собой одну строку внутри массива, чтобы оно выглядело так:
hash = {
'key1' => 'value' ,
'key2' => {
'sub1' => 'string' ,
'sub2' => 'string' ,
},
'shippingInfo' => {
'shippingType' => 'Calculated' ,
'shipToLocations' => 'Worldwide' ,
'expeditedShipping' => 'false' ,
'oneDayShippingAvailable' => 'false' ,
'handlingTime' => '3' ,
}
}
Я нашел это, но не смог заставить его работать https://gist.github.com/chris/b4138603a8fe17e073c6bc073eb17785

Как насчет чего-то вроде:
def deep_transform_values(hash)
return hash unless hash.is_a?(Hash)
hash.transform_values do |val|
if val.is_a?(Array) && val.length == 1
val.first
else
deep_transform_values(val)
end
end
end
Протестировано с чем-то вроде:
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
'an_integer' => 1,
'an_empty_array' => [],
'an_array_with_more_than_one_elements' => [1,2],
'a_symbol' => :symbol,
'a_string' => 'string'
}
}
Дает:
{
"key1"=>"value",
"key2"=>{
"sub1"=>"string",
"sub2"=>"string"
},
"shippingInfo"=> {
"shippingType"=>"Calculated",
"shipToLocations"=>"Worldwide",
"expeditedShipping"=>"false",
"oneDayShippingAvailable"=>"false",
"handlingTime"=>"3",
"an_integer"=>1,
"an_empty_array"=>[],
"an_array_with_more_than_one_elements"=>[1, 2],
"a_symbol"=>:symbol,
"a_string"=>"string"
}
}
После вашего вопроса в комментариях, я думаю, логика немного изменится:
class Hash
def deep_transform_values
self.transform_values do |val|
next(val.first) if val.is_a?(Array) && val.length == 1
next(val) unless val.respond_to?(:deep_transform_values)
val.deep_transform_values
end
end
end
Я обновил ответ вариантом для вашего случая @lacostenycoder.
это прекрасно работает. Я раньше не использовал next с argss. Можете ли вы указать мне на документацию для него?
Вызов deep_transform_values рекурсивно не нужен, так как все значения будут посещены в любом случае. Реализация обрабатывается через Active-support, поэтому вам понадобится Rails (или ссылка на гем).
В качестве альтернативы рассмотрите возможность использования объекта и разрешения инициализатору деконструировать некоторые ключи для вас.
Одна из причин, по которой многие люди, такие как я, начали использовать Ruby в пользу Perl, заключалась в лучшем выражении объектов вместо примитивов, таких как массивы и хэши. Используйте это в своих интересах!
class ShippingStuff # You've kept the data vague
def initialize key1:, key2:, shippingInfo:
@blk = -> val {
val.respond_to?(:push) && val.size == 1 ?
val.first :
cleankeys(val)
}
@key1 = cleankeys key1
@key2 = cleankeys key2
@shippingInfo = shippingInfo
end
attr_reader :key1, :key2, :shippingInfo
# basically a cut down version of what
# Sebastian Palma answered with
def cleankeys data
if data.respond_to? :transform_values
data.transform_values &@blk
else
@blk.call(data)
end
end
end
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
}
}
shipper = ShippingStuff.new hash.transform_keys!(&:to_sym)
shipper.key1
# "value"
shipper.key2
# {"sub1"=>"string", "sub2"=>"string"}
shipper.shippingInfo
# {"shippingType"=>["Calculated"], "shipToLocations"=>["Worldwide"], "expeditedShipping"=>["false"], "oneDayShippingAvailable"=>["false"], "handlingTime"=>["3"]}
В том же духе я бы даже создал класс Info для данных shippingInfo.
Вы можете столкнуться с другой проблемой, если key1 и key2 являются динамическими, но и это можно обойти (двойной знак для одного).
Это, вероятно, лучший образец, хотя и немного более жесткий. Структура данных здесь была неконкретным примером,
@lacostenycoder Конечно, просто хотел дать альтернативу, я не критикую. «Только мои 2 цента», возможно, делает это критикой, вероятно, потому, что люди используют это, прежде чем критиковать других, верно? Я должен найти фразу-замену… (o_º)
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string'],
},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide', 'Web'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3'],
}
}
def recurse(hash)
hash.transform_values do |v|
case v
when Array
v.size == 1 ? v.first : v
when Hash
recurse v
else
# raise exception
end
end
end
recurse hash
#=> {"key1"=>"value",
# "key2"=>{
# "sub1"=>"string",
# "sub2"=>"string"
# },
# "shippingInfo"=>{
# "shippingType"=>"Calculated",
# "shipToLocations"=>["Worldwide", "Web"],
# "expeditedShipping"=>"false",
# "oneDayShippingAvailable"=>"false",
# "handlingTime"=>"3"
# }
# }
Я вижу, что мой ответ похож на предыдущий ответ @Sebastian.
да, очень похоже, за исключением использования стиля переключателя, но все еще работает. это хорошо переводится как патч обезьяны для класса Hash?
Я заметил много ответов с ненужной рекурсией. Текущая версия Ruby 2.7.x с ActiveSupport (я тестировал с 6.1.4.4) позволит вам сделать это:
Входные данные:
hash = {
'key1' => ['value'],
'key2' => {
'sub1' => ['string'],
'sub2' => ['string']},
'shippingInfo' => {
'shippingType' => ['Calculated'],
'shipToLocations' => ['Worldwide', 'Web'],
'expeditedShipping' => ['false'],
'oneDayShippingAvailable' => ['false'],
'handlingTime' => ['3']}}
Решение:
hash.deep_transform_values do |value|
# whatever you need to do to any nested value, like:
if value == value.to_i.to_s
value.to_i
else
value
end
end
В приведенном выше примере будет возвращено преобразование типа String в Integer.
Hash#deep_transform_values требует active_support, и вы должны использовать правильную версию. Включите эти детали, чтобы сделать этот ответ действительным.
@lacostenycoder Я упомянул ActiveSupport, и рубиновая версия для полноты также добавит версию драгоценного камня.
вам нужны эти две строки, прежде чем вы создадите новый хеш-объект. require 'active_support' require 'active_support/core_ext'
Это хорошо работает для моего варианта использования. Однако, если бы я хотел исправить класс Hash для обезьян, как я могу изменить его для работы с рекурсией, чтобы я мог вызывать его для экземпляра Hash?