Access values for JSON.parse response

Hello, Does anyone know who to get the values from a nested hash in
Ruby.
I made the following request which after doing the JSON.parse, it
returns a nested hash with arrays. So, I am trying to figure out how to
access the values in the response. See below for response.

uri =
URI.parse(‘http://nvillette.dev.targetspot.com/tod.php?station=KRAYFM&length=30&zip=28902’)

res = Net::HTTP.get_response(uri)

result= res.body

puts parsed = JSON.parse(result)

Ex. How would I verify that the duration is 30?

Response:

{“TOD”:{“version”:“1.0”,“AdBreaks”:{“AdBreak”:[{“adType”:“AUDIO”,“duration”:30,“adCount”:1,“Ad”:[{“id”:611,“acc”:225,“lastModified”:“2013-03-11T19:22:15”,“order”:1,“adType”:“AUDIO”,“duration”:30,“MediaFile”:{“uri”:“http://content.dev.targetspot.com/content/act/225/adunit/86/02/1363029493701178_225.mp3",“type”:“audio/mpeg”,“bitrate”:64},“Impression”:{“Log”:[{“uri”:“http://nvillette.dev.targetspot.com/log.php?data=VVNFUj00YzRhN2FjZi1jMWIzLTQxMWItOTAzMy1kMTZiMzI0YTM3YzMmU1RBVD1LUkFZRk0mSUQ9NjExJkFDQ09VTlRJRD0yMjUmQURVTklUSUQ9NTEyJlBSPTI1Jk1QUj0yNSZDVD1TMzAmUkVQPTkwMCZaPTI4OTAyJk1BUktFVElEPTUyNCZTVEFURT1OQyZHRU9QUkVGSUQ9MSZSRUdJT05JRD04JkNBQ0hFQlVTVEVSPTEzNjMwMzM2ODcmQk49MyZWPVRPRCZHPTAmQT0xJkk9MCZWSVNVQUxNT0RFPTImRkxBU0hFTkFCTEVEPTAmVEhJUkRQQVJUWVNUQVRVUz0xJlRTQU5BTFlUSUNTPTEmTVNBPTAmQURTT1VSQ0VTRUxFQ1RFRD0xJkFEU09VUkNFQVRURU1QVEVEJTVCMCU1RD0xJk1FVEhPRD0yJk1PQklMRT0wJlBSRVJPTEw9MCZDTEs9MCZET1dOTE9BRD0wJkNBTExDTElDSz0wJlJBVEVMT0c9MCZJR05PUkVfR0VPX0lQPTAmVFo9QW1lcmljYSUyRk5ld19Zb3JrJkNUQ0Y9LTEmU1RDRj0tMSZDSUNGPS0xJkdNPVpJUCZEUz0wLjAwMDAwMDAwMDAmQURCUkVBSz0zMA==”,“event”:“complete”,“source”:“TargetSpot”}]},“CompanionVisual”:{“MediaFile”:[{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/180/135/1363029483307708_225.jpg”,“type”:“jpg”,“width”:180,“height”:135},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/180/150/1363029483307708_225.jpg”,“type”:“jpg”,“width”:180,“height”:150},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/300/250/1363029483307708_225.jpg”,“type”:“jpg”,“width”:300,“height”:250},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/320/240/1363029483307708_225.jpg”,“type”:“jpg”,“width”:320,“height”:240},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/336/280/1363029483307708_225.jpg”,“type”:“jpg”,“width”:336,"height”:280}]}}]}]}}}

On Mar 11, 2013 4:16 PM, “Matthew K.” removed_email_address@domain.invalid wrote:

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’][0][‘duration’]

or

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’].select{|ad|
ad[‘duration’] == 30 }

Also note: this is yucky. Yucky yucky yucky. A saner person might use
some local variables. Even one will help a lot:

adbreaks = parsed[‘TOD’][‘AdBreaks’][‘AdBreak’]

while I’m here: why does ‘AdBreaks’ hold one item,

but ‘AdBreak’ hold an array? Odd.

These are the vagaries of XML :slight_smile:

Nicole V. wrote in post #1101133:

Hello, Does anyone know [how] to get the values from a nested hash in
Ruby.

Yep.

I made the following request which after doing the JSON.parse, it
returns a nested hash with arrays. So, I am trying to figure out how to
access the values in the response. See below for response.

Since your “parsed” object is a Hash, you can access the first level of
nesting with:
parsed[‘TOD’]
which returns a Hash, so you can access the next level with:
parsed[‘TOD’][‘AdBreaks’]
and so on.

At some point in your structure there’s an Array instead of a Hash, so
you’d need to use something like:

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’][0][‘duration’]

or

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’].first[‘duration’]

But personally I’d probably go for one of the following:

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’].map{|ad|
ad[‘duration’] }

or

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’].any?{|ad|
ad[‘duration’] == 30 }

or

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’].select{|ad|
ad[‘duration’] == 30 }

Also note: this is yucky. Yucky yucky yucky. A saner person might use
some local variables. Even one will help a lot:

adbreaks = parsed[‘TOD’][‘AdBreaks’][‘AdBreak’]

while I’m here: why does ‘AdBreaks’ hold one item,

but ‘AdBreak’ hold an array? Odd.

lengths_of_breaks = adbreaks.map {|ad| ad[‘duration’] }
list_of_30s_breaks = adbreaks.select {|ad| ad[‘duration’] == 30 }

etc.

Any reason you don’t just write a recursive method? I find that’s the
easiest way to extract values from nested structures.

tamouse mailing lists wrote in post #1101156:

On Mar 11, 2013 4:16 PM, “Matthew K.” removed_email_address@domain.invalid wrote:

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’][0][‘duration’]

or

parsed[‘TOD’][‘AdBreaks’][‘AdBreak’].select{|ad|
ad[‘duration’] == 30 }

Also note: this is yucky. Yucky yucky yucky. A saner person might use
some local variables. Even one will help a lot:

adbreaks = parsed[‘TOD’][‘AdBreaks’][‘AdBreak’]

while I’m here: why does ‘AdBreaks’ hold one item,

but ‘AdBreak’ hold an array? Odd.

These are the vagaries of XML :slight_smile:

I have another question. By looking at my response, I am trying to go
deeper to access this value: “Impression”:{“Log”:[{“uri”

I am attempting to do the following:

uri=
parsed[‘TOD’][‘AdBreaks’][‘AdBreak’][0][‘Ad’][0][‘MediaFile’][‘Impression’][‘Log’][0][‘uri’]

but it does not work. I get the following: NoMethodError: undefined
method `[]’ for nil:NilClass

This seems to work fine, which is a level above:

bitrate=parsed[‘TOD’][‘AdBreaks’][‘AdBreak’][0][‘Ad’][0][‘MediaFile’][‘bitrate’]

Thanks

Hi,

On 3/11/13 9:35 PM, Nicole V. wrote:

puts parsed = JSON.parse(result)

Ex. How would I verify that the duration is 30?

Response:

{“TOD”:{“version”:“1.0”,“AdBreaks”:{“AdBreak”:[{“adType”:“AUDIO”,“duration”:30,“adCount”:1,“Ad”:[{“id”:611,“acc”:225,“lastModified”:“2013-03-11T19:22:15”,“order”:1,“adType”:“AUDIO”,“duration”:30,“MediaFile”:{“uri”:“http://content.dev.targetspot.com/content/act/225/adunit/86/02/1363029493701178_225.mp3",“type”:“audio/mpeg”,“bitrate”:64},“Impression”:{“Log”:[{“uri”:"http://nvillette.dev.targetspot.com/log.php?data=VVNFUj00YzRhN2FjZi1jMWIzLTQxMWItOTAzMy1kMTZiMzI0YTM3YzMmU1RBVD1LUkFZRk0mSUQ9NjExJkFDQ09VTlRJRD0yMjUmQURVTklUSUQ9NTEyJlBSPTI1Jk1QUj0yNSZDVD1TMzAmUkVQPTkwMCZaPTI4OTAyJk1BUktFVElEPTUyNCZTVEFURT1OQyZHRU9QUkVGSUQ9MSZSRUdJT05JRD04JkNBQ0hFQlVTVEVSPTEzNjMwMzM2ODcmQk49MyZWPVRPRCZHPTAmQT0xJkk9MCZWSVNVQUxNT0RFPTImRkxBU0hFTkFCTEVEPTAmVEhJUkRQQVJUWVNUQVRVUz0xJlRTQU5BTFlUSUNTPTEmTVNBPTAmQURTT1VSQ0VTRUxFQ1RFRD0xJkFEU09VUkNFQVRURU1QVEVEJTVCMCU1RD0xJk1FVEhPRD0yJk1PQklMRT0wJlBSRVJPTEw9MCZDTEs9MCZET1dOTE9BRD0wJkNBTExDTElDSz0wJlJBVEVMT0c9MCZJR05PUkVfR0VPX0lQPTAmVFo9QW1lcmljYSUyRk5l
d19Zb3JrJkNUQ0Y9LTEmU1RDRj0tMSZDSUNGPS0xJkdNPVpJUCZEUz0wLjAwMDAwMDAwMDAmQURCUkVBSz0zMA==”,“event”:“complete”,“source”:“TargetSpot”}]},“CompanionVisual”:{“MediaFile”:[{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/180/135/1363029483307708_225.jpg",“type”:“jpg”,“width”:180,“height”:135},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/180/150/1363029483307708_225.jpg”,“type”:“jpg”,“width”:180,“height”:150},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/300/250/1363029483307708_225.jpg”,“type”:“jpg”,“width”:300,“height”:250},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/320/240/1363029483307708_225.jpg”,“type”:“jpg”,“width”:320,“height”:240},{“uri”:“http://content.dev.targetspot.com/content/act/225/visual/89/9f/336/280/1363029483307708_225.jpg”,“type”:“jpg”,“width”:336,"height”:280}]}}]}]}}}

If you want to access the returned structure like you would in
Javascript, you can use the object_class argument with
JSON::GenericObject like this:

parsed = JSON.parse result, object_class: JSON::GenericObject
[…]

Then you can use method calls for the path your interested in:

Joel P. wrote in post #1101249:

Any reason you don’t just write a recursive method? I find that’s the
easiest way to extract values from nested structures.

I tried but I just couldn’t seem to get it to work correctly.

Thanks.

Joel P. wrote in post #1101264:

There’s probably a simpler way to lay this out, but this worked for me:

def recursive_find( key, object )
case object
when Array
object.each do |el|
if el.is_a?(Hash) || el.is_a?(Array)
res = recursive_find( key, el )
return res if res
end
end
when Hash
return object[key] if object.has_key?( key )
object.each do |k,v|
if v.is_a?(Hash) || v.is_a?(Array)
res = recursive_find( key, v )
return res if res
end
end
end
nil
end

irb(main):022:0> recursive_find ‘duration’, h
=> 30

Joel,

I do have one question for you. What happens when there are duplicate
names in the response. For ex. duration and uri appear in different
arrays, so how would you specify which one you want?

There’s probably a simpler way to lay this out, but this worked for me:

def recursive_find( key, object )
case object
when Array
object.each do |el|
if el.is_a?(Hash) || el.is_a?(Array)
res = recursive_find( key, el )
return res if res
end
end
when Hash
return object[key] if object.has_key?( key )
object.each do |k,v|
if v.is_a?(Hash) || v.is_a?(Array)
res = recursive_find( key, v )
return res if res
end
end
end
nil
end

irb(main):022:0> recursive_find ‘duration’, h
=> 30

Nicole V. wrote in post #1101285:

Joel P. wrote in post #1101264:

There’s probably a simpler way to lay this out, but this worked for me:
irb(main):022:0> recursive_find ‘duration’, h
=> 30

Joel,

I do have one question for you. What happens when there are duplicate
names in the response. For ex. duration and uri appear in different
arrays, so how would you specify which one you want?

Well, one way I thought of was to have it do a select instead of a find
(i.e. return all the matches, not just the first). To make it useful, I
had it return the path to the value as well as the value itself.

def recursive_select( key, object, path=[] )
matches = []
case object
when Array
object.each_with_index do |el, i|
if el.is_a?(Hash) || v.is_a?(Array)
res = recursive_select( key, el, path+[i] )
matches += res if res
end
end
when Hash
matches << {‘path’=>path+[key], ‘value’=>object[key]} if
object.has_key? key
object.each do |k,v|
if v.is_a?(Hash) || v.is_a?(Array)
res = recursive_select( key, v, path+[k] )
matches += res if res
end
end
end
matches.any? ? matches : nil
end

irb(main):005:0> recursive_select ‘duration’, h
=> [{“path”=>[“TOD”, “AdBreaks”, “AdBreak”, 0, “duration”],
“value”=>30}, {“path”=>[“TOD”, “AdBreaks”, “AdBreak”, 0, “Ad”, 0,
“duration”], “value”=>30}]

By the way, I corrected this on the forum but it’s still wrong in
Matthew’s quote and presumably the emails:

This:
if el.is_a?(Hash) || v.is_a?(Array)

Should be this:
if el.is_a?(Hash) || el.is_a?(Array)

Joel P. wrote in post #1101386:

By the way, I corrected this on the forum but it’s still wrong in
Matthew’s quote and presumably the emails:

This:
if el.is_a?(Hash) || v.is_a?(Array)

Should be this:
if el.is_a?(Hash) || el.is_a?(Array)

Thanks for your help. I like the idea of seeing the path.

Joel P. wrote in post #1101386:

By the way, I corrected this on the forum but it’s still wrong in
Matthew’s quote and presumably the emails:

This:
if el.is_a?(Hash) || v.is_a?(Array)

Should be this:
if el.is_a?(Hash) || el.is_a?(Array)

Ooh, I missed that one too. I’d fix my post, but it’s been sitting too
long.

Nicole V. wrote in post #1101455:

Joel P. wrote in post #1101386:

By the way, I corrected this on the forum but it’s still wrong in
Matthew’s quote and presumably the emails:

This:
if el.is_a?(Hash) || v.is_a?(Array)

Should be this:
if el.is_a?(Hash) || el.is_a?(Array)

Thanks for your help. I like the idea of seeing the path.

Joel,

What I need to do is verify that specific values exist in the response.
For example, I expect the duration to be 30. So in ruby I can use the
following:

val=recursive_select( “duration”, parsed)
assert_equal(30,val,“Item NOT found”)

Your function returns the following which is great because I need to be
able to see all of the values for duration.

value30pathTODAdBreaksAdBreak0duration
value15pathTODAdBreaksAdBreak0Ad0duration
value15pathTODAdBreaksAdBreak0Ad1duration

Any suggestions on how I would go about verifying that duration is ‘30’?
or duration is ‘15’ in an assertion? Do I need to use the strip method
to grab the values? I’m just trying to find the best way to handle this.

Thanks again for your assistance.

Nicole V. wrote in post #1101490:

Nicole V. wrote in post #1101455:
Joel,

What I need to do is verify that specific values exist in the response.
For example, I expect the duration to be 30. So in ruby I can use the
following:

val=recursive_select( “duration”, parsed)
assert_equal(30,val,“Item NOT found”)

Your function returns the following which is great because I need to be
able to see all of the values for duration.

Joel’s doesn’t. Mine does. :wink:

value30pathTODAdBreaksAdBreak0duration
value15pathTODAdBreaksAdBreak0Ad0duration
value15pathTODAdBreaksAdBreak0Ad1duration

That’s the .to_s of what it returns. It’s actually:

[
{“value”=>30, “path”=>[“TOD”,“AdBreaks”,“AdBreak”,0,“duration”]},
{“value”=>15,
“path”=>[“TOD”,“AdBreaks”,“AdBreak”,0,“Ad”,0,“duration”]},
{“value”=>15,
“path”=>[“TOD”,“AdBreaks”,“AdBreak”,0,“Ad”,1,“duration”]},
]

Any suggestions on how I would go about verifying that duration is ‘30’?
or duration is ‘15’ in an assertion? Do I need to use the strip method
to grab the values? I’m just trying to find the best way to handle this.

Thanks again for your assistance.

I made a gist https://gist.github.com/phluid61/5157640 but I think I
broke github.

In summary, the relevant points are:

# test that there are any durations
assert( !val.empty?, "Item NOT found" )

# test that _ANY_ durations are good
assert(
  val.any?{|v| v['value'] == 30 },
  "Item(s) found, but none are #{target}" )

# test that _ALL_ durations are good
val.each do |v|
  assert_equal(30, v['value'],
    "Item /#{v['path'].join '/'} = #{v['value']}" )
end

If it’s allowed to be 30 or 15 you might want some better logic, like:

val.any?{|v| [15,30].include? v['value'] }

and maybe

val.each do |v|
  assert( [15,30].include? v['value'], # ...

Matthew K. wrote in post #1101504:

# test that _ANY_ durations are good
assert(
  val.any?{|v| v['value'] == 30 },
  "Item(s) found, but none are #{target}" )

Oops, sorry, that should be 30 instead of #{target}

If it’s allowed to be 30 or 15 you might want some better logic, like:

val.any?{|v| [15,30].include? v['value'] }

and maybe

val.each do |v|
  assert( [15,30].include? v['value'], # ...

oops…I’m getting you guys mixed up…Thanks Matthew… You guys have
been really helpful.