Как глубоко преобразовать значения в хеш Ruby

У меня есть хэш, который выглядит так:

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

Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
3
0
4 454
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Как насчет чего-то вроде:

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

Это хорошо работает для моего варианта использования. Однако, если бы я хотел исправить класс Hash для обезьян, как я могу изменить его для работы с рекурсией, чтобы я мог вызывать его для экземпляра Hash?

lacostenycoder 11.03.2019 02:27

Я обновил ответ вариантом для вашего случая @lacostenycoder.

Sebastian Palma 11.03.2019 02:34

это прекрасно работает. Я раньше не использовал next с argss. Можете ли вы указать мне на документацию для него?

lacostenycoder 11.03.2019 02:41
Этот — это то, что есть в документации по Ruby.
Sebastian Palma 11.03.2019 02:46

Вызов deep_transform_values рекурсивно не нужен, так как все значения будут посещены в любом случае. Реализация обрабатывается через Active-support, поэтому вам понадобится Rails (или ссылка на гем).

Dennis 24.12.2021 11:48

В качестве альтернативы рассмотрите возможность использования объекта и разрешения инициализатору деконструировать некоторые ключи для вас.

Одна из причин, по которой многие люди, такие как я, начали использовать 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 11.03.2019 04:32

@lacostenycoder Конечно, просто хотел дать альтернативу, я не критикую. «Только мои 2 цента», возможно, делает это критикой, вероятно, потому, что люди используют это, прежде чем критиковать других, верно? Я должен найти фразу-замену… (o_º)

ian 11.03.2019 04:39
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.

Cary Swoveland 11.03.2019 07:19

да, очень похоже, за исключением использования стиля переключателя, но все еще работает. это хорошо переводится как патч обезьяны для класса Hash?

lacostenycoder 11.03.2019 12:34

Я заметил много ответов с ненужной рекурсией. Текущая версия 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 29.12.2021 23:04

@lacostenycoder Я упомянул ActiveSupport, и рубиновая версия для полноты также добавит версию драгоценного камня.

Dennis 17.01.2022 17:28

вам нужны эти две строки, прежде чем вы создадите новый хеш-объект. require 'active_support' require 'active_support/core_ext'

lacostenycoder 19.01.2022 18:54

Другие вопросы по теме