LoginSignup
19
13

More than 5 years have passed since last update.

Rubyでネストしたhashの未定義の要素にnilを返させる方法まとめ

Posted at

ネストした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メソッドは引数にkeydefaultを指定し、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を使う方法で回避しています。

19
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13