I'm working with sabre api ( test licence ) in my rails application,
i get a JSON response and when I parse it a get this hash
{
"OriginLocation"=>"DEN",
"Destinations"=>[
{
"Rank"=>1,
"Destination"=>{
"DestinationLocation"=>"LAX",
"AirportName"=>"Los Angeles International Airport",
"CityName"=>"Los Angeles",
"CountryCode"=>"US",
"CountryName"=>"United States",
"RegionName"=>"North America",
"Type"=>"Airport"
}
},
{
"Rank"=>2,
"Destination"=>{
"DestinationLocation"=>"LAS",
"AirportName"=>"McCarran International Airport",
"CityName"=>"Las Vegas",
"CountryCode"=>"US",
"CountryName"=>"United States",
"RegionName"=>"North America",
"Type"=>"Airport"
}
},
{
"Rank"=>3,
"Destination"=>{
"DestinationLocation"=>"CHI",
"CountryCode"=>"US",
"CountryName"=>"United States",
"RegionName"=>"North America",
"MetropolitanAreaName"=>"Chicago",
"Links"=>[
{
"rel"=>"airportsInCity",
"href"=>"https://api.test.sabre.com/v1/lists/supported/cities/CHI/airports"
}
],
"Type"=>"City"
}
}
...
}
How can i extract the data (ex: destination) information from it?
I tried this code but i get an error " undefined method ``each' for nil:NilClass "
#hash['Destinations'].each do |key, value|
puts key
value.each do |k,v|
puts k
puts v
end
end
Even though you've specified key and value, only key is populated (with the entire hash data), and value is nil. That's why you're getting an error.
Assuming you're on Ruby 2.3 or above, I suggest you use something like this instead:
#hash['Destinations'].map { |d| d.dig('Destination', 'CityName') }
#=> ["Los Angeles", "Las Vegas", nil]
You can read up more on dig here. It's useful for this kind of deeply nested data, and will protect you from keys that don't exist (and return nil instead); as long as you don't mix its usage on arrays and hashes.
Related
I'm having an issue like this. Not all zones are returning with:
ActiveSupport::TimeZone.all.sort_by {|t| t.name}.map { |tz|
#symbol = tz.tzinfo.identifier.gsub(/[^_a-zA-Z0-9]/, '_').squeeze('_').upcase!
tz.to_s #> (GMT+00:00) Edinburgh for example
}
I need to use the .to_s to get the UTC (GMT+00:00). With the above, London is missing and I assume others. This one works great:
ActiveSupport::TimeZone::MAPPING.sort_by {|k,v| k}.map { |k,v|
#symbol = k.gsub(/[^_a-zA-Z0-9]/, '_').squeeze('_').upcase!
k #> London London is included with this method
}
I cannot use this method because I do not know how to get the (GMT+00:00) in (GMT+00:00) London
Has the bug return? How to get all the zones show for the first example?
Edit.
I'm using GraphQL-ruby. I've created an enum to return a list of time zones:
# Taken from: https://gist.github.com/pedrocarmona/f41d25e631c1144045971c319f1c9e17
class Types::TimeZoneEnumType < Types::BaseEnum
ActiveSupport::TimeZone.all.sort_by {|t| t.name}.map { |tz|
symbol = tz.tzinfo.identifier.gsub(/[^_a-zA-Z0-9]/, '_').squeeze('_').upcase
value("TZ_#{symbol}", tz.to_s)
}
end
Then inside query_type.rb
[..]
field :time_zones, Types::TimeZoneEnumType, null: false
[..]
Next, inside graphiql, I make the query:
query timeZones{
__type(name: "TimeZoneEnum") {
enumValues {
name
description
}
}
}
Which returns something like, except London:
[
[..]
{
"name": "TZ_AMERICA_LA_PAZ",
"description": "(GMT-04:00) La Paz"
},
{
"name": "TZ_AMERICA_LIMA",
"description": "(GMT-05:00) Lima"
},
{
"name": "TZ_EUROPE_LISBON",
"description": "(GMT+00:00) Lisbon"
},
{
"name": "TZ_EUROPE_LJUBLJANA",
"description": "(GMT+01:00) Ljubljana"
},
{
"name": "TZ_EUROPE_MADRID",
"description": "(GMT+01:00) Madrid"
},
[..]
]
After Ljubljana I should see "London" but it's not there.
If I run
ActiveSupport::TimeZone.all.sort_by {|t| t.name}.map { |tz|
[ tz.tzinfo.identifier.gsub(/[^_a-zA-Z0-9]/, '_').squeeze('_').upcase, tz.to_s ]
}.sort
the result includes the entries ["EUROPE_LONDON", "(GMT+00:00) Edinburgh"], ["EUROPE_LONDON", "(GMT+00:00) London"], i.e. EUROPE_LONDON is duplicated.
I don't know how the GraphQL library is operating, but I'm assuming it's deduplicating the data and returning a single entry for EUROPE_LONDON (enums are normally unique). Moscow is the same - it has values for Moscow and St Petersburg - so you could test by looking at the results for EUROPE_MOSCOW.
file= File.read('customers.json')
data_hash= JSON.parse( file)
# id=data_hash['customers'].map { |x| x['id'] }
data_hash['customers']['user_id'].each do |user|
latitude=user['customers']['latitude']
puts latitude
end
what error I am facing?
no implicit conversion of String into Integer (TypeError)
below is my json file
{
"customers" :[
{"latitude": "12.986375", "user_id": "12", "name": "Chris", "longitude": "77.043701"},
{"latitude": "11.92893", "user_id": "1", "name": "Alice", "longitude": "78.27699"},
{"latitude": "11.8856167", "user_id": "2", "name": "Ian", "longitude": "78.4240911"}
]
}
If you are trying to get latitude or something you should do it like:
file = File.read('customers.json')
data_hash = JSON.parse(file)
data_hash["customers"].each do |user|
latitude = user["latitude"]
puts latitude
end
OUTPUT:
12.986375
11.92893
11.8856167
The no implicit conversion of String into Integer (TypeError) error occurs when you try to treat an array as a hash. Since arrays expect integer keys for indexing Ruby will try to implicitly cast whatever gets passed to [] to an integer:
[]['a']
TypeError: no implicit conversion of String into Integer
In your case it's the data_hash['customers'] node that's an array.
You can actually just iterate through each item in data_hash[customers] mapping the value for the :latitude key back to an array via map.
puts data_hash["customers"].map { |h| h["latitude"] }
i need to create a hash/array where 2 elements are stored: the country code, and the number of times the country occurred.
I want to vet some conceptual logic: i want to create a helper method that passes in a list of countries. Then, I loop through each country and will merge the country code into the hash through a series of if statements:
#map_country = Hash.new
if country == "United States"
#map_country.merge(:us => ??)
I'm not quite sure how I can add a counter to push into the hash. Can anyone help? Basically, I want to achieve how many times "United States" shows up.
Also, once I have this Hash completed - I want to do something different to each country based on the count. How do I go about picking out the value from the key? Moreover, how do I get just the key?
<% if #map_country[:country] > 5 %>
... do this with #map_country...
Thanks! Apologies if this is confusing, but really could use some help here. Thanks!
To me it sounds like you're trying to count occurrences which you can do with the #inject method:
[1] pry(main)> countries = ["United States", "Canada", "United States", "Mexico"]
=> ["United States", "Canada", "United States", "Mexico"]
[2] pry(main)> countries.inject({}) { |hash, ctr| hash[ctr] = hash[ctr].to_i + 1; hash }
=> {"United States"=>2, "Canada"=>1, "Mexico"=>1}
Then say you want to do something with that hash, you could loop through it like this:
[3] pry(main)> occ = countries.inject({}) { |hash, ctr| hash[ctr] = hash[ctr].to_i + 1; hash }
=> {"United States"=>2, "Canada"=>1, "Mexico"=>1}
[4] pry(main)> occ.each do |country, val|
[4] pry(main)* if val == 2
[4] pry(main)* puts "There are two occurences of #{country}"
[4] pry(main)* end
[4] pry(main)* end
There are two occurences of United States
If you're set on using a Hash (rather than a custom class) for this then just use a default_proc to auto-vivify entries with zeros and you a simple increment is all you need:
#map_country = Hash.new { |h, k| h[k] = 0 }
if country == 'United States'
#map_country[:us] += 1
I'm using a Ruby script to interface with an application API and the results being returned are in a JSON format. For example:
{
"incidents": [
{
"number": 1,
"status": "open",
"key": "abc123"
}
{
"number": 2,
"status": "open",
"key": "xyz098"
}
{
"number": 3,
"status": "closed",
"key": "lmn456"
}
]
}
I'm looking to search each block for a particular "key" value (yzx098 in this example) and return the associated "number" value.
Now, I'm very new to Ruby and I'm not sure if there's already a function to help accomplish this. However, a couple days of scouring the Googles and Ruby resource books hasn't yielded anything that works.
Any suggestions?
First of all, the JSON should be as below: (note the commas)
{
"incidents": [
{
"number": 1,
"status": "open",
"key": "abc123"
},
{
"number": 2,
"status": "open",
"key": "xyz098"
},
{
"number": 3,
"status": "closed",
"key": "lmn456"
}
]
}
Strore the above json in a variable
s = '{"incidents": [{"number": 1,"status": "open","key": "abc123"},{"number": 2,"status": "open","key": "xyz098"},{"number": 3,"status": "closed","key": "lmn456"}]}'
Parse the JSON
h = JSON.parse(s)
Find the required number using map
h["incidents"].map {|h1| h1['number'] if h1['key']=='xyz098'}.compact.first
Or you could also use find as below
h["incidents"].find {|h1| h1['key']=='xyz098'}['number']
Or you could also use select as below
h["incidents"].select {|h1| h1['key']=='xyz098'}.first['number']
Do as below
# to get numbers from `'key'`.
json_hash["incidents"].map { |h| h['key'][/\d+/].to_i }
json_hash["incidents"] - will give you the value of the key "incidents", which is nothing but an array of hash.
map to iterate thorough each hash and collect the value of 'key'. Then applying Hash#[] to each inner hash of the array, to get the value of "key". Then calling str[regexp], to get only the number strings like '098' from "xyz098", finally applying to_i to get the actual integer from it.
If the given hash actually a json string, then first parse it using JSON::parse to convert it to a hash.Then do iterate as I said above.
require 'json'
json_hash = JSON.parse(json_string)
# to get values from the key `"number"`.
json_hash["incidents"].map { |h| h['number'] } # => [1, 2, 3]
# to search and get all the numbers for a particular key match and take the first
json_hash["incidents"].select { |h| h['key'] == 'abc123' }.first['number'] # => 1
# or to search and get only the first number for a particular key match
json_hash["incidents"].find { |h| h['key'] == 'abc123' }['number'] # => 1
I got two JSON that are structured like this. First one comes from an API:
[
{
"course_code":"Basic 101 - 0913",
"name":"Basic 101",
"start_at":"2013-09-16T00:00:00+02:00",
"end_at":"2013-10-13T23:55:00+02:00",
"workflow_state":"available"
},
{"course_code":"Medium 201 - 0913",
"name":"Medium 201",
"start_at":"2013-08-06T16:55:25+02:00",
"end_at":null,
"workflow_state":"available"
}
]
The second one is a JSON export from my database:
[
{
"id":1,
"course_id":"Basic 101",
"name":"Basic Level",
"description":"blablabla",
"discipline_id":"1",
"duration":"28",
"created_at":null,
"updated_at":null
},
{
"id":2,
"course_id":"Medium 201",
"name":"Medium Level",
"description":"blablabla",
"discipline_id":"1",
"duration":"28",
"created_at":null,
"updated_at":null
}
]
I would like to merge these two JSON into one, with matched :name in the first JSON and :course_id in the second one.
If you know good tutorials on using JSON in Rails, I'm really interested.
This isn't really a JSON issue.
When parsing JSON data it returns arrays and hashes.
One way of merging it in this case would be to loop through the data and check for the parameters you want/need to match. Once you find a match you can either manually create a new Hash with the needed data or you could use
hash1.merge(hash2)
http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-merge
which would return a hash consisting of both Hashes - attributes with the same name would be overwritten in the first hash.
Just a quick answer, to let you know where to go. Assuming first json is in json1 and second is in json2 variables, this code:
require 'json'
arr1 = JSON.parse(json1)
arr2 = JSON.parse(json2)
mrg = []
arr1.each do |el1|
arr2.each do |el2|
if el2['course_id'] == el1['name']
mrg.push(el1.merge(el2))
end
end
end
p mrg
Will print:
[
{
"course_code"=>"Basic 101 - 0913",
"name"=>"Basic Level",
"start_at"=>"2013-09-16T00:00:00+02:00",
"end_at"=>"2013-10-13T23:55:00+02:00",
"workflow_state"=>"available",
"id"=>1,
"course_id"=>"Basic 101",
"description"=>"blablabla",
"discipline_id"=>"1",
"duration"=>"28",
"created_at"=>nil,
"updated_at"=>nil
},
{
"course_code"=>"Medium 201 - 0913",
"name"=>"Medium Level",
"start_at"=>"2013-08-06T16:55:25+02:00",
"end_at"=>nil,
"workflow_state"=>"available",
"id"=>2,
"course_id"=>"Medium 201",
"description"=>"blablabla",
"discipline_id"=>"1",
"duration"=>"28",
"created_at"=>nil,
"updated_at"=>nil
}
]