Qual é uma maneira elegante no Ruby de saber se uma variável é um Hash ou uma matriz?

140

Para verificar o que @some_varé, estou fazendo um

if @some_var.class.to_s == 'Hash' 

Estou certo de que existe uma maneira mais elegante de verificar se @some_varé um Hashou um Array.

drhyde
fonte
1
Andrew: Estou chamando uma API e, no JSON, recebo de volta, se houver vários resultados, obter uma matriz, mas se houver apenas uma, recebo um Hash em vez de uma única matriz. Existe um avanço melhor do que fazer a verificação Array vs Hash?
drhyde
2
Então você pega um [result_hash, another_result_hash]ou single_result_hash? Quem criou essa API não estava fazendo um bom trabalho!
Andrew Grimm
2
Você está certo, Andrew, e aposto que é muito mais fácil conseguir que as pessoas que escreveram a API o corrijam do que testar um hash versus um array.
Olivier 'Ölbaum' Scherler
Eu tenho a mesma situação que @drhyde. No meu caso, a terceira parte é HTTParty que, após analisar um arquivo XML, não tem como decidir qual é a melhor maneira de lidar com uma situação em que um elemento pode ter 1-n filhos.
sujo Henry
Você deve assistir ao screencast de Avdi Grimms: rubytapas.com/2016/10/17/episode-451-advanced-class-membership e provavelmente fazer com que os cabos respondam ao aceito.
Alexander Presber

Respostas:

261

Você pode apenas fazer:

@some_var.class == Hash

ou também algo como:

@some_var.is_a?(Hash)

Vale a pena notar que o "is_a?" O método é verdadeiro se a classe estiver em qualquer lugar da árvore de ascendência dos objetos. por exemplo:

@some_var.is_a?(Object)  # => true

o acima é verdadeiro se @some_var for uma instância de um hash ou outra classe que deriva de Object. Então, se você deseja uma correspondência estrita no tipo de classe, use o == ou instance_of? provavelmente é o que você está procurando.

Pete
fonte
20
is_a?é a melhor opção, pois também retorna truepara subclasses.
Fábio Batista
E, claro, você também deixar de fora os suportes, por isso @som_var.is_a? Hashfunciona também :)
Gus Shortz
7
Cuidado, isso pode realmente te ferrar se alguém acabar passando uma instância do ActiveSupport :: HashWithIndifferentAccess. O que responde como um hash, mas na verdade não é um hash. :(
unflores
Eu também gostaria de obter mais respostas detalhadas sobre a digitação de patos, já que o autor original aparentemente estava procurando uma maneira ruby ​​de fazer uma verificação de tipo.
NobodysNightmare
3
O @unflores ActiveSupport::HashWithIndifferentAccessherda do Hash, portanto @some_var.is_a?(Hash), retornará true também neste caso. (Eu só tinha o mesmo caso de perguntas e HashWithIndifferentAccess e tem um pouco confuso desde o seu comentário ... então eu queria esclarecer)
Stilzk1n
38

Primeiro de tudo, a melhor resposta para a pergunta literal é

Hash === @some_var

Mas a pergunta realmente deveria ter sido respondida, mostrando como digitar patos aqui. Isso depende um pouco do tipo de pato que você precisa.

@some_var.respond_to?(:each_pair)

ou

@some_var.respond_to?(:has_key?)

ou mesmo

@some_var.respond_to?(:to_hash)

pode estar certo dependendo da aplicação.

cabo
fonte
25

Normalmente, em rubi, quando você está procurando por "tipo", na verdade deseja o "tipo de pato" ou "faz é charlatão como um pato?" Você veria se ele responde a um determinado método:

@some_var.respond_to?(:each)

Você pode iterar sobre @some_var porque ele responde a: each

Se você realmente quer saber o tipo e se é Hash ou Array, pode fazer:

["Hash", "Array"].include?(@some_var.class)  #=> check both through instance class
@some_var.kind_of?(Hash)    #=> to check each at once
@some_var.is_a?(Array)   #=> same as kind_of
Brandon
fonte
Isso não funcionará se o seu código depender da ordem dos dados (por exemplo, se você estiver usando each_with_index). A ordem dos elementos é implementada de forma diferente entre hash e matrizes e é diferente entre as versões de rubi (. Intertwingly.net/slides/2008/oscon/ruby19/22 )
juan2raid
1
@ juan2raid: Se a ordem é importante, ligue sortprimeiro.
Andrew Grimm
@Brandon segundo exemplo deve converter a classe a corda <br/>.["Hash", "Array"].include?(@some_var.class.to_s) #=> check both through instance class
OBCENEIKON
12
Hash === @some_var #=> return Boolean

isso também pode ser usado com a declaração de caso

case @some_var
when Hash
   ...
when Array
   ...
end
GutenYe
fonte
4

Eu uso isso:

@var.respond_to?(:keys)

Funciona para Hash e ActiveSupport :: HashWithIndifferentAccess.

drinor
fonte
4

Na prática, você frequentemente desejará agir de maneira diferente, dependendo se uma variável é uma matriz ou um hash, e não apenas uma mera informação. Nessa situação, um idioma elegante é o seguinte:

case item
  when Array
   #do something
  when Hash
   #do something else
end

Observe que você não chama o .classmétodo item.

Daniel Szmulewicz
fonte
2

Você pode usar instance_of?

por exemplo

@some_var.instance_of?(Hash)
Shiv
fonte
Observe que isso não funciona para subclasses ! Por exemplo, se você tiver um ActiveRecord::Collectione tentar instance_of?(Array), isso retornará false, ao passo is_a?(Array)que retornará true.
9788 Tom Ellis
0

Se você deseja testar se um objeto é estritamente ou estende a Hash, use:

value = {}
value.is_a?(Hash) || value.is_a?(Array) #=> true

Mas, para valorizar a digitação de pato de Ruby, você pode fazer algo como:

value = {}
value.respond_to?(:[]) #=> true

É útil quando você deseja acessar apenas algum valor usando a value[:key]sintaxe.

Observe que Array.new["key"]isso aumentará a TypeError.

Vinicius Brasil
fonte
-1
irb(main):005:0> {}.class
=> Hash
irb(main):006:0> [].class
=> Array
Spyros
fonte