Feb 12, 2017

“Neatly” dealing with JSON.parse-d Hashes in Ruby

So you use some REST API that responds with JSON with a ton of nested objects and arrays, an example of this is the Google Maps Direction API.

All you’re interested in is a value that is deep within the hash-array tunnel.

{
  "status": "OK",
  "geocoded_waypoints" : [
     {
        "geocoder_status" : "OK",
        "place_id" : "ChIJ7cv00DwsDogRAMDACa2m4K8",
        "types" : [ "locality", "political" ]
     },
     {
        "geocoder_status" : "OK",
        "place_id" : "ChIJ69Pk6jdlyIcRDqM1KDY3Fpg",
        "types" : [ "locality", "political" ]
     },
     {
        "geocoder_status" : "OK",
        "place_id" : "ChIJgdL4flSKrYcRnTpP0XQSojM",
        "types" : [ "locality", "political" ]
     },
     {
        "geocoder_status" : "OK",
        "place_id" : "ChIJE9on3F3HwoAR9AhGJW_fL-I",
        "types" : [ "locality", "political" ]
     }
  ],
  "routes": [ {
    "summary": "I-40 W",
    "legs": [ {
      "steps": [ {
        "travel_mode": "DRIVING",
        "start_location": {
          "lat": 41.8507300,
          "lng": -87.6512600
        },
        "end_location": {
          "lat": 41.8525800,
          "lng": -87.6514100
        },
        "polyline": {
          "points": "a~l~Fjk~uOwHJy@P"
        },
        "duration": {
          "value": 19,
          "text": "1 min"
        },
    ...oh there's more, so much more...
}

For example, say we’re interested in the duration value (it is 19 in the example response above). To access this you’ll have to do this

response_hash = JSON.parse(response)
duration_in_seconds = response_hash["routes"][0]["legs"][0]["duration"]["value"]

Now imagine you had to access the duration value for many different responses and probably compare or sort them. You’ll find that you’ll have a lot of response_hash["routes"][0]["legs"][0]["duration"]["value"] sprinkled everywhere.

Sprinkling ugly code

To solve this problem we’re going to use two nifty things Ruby provides:

1. Hash#dig

.dig this is a method that was introduced in Ruby 2.30 for hashes and arrays.

duration_in_seconds = response_hash.dig("routes", 0, "legs", 0, "duration", "value")

Pretty simple right?

You can access a nested value by passing the keys and indexes as arguments in the correct order.

2. The splat operator “*”

The splat operator converts an array into a list of arguments.

Mind Bown

You can have an array a = [1, 2, 3] and covert it into a list of arguments for some_method like this:

 some_method(*a) 

This is equivalent to :

some_method(1, 2, 3)

So for our use case, you can save the arguments of .dig as an array and use it anywhere you want without having to retype/copy and paste that ugly line of square brackets and quotation marks.

 response_hash = JSON.parse(response)
nested_keys = ["routes", 0, "legs", 0, "duration", "value"]
duration_in_seconds = response_hash.dig(*nested_keys)

You can use .dig(*nested_keys) anywhere in your code when you’re trying to access the duration value.

It’ll make everything look a bit cleaner.

Copied to clipboard!