Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

ネストした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を使う方法で回避しています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした