ネストしたhashで未定義の要素を取得した場合、親要素から未定義の場合エラーになってしまう。たとえば下のコードでは、hash_2[:b]
がnil
を返してしまうので、hash_2[:b][:b]
を取得すると#<NoMethodError: undefined method '[]' for nil:NilClass>
のようなエラーが返ってきます。これを回避し、 hash_2[:b][:b]
がnil
を返すようにする方法をまとめました。
hash_2 = { a: {a: "A"} }
p "a.aを取り出す"
a = hash_2[:a][:a] # Aが出力される
p a
p "a.bを取り出す"
a = hash_2[:a][:b] # nilが出力される
p a
p "b.bを取り出す"
begin
b = hash_2[:b][:b] # エラーになるから何とかしたい
p b
rescue => e
p e
end
実行環境
以下の二つのバージョンでの動作確認を行いました。rubyのバージョンによりできることが少し変わっています。
- ruby 2.2.9p480 (2017-12-15 revision 61259) [x86_64-linux]
- ruby 2.3.6p384 (2017-12-14 revision 61254) [x86_64-linux]
回避策
fetchを使う
Hashのfetchメソッドは引数にkey
とdefault
を指定し、key
が存在すればその値を、存在しなければ指定したdefault
を返します。これを使い、以下のようにhashの中間の要素にはdefault
に空のhash{}
を、末端の要素にnil
を指定します。
hash_2 = { a: {a: "A"} }
p "fetchを使う"
b = hash_2.fetch(:b, {}).fetch(:b, nil)
p b
コードが横に長くなるのが難点。
&&を使う
hash_2[:b] && hash_2[:b][:b]
とします。この場合、hash_2[:b]
が評価された時点でnilなのでそのままnilを返します(hash_2[:b][:b]
は評価しないのでエラーにならない)。hash_2[:a] && hash_2[:a][:a]
の場合は前から順番に評価されていきhash_2[:a][:a]
が返されます。また&&
の代わりにand
にすると{:a=>"A"}
を、つまりhash_2[:a] && hash_2[:a][:a]
の先頭のhash_2[:a]
を返してくるのでダメです。
hash_2 = { a: {a: "A"} }
p "&&を使う"
b = hash_2[:b] && hash_2[:b][:b]
p b
これもfetch同様、横に長くなりますね。
rescueを使う
hash_2[:b][:b]
にアクセスしたときのエラーを後ろに置いたresqueで拾って、nilを返すようにします。
hash_2 = { a: {a: "A"} }
p "rescueを使う"
b = hash_2[:b][:b] rescue nil
p b
この書き方だとNoMethodError
以外もnil
を返してしまうのが気になりますが、ネストが何段でもコードが短く済むのと、空配列や0等任意の値を返すようにできるのは便利です。
また、{ b: "B" }
のようにhash_2[:b]
が定義されて、hash_2[:b][:b]
が未定義でもnil
になります(他の方法はだいたいエラーになる)。
mergeを使う
あらかじめ、全ての要素をnil
であらかじめ定義したhashを別に作成し、mergeすることで、定義済みの要素はそのまま未定義の要素はnil
になります。
hash_2 = { a: {a: "A"} }
p "mergeを使う"
nil_hash = {
a: {a: nil},
b: {b: nil}
}
merge_hash = nil_hash.merge(hash_2)
b = merge_hash[:b][:b]
p b
yamlやjsonをロードするとき等、形式は決まっている場合には、最初に一回mergeすれば、以後、nil
を気にしないで良いのは便利だが、あまり使いたいものではないですね。
to_hを使う(2.0から?)
to_h
はhashならそのままhashを、nil
なら{}
を返します。
hash_2 = { a: {a: "A"} }
p "to_hを使う(2.0から?)"
b = hash_2.2.0から[:b].to_h[:b]
p b
fetchよりは短いので2.0からはアリかも
&.を使う(2.3から)
ぼっち演算子を使うとobject が nil でないときにメソッドを呼びます。これで、[]
メソッドを読んで取得します。
hash_2 = { a: {a: "A"} }
p "&.を使う"
b = hash_2&.[](:b)&.[](:b)
p b
長さ的にはto_hと変わらないのと2.3からは次のdigの方がよさそうです。
digを使う(2.3から)
hashのネストした要素にアクセスし未定義ならnilを返してくれます。
hash_2 = { a: {a: "A"} }
p "digを使う(2.3から)"
b = hash_2.dig(:b, :b)
p b
基本的にこれを使うべきみたいです。
まとめ
2.3以上ならdigが便利です。ただ、古いバージョンのRubyを使う場合は私はrescueを使う方法で回避しています。