Convert a hash into a struct

Ruby

Ruby Problem Overview


How can I convert a hash into a struct in ruby?

Given this:

h = { :a => 1, :b => 2 }

I want a struct such that:

s.a == 1
s.b == 2

Ruby Solutions


Solution 1 - Ruby

If it doesn't specifically have to be a Struct and instead can be an OpenStruct:

pry(main)> require 'ostruct'
pry(main)> s = OpenStruct.new(h)
=> #<OpenStruct a=1, b=2>
pry(main)> puts s.a, s.b
1
2

Solution 2 - Ruby

If you already have a struct defined, and you want to instantiate an instance with a hash:

Person = Struct.new(:first_name, :last_name, :age)

person_hash = { first_name: "Foo", last_name: "Bar", age: 29 }

person = Person.new(*person_hash.values_at(*Person.members))

=> #<struct Person first_name="Foo", last_name="Bar", age=29>

Solution 3 - Ruby

Since Hash key order is guaranteed in Ruby 1.9+:

Struct.new(*h.keys).new(*h.values)

Solution 4 - Ruby

This is based on @elado's answer above, but using the keyword_init value (Struct Documentation)

You could simply do this:

Person = Struct.new(:first_name, :last_name, :age, keyword_init: true)

person_hash = { first_name: "Foo", last_name: "Bar", age: 29 }

person = Person.new(person_hash)

=> #<struct Person first_name="Foo", last_name="Bar", age=29>

Solution 5 - Ruby

The following creates a struct from a hash in a reliable way (since hash order is not guaranteed in ruby):

s = Struct.new(*(k = h.keys)).new(*h.values_at(*k))

Solution 6 - Ruby

Having Hash#to_struct is quite practical:

class Hash
  def to_struct
    Struct.new(*keys).new(*values)
  end
end

And some examples:

>> { a: 1, b: 2 }.to_struct
=> #<struct a=1, b=2>
>> { a: 1, b: 2 }.to_struct.a
=> 1
>> { a: 1, b: 2 }.to_struct.b
=> 2
>> { a: 1, b: 2 }.to_struct.c
NoMethodError: undefined method `c` for #<struct a=1, b=2>

Deep to_struct that works with arrays:

class Array
  def to_struct
    map { |value| value.respond_to?(:to_struct) ? value.to_struct : value }
  end
end

class Hash
  def to_struct
    Struct.new(*keys).new(*values.to_struct)
  end
end

Solution 7 - Ruby

Here's an example to map the values to the proper order of the Struct:

require 'securerandom'

Message = Struct.new(:to, :from, :message, :invitee)

message_params = {from: "[email protected]", to: "[email protected]",
        invitee: SecureRandom.uuid, message: "hello"}

if Message.members.sort == message_params.keys.sort
  # Do something with the return struct object here
  Message.new *Message.members.map {|k| message_params[k] } 
else
  raise "Invalid keys for Message"
end

Solution 8 - Ruby

This gives a clean plain read-only object, similar to a ruby Struct but with deep conversion and extra to_h method to get struct at any point as Hash.

Example

foo = {a:{b:{c:123}}}.to_struct
foo.a.b.c # 123
foo.a.to_h # {b:{c:123}}

Ruby code

class Hash
  def to_struct
    Class.new.tap do |c|
      c.define_singleton_method(:to_h) do
        m_list = methods(false) - [:to_h]
        m_list.inject({}) do |h, m|
          h[m] = send(m)
          h[m] = h[m].to_h if h[m].class == Class
          h
        end
      end

      each do |k, v|
        v = v.to_struct if v.class == Hash
        c.define_singleton_method(k) { v }
      end
    end
  end
end

Not exactly the answer to a question (not a ruby Struct object), but I needed just this while looking for an answer, so I will just post the answer here.

Solution 9 - Ruby

If you need a recursive version, here's a neat hack/solution

a_hash = {a: {b: {c: 'x'}}}
structs_inside_structs = JSON.parse(
  a_hash.to_json, object_class: OpenStruct
)
# => #<OpenStruct a=#<OpenStruct b=#<OpenStruct c="x">>>
structs_inside_structs.a.b.c
# => "x"

Solution 10 - Ruby

You can convert from Hash to Struct using the following code:

Struct.new(*my_hash.keys.map(&:to_sym)).new(*my_hash.values)

Ensure you convert all keys to symbols, as it will error on String keys, NameError: identifier my_key needs to be constant

I personally recommend adding a monkey patch to the Hash class because this is such a powerful action

# config/initializers/core_extensions.rb

Hash.class_eval do
  def to_struct
    Struct.new(*keys.map(&:to_sym)).new(*values)
  end
end

Solution 11 - Ruby

TL;DR;

use OpenStruct.new(hash)

New Better way

hash = {"Name" => "Taimoor", "id" => "222", "SomeKey" => "Some value", "attributes" => {"type" => 'User', 'role' => 'manager'}}
OpenStruct.new(hash)

This will only convert 1st level of hash to struct. To convert nested attributes hash to struct I did this

hash.attributes = OpenStruct.new(hash.attributes)

OLD Way

I had a hash with string keys

{"Name" => "Taimoor", "id" => "222", "SomeKey" => "Some value"}

So I need to first convert keys to symbols hash.keys.map(&:to_sym) and to access those keys in the original hash, I used the hash.with_indifferent_access method on the hash.

def hash_to_struct(hash)
    Struct.new(*(k = hash.keys.map(&:to_sym)))
        .new(*hash.with_indifferent_access.values_at(*k))
end

Now it will work for both symbol and string type keys of the hash.

Note: This will convert the hash to struct at one level only. For nested hash, you need to call this method on each level of nesting.

Solution 12 - Ruby

require 'ds_hash'

data = {a: {b: 123 }}.to_struct

data.a.b == 123       # true
data.a   == {b: 123 } # true

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
QuestionalfView Question on Stackoverflow
Solution 1 - RubyDave NewtonView Answer on Stackoverflow
Solution 2 - RubyeladoView Answer on Stackoverflow
Solution 3 - RubyPhrogzView Answer on Stackoverflow
Solution 4 - Ruby8bitheroView Answer on Stackoverflow
Solution 5 - RubyalfView Answer on Stackoverflow
Solution 6 - RubyDorianView Answer on Stackoverflow
Solution 7 - Ruby6ft DanView Answer on Stackoverflow
Solution 8 - RubyDino ReicView Answer on Stackoverflow
Solution 9 - RubyGrzegorzView Answer on Stackoverflow
Solution 10 - RubyWeston GangerView Answer on Stackoverflow
Solution 11 - RubyTaimoor ChangaizView Answer on Stackoverflow
Solution 12 - Rubynardele salomonView Answer on Stackoverflow