Ruby JSON parse changes Hash keys - ruby-on-rails

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

Related

Convert an array of hash with symbols

I have an array of hashes:
[{'object' => 'ob1', 'quantity' => '2'}, {'object' => 'ob2', 'quantity' => '3'}, .....]
I want to convert it to symbolized form:
[{:object => 'ob1', :quantity => '2'}, {:object => 'ob2', :quantity => '3'}, .....]
tried with:
symbolized_array = array.each => { |c| c.to_options }
but i didn't obtained any conversion, the symbolized_array is same as array
why?
Since ruby 2.5 there's Hash#transform_keys:
array.map{|hash| hash.transform_keys(&:to_sym) }
Before that it was available in activesupport (part of rails) along with shortcut symbolize_keys
You tagged rails so you can use symbolize_keys
array.map(&:symbolize_keys)
Use below code. Then you will get expected output
array.map! {|my_hash| my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}}
Or simply you can use array.map(&:symbolize_keys). This code will be work on rails environment
i didn't obtained any conversion […] why?
to_options does return a new hash with the keys symbolized, but you didn't use that new hash – each merely traverses the array and at the end returns the array.
If you want to pick up the blocks results as the new array element, you have to use map:
array.map { |c| c.to_options } # or array.map(&:to_options)
Alternatively there's to_options! (with a !) which would work along with each:
array.each { |c| c.to_options! } # or array.each(&:to_options!)
That's because to_options! modifies the hashes in-place.
Note that to_options is an alias for symbolize_keys which might be a little clearer.

Extracting specific JSON data

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.

converting ruby hash into json object

So I am iterating through a set of data and building a hash from it:
clean_response = Array.new
response.each_with_index do |h, idx|
clean_response <<
{
:lat => h["location"]["latitude"],
:lg => h["location"]["longitude"],
:place => h["location"]["name"],
#This grabs the entire hash at "location" because we are wanting all of that data
:profile_picture => h["user"]["profile_picture"],
:hash_tags => h["tags"],
:username => h["user"]["username"],
:fullname => h["user"]["full_name"],
:created_time => (Time.at(h["created_time"].to_i)).to_s,
:image => h["images"]["low_resolution"]["url"] # we can replace this with whichever resolution.
}
end
Which return an array of hashes like so:
[{:lat=>40.7486382,
:lg=>-73.9487686,
:place=>"The Cliffs at LIC",
:profile_picture=>"http://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-19/s150x150/12104940_1653775014895036_286845624_a.jpg",
:hash_tags=>["bouldering"],
:username=>"denim_climber",
:fullname=>"DenimClimber",
:created_time=>2015-10-13 22:58:09 -0400,
:image=>"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s320x320/e35/11856571_1062082890510188_611068928_n.jpg"},
{:lat=>40.7459602,
:lg=>-73.9574966,
:place=>"SHI",
:profile_picture=>"http://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-19/11348212_1453525204954535_631200718_a.jpg",
:hash_tags=>["cousins", "suchafunmoment", "johnlennonstyle"],
:username=>"xiomirb",
:fullname=>"Xiomi",
:created_time=>2015-10-13 22:57:21 -0400,
:image=>"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s320x320/e35/11375290_1688934151392424_2009781937_n.jpg"}]
I'd like to convert this data to json and then serve it to a specific view.
How can I convert this? I tried the .to_json method but it doesn't return a well formatted one since my UI isn't binding to the data.
You can convert a Ruby hash into JSON using to_json:
require 'json'
your_hash.to_json # gives you a JSON object
But, in your case the data is an array of hashes, but NOT a hash. So, your to_json would not work.
I am not quite sure how you want to do this, but one possibility is to loop through the array of hashes, get each hash and convert that to a JSON object using to_json call (like shown above) and build a new array of JSON objects. This way, you can build an array of JSON objects from an array of hashes.
array_of_json = []
# loop through the array of hashes
clean_response.each do |hash|
array_of_json << hash.to_json
end
array_of_json # array of JSON objects
If by "serve it to a specific view" you mean pass it to a .haml or .erb template, you can pass the array of hashes as is. Both haml and erb will allow you to iterate over the array, and even the hash if you want.
If you mean you want to hand a json string to the browser, #to_json should work fine. Other options are jbuilder or oat when you want to refine what is sent, but to_json should "serve" you well!

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...

Rails group by id uses a string for hash key

My code looks like this:
hash = MyModel.count(:group => 'id', :conditions => 'bla = "bla"')
The returned Hash has keys that are strings. I want them to be ints. I know it would be possible to convert the Hash manually using something like a map construct.
Edit:
Thanks for the responses. Have realised it was a json conversion process that was turning the ids into Strings and rails does in fact use the Fixnum as one might expect.
hash = MyModel.count(group: 'id', conditions: 'bla = "bla"')
should have Fixnum keys by default since id is an instance of Fixnum.
What happens is that ActiveRecord always fetch result as strings and then Rails takes care of converting them to other datatypes according to the type of the database column (we say that they are typecast).
So it's maybe a Rails bug or the 'id' column is not set as integer(which would be surprising).
If you can't fix it, convert them manually:
hash.each_with_object({}) do |(key, value), hash|
hash[key.to_i] = value
end
When I use your code I get integer keys (rails 3.07), what's the column type of id?
If you want to do it manually:
new_hash = hash.inject({}){|h,a| h[a.first.to_i] = a.last; h}
new_hash = Hash[hash.map { |k, v| [k.to_i, v] }

Resources