【发布时间】:2012-08-14 23:16:44
【问题描述】:
如何在 ruby 中将哈希转换为结构?
鉴于此:
h = { :a => 1, :b => 2 }
我想要一个这样的结构:
s.a == 1
s.b == 2
【问题讨论】:
标签: ruby
如何在 ruby 中将哈希转换为结构?
鉴于此:
h = { :a => 1, :b => 2 }
我想要一个这样的结构:
s.a == 1
s.b == 2
【问题讨论】:
标签: ruby
这基于上面@elado 的回答,但使用keyword_init 值(Struct Documentation)
你可以这样做:
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>
【讨论】:
keyword_init: true 仅在 Ruby 2.5 中引入,而 2.4 仍在支持范围内。
以下内容以可靠的方式从哈希创建结构体(因为 ruby 不保证哈希顺序):
s = Struct.new(*(k = h.keys)).new(*h.values_at(*k))
【讨论】:
如果不是特别必须是Struct,而是可以是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
【讨论】:
pry(main)> puts s.a, s.b 或者第 2 行应该是 pry(main)> o = OpenStruct.new(h)
如果您需要递归版本,这里有一个简洁的 hack/解决方案
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"
【讨论】:
由于 Ruby 1.9+ 中保证了哈希键顺序:
Struct.new(*h.keys).new(*h.values)
【讨论】:
Struct.new(*h.keys) raises: NameError: identifier my_key needs to be constant
Struct.new(*h.keys.map(&:to_sym)).new(*h.values)
如果你已经定义了一个结构,并且你想用哈希实例化一个实例:
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>
【讨论】:
拥有Hash#to_struct 非常实用:
class Hash
def to_struct
Struct.new(*keys).new(*values)
end
end
还有一些例子:
>> { 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>
适用于数组的深层to_struct:
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
【讨论】:
['name'],需要符号化keys。
这提供了一个干净的纯只读对象,类似于 ruby 结构,但具有深度转换和额外的to_h 方法,可以在任何时候将结构作为哈希。
例子
foo = {a:{b:{c:123}}}.to_struct
foo.a.b.c # 123
foo.a.to_h # {b:{c:123}}
Ruby 代码
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
不完全是问题的答案(不是 ruby Struct 对象),但我在寻找答案时只需要这个,所以我将在此处发布答案。
【讨论】:
这是一个将值映射到 Struct 正确顺序的示例:
require 'securerandom'
Message = Struct.new(:to, :from, :message, :invitee)
message_params = {from: "my@email.address", to: "your@email.address",
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
【讨论】:
您可以使用以下代码从 Hash 转换为 Struct:
Struct.new(*my_hash.keys.map(&:to_sym)).new(*my_hash.values)
确保将所有键转换为符号,因为它会在字符串键上出错,NameError: identifier my_key needs to be constant
我个人建议在 Hash 类中添加一个猴子补丁,因为这是一个非常强大的操作
# config/initializers/core_extensions.rb
Hash.class_eval do
def to_struct
Struct.new(*keys.map(&:to_sym)).new(*values)
end
end
【讨论】:
TL;DR;
使用 OpenStruct.new(hash)
新的更好的方法
hash = {"Name" => "Taimoor", "id" => "222", "SomeKey" => "Some value", "attributes" => {"type" => 'User', 'role' => 'manager'}}
OpenStruct.new(hash)
这只会将第一级散列转换为结构。要将嵌套属性哈希转换为结构,我这样做了
hash.attributes = OpenStruct.new(hash.attributes)
老路
我有一个带有字符串键的哈希
{"Name" => "Taimoor", "id" => "222", "SomeKey" => "Some value"}
所以我需要首先将键转换为符号hash.keys.map(&:to_sym) 并访问原始哈希中的这些键,我在哈希上使用了hash.with_indifferent_access 方法。
def hash_to_struct(hash)
Struct.new(*(k = hash.keys.map(&:to_sym)))
.new(*hash.with_indifferent_access.values_at(*k))
end
现在它适用于哈希的符号和字符串类型键。
注意:这只会将哈希转换为一级结构。对于嵌套散列,需要在每一层嵌套上调用该方法。
【讨论】:
require 'ds_hash'
data = {a: {b: 123 }}.to_struct
data.a.b == 123 # true
data.a == {b: 123 } # true
【讨论】: