using an alias name for hash key value - ruby

I have some json data that I receive and that I JSON.parse to a hash. The hash key names are integer strings like data["0"], data["1"], data["2"], etc... where each value correspond to a state. Like 0 => START, 1 => STOP, 2 => RESTART.
I can't change the source json data to make the key more readable. Each hash will have 5 pairs that correspond to 5 different states.
I was wondering if there was a nice way for me to alias the numbers as meaningful names so when referencing the hash key value I don't have to use the number.
At the moment I'm using constants like below, but was thinking there might be a nicer, more Ruby way. Use another hash or struct so I can use data[STATES.start] or something?
STATE_START = "0"
STATE_STOP = "1"
STATE_RESTART = "2"
data = JSON.parse value
puts data[STATE_START]
Thanks

I think constants are fine. But if you want to rubify this code a bit, you can, for example, wrap the source hash in an object that will translate method names.
class MyHash
def initialize(hash)
#hash = hash
end
MAPPING = {
start: '0',
stop: '1',
restart: '2',
}
# dynamically define methods like
#
# def start
# #hash['0']
# end
#
# or you can use method_missing
MAPPING.each do |method_name, hash_key|
define_method method_name do
#hash[hash_key]
end
end
end
mh = MyHash.new({'0' => 'foo', '1' => 'bar'})
mh.start # => "foo"
mh.stop # => "bar"

Related

Does ruby has a hash mode which only support updating exist key but not adding new keys?

For example, I have a hash, in which updating is valid but adding new key is invalid.
opts = {
url: 'www.google.com',
local: 'disk',
limit: 10
}
opts[:url] = 'www.facebook.com' # valid
opts[:other] = 'www.apple.com' # should raise an error
Using a built-in like Struct as #stefan suggested is a nice fast solution. Only the initiation is a bit weird.
opts = Struct.new(:url, :local, :limit).new(
'www.google.com', 'disk', 10
)
opts[:url] = 'www.facebook.com'
opts[:other] = 'www.apple.com' # NameError: no member 'other' in struct
opts.to_h #=> {:url=>"www.facebook.com", :local=>"disk", :limit=>10}
However, if you want to you can also somewhat easily build your own solution to have more control over how it works and to be more similar to an actual hash.
class MyHash < Hash
def initialize(hash)
super()
update(hash)
end
def []=(key, value)
raise 'Unknown key passed to MyHash' unless key?(key) # or whatever error you want
super
end
end
opts = MyHash.new(
url: 'www.google.com',
local: 'disk',
limit: 10
)
opts[:url] = 'www.facebook.com'
opts[:other] = 'www.apple.com' # RuntimeError: Unknown key passed to MyHash
opts #=> {:url=>"www.facebook.com", :local=>"disk", :limit=>10}
MyHash is a Hash for basically all purposes and can be used anywhere a regular hash can. Do note though, that this only overrides the direct setter ([]=). Indirectly assigning new values via update (merge!) for example still works.
While using Struct is a better option you can also consider to Freeze the Hash.
But you cannot update directly the key, so it doesn't work with all keys object, for example Integers, so you need to use keys as String, Arrays, etc:
h = {a: 'aa', b: 'bb', c: '1', ary: [1]}
h.freeze
h[:b].replace 'ss'
h[:c].replace '2'
h[:ary] << 10
h
#=> {:a=>"aa", :b=>"ss", :c=>"2", :ary=>[1, 10]}
h[:d] = '10'
#=> can't modify frozen Hash: {:a=>"aa", :b=>"ss", :c=>"2", :ary=>[1, 10]} (FrozenError)

How to parse data from a file into a Hash

I am trying to parse and store some data from a file into a hash map, not using regular expressions but string comparison, and I am getting some errors I tried to fix but didn't solve the problem.
The file has a structure like:
"key" + "double colon" + "value"
in every line. This structure is repeated along the file, and every data has an ID key, almost everything has at least one "is_a" key, and may also have "is_obsolete" and "replaced_by" keys.
I'm trying to parse it like this:
def get_hpo_data(hpofile="hp.obo")
hpo_data = Hash.new() #Hash map where i want to store all IDs
File.readlines(hpofile).each do |line|
if line.start_with? "id:" #if line is an ID
hpo_id = line[4..13] #Store ID value
hpo_data[hpo_id] = Hash.new() #Setting up hash map for that ID
hpo_data[hpo_id]["parents"] = Array.new()
elsif line.start_with? "is_obsolete:" #If the ID is obsolete
hpo_data[hpo_id]["is_obsolete"] = true #store value in the hash
elsif line.start_with? "replaced_by:" #If the ID is obsolete
hpo_data[hpo_id]["replaced_by"] = line[13..22]
#Store the ID term it was replaced by
elsif line.start_with? "is_a:" #If the ID has a parent ID
hpo_data[hpo_id]["parents"].push(line[6..15])
#Store the parent(s) in the array initialized before
end
end
return hpo_data
end
The structure I was expecting to be created is a global hash in which every ID also is a hash with its diferent data (one string data, one boolean and an array with a variable length depending the number of ID parents of that ID term, but I'm getting the following error:
table_combination.rb:224:in `block in get_hpo_data': undefined method `[]=' for nil:NilClass (NoMethodError)
This time the error is pointing to the replaced_by elsif statement, but I also get it with any of other elsif statements, so the code does not work parsing "is_obsolete", "replaced_by" and "is_a" properties. If I try deleting these statements, the code succesfully creates the global hash with every ID term as a hash.
I also tried giving default values for every hash but it does not solve the problem. I'm even getting a new error not seen before:
table_combination.rb:233:in '[]': no implicit conversion of String into Integer (TypeError)
at this line:
hpo_data[hpo_id]["parents"].push(line[6..15])
Here is an example of how the file looks like for two terms showing the different keys I want to take care of:
[Term]
id: HP:0002578
name: Gastroparesis
def: "Decreased strength of the muscle layer of stomach, which leads to a decreased ability to empty the contents of the stomach despite the absence of obstruction." [HPO:probinson]
subset: hposlim_core
synonym: "Delayed gastric emptying" EXACT layperson [ORCID:0000-0001-5208-3432]
xref: MSH:D018589
xref: SNOMEDCT_US:196753007
xref: SNOMEDCT_US:235675006
xref: UMLS:C0152020
is_a: HP:0002577 ! Abnormality of the stomach
is_a: HP:0011804 ! Abnormal muscle physiology
[Term]
id: HP:0002564
name: obsolete Malformation of the heart and great vessels
is_obsolete: true
replaced_by: HP:0030680
There might be more errors hidden in your code, but one problem is indeed that your hpo_data doesn't have default values.
Calling hpo_data[hpo_id]["replaced_by"] = line[13..22] fails if hpo_id hasn't been initialized.
You could define hpo_data like this:
hpo_data = Hash.new { |hash, key| hash[key] = {'parents' => [] } }
and remove
hpo_data = Hash.new() #Hash map where i want to store all IDs
and
hpo_data[hpo_id] = Hash.new() #Setting up hash map for that ID
hpo_data[hpo_id]["parents"] = Array.new()
Any time you call hpo_data[hpo_id], it will be automatically defined to {"parents"=>[]}.
As an example:
hpo_data = Hash.new { |hash, key| hash[key] = {'parents' => [] } }
# => {}
hpo_data[1234]
# => {"parents"=>[]}
hpo_data[1234]["parents"] << 6
# => [6]
hpo_data
# => {1234=>{"parents"=>[6]}}
hpo_data[42]["is_obsolete"] = true
# => true
hpo_data
# => {1234=>{"parents"=>[6]}, 42=>{"parents"=>[], "is_obsolete"=>true}}

Set optional parameter put the others with a default value at nil

I face a weirb problem with optionals parameters in ruby.
This is my code :
def foo options={:test => true}
puts options[:test]
end
foo # => puts true
foo :lol => 42 # => puts nil
I can not figure out why the second call puts nil.
Is seems that putting an other parameter set :test to nil.
Thanks.
It happens because if it is a default parameter, passing a hash parameter will completely overwrite it (ie. it sets options = {:lol => 42}), so the options[:test] key no longer exists.
To give particular hash keys default values, try:
def foo options={}
options = {:test => true}.merge options
puts options[:test]
end
In this case, we merge a hash with default values for certain keys ({:test => true}), with another hash (containing the key=>values in the argument). If a key occurs in both hash objects, the value in the hash passed to the merge function will take precedence.

Ruby multiple named arguments

I'm very new to ruby and I'm trying to write a web application using the rails framework. Through reading I've seen methods being called like this:
some_method "first argument", :other_arg => "value1", :other_arg2 => "value2"
Where you can pass an unlimited number of arguments.
How do you create a method in ruby that can be used in this way?
Thanks for the help.
That works because Ruby assumes the values are a Hash if you call the method that way.
Here is how you would define one:
def my_method( value, hash = {})
# value is requred
# hash can really contain any number of key/value pairs
end
And you could call it like this:
my_method('nice', {:first => true, :second => false})
Or
my_method('nice', :first => true, :second => false )
This is actually just a method that has a hash as an argument, below is a code example.
def funcUsingHash(input)
input.each { |k,v|
puts "%s=%s" % [k, v]
}
end
funcUsingHash :a => 1, :b => 2, :c => 3
Find out more about hashes here http://www-users.math.umd.edu/~dcarrera/ruby/0.3/chp_03/hashes.html
Maybe that *args can help you?
def meh(a, *args)
puts a
args.each {|x| y x}
end
Result of this method is
irb(main):005:0> meh(1,2,3,4)
1
--- 2
--- 3
--- 4
=> [2, 3, 4]
But i prefer this method in my scripts.
You can make the last argument be an optional hash to achieve that:
def some_method(x, options = {})
# access options[:other_arg], etc.
end
However, in Ruby 2.0.0, it is generally better to use a new feature called keyword arguments:
def some_method(x, other_arg: "value1", other_arg2: "value2")
# access other_arg, etc.
end
The advantages of using the new syntax instead of using a hash are:
It is less typing to access the optional arguments (e.g. other_arg instead of options[:other_arg]).
It is easy to specify a default value for the optional arguments.
Ruby will automatically detect if an invalid argument name was used by the caller and throw an exception.
One disadvantage of the new syntax is that you cannot (as far as I know) easily send all of the keyword arguments to some other method, because you don't have a hash object that represents them.
Thankfully, the syntax for calling these two types of methods is the same, so you can change from one to the other without breaking good code.

How do I convert a String object into a Hash object?

I have a string which looks like a hash:
"{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }"
How do I get a Hash out of it? like:
{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }
The string can have any depth of nesting. It has all the properties how a valid Hash is typed in Ruby.
For different string, you can do it without using dangerous eval method:
hash_as_string = "{\"0\"=>{\"answer\"=>\"1\", \"value\"=>\"No\"}, \"1\"=>{\"answer\"=>\"2\", \"value\"=>\"Yes\"}, \"2\"=>{\"answer\"=>\"3\", \"value\"=>\"No\"}, \"3\"=>{\"answer\"=>\"4\", \"value\"=>\"1\"}, \"4\"=>{\"value\"=>\"2\"}, \"5\"=>{\"value\"=>\"3\"}, \"6\"=>{\"value\"=>\"4\"}}"
JSON.parse hash_as_string.gsub('=>', ':')
Quick and dirty method would be
eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }")
But it has severe security implications.
It executes whatever it is passed, you must be 110% sure (as in, at least no user input anywhere along the way) it would contain only properly formed hashes or unexpected bugs/horrible creatures from outer space might start popping up.
The string created by calling Hash#inspect can be turned back into a hash by calling eval on it. However, this requires the same to be true of all of the objects in the hash.
If I start with the hash {:a => Object.new}, then its string representation is "{:a=>#<Object:0x7f66b65cf4d0>}", and I can't use eval to turn it back into a hash because #<Object:0x7f66b65cf4d0> isn't valid Ruby syntax.
However, if all that's in the hash is strings, symbols, numbers, and arrays, it should work, because those have string representations that are valid Ruby syntax.
I had the same problem. I was storing a hash in Redis. When retrieving that hash, it was a string. I didn't want to call eval(str) because of security concerns. My solution was to save the hash as a json string instead of a ruby hash string. If you have the option, using json is easier.
redis.set(key, ruby_hash.to_json)
JSON.parse(redis.get(key))
TL;DR: use to_json and JSON.parse
Maybe YAML.load ?
The solutions so far cover some cases but miss some (see below). Here's my attempt at a more thorough (safe) conversion. I know of one corner case which this solution doesn't handle which is single character symbols made up of odd, but allowed characters. For example {:> => :<} is a valid ruby hash.
I put this code up on github as well. This code starts with a test string to exercise all the conversions
require 'json'
# Example ruby hash string which exercises all of the permutations of position and type
# See http://json.org/
ruby_hash_text='{"alpha"=>{"first second > third"=>"first second > third", "after comma > foo"=>:symbolvalue, "another after comma > foo"=>10}, "bravo"=>{:symbol=>:symbolvalue, :aftercomma=>10, :anotheraftercomma=>"first second > third"}, "charlie"=>{1=>10, 2=>"first second > third", 3=>:symbolvalue}, "delta"=>["first second > third", "after comma > foo"], "echo"=>[:symbol, :aftercomma], "foxtrot"=>[1, 2]}'
puts ruby_hash_text
# Transform object string symbols to quoted strings
ruby_hash_text.gsub!(/([{,]\s*):([^>\s]+)\s*=>/, '\1"\2"=>')
# Transform object string numbers to quoted strings
ruby_hash_text.gsub!(/([{,]\s*)([0-9]+\.?[0-9]*)\s*=>/, '\1"\2"=>')
# Transform object value symbols to quotes strings
ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>\s*:([^,}\s]+\s*)/, '\1\2=>"\3"')
# Transform array value symbols to quotes strings
ruby_hash_text.gsub!(/([\[,]\s*):([^,\]\s]+)/, '\1"\2"')
# Transform object string object value delimiter to colon delimiter
ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>/, '\1\2:')
puts ruby_hash_text
puts JSON.parse(ruby_hash_text)
Here are some notes on the other solutions here
#Ken Bloom and #Toms Mikoss's solutions use eval which is too scary for me (as Toms rightly points out).
#zolter's solution works if your hash has no symbols or numeric keys.
#jackquack's solution works if there are no quoted strings mixed in with the symbols.
#Eugene's solution works if your symbols don't use all the allowed characters (symbol literals have a broader set of allowed characters).
#Pablo's solution works as long as you don't have a mix of symbols and quoted strings.
This short little snippet will do it, but I can't see it working with a nested hash. I think it's pretty cute though
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)
Steps
1. I eliminate the '{','}' and the ':'
2. I split upon the string wherever it finds a ','
3. I split each of the substrings that were created with the split, whenever it finds a '=>'. Then, I create a hash with the two sides of the hash I just split apart.
4. I am left with an array of hashes which I then merge together.
EXAMPLE INPUT: "{:user_id=>11, :blog_id=>2, :comment_id=>1}"
RESULT OUTPUT: {"user_id"=>"11", "blog_id"=>"2", "comment_id"=>"1"}
I prefer to abuse ActiveSupport::JSON. Their approach is to convert the hash to yaml and then load it. Unfortunately the conversion to yaml isn't simple and you'd probably want to borrow it from AS if you don't have AS in your project already.
We also have to convert any symbols into regular string-keys as symbols aren't appropriate in JSON.
However, its unable to handle hashes that have a date string in them (our date strings end up not being surrounded by strings, which is where the big issue comes in):
string = '{'last_request_at' : 2011-12-28 23:00:00 UTC }'
ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\\1').gsub('=>', ' : '))
Would result in an invalid JSON string error when it tries to parse the date value.
Would love any suggestions on how to handle this case
works in rails 4.1 and support symbols without quotes {:a => 'b'}
just add this to initializers folder:
class String
def to_hash_object
JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\\1"').gsub('=>', ': ')).symbolize_keys
end
end
Please consider this solution. Library+spec:
File: lib/ext/hash/from_string.rb:
require "json"
module Ext
module Hash
module ClassMethods
# Build a new object from string representation.
#
# from_string('{"name"=>"Joe"}')
#
# #param s [String]
# #return [Hash]
def from_string(s)
s.gsub!(/(?<!\\)"=>nil/, '":null')
s.gsub!(/(?<!\\)"=>/, '":')
JSON.parse(s)
end
end
end
end
class Hash #:nodoc:
extend Ext::Hash::ClassMethods
end
File: spec/lib/ext/hash/from_string_spec.rb:
require "ext/hash/from_string"
describe "Hash.from_string" do
it "generally works" do
[
# Basic cases.
['{"x"=>"y"}', {"x" => "y"}],
['{"is"=>true}', {"is" => true}],
['{"is"=>false}', {"is" => false}],
['{"is"=>nil}', {"is" => nil}],
['{"a"=>{"b"=>"c","ar":[1,2]}}', {"a" => {"b" => "c", "ar" => [1, 2]}}],
['{"id"=>34030, "users"=>[14105]}', {"id" => 34030, "users" => [14105]}],
# Tricky cases.
['{"data"=>"{\"x\"=>\"y\"}"}', {"data" => "{\"x\"=>\"y\"}"}], # Value is a `Hash#inspect` string which must be preserved.
].each do |input, expected|
output = Hash.from_string(input)
expect([input, output]).to eq [input, expected]
end
end # it
end
Here is a method using whitequark/parser which is safer than both gsub and eval methods.
It makes the following assumptions about the data:
Hash keys are assumed to be a string, symbol, or integer.
Hash values are assumed to be a string, symbol, integer, boolean, nil, array, or a hash.
# frozen_string_literal: true
require 'parser/current'
class HashParser
# Type error is used to handle unexpected types when parsing stringified hashes.
class TypeError < ::StandardError
attr_reader :message, :type
def initialize(message, type)
#message = message
#type = type
end
end
def hash_from_s(str_hash)
ast = Parser::CurrentRuby.parse(str_hash)
unless ast.type == :hash
puts "expected data to be a hash but got #{ast.type}"
return
end
parse_hash(ast)
rescue Parser::SyntaxError => e
puts "error parsing hash: #{e.message}"
rescue TypeError => e
puts "unexpected type (#{e.type}) encountered while parsing: #{e.message}"
end
private
def parse_hash(hash)
out = {}
hash.children.each do |node|
unless node.type == :pair
raise TypeError.new("expected child of hash to be a `pair`", node.type)
end
key, value = node.children
key = parse_key(key)
value = parse_value(value)
out[key] = value
end
out
end
def parse_key(key)
case key.type
when :sym, :str, :int
key.children.first
else
raise TypeError.new("expected key to be either symbol, string, or integer", key.type)
end
end
def parse_value(value)
case value.type
when :sym, :str, :int
value.children.first
when :true
true
when :false
false
when :nil
nil
when :array
value.children.map { |c| parse_value(c) }
when :hash
parse_hash(value)
else
raise TypeError.new("value of a pair was an unexpected type", value.type)
end
end
end
and here are some rspec tests verifying that it works as expected:
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe HashParser do
describe '#hash_from_s' do
subject { described_class.new.hash_from_s(input) }
context 'when input contains forbidden types' do
where(:input) do
[
'def foo; "bar"; end',
'`cat somefile`',
'exec("cat /etc/passwd")',
'{:key=>Env.fetch("SOME_VAR")}',
'{:key=>{:another_key=>Env.fetch("SOME_VAR")}}',
'{"key"=>"value: #{send}"}'
]
end
with_them do
it 'returns nil' do
expect(subject).to be_nil
end
end
end
context 'when input cannot be parsed' do
let(:input) { "{" }
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'with valid input' do
using RSpec::Parameterized::TableSyntax
where(:input, :expected) do
'{}' | {}
'{"bool"=>true}' | { 'bool' => true }
'{"bool"=>false}' | { 'bool' => false }
'{"nil"=>nil}' | { 'nil' => nil }
'{"array"=>[1, "foo", nil]}' | { 'array' => [1, "foo", nil] }
'{foo: :bar}' | { foo: :bar }
'{foo: {bar: "bin"}}' | { foo: { bar: "bin" } }
end
with_them do
specify { expect(subject).to eq(expected) }
end
end
end
end
I built a gem hash_parser that first checks if a hash is safe or not using ruby_parser gem. Only then, it applies the eval.
You can use it as
require 'hash_parser'
# this executes successfully
a = "{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' },
:key_b => { :key_1b => 'value_1b' } }"
p HashParser.new.safe_load(a)
# this throws a HashParser::BadHash exception
a = "{ :key_a => system('ls') }"
p HashParser.new.safe_load(a)
The tests in https://github.com/bibstha/ruby_hash_parser/blob/master/test/test_hash_parser.rb give you more examples of the things I've tested to make sure eval is safe.
This method works for one level deep hash
def convert_to_hash(str)
return unless str.is_a?(String)
hash_arg = str.gsub(/[^'"\w\d]/, ' ').squish.split.map { |x| x.gsub(/['"]/, '') }
Hash[*hash_arg]
end
example
> convert_to_hash("{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }")
=> {"key_a"=>"value_a", "key_b"=>"value_b", "key_c"=>""}
I came to this question after writing a one-liner for this purpose, so I share my code in case it helps somebody. Works for a string with only one level depth and possible empty values (but not nil), like:
"{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }"
The code is:
the_string = '...'
the_hash = Hash.new
the_string[1..-2].split(/, /).each {|entry| entryMap=entry.split(/=>/); value_str = entryMap[1]; the_hash[entryMap[0].strip[1..-1].to_sym] = value_str.nil? ? "" : value_str.strip[1..-2]}
Ran across a similar issue that needed to use the eval().
My situation, I was pulling some data from an API and writing it to a file locally. Then being able to pull the data from the file and use the Hash.
I used IO.read() to read the contents of the file into a variable. In this case IO.read() creates it as a String.
Then used eval() to convert the string into a Hash.
read_handler = IO.read("Path/To/File.json")
puts read_handler.kind_of?(String) # Returns TRUE
a = eval(read_handler)
puts a.kind_of?(Hash) # Returns TRUE
puts a["Enter Hash Here"] # Returns Key => Values
puts a["Enter Hash Here"].length # Returns number of key value pairs
puts a["Enter Hash Here"]["Enter Key Here"] # Returns associated value
Also just to mention that IO is an ancestor of File. So you can also use File.read instead if you wanted.
I had a similar issue when trying to convert a string to a hash in Ruby.
The result from my computations was this:
{
"coord":{"lon":24.7535,"lat":59.437},
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
"base":"stations",
"main":{"temp":283.34,"feels_like":281.8,"temp_min":282.33,"temp_max":283.34,"pressure":1021,"humidity":53},
"visibility":10000,
"wind":{"speed":3.09,"deg":310},
"clouds":{"all":75},
"dt":1652808506,
"sys":{"type":1,"id":1330,"country":"EE","sunrise":1652751796,"sunset":1652813502},
"timezone":10800,"id":588409,"name":"Tallinn","cod":200
}
I checked the type value and confirmed that it was of the String type using the command below:
result =
{
"coord":{"lon":24.7535,"lat":59.437},
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
"base":"stations",
"main":{"temp":283.34,"feels_like":281.8,"temp_min":282.33,"temp_max":283.34,"pressure":1021,"humidity":53},
"visibility":10000,
"wind":{"speed":3.09,"deg":310},
"clouds":{"all":75},
"dt":1652808506,
"sys":{"type":1,"id":1330,"country":"EE","sunrise":1652751796,"sunset":1652813502},
"timezone":10800,"id":588409,"name":"Tallinn","cod":200
}
puts result.instance_of? String
puts result.instance_of? Hash
Here's how I solved it:
All I had to do was run the command below to convert it from a String to a Hash:
result_new = JSON.parse(result, symbolize_names: true)
And then checked the type value again using the commands below:
puts result_new.instance_of? String
puts result_new.instance_of? Hash
This time it returned true for the Hash

Resources