Jaki jest elegancki sposób w Rubim, aby stwierdzić, czy zmienna jest hashem czy tablicą?

140

Aby sprawdzić, co @some_varjest, robię plik

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

Jestem pewien, że istnieje bardziej elegancki sposób sprawdzenia, czy @some_varjest a, Hashczy Array.

drhyde
źródło
1
Andrew: Wywołuję API iw JSON otrzymuję z powrotem, jeśli jest wiele wyników, otrzymuję Array, ale jeśli jest tylko jeden, otrzymuję Hash, a nie pojedynczy element Array. Czy jest lepszy naprzód niż wykonanie testu Array vs Hash?
drhyde
2
Więc masz albo [result_hash, another_result_hash]albo single_result_hash? Ktokolwiek stworzył to API, nie wykonał dobrej roboty!
Andrew Grimm
2
Masz rację, Andrew, i założę się, że o wiele łatwiej jest przekonać ludzi, którzy napisali interfejs API, aby to naprawili, niż przetestować wartość skrótu w porównaniu z tablicą.
Olivier 'Ölbaum' Scherler
mam taką samą sytuację jak @drhyde. W moim przypadku stroną trzecią jest HTTParty, który po przeanalizowaniu pliku XML nie ma możliwości zdecydowania, jaki jest najlepszy sposób obsługi sytuacji, w której element może mieć 1-n dzieci.
Dirty Henry
Powinieneś obejrzeć screencast Avdi Grimms: rubytapas.com/2016/10/17/episode-451-advanced-class-membership i prawdopodobnie sprawić, by cabos odpowiedzieli na zaakceptowany.
Alexander Presber

Odpowiedzi:

261

Możesz po prostu zrobić:

@some_var.class == Hash

lub też coś takiego:

@some_var.is_a?(Hash)

Warto zauważyć, że „is_a?” metoda jest prawdą, jeśli klasa znajduje się w dowolnym miejscu w drzewie przodków obiektów. na przykład:

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

powyższe jest prawdziwe, jeśli @some_var jest instancją skrótu lub innej klasy, która pochodzi z Object. Tak więc, jeśli chcesz ścisłego dopasowania do typu klasy, używając == lub instance_of? metoda jest prawdopodobnie tym, czego szukasz.

Pete
źródło
20
is_a?jest najlepszą opcją, ponieważ zwraca również truedla podklas.
Fábio Batista
I oczywiście pomijasz też nawiasy, więc @som_var.is_a? Hashteż działa :)
Gus Shortz
7
Uważaj, to może cię naprawdę schrzanić, jeśli ktoś przekaże Ci wystąpienie ActiveSupport :: HashWithIndifferentAccess. Który reaguje jak hash, ale w rzeczywistości nie jest hashem. :(
unflores
Chciałbym również, aby więcej odpowiedzi zawierało szczegóły dotyczące pisania kaczkami, ponieważ pierwotny autor najwyraźniej szukał rubinowego sposobu sprawdzania typu.
NobodysNightmare
3
@unflores ActiveSupport::HashWithIndifferentAccessdziedziczy po Hash, dlatego też @some_var.is_a?(Hash)w tym przypadku zwróci wartość true. (Właśnie miałem to samo pytanie i sprawę HashWithIndifferentAccess i trochę się pomyliłeś po twoim komentarzu ... więc chciałem wyjaśnić)
Stilzk1n
38

Przede wszystkim najlepszą odpowiedzią na dosłowne pytanie jest

Hash === @some_var

Ale na pytanie naprawdę należało odpowiedzieć, pokazując, jak tutaj pisać kaczkę. To zależy trochę od tego, jakiego rodzaju kaczki potrzebujesz.

@some_var.respond_to?(:each_pair)

lub

@some_var.respond_to?(:has_key?)

lub nawet

@some_var.respond_to?(:to_hash)

może mieć rację w zależności od aplikacji.

Cabo
źródło
25

Zwykle w rubinowym, gdy szukasz „typu”, w rzeczywistości chcesz „typ kaczki” lub „czy to szarlatan jak kaczka?”. Zobaczysz, czy reaguje na określoną metodę:

@some_var.respond_to?(:each)

Możesz iterować po @some_var, ponieważ odpowiada na: each

Jeśli naprawdę chcesz znać typ i czy jest to Hash lub Array, możesz zrobić:

["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
źródło
To nie zadziała, jeśli twój kod jest zależny od kolejności danych (np. Jeśli używasz each_with_index). Kolejność elementów jest różnie zaimplementowana między hashesami i tablicami i jest różna między wersjami ruby. ( Intertwingly.net/slides/2008/oscon/ruby19/22 )
juan2raid
1
@ juan2raid: Jeśli porządek jest ważny, najpierw zadzwoń sortdo niego.
Andrew Grimm
Drugi przykład @Brandon powinien przekonwertować klasę na ciąg. <br/>["Hash", "Array"].include?(@some_var.class.to_s) #=> check both through instance class
OBCENEIKON
12
Hash === @some_var #=> return Boolean

można tego również użyć z instrukcją case

case @some_var
when Hash
   ...
when Array
   ...
end
GutenYe
źródło
4

Używam tego:

@var.respond_to?(:keys)

Działa dla Hash i ActiveSupport :: HashWithIndifferentAccess.

drinor
źródło
4

W praktyce często będziesz chciał zachowywać się inaczej w zależności od tego, czy zmienna jest tablicą, czy skrótem, a nie tylko zwykłym powiedzeniem. W tej sytuacji elegancki idiom jest następujący:

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

Pamiętaj, że nie wywołujesz .classmetody item.

Daniel Szmulewicz
źródło
2

Możesz użyć instance_of?

na przykład

@some_var.instance_of?(Hash)
Shiv
źródło
Zauważ, że to nie działa w przypadku podklas ! Na przykład, jeśli masz ActiveRecord::Collectioni spróbuj, instance_of?(Array)to zwróci false, podczas gdy is_a?(Array)zwróci true.
Tom Lord
0

Jeśli chcesz sprawdzić, czy obiekt jest ściśle lub rozszerza a Hash, użyj:

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

Ale aby zyskać wartość pisania kaczego Rubiego, możesz zrobić coś takiego:

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

Jest to przydatne, gdy chcesz uzyskać dostęp tylko do niektórych wartości za pomocą value[:key]składni.

Pamiętaj, że Array.new["key"]podniesie TypeError.

Vinicius Brasil
źródło
-1
irb(main):005:0> {}.class
=> Hash
irb(main):006:0> [].class
=> Array
Spyros
źródło