How do I deserialize YAML documents from external sources and have full access on class members?
In Ruby any object can be transferred, i.e. serialized, to a YAML document by saving the output of the "to_yaml
" method to a file. Afterwards, this YAML file can be read again, i.e. deserialized, by using the YAML::load
method. Moreover, one has full access on all members of the underlying class/object.
All of this is valid as long I'm using Ruby as a single platform. Once I serialize objects in Java and deserialize them under Ruby, I cannot access the object any more because of a NoMethodError
exception. This is due to to the way objects/local data types are named under different systems.
Given a Ruby class "Car":
# A simple class describing a car
#
class Car
attr :brand, :horsepower, :color, :extra_equipment
def initialize(brand, horsepower, color, extra_equipment)
@brand = brand
@horsepower = horsepower
@color = color
@extra_equipment = extra_equipment
end
end
Creating a simple instance:
# creating new instance of class 'Car' ...
porsche = Car.new("Porsche", 180, "red", ["sun roof", "air conditioning"])
Calling porsche.to_yaml
results in the following output:
--- !ruby/object:Car
brand: Porsche
color: red
extra_equipment:
- sun roof
- air conditioning
horsepower: 180
I test deserialization by loading the YAML output:
# reading existing yaml file from file system
sample_car = YAML::load(File.open("sample.yaml"))
puts sample_car.brand # returns "Porsche"
This works as expected, but now let's assume the YAML document was produced by a different system and lacks any reference to Ruby, although having a yaml-conform o开发者_Python百科bject description, "!Car
", instead of "!ruby/object:Car
":
--- !Car
brand: Porsche
color: red
extra_equipment:
- sun roof
- air conditioning
horsepower: 180
This code:
# reading existing yaml file from file system
sample_car = YAML::load(File.open("sample.yaml"))
puts sample_car.brand # returns "Porsche"
returns this exception:
/path/yaml_to_object_converter.rb.rb:27:in `<main>':
undefined method `brand' for #<YAML::DomainType:0x9752bec> (NoMethodError)
Is there a way to deal with objects defined in "external" YAML documents?
For me sample_car
in the IRB shell evaluates to:
=> #<Syck::DomainType:0x234df80 @domain="yaml.org,2002", @type_id="Car", @value={"brand"=>"Porsche", "color"=>"red", "extra_equipment"=>["sun roof", "air conditioning"], "horsepower"=>180}>
Then I issued sample_car.value
:
=> {"brand"=>"Porsche", "color"=>"red", "extra_equipment"=>["sun roof", "air conditioning"], "horsepower"=>180}
Which is a Hash. This means, that you can construct your Car object by adding a class method to Car
like so:
def self.from_hash(h)
Car.new(h["brand"], h["horsepower"], h["color"], h["extra_equipment"])
end
Then I tried it:
porsche_clone = Car.from_hash(sample_car.value)
Which returned:
=> #<Car:0x236eef0 @brand="Porsche", @horsepower=180, @color="red", @extra_equipment=["sun roof", "air conditioning"]>
That's the ugliest way of doing it. There might be others. =)
EDIT (19-May-2011): BTW, Just figured a lot easier way:
def from_hash(o,h)
h.each { |k,v|
o.send((k+"=").to_sym, v)
}
o
end
For this to work in your case, your constructor must not require parameters. Then you can simply do:
foreign_car = from_hash(Car.new, YAML::load(File.open("foreign_car.yaml")).value)
puts foreign_car.inspect
...which gives you:
#<Car:0x2394b70 @brand="Porsche", @color="red", @extra_equipment=["sun roof", "air conditioning"], @horsepower=180>
精彩评论