Extracting specific JSON data - ruby-on-rails

I receive (similar to) the following JSON data:
{"accountId"=>"some-private-really-long-account-id",
"stats"=>
{"score"=>
{"globalScore"=>
[{"key"=>"lifetimeScore", "value"=>"571",
"key"=>"someOtherKeyHere", "value"=>"someValue"}]}
I am not quite sure how I would get the lifetime score. I've tried doing stuff like this:
puts data["globalScore"]["lifetimeScore"]["value"]
But that doesn't work. (data is of course the JSON data received).

I believe the problem here is that data["globalScore"]["lifetimeScore"]["value"] doesn't reference a valid "path" within the JSON. Better formatting helps to clarify this:
hash = {
"accountId" => "some-private-really-long-account-id",
"stats" => {
"score" => {
"globalScore" => [
{
"key" => "lifetimeScore",
"value" => "571",
"key" => "someOtherKeyHere",
"value" => "someValue"
}
]
}
}
}
This Ruby hash has some issues since a hash can't actually have multiple values for a given key, but that aside,
hash['stats']['score']['globalScore'][0]['value']
is a perfectly valid way to access the 'value' field.
My point is that the problem with the original question is not that hash#dig(...) should be used (as shown by #Phlip), it is that the "path" through the Hash data structure was actually invalid.
hash.dig("globalScore", "lifetimeScore", "value)
will fail just like the bracketed syntax in the original question.

Use JSON.parse(body) to convert your json to a hash. Then use hash.dig('stats', 'score', 'globalScore', 0, 'value') to run queries on that hash.

Related

Order Hash and delete first key-value pair

I have a Hash with timestamp as keys.
hash = {
"2016-05-31T22:30:58+02:00" => {
"path" => "/",
"method" => "GET"
},
"2016-05-31T22:31:23+02:00" => {
"path" => "/tour",
"method" => "GET"
},
"2016-05-31T22:31:05+02:00" => {
"path" => "/contact_us",
"method" => "GET"
}
}
I order the collection and get the first pair like this:
hash.sort_by {|k, _| k}.first.first
But how do I remove it?
The delete method requires you to know the exakt spelling of the key. Of course I could return the key and then use it in the delete method, but I was thinking if there was any more straight forward way?
The method you are looking for is hash.keys it returns an array of the keys:
hash.delete(hash.keys.min)
EDIT: I've updated the answer to reflect that keys must be sorted first, this has been added in the original question and brought up by #Shadwell in comments to this post.
I replaced hash.keys.sort.first for hash.keys.min as suggested by #Cary Swoveland, it is not only more performant but better semantically.
Also note that shift can be used on the Hash values.
hash.shift
Removes the first key-value pair from the hash. Works on arrays too.
As the OP has not stated that the hash's keys are in sorted order, we must assume that there is no guarantee that they are.
hash = { "2016-05-31T22:31:05+02:00"=>{ "path"=>"/tour", "method"=>"GET" },
"2016-05-31T22:30:58+02:00"=>{ "path"=>"/", "method"=>"GET" },
"2016-05-31T22:31:23+02:00"=>{ "path"=>"/contact_us", "method"=>"GET" } }
First, find the smallest key (the second one):
smallest_key = hash.keys.min
#=> "2016-05-31T22:30:58+02:00"
This is obviously more efficient than sorting the keys then taking the smallest.
Because the date-time strings are in iso8601 format, they can be sorted as strings, without having to first convert them to time objects.
Then use Hash#reject to obtain the desired hash:
hash.reject { |k,_| k == smallest_key }
#=> {"2016-05-31T22:31:05+02:00"=>{"path"=>"/tour", "method"=>"GET"},
# "2016-05-31T22:31:23+02:00"=>{"path"=>"/contact_us", "method"=>"GET"}}
To change hash in place, write
hash.delete(smallest_key }
hash

how to work with array of hashes

[[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
is the array
how can I get the value corresponding to particular value.
say High returns 5 in this.
or how to convert this array of hashes to an array so that searching becomes easy.
I tried:
find_all { |v| v['name'] == "Low" }
but it says:
cant convert String to Integer
please provide some guidance
How about making a single hash out of it for efficient querying?
arr.flatten.reduce(:merge)
#=> {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
If you have some code like:
array = [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
Then turn it into an ruby hash:
hash = array.inject({}) {|h, e| h.merge(e.first) }
# => {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
So you can find 'Low' value easily :
hash['Low']
# => 3
EDIT: The answer of Mark Thomas is pretty great, and shorter than the inject since it does the same thing. He wrote it before I answered. Nice ;)
In the general case, the hashes won't be unique, so you need to filter rather than pick one via indexing. For example, let's say you have this:
arr = [[{:apple => 'abc'}], [{:banana => 'def'}], [{:coconut => 'ghi'}]]
# => [[{:apple=>"abc"}], [{:banana=>"def"}], [{:coconut=>"ghi"}]]
Now let's suppose you want to get the value corresponding to any hash with a :coconut key. Then just use:
arr.flatten.map { |h| h[:coconut] }.compact
# => ["ghi"]
That gives you the list of answers. In this case there's only one matching key, so there's only one entry in the array. If there were other hashes that had a :coconut key in there, then you'd have something like:
# => ["ghi", "jkl", "mno"]
On the whole, though, that's a very unusual data structure to have. If you control the structure, then you should consider using objects that can return you sensible answers in the manner that you'd like, not hashes.
You could simply call #flatten on the original array. That would give you an array of hashes. What I think you would really want is just one hash.
1.8.7 :006 > [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]].flatten
=> [{"Postponed"=>10}, {"Low"=>3}, {"Medium"=>4}, {"High"=>5}]
I would ask, what are you doing to get that original structure? Can that be changed?
How about this?
arr = [
[{"Postponed"=>10}],
[{"Low"=>3}],
[{"Medium"=>4}],
[{"High"=>5}]
]
arr1 = []
arr.each{|a|
arr1.push(a[0])
}
Although I wonder if you really just want to get one hash, which you'd do like so:
myHash = {}
arr.each{|a|
a[0].each{|b, c|
myHash[b] = c
}
}
You would then access it like myHash["Postponed"]

How to get key from json file format..?

I'm using json gem in ruby..,My ruby code is here..,
require 'json'
json = JSON.generate [1, 2, [{"pi" => 3.141}, {"integer" => 1234567890}], {"subject" => "Mathematics"}, {"Float"=> 1.324343}, {"number"=> 232132435}]
generator = JSON.parse json
puts generator[2][1]
My key-value pair is working fine.But, I'm trying to print only the key not an value from index[2] such as either an "integer" or "pi".
Is it possible..?
In your case generator[2][1] is a Hash {"integer"=>1234567890}. In order to get all keys from hash you can use Hash#keys method, and then take first (as far as it is the only key in the hash)
generator[2][1].keys.first # => "integer"
You can learn more about Hash methods in this documentation.
Your data structure at generator[2] looks strange, maybe you better use a single Hash for such casese:
{"pi" => 3.141, "integer" => 1234567890} # etc...

Ruby JSON parse changes Hash keys

Lets say I have this Hash:
{
:info => [
{
:from => "Ryan Bates",
:message => "sup bra",
:time => "04:35 AM"
}
]
}
I can call the info array by doing hash[:info].
Now when I turn this into JSON (JSON.generate), and then parse it (JSON.parse), I get this hash:
{
"info" => [
{
"from" => "Ryan Bates",
"message" => "sup bra",
"time" => "04:35 AM"
}
]
}
Now if I use hash[:info] it returns nil, but not if I use hash["info"].
Why is this? And is there anyway to fix this incompatibility (besides using string keys from the start)?
The JSON generator converts symbols to strings because JSON does not support symbols. Since JSON keys are all strings, parsing a JSON document will produce a Ruby hash with string keys by default.
You can tell the parser to use symbols instead of strings by using the symbolize_names option.
Example:
original_hash = {:info => [{:from => "Ryan Bates", :message => "sup bra", :time => "04:35 AM"}]}
serialized = JSON.generate(original_hash)
new_hash = JSON.parse(serialized, {:symbolize_names => true})
new_hash[:info]
#=> [{:from=>"Ryan Bates", :message=>"sup bra", :time=>"04:35 AM"}]
Reference: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/json/rdoc/JSON.html#method-i-parse
In short, no. Think about it this way, storing symbols in JSON is the same as storing strings in JSON. So you cannot possibly distinguish between the two when it comes to parsing the JSON string. You can of course convert the string keys back into symbols, or in fact even build a class to interact with JSON which does this automagically, but I would recommend just using strings.
But, just for the sake of it, here are the answers to this question the previous times it's been asked:
what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?
ActiveSupport::JSON decode hash losing symbols
Or perhaps a HashWithIndifferentAccess
I solved my similar issue with calling the with_indifferent_access method on it
Here I have a json string and we can assign it to variable s
s = "{\"foo\":{\"bar\":\"cool\"}}"
So now I can parse the data with the JSON class and assign it to h
h = JSON.parse(s).with_indifferent_access
This will produce a hash that can accept a string or a symbol as the key
h[:foo]["bar"]
#=> "cool"
Use ActiveSupport::JSON.decode, it will allow you to swap json parsers easier
Use ActiveSupport::JSON.decode(my_json, symbolize_names: true)
This will recursively symbolize all keys in the hash.
(confirmed on ruby 2.0)
It's possible to modify all the keys in a hash to convert them from a string to a symbol:
symbol_hash = Hash[obj.map{ |k,v| [k.to_sym, v] }]
puts symbol_hash[:info]
# => {"from"=>"Ryan Bates", "message"=>"sup bra", "time"=>"04:35 AM"}
Unfortunately that doesn't work for the hash nested inside the array. You can, however, write a little recursive method that converts all hash keys:
def symbolize_keys(obj)
#puts obj.class # Useful for debugging
return obj.collect { |a| symbolize_keys(a) } if obj.is_a?(Array)
return obj unless obj.is_a?(Hash)
return Hash[obj.map{ |k,v| [k.to_sym, symbolize_keys(v)] }]
end
symbol_hash = symbolize_keys(hash)
puts symbol_hash[:info]
# => {:from=>"Ryan Bates", :message=>"sup bra", :time=>"04:35 AM"}
You can't use that option like this
ActiveSupport::JSON.decode(str_json, symbolize_names: true)
In Rails 4.1 or later, ActiveSupport::JSON.decode no longer accepts
an options hash for MultiJSON. MultiJSON reached its end of life and
has been removed.
You can use symbolize_keys to handle it.
Warning: It works only for JSON strings parsed to hash.
ActiveSupport::JSON.decode(str_json).symbolize_keys

Ruby/Rails - Can't access JSON object attributes directly in controller

I am setting up an API.
The client (using HTTParty) posts this to the API:
{:body =>
{
:product=> {:description=>"some text", :cost => "11.99"},
:brand=> {:name=>"BrandName", :etc =>"hey"}
}
}
The server/api receives the post.
Now, if I access params[:brand] I get:
{"name"=>"BrandName", "etc" =>"hey"}
If I do this:
Brand.new(params[:brand])
Then I get a new Brand object with the "name" and "etc" attributes populated correctly.
However, if I try to access params[:brand][:name], I just get nil
Any ideas?
Thanks.
Use params[:brand]["name"] or params["brand"]["name"]
Hash keys can be any sort of object. Common rails practice is to use symbols as hash keys, but when translated from JSON, the keys are likely to be strings.

Resources