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?statio...)
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 2013-03-11 21:35
on 2013-03-11 22:14
Nicole Villette 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.
on 2013-03-12 04:30
On Mar 11, 2013 4:16 PM, "Matthew Kerwin" <lists@ruby-forum.com> 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 :)
on 2013-03-12 17:41
tamouse mailing lists wrote in post #1101156: > On Mar 11, 2013 4:16 PM, "Matthew Kerwin" <lists@ruby-forum.com> 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 :) 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
on 2013-03-12 18:24
Any reason you don't just write a recursive method? I find that's the easiest way to extract values from nested structures.
on 2013-03-12 18:31
Joel Pearson 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.
on 2013-03-12 19:09
Hi, On 3/11/13 9:35 PM, Nicole Villette 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:
on 2013-03-12 20:05
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
on 2013-03-12 22:18
Joel Pearson 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?
on 2013-03-13 00:58
Nicole Villette wrote in post #1101285: > Joel Pearson 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}]
on 2013-03-13 12:15
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)
on 2013-03-13 12:21
Joel Pearson 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.
on 2013-03-13 16:58
Joel Pearson 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.
on 2013-03-13 21:33
Nicole Villette wrote in post #1101455: > Joel Pearson 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.
on 2013-03-14 01:23
Nicole Villette wrote in post #1101490: > Nicole Villette 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. ;) > 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'], # ...
on 2013-03-14 02:06
Matthew Kerwin 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}
on 2013-03-14 17:42
> 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.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.