what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?

Ruby on-RailsRubyJson

Ruby on-Rails Problem Overview


I am wondering what is the best way to convert a json formatted key value pair to ruby hash with symbol as key: example:

{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==> 
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }

Is there a helper method can do this?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

using the json gem when parsing the json string you can pass in the symbolize_names option. See here: http://flori.github.com/json/doc/index.html (look under parse)

eg:

>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"} 

Solution 2 - Ruby on-Rails

Leventix, thank you for your answer.

The Marshal.load(Marshal.dump(h)) method probably has the most integrity of the various methods because it preserves the original key types recursively.

This is important in case you have a nested hash with a mix of string and symbol keys and you want to preserve that mix upon decode (for instance, this could happen if your hash contains your own custom objects in addition to highly complex/nested third-party objects whose keys you cannot manipulate/convert for whatever reason, like a project time constraint).

E.g.:

h = {
      :youtube => {
                    :search   => 'daffy',                 # nested symbol key
                    'history' => ['goofy', 'mickey']      # nested string key
                  }
    }

Method 1: JSON.parse - symbolizes all keys recursively => Does not preserve original mix

JSON.parse( h.to_json, {:symbolize_names => true} )
  => { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } } 

Method 2: ActiveSupport::JSON.decode - symbolizes top-level keys only => Does not preserve original mix

ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
  => { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }

Method 3: Marshal.load - preserves original string/symbol mix in the nested keys. PERFECT!

Marshal.load( Marshal.dump(h) )
  => { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }

Unless there is a drawback that I'm unaware of, I'd think Method 3 is the way to go.

Cheers

Solution 3 - Ruby on-Rails

There isn't anything built in to do the trick, but it's not too hard to write the code to do it using the JSON gem. There is a symbolize_keys method built into Rails if you're using that, but that doesn't symbolize keys recursively like you need.

require 'json'

def json_to_sym_hash(json)
  json.gsub!('\'', '"')
  parsed = JSON.parse(json)
  symbolize_keys(parsed)
end

def symbolize_keys(hash)
  hash.inject({}){|new_hash, key_value|
    key, value = key_value
    value = symbolize_keys(value) if value.is_a?(Hash)
    new_hash[key.to_sym] = value
    new_hash
  }
end

As Leventix said, the JSON gem only handles double quoted strings (which is technically correct - JSON should be formatted with double quotes). This bit of code will clean that up before trying to parse it.

Solution 4 - Ruby on-Rails

Recursive method:

require 'json'

def JSON.parse(source, opts = {})
  r = JSON.parser.new(source, opts).parse
  r = keys_to_symbol(r) if opts[:symbolize_names]
  return r
end

def keys_to_symbol(h)
  new_hash = {}
  h.each do |k,v|
    if v.class == String || v.class == Fixnum || v.class == Float
      new_hash[k.to_sym] = v
    elsif v.class == Hash
      new_hash[k.to_sym] = keys_to_symbol(v)
    elsif v.class == Array
      new_hash[k.to_sym] = keys_to_symbol_array(v)
    else
      raise ArgumentError, "Type not supported: #{v.class}"
    end
  end
  return new_hash
end

def keys_to_symbol_array(array)
  new_array = []
  array.each do |i|
    if i.class == Hash
      new_array << keys_to_symbol(i)
    elsif i.class == Array
      new_array << keys_to_symbol_array(i)
    else
      new_array << i
    end
  end
  return new_array
end

Solution 5 - Ruby on-Rails

Of course, there is a json gem, but that handles only double quotes.

Solution 6 - Ruby on-Rails

Another way to handle this is to use YAML serialization/deserialization, which also preserves the format of the key:

YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml) 
=> {:test=>{"test"=>{":test"=>5}}}

Benefit of this approach it seems like a format that is better suited for REST services...

Solution 7 - Ruby on-Rails

The most convenient way is by using the nice_hash gem: https://github.com/MarioRuiz/nice_hash

require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"

# on my_hash will have the json as a hash
my_hash = my_str.json

# or you can filter and get what you want
vals = my_str.json(:age, :city)

# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]

Solution 8 - Ruby on-Rails

If you think you might need both string and symbol keys:

JSON.parse(json_string).with_indifferent_access

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionez.View Question on Stackoverflow
Solution 1 - Ruby on-RailsjaiView Answer on Stackoverflow
Solution 2 - Ruby on-RailsfrankView Answer on Stackoverflow
Solution 3 - Ruby on-RailsmadlepView Answer on Stackoverflow
Solution 4 - Ruby on-RailsOel RocView Answer on Stackoverflow
Solution 5 - Ruby on-RailsLeventixView Answer on Stackoverflow
Solution 6 - Ruby on-Railsbert bruynoogheView Answer on Stackoverflow
Solution 7 - Ruby on-RailsMario RuizView Answer on Stackoverflow
Solution 8 - Ruby on-RailsthisismydesignView Answer on Stackoverflow