Icon Rufen Sie uns an
+49 441.309197-69 +49 441.309197-69
 
EN

yaml in Java and Ruby: Welcome yamlbeans!

Posted by Daniel Rauer on Monday, December 07, 2009

During our hackathon at the North Sea Bernd and I were thinking about how to share objects between Java and Ruby. We had to decide between JSON and YAML, both very powerful, slim and well supported in both languages.
YAML has won the award, because there are no major differences between YAML and JSON, and Ruby offers excellent support for YAML.
So, the Ruby side was done, now we came to the Java part: Reading about Java and YAML you will always find the following libraries:

To make a long story short: None of them work out of the box for our purposes, which is importing a Ruby generated YAML into JAVA, generating the Java beans and write back these beans as YAML our Ruby app can parse. Not a big deal, you might think.
The YAML file our Ruby code produces looks quite similar to this one:

--- !ruby/object:Test::Module::Object
  sub1: !ruby/object:Test::Module::Sub1
    subsub1s:
    - !ruby/object:Test::Module::Sub1::SubSub1
      att1: []
      att2: true
      att3: 11111
      att5:
    att1: []
    att2: 0
    att3: []
  sub2: !ruby/object:Test::Module::Sub2
    att1: MyString
    att2:
    - entry1
    att3: 12345
    subsub2: !ruby/object:Test::Module::Sub2::SubSub2
      att1: true
      att2: true

JvYaml was the first we tried: The code to generate the beans out of the YAML should be easy like that:

Module module = (Module)YAML.load(new FileReader("example.yml"));

Result:

java.lang.ClassCastException: org.jvyaml.PrivateType cannot be cast to net.bytemine.jvyaml.Module

To come to its defense: JvYaml is no longer supported, last version is from september 2006, and it is not documented :(

This was leading us to JYaml, also with a very slim api:

Module module = Yaml.loadType(new File("example.yml"), Module.class);

This produces an instance of the Module class, but none of its attributes or subclasses where instanciated. Also gambling around with the jyaml.yml config file did not work any better.
Result: JYaml is better maintained (last release in august 2007 ;) ), and much better documented than JvYaml. But still not working for us.

snakeyaml:


Config config = (Config)new Yaml().load(new FileInputStream(new File(“example.yml”)));

produces the following error:

Can't construct a java object for !ruby/object:Test::Module::Object; exception=Class not found: st::Module::Object

Because snakeyaml seems to have a syntactical problem with this YAML document we did no further investigations on snakeyaml. But: It is very well documented, at just yesterday somebody was committing something to the project. Perhaps snakeyaml will become more interesting by the time.

The solution:
Late at night I stumbled over a different project: yamlbeans. This library seemes to be highly flexible in its configuration and is documented in an excellent manor. So I gave it a try, and after some reading and testing I finally was able to extract our whole class hierarchy from the YAML. Even better: The exported YAML looked quite like the original. After patching three lines of code into the library we finally got the solution on how to exchange objects between both worlds.
This patch has been already applied to the newest version 1.0 by Nate, who is the maintainer and author of yamlbeans.

And finally here is the code doing the job for us:


YamlReader reader = new YamlReader(new FileReader(“example.yml”));
reader.getConfig().setClassTag(“ruby/object:Test::Module::Object”, Module.class);
reader.getConfig().setClassTag(“ruby/object:Test::Module::Sub1”, Sub1.class);

Module module = reader.read(Module.class);

YamlWriter writer = new YamlWriter(new FileWriter(“example-out.yml”));
// writes also false values
writer.getConfig().writeConfig.setWriteDefaultValues(true);
// writes root tag with prefix ’—- ’
writer.getConfig().writeConfig.setExplicitFirstDocument(true);
// writes out every unknown classname
writer.getConfig().writeConfig.setAlwaysWriteClassname(true);
writer.getConfig().setClassTag(“ruby/object:Test::Module::Object”, Module.class);

writer.write(module);
writer.close();

Thanks again to Nate of the yamlbeans project, he did an excellent job!