RubyでネストしているHashをいい感じにmergeする

結論

deep_mergeを使う。

やりたいこと

こんな感じでネストされた Hash データがあるとする。

def nested_hash_data
  {
    'target1' => {
      'category1_1' => {
        'resource1_1_1' => {
          'parameter' => 'hogehoge',
        },
        'resource1_1_2' => {
          'parameter' => 'hogehoge',
        },
      },
      'category1_2' => {
        'resource1_2_1' => {
          'parameter' => 'hogehoge',
        },
      },
    },
  }
end

「ある条件のときに特定の resource を追加したいよ〜」という要件が出てくる。
このとき追加差分がわかりやすいように元データと同じ構造で定義していたとする。

def ex_data
  {
    'target1' => {
      'category1_1' => {
        'resource1_1_3' => {
          'parameter' => 'hogehoge',
        },
      },
      'category1_2' => {
        'resource1_2_2' => {
          'parameter' => 'hogehoge',
        },
      },
    },
  }
end

A と B を単純に merge して、各 category に resource1_1_3resource1_2_2 を追加したい。期待値としては ↓ な感じ。

{
  'target1' => {
    'category1_1' => {
      'resource1_1_1' => {
        'parameter' => 'hogehoge',
      },
      'resource1_1_2' => {
        'parameter' => 'hogehoge',
      },
      'resource1_1_3' => {
        'parameter' => 'hogehoge',
      },
    },
    'category1_2' => {
      'resource1_2_1' => {
        'parameter' => 'hogehoge',
      },
      'resource1_2_2' => {
        'parameter' => 'hogehoge',
      },
    },
  },
}

課題感

A と B を単純に merge メソッドに渡すと期待通りにマージされない。

# nested_hash_data.merge(ex_data)
{
  'target1' => {
    'category1_1' => {
      'resource1_1_3' => {
        'parameter' => 'hogehoge',
      },
    },
    'category1_2' => {
      'resource1_2_2' => {
        'parameter' => 'hogehoge',
      },
    },
  },
}

解決方法

deep_mergeを使う。

$ gem install 'deep_merge'
require 'deep_merge'
nested_hash_data.deep_merge(ex_data)

gem を別途入れないとネストされた Hash データをいい感じに merge できないみたい。

余談

今回は deep_merge を見ていたけど activesupport を入れるといろいろと便利なメソッドが提供されていそう。

環境

$ ruby --version
ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [arm64-darwin21]

使っている deep_merge が Ruby の require version を >=0 としているので何でも動くはず。