Ruby converte Objeto em Hash


127

Digamos que eu tenho um Giftobjeto com @name = "book"& @price = 15.95. Qual é a melhor maneira de converter isso para o Hash {name: "book", price: 15.95}em Ruby, não para o Rails (embora fique à vontade para dar a resposta do Rails também)?


16
@ Gift.attributes.to_options faria?
Sr. L

1
1) O presente é um objeto ActiveRecord? 2) podemos assumir que @ name / @ price não são apenas variáveis ​​de instância, mas também acessadores de leitores? 3) você deseja apenas nome e preço ou todos os atributos de um presente, sejam eles quais forem?
tokland

@tokland, 1) não, Gifté exatamente como o @nash definiu , exceto 2) com certeza, as variáveis ​​da instância podem ter acessadores de leitor. 3) Todos os atributos presentes.
Ma11hew28

Está bem. A questão sobre o acesso às variáveis ​​/ leitores da instância era saber se queria um acesso externo (nash) ou método interno (levinalex). Atualizei minha resposta para a abordagem "interna".
tokland

Respostas:


80
class Gift
  def initialize
    @name = "book"
    @price = 15.95
  end
end

gift = Gift.new
hash = {}
gift.instance_variables.each {|var| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}

Alternativamente com each_with_object:

gift = Gift.new
hash = gift.instance_variables.each_with_object({}) { |var, hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }
p hash # => {"name"=>"book", "price"=>15.95}

3
Você pode usar o inject para ignorar a inicialização da variável: gift.instance_variables.inject ({}) {| hash, var | hash [var.to_s.delete ("@")] = presente.instância_variavel_get (var); hash}
Jordan

8
Agradável. Substituí var.to_s.delete("@")por var[1..-1].to_sympara obter símbolos.
Ma11hew28

3
Não use injetar, uso gift.instance_variables.each_with_object({}) { |var,hash| hash[var.to_s.delete("@")] = gift.instance_variable_get(var) }e livrar-se da fuga; hash
Narfanator

1
Eu nunca vou entender o fetiche por rubi each. mape injectsão muito mais poderosos. Este é um aspecto de design que eu tenho com o Ruby: mape injecté implementado com each. É simplesmente ciência da computação ruim.
Nate Symer 21/04

Um pouco mais conciso:hash = Hash[gift.instance_variables.map { |var| [var.to_s[1..-1], gift.instance_variable_get(var)] } ]
Marvin


48

Implementar #to_hash?

class Gift
  def to_hash
    hash = {}
    instance_variables.each { |var| hash[var.to_s.delete('@')] = instance_variable_get(var) }
    hash
  end
end


h = Gift.new("Book", 19).to_hash

Tecnicamente, deve ser .to_hash, já que # indica métodos de classe.
Caleb

6
Na verdade não. A documentação do RDoc diz: Use :: for describing class methods, # for describing instance methods, and use . for example code(fonte: ruby-doc.org/documentation-guidelines.html ) Além disso, a documentação oficial (como o ruby CHANGELOG , github.com/ruby/ruby/blob/v2_1_0/NEWS ) usa, #por exemplo, métodos e o ponto para métodos de classe de maneira bastante consistente.
levinalex

Por favor, use injetar em vez deste antipadrão.
YoTengoUnLCD

Variante de uma linha usando each_with_object:instance_variables.each_with_object(Hash.new(0)) { |element, hash| hash["#{element}".delete("@").to_sym] = instance_variable_get(element) }
anothermh 28/03



13

Para objetos de registro ativo

module  ActiveRecordExtension
  def to_hash
    hash = {}; self.attributes.each { |k,v| hash[k] = v }
    return hash
  end
end

class Gift < ActiveRecord::Base
  include ActiveRecordExtension
  ....
end

class Purchase < ActiveRecord::Base
  include ActiveRecordExtension
  ....
end

e depois é só ligar

gift.to_hash()
purch.to_hash() 

2
engraçado, não faz parte da estrutura do Rails. Parece uma coisa útil para ter lá.
Magne

O método attribute retorna um novo hash com os valores em - portanto, não é necessário criar outro no método to_hash. Assim: attribute_names.each_with_object ({}) {| name, attrs | attrs [nome] = read_attribute (nome)}. Veja aqui: github.com/rails/rails/blob/master/activerecord/lib/…
Chris Kimpton

você poderia ter feito isso com o mapa, sua implementação de efeito colateral está machucando minha mente, cara!
Nate Symer # 23/16

11

Se você não está em um ambiente Rails (ou seja, não possui o ActiveRecord disponível), isso pode ser útil:

JSON.parse( object.to_json )

11
class Gift
  def to_hash
    instance_variables.map do |var|
      [var[1..-1].to_sym, instance_variable_get(var)]
    end.to_h
  end
end

6

Você pode escrever uma solução muito elegante usando um estilo funcional.

class Object
  def hashify
    Hash[instance_variables.map { |v| [v.to_s[1..-1].to_sym, instance_variable_get v] }]
  end
end

4

Você deve substituir o inspectmétodo do seu objeto para retornar o hash desejado ou apenas implementar um método semelhante sem substituir o comportamento padrão do objeto.

Se você quiser ficar mais sofisticado, pode iterar sobre as variáveis ​​de instância de um objeto com object.instance_variables


4

Recursively converter seus objetos em Hash usando 'Hashable' gem ( https://rubygems.org/gems/hashable ) Exemplo

class A
  include Hashable
  attr_accessor :blist
  def initialize
    @blist = [ B.new(1), { 'b' => B.new(2) } ]
  end
end

class B
  include Hashable
  attr_accessor :id
  def initialize(id); @id = id; end
end

a = A.new
a.to_dh # or a.to_deep_hash
# {:blist=>[{:id=>1}, {"b"=>{:id=>2}}]}

4

Pode querer tentar instance_values. Isso funcionou para mim.


1

Produz uma cópia superficial como um objeto de hash apenas dos atributos do modelo

my_hash_gift = gift.attributes.dup

Verifique o tipo do objeto resultante

my_hash_gift.class
=> Hash

0

Se você precisar que objetos aninhados também sejam convertidos.

# @fn       to_hash obj {{{
# @brief    Convert object to hash
#
# @return   [Hash] Hash representing converted object
#
def to_hash obj
  Hash[obj.instance_variables.map { |key|
    variable = obj.instance_variable_get key
    [key.to_s[1..-1].to_sym,
      if variable.respond_to? <:some_method> then
        hashify variable
      else
        variable
      end
    ]
  }]
end # }}}


0

Para fazer isso sem o Rails, uma maneira limpa é armazenar atributos em uma constante.

class Gift
  ATTRIBUTES = [:name, :price]
  attr_accessor(*ATTRIBUTES)
end

E então, para converter uma instância de Giftpara a Hash, você pode:

class Gift
  ...
  def to_h
    ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
      memo[attribute_name] = send(attribute_name)
    end
  end
end

Essa é uma boa maneira de fazer isso, pois incluirá apenas o que você define attr_accessore não todas as variáveis ​​de instância.

class Gift
  ATTRIBUTES = [:name, :price]
  attr_accessor(*ATTRIBUTES)

  def create_random_instance_variable
    @xyz = 123
  end

  def to_h
    ATTRIBUTES.each_with_object({}) do |attribute_name, memo|
      memo[attribute_name] = send(attribute_name)
    end
  end
end

g = Gift.new
g.name = "Foo"
g.price = 5.25
g.to_h
#=> {:name=>"Foo", :price=>5.25}

g.create_random_instance_variable
g.to_h
#=> {:name=>"Foo", :price=>5.25}

0

Comecei a usar estruturas para facilitar as conversões de hash. Em vez de usar uma estrutura simples, crio minha própria classe derivada de um hash, isso permite que você crie suas próprias funções e documenta as propriedades de uma classe.

require 'ostruct'

BaseGift = Struct.new(:name, :price)
class Gift < BaseGift
  def initialize(name, price)
    super(name, price)
  end
  # ... more user defined methods here.
end

g = Gift.new('pearls', 20)
g.to_h # returns: {:name=>"pearls", :price=>20}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.