I'm parsing a JSON result into a Ruby hash. The JSON result looks like this:
{
"records": [
{
"recordName": "7DBC4FAD-D18C-476A-89FB-14A515098F34",
"recordType": "Media",
"fields": {
"data": {
"value": {
"fileChecksum": "ABCDEFGHIJ",
"size": 9633842,
"downloadURL": "https://cvws.icloud-content.com/B/ABCDEF"
},
"type": "ASSETID"
}
},
"recordChangeTag": "ii23box2",
"created": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
},
"modified": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
}
}
]
}
I can't guarantee that it'll return with any/all those values, or that each value will be of a certain type (e.g. Array, Hash, string, number), and if I call it incorrectly then I get a crash.
Right now I need the downloadURL for the first item in the 'records' array, or to write it as I might with the Swift library SwiftyJSON (which I'm far more familiar with):
json["records"][0]["fields"]["data"]["value"]["downloadURL"]
I'm wondering what the safest/best/standard way to do this safely in Ruby is. Perhaps I'm thinking about it wrong?
In ruby 2.3 and above you can use Hash#dig and Array#dig
json = JSON.parse(...)
json.dig('records', 0, 'fields', 'data', 'value', 'downloadURL')
You'll get nil if any of the intermediate values is nil. If one of the intermediate values doesn't have a dig method, for example if `json['records'][0]['fields'] was unexpectedly an integer this will raise TypeError.
From the documentation (http://ruby-doc.org/stdlib-2.2.3/libdoc/json/rdoc/JSON.html):
require 'json'
my_hash = JSON.parse('{"hello": "goodbye"}')
puts my_hash["hello"] => "goodbye"
If you're worried that you might not have some data. See this question:
Equivalent of .try() for a hash to avoid "undefined method" errors on nil?
You can recursively search each object contained in the json object using
the recurse_proc method of the JSON module.
Here is an example using the data you provided.
require 'json'
json_string = '{
"records": [
{
"recordName": "7DBC4FAD-D18C-476A-89FB-14A515098F34",
"recordType": "Media",
"fields": {
"data": {
"value": {
"fileChecksum": "ABCDEFGHIJ",
"size": 9633842,
"downloadURL": "https://cvws.icloud-content.com/B/ABCDEF"
},
"type": "ASSETID"
}
},
"recordChangeTag": "ii23box2",
"created": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
},
"modified": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
}
}
]
}'
json_obj = JSON.parse(json_string)
JSON.recurse_proc(json_obj) do |obj|
if obj.is_a?(Hash) && obj['downloadURL']
puts obj['downloadURL']
end
end
Update Based on Frederick's answer and Cary's comment
I originally assumed you just wanted to find the downloadURL somewhere in the json without crashing, but based on Frederick's answer and Cary's comment, it's reasonable to assume that you only want to find the downloadURL if it is at the exact path, rather than if it just exists. Building on Frederick's answer and Cary's comment here are a couple of other options that should safely find the downloadURL at the expected path.
path = ['records', 0, 'fields', 'data', 'value', 'downloadURL']
parsed_json_obj = JSON.parse(json_string)
node_value = path.reduce(parsed_json_obj) do |json,node|
if json.is_a?(Hash) || (json.is_a?(Array) && node.is_a?(Integer))
path = path.drop 1
json[node]
else
node unless node == path.last
end
end
puts node_value || "not_found"
path = ['records', 0, 'fields', 'data', 'value', 'downloadURL']
begin
node_value = parsed_json_obj.dig(*path)
rescue TypeError
node_value = "not_found"
end
puts node_value || "not_found"
BTW, this assumes the json is at least valid, if that is not a given you might want to wrap the JSON.parse in a begin-rescue-end block as well.
Related
I am building a Rails 5 app.
In this app I have connected to the Google Calendar API.
The connection works fine and I get a list of calendars back.
What I need to do is to get the Id and Summary of this JSON object that I get back from Google.
This is what I get
[{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
},
{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
}]
This is what I want to end up with
[{
"id": "xxx.com_asae#group.calendar.google.com",
"title": "My office calendar",
}]
The purpose of this is that I want to populate a selectbox using Selectize plugin
Another way to achieve removing of certain keys in your hash is by using Hash#reject method:
response = { your_json_response }
expected = [response[0].reject {|k| k != :id && k != :summary}]
The original response remains unchanged while a mutated copy of the original response is returned.
You can filter the desierd keys with the select method:
responde = {your_json_response}
expected = [response[0].select{|k,v| ['id','title'].include?(k)}]
response[0] retrieves the hash, and the select compares each key with the ones you want and returns a hash with only those key: value pairs.
EDIT: I missed that you don't have a "title" key on the original response, I would do this then:
response = {your_json_response}
h = response[0]
expected = [{'id' => h['id'], 'title' => h['summary']}]
EDIT 2: Sorry, the first example was not clear that there would be multiple hashes
expected = response.map{|h| {'id' => h['id'], 'title' => h['summary']}}
map iterates over each element of response and returns the result of the block applied for each iteration as an array, so the blocks is apllied to each h and it generates a new hash from it
I suggest this approach.
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }
It returns just the required pairs:
# => [{:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}, {:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}]
To remove duplicates, just do expected.uniq
If you need to change the key name :summary to :title do:
expected = expected.each { |h| h[:title] = h.delete(:summary) }
One liner
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }.each { |h| h[:title] = h.delete(:summary) }.uniq
Of course, maybe it is better to move .uniq as first method expected = response.uniq.each { .....
I currently have a nested JSON object which resembles
{
"People": [
{
"Name": "James",
"Age": "18",
"Gender": "Male",
"Sports": []
},
{
"Name": "Sarah",
"Age": "19",
"Gender": "Female",
"Sports": [
"Soccer",
"Basketball",
"Football"
]
}
]
}
Being new to Ruby, I aim to filter throught the entire json and return only the json object/objects in which the "Sports" array has content. So in the above scenario I expect to obtain the object below as a final outcome:
{
"Name": "Sarah",
"Age": "19",
"Gender": "Female",
"Sports": [
"Soccer",
"Basketball",
"Football"
]
}
Will I have to initiate a new method to perform such an act? Or would using regular ruby calls work in this case?
Although #philipyoo answer is right, it miss an explanation on how to "filter" the parsed JSON. If you are new to ruby, take a look at Array#keep_if : http://ruby-doc.org/core-2.2.0/Array.html#method-i-keep_if
require 'json'
people = JSON.parse("{long JSON data ... }")
people_with_sports = people.fetch('People', []).keep_if do |person|
!person.fetch('Sports', []).empty?
end
If you're getting a JSON object from a request, you want to parse it and then you can traverse the hash and arrays to find the information you need. See http://ruby-doc.org/stdlib-2.0.0/libdoc/json/rdoc/JSON.html
In your case, something like this:
require 'json'
parsed_json = JSON.parse('{"People": [ ... ]}')
parsed_json["People"].each do |person|
puts person if person["name"] == "Sarah"
end
These is sample response of hashes in ruby.
Eg:-
find abcd1234
should give me
i was able to find by but it's not sufficent
I have response of sth like these and list keep on going different value but same structure
[
{
"addon_service": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "heroku-postgresql"
},
"config_vars": [
"FOO",
"BAZ"
],
"created_at": "2012-01-01T12:00:00Z",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "acme-inc-primary-database",
"plan": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "heroku-postgresql:dev"
},
"app": {
"id"=>"342uo23iu4io23u4oi2u34",
"name"=>"heroku-staging"},
},
"provider_id": "abcd1234",
"updated_at": "2012-01-01T12:00:00Z",
"web_url": "https://postgres.heroku.com/databases/01234567-89ab-cdef-0123-456789abcdef"
} .........
]
can anyone know how to grab those?
You can iterate all array element (a hash) and display its content if the hash meet your requirement:
element_found = 0
YOUR_DATA.each do |element|
if element["provider_id"].match(/abcd1234/)
element_found += 1
puts "addon_service: #{element['addon_service']['name']}"
puts "app: #{element['app']['name']}"
end
end
if element_found == 0 puts "Sorry match didn't found"
Since the elements of the array are hashes you can select the appropriate ones by matching the desired key.
select {|app| app[:provider_id] == "abcd1234"}
Do you know what to do with the element once you select it?
I think you want some of the items from the hash, but not all of them.
That might look like:
select {|app| app[:provider_id] == "abcd1234"}.map {|app| app.select {|key, v| [:addon_service, :app].include?(key) } }
I have this JSON and I am trying to send it to a Rails API from Postman:
{"object":
{
"type": "out",
"vars":
{
"x": "x",
"y": "y"
},
"values":
{
"ts": "timestamp",
"ok":
{
"total": 2,
"min": "x",
"max": "y"
},
"error":
{
"total": 2,
"error1": "first",
"error2": "second"
}
}
}
}
I need to convert this into a Hash in my model so that I can manipulate it with before_create. Here's what I came with:
object = self.to_json # => converts object to json
object = JSON.parse(object) # => converts json to hash
1st problem: I get this (id=>nil is not relevant since it will be inserted automatically in the database):
{"id"=>nil, "type"=>"out", "vars"=>{"x"=>"x", "y"=>"y"}, "values"=>{"ts"=>"timestamp", "ok"=>"{\"total\"=>2, \"min\"=>\"x\", \"max\"=>\"y\"}", "error"=>"{\"total\"=>2, \"error1\"=>\"first\", \"error2\"=>\"second\"}"}, "created_at"=>"2015-01-29T15:45:01.329Z", "updated_at"=>"2015-01-29T15:45:01.329Z"}
and when I try to manipulate object["values"]["ok"], Rails sends the error:
unexpected token at '"{\"total\"=\u003e2, \"min\"=\u003e\"x\", \"max\"=\u003e\"y\"}"'
2nd problem: I can only call object["values"], and I want to call it with a symbol, not a string object[:values].
Solved my issues using:
object = self.as_json.with_indifferent_access
# => allowing me to use a symbol key instead of a string
ok_vals = object[:values][:ok].as_json.gsub(/\=\>/, ':')
# => allowing to change json string '{"val1"=>"val1", "val2"=>"val2"}' to '{"val1":"val1", "val2":"val2"}'
ok_vals = JSON.parse(ok_vals)
# => which transform json string to hash {val1: "val1", val2: "val2"}
Feel free to make any suggestions to this code. Thanks for the help.
I have the following JSON string returned by a remote server:
{
"users": [
{
"user_id": 1,
"name": "Chris Rivers",
},
{
"user_id": 3,
"name": "Peter Curley",
}
]
}
I'd like to iterate the users.
ActiveSupport::JSON.decode(response.body)["users"].each do |user|
puts user["name"]
end
As far as I understand, the problem is: ruby doesn't recognize ActiveSupport::JSON.decode(response.body)["users"] as an array, and thus puts returns me only the first user.
How do I solve that problem?
What you have pasted is not valid JSON. The trailing comma after on each "name" is a problem
"name": "Chris Rivers",
You'll get a LoadError trying to decode this with ActiveSupport::JSON.decode
MultiJson::LoadError: 399: unexpected token at '{"user_id": 1,"name": "Chris Rivers",},{"user_id": 3,"name": "Peter Curley",}]}'
If we clean up the JSON, turning it into something ActiveSupport::JSON.decode can understand
"{\"users\": [{\"user_id\": 1,\"name\": \"Chris Rivers\"},{\"user_id\": 3,\"name\": \"Peter Curley\"}]}"
you'll see there is no issue iterating over each object in "users" (x below is the above JSON string)
[8] pry(main)> ActiveSupport::JSON.decode(x)["users"].map { |user| user["name"] }
=> ["Chris Rivers", "Peter Curley"]
Does your source data actually have the trailing commas after each user's name? I get a parse error for that, but your code works as you want it to if I remove them:
json = '{ "users": [ { "user_id": 1, "name": "Chris Rivers" }, { "user_id": 3, "name": "Peter Curley" } ]}'
ActiveSupport::JSON.decode(json)["users"].each do |user|
puts user["name"]
end
The problem isn't not recognizing the array, it's the trailing commas after the "name" elements.
Removing those allows JSON parsing to proceed normally:
pry(main)> ActiveSupport::JSON.decode(s)["users"]
=> [{"user_id" => 1, "name" => "Chris Rivers"},
{"user_id" => 3, "name" => "Peter Curley"}]