Как мне десериализовать документы YAML из внешних источников и получить полный доступ к членам класса?

#ruby #serialization #yaml

#ruby #сериализация #yaml

Вопрос:

В Ruby любой объект может быть перенесен, то есть сериализован, в документ YAML путем сохранения выходных данных метода « to_yaml » в файл. После этого этот файл YAML можно прочитать снова, т. е. десериализовать, с помощью YAML::load метода. Более того, у каждого есть полный доступ ко всем членам базового класса / объекта.

Все это справедливо, пока я использую Ruby как единую платформу. Как только я сериализую объекты в Java и десериализую их в Ruby, я больше не могу получить доступ к объекту из-за NoMethodError исключения. Это связано с тем, как объекты / локальные типы данных называются в разных системах.

Учитывая класс Ruby «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 new instance of class 'Car' ...
porsche = Car.new("Porsche", 180, "red", ["sun roof", "air conditioning"])
  

Вызов porsche.to_yaml приводит к следующему выводу:

 --- !ruby/object:Car 
brand: Porsche
color: red
extra_equipment: 
- sun roof
- air conditioning
horsepower: 180
  

Я тестирую десериализацию, загружая выходные данные YAML:

 # reading existing yaml file from file system
sample_car = YAML::load(File.open("sample.yaml"))
puts sample_car.brand # returns "Porsche"
  

Это работает, как ожидалось, но теперь давайте предположим, что документ YAML был создан другой системой и в нем отсутствуют какие-либо ссылки на Ruby, хотя в описании объекта, соответствующем yaml, « !Car «, вместо « !ruby/object:Car «:

 --- !Car 
brand: Porsche
color: red
extra_equipment: 
- sun roof
- air conditioning
horsepower: 180
  

Этот код:

 # reading existing yaml file from file system
sample_car = YAML::load(File.open("sample.yaml"))
puts sample_car.brand # returns "Porsche"
  

возвращает это исключение:

 /path/yaml_to_object_converter.rb.rb:27:in `<main>':
undefined method `brand' for #<YAML::DomainType:0x9752bec> (NoMethodError)
  

Есть ли способ работать с объектами, определенными во «внешних» документах YAML?

Комментарии:

1. возможно, это поможет: blog.bytemine.net/2009/12/07 /…

2. Является ли «члены класса» (из названия) термином Java?

3. @Andrew: Я признаю, что это скорее терминология Java и, следовательно, немного неточно в данном контексте Ruby. Я имел в виду переменные класса и методы класса.

4. Я не вижу здесь никаких переменных класса или методов класса — только переменные экземпляра и методы экземпляра.

Ответ №1:

Для меня sample_car в оболочке IRB вычисляется как:

 => #<Syck::DomainType:0x234df80 @domain="yaml.org,2002", @type_id="Car", @value={"brand"=>"Porsche", "color"=>"red", "extra_equipment"=>["sun roof", "air conditioning"], "horsepower"=>180}>
  

Затем я выдал sample_car.value :

 => {"brand"=>"Porsche", "color"=>"red", "extra_equipment"=>["sun roof", "air conditioning"], "horsepower"=>180}
  

Который является хэшем. Это означает, что вы можете создать свой объект Car, добавив метод класса к Car вот так:

 def self.from_hash(h)
  Car.new(h["brand"], h["horsepower"], h["color"], h["extra_equipment"])
end
  

Затем я попробовал это:

 porsche_clone = Car.from_hash(sample_car.value)
  

Который вернул:

 => #<Car:0x236eef0 @brand="Porsche", @horsepower=180, @color="red", @extra_equipment=["sun roof", "air conditioning"]>
  

Это самый уродливый способ сделать это. Могут быть и другие. =)

РЕДАКТИРОВАТЬ (19 мая 2011): Кстати, просто придумал намного более простой способ:

 def from_hash(o,h)
  h.each { |k,v|
    o.send((k "=").to_sym, v)
  }
  o
end
  

Чтобы это сработало в вашем случае, вашему конструктору не должны требоваться параметры. Тогда вы можете просто сделать:

 foreign_car = from_hash(Car.new, YAML::load(File.open("foreign_car.yaml")).value)
puts foreign_car.inspect
  

…что дает вам:

 #<Car:0x2394b70 @brand="Porsche", @color="red", @extra_equipment=["sun roof", "air conditioning"], @horsepower=180>
  

Комментарии:

1. Я действительно не считаю это уродливым. Я считаю, что это защитное программирование сводит любые объекты к их базовым типам и отправляет их. Это глазурь на торте YAML, которая пытается помочь нам, отправляя объекты, но эта функция также мешает. Перейдите к базовым значениям, и данные должны перемещаться без проблем.

2. Ну, я сказал это только потому, что уверен, что есть драгоценный камень, который может автоматизировать процесс еще больше, без необходимости разработчику добавлять методы класса. 😉 В остальном метод аккуратный.

3. Да, gem оптимизирует код, который его вызывает, но я думаю, что это просто намазывает торт большим количеством глазури, чтобы скрыть тот факт, что торт комом. 🙂 Хотя я согласен с вами, повторное использование gem — хороший способ добиться этого. Я бы, вероятно, добавил to_yaml метод к рассматриваемым классам, но это один из многих недостатков моего мозга.

4. Я также согласен с тем, что программист должен обладать знаниями о классе (здесь «Car») и его свойствах. Затем он может реализовать метод «from_hash» и «реконструировать» объект. Но, насколько я могу судить, простого готового метода, позволяющего читать документы yaml, описывающие объекты из систем, отличных от Ruby, не существует. Особенно сильно вложенные объекты потребовали бы много времени.

5. Например, рассмотрим: — ! Автомобильный движок: !Клапаны geneclass по умолчанию: 6 допуск: !!float ‘1.39154E13’ кривошип: !DefaultCrankClass ширина: 100 высота: 200 материал: !DefaultMaterialClass тип: сталь производитель: West Coast Car Parts