Create nested object from YAML to access attribute

2019-06-22 05:37发布

I am completely new to ruby. I have to parse a YAML file to construct an object

YAML File

projects:
  - name: Project1
    developers:
      - name: Dev1
        certifications:
          - name: cert1
      - name: Dev2
        certifications:
          - name: cert2
  - name: Project2
    developers:
      - name: Dev1
        certifications:
          - name: cert3
      - name: Dev2
        certifications:
          - name: cert4

I want to create an object from this YAML for which I wrote the following code in Ruby

require 'yaml'
object = YAML.load(File.read('./file.yaml'))

I can successfully access the attributes of this object with [] For e.g.

puts object[projects].first[developers].last[certifications].first[name]
# prints ABC

However, I want to access the attributes via method calls

For e.g.

puts object.projects.first.developers.last.certifications.first.name
# should print ABC

Is there any way to construct such an object whose attributes can be accessed in the (dots) way mentioned above? I have read about OpenStruct and hashugar. I also want to avoid usage of third party gems

2条回答
放荡不羁爱自由
2楼-- · 2019-06-22 05:52

Nice answer from Xavier, but it can be shorter, just require yaml, json and ostruct and parse your YAML, convert it to JSON, parse it in an Openstruct (a Struct would also be possible) like this

object = JSON.parse(YAML.load(yaml).to_json, object_class: OpenStruct)

To load your YAML from a file it's

object = JSON.parse(YAML::load_file("./test.yaml").to_json, object_class: OpenStruct)

This gives

object
=>#<OpenStruct projects=[#<OpenStruct name="Project1", developers=[#<OpenStruct name="Dev1", certifications=[#<OpenStruct name="cert1">]>, #<OpenStruct name="Dev2", certifications=[#<OpenStruct name="cert2">]>]>, #<OpenStruct name="Project2", developers=[#<OpenStruct name="Dev1", certifications=[#<OpenStruct name="cert3">]>, #<OpenStruct name="Dev2", certifications=[#<OpenStruct name="cert4">]>]>]>
object.projects.first.developers.last.certifications.first.name 
=>cert2

I use this for loading configurations from file, a Yaml is easily to maintain and in your code it's easier to use than a configuration in Hash.

Don't do this for repetitive tasks.

查看更多
在下西门庆
3楼-- · 2019-06-22 06:14

If you are just experimenting, there is a quick and dirty way to do this:

class Hash
  def method_missing(name, *args)
    send(:[], name.to_s, *args)
  end
end

I wouldn't use that in production code though, since both method_missing and monkey-patching are usually recipes for trouble down the road.

A better solution is to recursively traverse the data-structure and replace hashes with openstructs.

require 'ostruct'
def to_ostruct(object)
  case object
  when Hash
    OpenStruct.new(Hash[object.map {|k, v| [k, to_ostruct(v)] }])
  when Array
    object.map {|x| to_ostruct(x) }
  else
    object
  end
end

puts to_ostruct(object).projects.first.developers.last.certifications.first.name

Note that there are potentially performance issues with either approach if you are doing them a lot - if your application is time-sensitive make sure you benchmark them! This probably isn't relevant to you though.

查看更多
登录 后发表回答