Ruby - How to parse string to array of hashes - ruby

I have a string stored in a database like so:
images = '[{"id":1,"type":"Image","image_id":"asdf123"},{"id":2,"type":"Image","image_id":"asdf456"},{"id":3,"type":"Image","image_id":"asdf890"}]'
And would like to convert it to an array so I can do something like:
images.each do |image|
puts image.image_id
end
Is it really just a matter of removing the outer square brackets and then following the procedure from this question Converting a Ruby String into an array or is there a more direct/elegant method?

That format is called JavaScript Object Notation (JSON) and can be parsed by a builtin Ruby library:
require 'json'
images_str = '[{"id":1,"type":"Image","image_id":"asdf123"},{"id":2,"type":"Image","image_id":"asdf456"},{"id":3,"type":"Image","image_id":"asdf890"}]'
images = JSON.parse(images_str)
images.size # => 3
images[0].class # => Hash
images[0]['image_id'] # => "asdf123"
images.each { |x| puts "#{x['id']}: #{x['image_id']}" }
# 1: asdf123
# 2: asdf456
# 3: asdf890

Related

Ruby Yard documentation: how to add a "verbatim" (to generate something like a <pre> tag)

I want a piece of code, like a hash, to display with fixed typeface on the resulting html. Suppose this is the contents of my file:
=begin
One example of valid hash to this function is:
{
:name => "Engelbert",
:id => 1345
}
=end
def f hash_param
# ...
end
How to instruct yard (using the default of the version 0.9.15) so a yard doc file.rb will generate, for the hash example, the equivalent of adding 4 backslashes to the markdown format, or 4 starting empty spaces to stackoverflow, or the <pre> tag in html, resulting in a verbatim/fixed typeface format in the resulting html?
Expected output:
One example of valid hash to this function is:
{
:name => "Engelbert",
:id => 1345
}
EDIT
> gem install redcarpet
> yard doc --markup-provider redcarpet --markup markdown - file.rb
Should wrap the contents of file.rb within a <pre> tag, producing this page.
Use #example
Show an example snippet of code for an object. The first line is an optional title.
# #example One example of valid hash to this function is:
# {
# :name => "Engelbert",
# :id => 1345
# }
def f hash_param
# ...
end
Maybe I don't get your question:
the equivalent of adding 4 backslashes to the markdown format, or 4 starting empty spaces to stackoverflow
If I use the 4 starting empty spaces in my code like this:
=begin
One example of valid hash to this function is:
{
:name => "Engelbert",
:id => 1345
}
=end
def f hash_param
# ...
end
then I get
But maybe you can also use #option:
#param hash_param
#option hash_param [String] :name The name of...
#option hash_param [Integer] :id The id of...
and you get:
Disclaimer: I used yard 0.9.26 for my examples.

ruby object to_s gives unexpected output

What is the correct way to view the output of the puts statements below? My apologies for such a simple question.... Im a little rusty on ruby. github repo
require 'active_support'
require 'active_support/core_ext'
require 'indicators'
my_data = Indicators::Data.new(Securities::Stock.new(:symbol => 'AAPL', :start_date => '2012-08-25', :end_date => '2012-08-30').output)
puts my_data.to_s #expected to see Open,High,Low,Close for AAPL
temp=my_data.calc(:type => :sma, :params => 3)
puts temp.to_s #expected to see an RSI value for each data point from the data above
Maybe check out the awesome_print gem.
It provides the .ai method which can be called on anything.
An example:
my_obj = { a: "b" }
my_obj_as_string = my_obj.ai
puts my_obj_as_string
# ... this will print
# {
# :a => "b"
# }
# except the result is colored.
You can shorten all this into a single step with ap(my_obj).
There's also a way to return objects as HTML. It's the my_obj.ai(html: true) option.
Just use .inspect method instead of .to_s if you want to see internal properties of objects.

Difficulty processing json with ruby

I have the following json...
{
"NumPages":"17",
"Page":"1",
"PageSize":"50",
"Total":"808",
"Start":"1",
"End":"50",
"FirstPageUri":"/v3/results?PAGE=1",
"LastPageUri":"/v3/results?PAGE=17",
"PreviousPageUri":"",
"NextPageUri":"/v3/results?PAGE=2",
"User":[
{
"RowNumber":"1",
"UserId":"86938",
"InternalId":"",
"CompletionPercentage":"100",
"DateTimeTaken":"2014-06-18T01:43:25Z",
"DateTimeLastUpdated":"2014-06-18T01:58:11Z",
"DateTimeCompleted":"2014-06-18T01:58:11Z",
"Account":{
"Id":"655",
"Name":"Technical Community College"
},
"FirstName":"Matthew",
"LastName":"Knice",
"EmailAddress":"knice#gmail.com",
"AssessmentResults":[
{
"Title":"Life Factors",
"Code":"LifeFactors",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"58",
"Readiness":"fail",
"DateTimeCompleted":"2014-06-18T01:46:00Z"
},
{
"Title":"Learning Styles",
"Code":"LearnStyles",
"IsComplete":"0"
},
{
"Title":"Personal Attributes",
"Code":"PersonalAttributes",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"52.08",
"Readiness":"fail",
"DateTimeCompleted":"2014-06-18T01:49:00Z"
},
{
"Title":"Technical Competency",
"Code":"TechComp",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"100",
"Readiness":"pass",
"DateTimeCompleted":"2014-06-18T01:51:00Z"
},
{
"Title":"Technical Knowledge",
"Code":"TechKnowledge",
"IsComplete":"1",
"AttemptNumber":"1",
"Percent":"73.44",
"Readiness":"question",
"DateTimeCompleted":"2014-06-18T01:58:00Z"
},
{
"Title":"Reading Rate & Recall",
"Code":"Reading",
"IsComplete":"0"
},
{
"Title":"Typing Speed & Accuracy",
"Code":"Typing",
"IsComplete":"0"
}
]
},
{
"RowNumber":"2",
"UserId":"8654723",
"InternalId":"",
"CompletionPercentage":"100",
"DateTimeTaken":"2014-06-13T14:37:59Z",
"DateTimeLastUpdated":"2014-06-13T15:00:12Z",
"DateTimeCompleted":"2014-06-13T15:00:12Z",
"Account":{
"Id":"655",
"Name":"Technical Community College"
},
"FirstName":"Virginia",
"LastName":"Bustas",
"EmailAddress":"bigBusta#students.college.edu",
"AssessmentResults":[
{
...
I need to start processing where you see "User:" The stuff at the beginning (numpages, page, ect) I want to ignore. Here is the processing script I am working on...
require 'csv'
require 'json'
CSV.open("your_csv.csv", "w") do |csv| #open new file for write
JSON.parse(File.open("sample.json").read).each do |hash| #open json to parse
csv << hash.values
end
end
Right now this fails with the error:
convert.rb:6:in `block (2 levels) in <main>': undefined method `values' for ["NumPages", "17"]:Array (NoMethodError)
I have ran the json through a parser, and it seems to be valid. What is the best way to only process the "User" data?
You have to look at the structure of the JSON object being created. Here's a very small subset of your document being parsed, which makes it easier to see and understand:
require 'json'
foo = '{"NumPages":17,"User":[{"UserId":12345}]}'
bar = JSON[foo]
# => {"NumPages"=>17, "User"=>[{"UserId"=>12345}]}
bar['User'].first['UserId'] # => 12345
foo contains the JSON for a hash. bar contains the Ruby object created by the JSON parser after it reads foo.
User is the key pointing to an array of hashes. Because it's an array, you have to specify which of the hashes in the array you want to look at, which is what bar['User'].first does.
An alternate way to access that sub-hash is:
bar['User'][0]['UserId'] # => 12345
If there were multiple hashes inside the array, you could access them by using the appropriate index value. For example, if there are two hashes, and I want the second one:
foo = '{"NumPages":17,"User":[{"UserId":12345},{"UserId":12346}]}'
bar = JSON[foo]
# => {"NumPages"=>17, "User"=>[{"UserId"=>12345}, {"UserId"=>12346}]}
bar['User'].first['UserId'] # => 12345
bar['User'][0]['UserId'] # => 12345
bar['User'][1]['UserId'] # => 12346
I'm wondering if I am going down the wrong road with the JSON.parse(File.open("sample.json").read).each do |hash|?
Yes, you are. You need to understand what you're doing, and break your code into digestible pieces so they make sense to you. Consider this:
require 'csv'
require 'json'
json_object = JSON.parse(File.read("sample.json"))
CSV.open("your_csv.csv", "w") do |csv| #open new file for write
csv << %w[RowNumber UserID AccountID AccountName FirstName LastName EmailAddress]
json_object['User'].each do |user_hash|
puts 'RowNumber: %s' % user_hash['RowNumber']
puts 'UserID: %s' % user_hash['UserID']
account = user_hash['UserID']['Account']
puts 'Account->Id: %s' % account['Id']
puts 'Account->Name: %s' % account['Name']
puts 'FirstName: %s' % user_hash['FirstName']
puts 'LastName: %s' % user_hash['LastName']
puts 'EmailAddress: %s' % user_hash['EmailAddress']
csv << [
user_hash['RowNumber'],
user_hash['UserID'],
account['Id'],
account['Name'],
user_hash['FirstName'],
user_hash['LastName'],
user_hash['EmailAddress']
]
end
end
This reads the JSON file and parses it into a Ruby object immediately. There is no special magic or anything else that happens with the file, it's opened, read, closed, and its content is passed to the JSON parser and assigned to json_object.
Once parsed, the CSV file is opened and a header row is written. It could have been written as part of the open statement but this is clearer for explaining what's going on.
json_object is a hash, so to access the 'User' data you have to use a normal hash access json_object['User']. The value for the User key is an array of hashes, so those need to be iterated over, which is what json_object['User'].each does, passing the hash elements of that array into the block as user_hash.
Inside that block it's pretty much the same thing as access the value for 'User', each "element" is a key/value pair, except 'Account' which is an embedded hash.
Read the error message. each called on a hash is giving you a sequence of arrays with two members (the key and value together). There is no values method on an array. And in any case if what you have is a hash there seems little point cycling through it with each; if you want the "User" entry in the hash, why don't you ask for it up front?
Just for posterity and context this is the script I ended up using in its entity. I needed to pull from a url, and process the results and move them to a simple CSV. I needed to wite the student id, first name, last name, and the score from each of 4 assessments to the csv.
require 'csv'
require 'json'
require 'curb'
c = Curl::Easy.new('myURL/m/v3/results')
c.http_auth_types = :basic
c.username = 'myusername'
c.password = 'mypassword'
c.perform
json_object = JSON.parse(c.body_str)
CSV.open("your_csv.csv", "w") do |csv| #open new file for write
csv << %w[UserID FirstName LastName LifeFactors PersonalAttributes TechComp TechKnowledge]
json_object['User'].each do |user_hash|
csv << [
user_hash['UserId'],
user_hash['FirstName'],
user_hash['LastName'],
user_hash['AssessmentResults'][0]['Percent'],
user_hash['AssessmentResults'][2]['Percent'],
user_hash['AssessmentResults'][3]['Percent'],
user_hash['AssessmentResults'][4]['Percent']
]
end
end

Simple JSON not working in Ruby

Code:
#!/usr/bin/ruby
require 'rubygems'
require 'open-uri'
require 'json'
def getData
file = open("http://goo.gl/BI6h7a")
#json = JSON.parse(file.read)
end
getData
cveIds = #json['cve_id']
puts cveIds
You can see the JSON response here: http://goo.gl/BI6h7a
Console:
./cve.rb:13:in `[]': can't convert String into Integer (TypeError) from ./cve.rb:13:in `<main>'
I don't know why this is happening. "Convert String into Integer"? WHAT?
The #json gets the content fine, but the cveIds doesn't.
The top element in the json that you're reading is actually an Array, each of its elements is actually a hash, it's like this:
[
{
"cve_id": "CVE-2014-3976"
// other key/value pairs
}
{
"cve_id": "CVE-2014-3975"
// other key/value pairs
}
{
"cve_id": "CVE-2014-3974"
// other key/value pairs
}
// .... more hashes
]
so #json is an array. And if you want to access any of its elements you have to access it with a numeric integer index like, so:
#json[0] # => { "cve_id": "CVE-2014-3976", // other key/value pairs }
I think you are trying to collect the cve_id fields of all these hashes, this can be done as follows:
cveIds = #json.collect { |h| h["cve_id"] }
# The result:
=> ["CVE-2014-3976", "CVE-2014-3975", "CVE-2014-3974", "CVE-2014-3962", "CVE-2014-3961",
"CVE-2014-3878", "CVE-2014-3871", "CVE-2014-3842", "CVE-2014-3806", "CVE-2014-3792",
"CVE-2014-3791", "CVE-2014-3443", "CVE-2014-3247", "CVE-2014-3246", "CVE-2014-3225",
"CVE-2014-3216", "CVE-2014-3139", "CVE-2014-3138", "CVE-2014-3008", "CVE-2014-2996",
"CVE-2014-2994", "CVE-2014-2976", "CVE-2014-2850", "CVE-2014-2847", "CVE-2014-2671",
"CVE-2014-2668", "CVE-2014-2588", "CVE-2014-2587","CVE-2014-2586", "CVE-2014-2579"]
I'm not a ruby developer but what you have there is a list if dictionaries.
My guess in order for you to read cve_id you need to create some kind of a for loop.
for example in python I would write it like this:
for line in my_data:
print line['cve_id']
I guess in ruby it would look like this:
for i in #json do
cveIds = i['cve_id']
puts cveIds
end
cveIds = #json['cve_id']
What are you doing here is equivalent to:
arr = [1, 2, 3, 4]
puts arr["hello"] # using a string here on an indexed based array!
Hence your error message about Ruby trying to convert a String to an int.
Try the following instead
cveIds = #json.first['cve_id'] # equivalent to #json[0]['cve_id']
puts cveIds
In the above code sample, we are getting the first element from the array, which is a hash we can then access cve_id from.

Ruby string to array conversion

I have a string that is URL encoded:
a = "%5B%22552A8619-6ECA-4A95-A798-C1E2CE75BFFF%22%2C%2264c19b5b2d0257ddb382dbd3660de3fd%22%2C%22share%22%5D"
If I URL decode this string then it will look like:
"[\"552A8619-6ECA-4A95-A798-C1E2CE75BFFF\",\"64c19b5b2d0257ddb382dbd3660de3fd\",\"share\"]"
From this string I want to get this array:
["552A8619-6ECA-4A95-A798-C1E2CE75BFFF","64c19b5b2d0257ddb382dbd3660de3fd","share"]
How to do that without nasty string replacements?
the_given_string.scan(/"(.*?)"/).flatten
The string is an array encoded using JSON:
require 'cgi'
require 'json'
a = "%5B%22552A8619-6ECA-4A95-A798-C1E2CE75BFFF%22%2C%2264c19b5b2d0257ddb382dbd3660de3fd%22%2C%22share%22%5D"
JSON[CGI::unescape(a)]
[
[0] "552A8619-6ECA-4A95-A798-C1E2CE75BFFF",
[1] "64c19b5b2d0257ddb382dbd3660de3fd",
[2] "share"
]
JSON[CGI::unescape(a)].last will return "share", putting you home free.
CGI::escape is used to remove the encoding, which turns it back to a "normal" JSON-encoded array.
JSON[] (AKA JSON.parse) converts it from the JSON notation back to a Ruby array.
You could delete characters and split, or evaluate it:
"[\"A798-C1E2CE75BFFF\",\"643fd\",\"share\"]".delete('\"[]').split(',')
# => ["A798-C1E2CE75BFFF", "643fd", "share"]
eval "[\"A798-C1E2CE75BFFF\",\"643fd\",\"share\"]"
# => ["A798-C1E2CE75BFFF", "643fd", "share"]
You could eval the string:
require 'cgi'
a = "%5B%22552A8619-6ECA-4A95-A798-C1E2CE75BFFF%22%2C%2264c19b5b2d0257ddb382dbd3660de3fd%22%2C%22share%22%5D"
x = eval( CGI.unescape(a))
p x #["552A8619-6ECA-4A95-A798-C1E2CE75BFFF", "64c19b5b2d0257ddb382dbd3660de3fd", "share"]
But eval is evil.
You could use , what you call nasty string replacement:
p CGI.unescape(a).gsub(/\A\["|"\]\Z/,'').split(/","/)
Or you could try JSON:
require 'cgi'
require 'json'
a = "%5B%22552A8619-6ECA-4A95-A798-C1E2CE75BFFF%22%2C%2264c19b5b2d0257ddb382dbd3660de3fd%22%2C%22share%22%5D"
x = JSON.load( CGI.unescape(a))

Resources