Refactoring - messy nested hash selector

Using JSON.parse, I’ve scraped a big chunk of JSON from a page into a hash. The problem is, when I need to access values in this hash, I end up with a really ugly array-chain.

    hash['entry_data']['TagPage'][0]['graphql']['hashtag']['edge_hashtag_to_media']['edges'].each { |obj|

      @shortcode_array.push obj['node']['shortcode']

    }

Is there any way I could refactor these arrays into their own thing, something like this (pseudo-code?)

hash_selector = ['entry_data']['TagPage'][0]['graphql']['hashtag']['edge_hashtag_to_media']['edges']

hash hash_selector.each { |obj| ... }

If you’re on Ruby 2.3+, you can utilize the dig method for hashes and arrays which is ideal in this case since you have an array in the middle. It also exits early and returns nil if one of the keys isn’t found so you don’t need to safe navigate the entire chain.

Also, you can use map instead of having to iterate on the array of hashes and pushing them into the array. (You could pluck if you have ActiveSupport). Not sure if @shortcode_array has pre-existing contents, but here’s one solution:

quarried_shortcodes = hash.dig('entry_data', 'TagPage', 0, 'graphql', 'hashtag', 'edge_hashtag_to_media', 'edges')&.map { |s| s.dig('node', 'shortcode') }

@shortcode_array += quarried_shortcodes || []

Yes, I meant quarry (not query)… coz you really diggin’ deep :slight_smile: lol

If you want a solution that has abstracted out (excavated?) out the digging path because you believe that to be important down the line, you could use dig with splat operator and make the path a constant.

DIG_PATH = ['entry_data', 'TagPage', 0, 'graphql', 'hashtag', 'edge_hashtag_to_media', 'edges'].freeze

quarried_shortcodes = hash.dig(*DIG_PATH)&.map { |s| s.dig('node', 'shortcode') }

@shortcode_array += quarried_shortcodes || []

Here’s more info regarding dig:
https://ruby-doc.org/core-2.3.0_preview1/Hash.html#method-i-dig

1 Like

Thanks for your input! After my initial post, I started researching dig, but was unsure that it provided the solution I was looking for. I couldn’t figure out how to pass in a constant as you demonstrate here. The splat operator seems to be the missing piece to the puzzle.

At the very least, with hash.dig, you can break the keys across multiple lines, which is much cleaner than a straight-up array chain. I like it!