Scala YAML parser examples using Snakeyaml

Summary: A Scala YAML parsing example using the Snakeyaml parser.

If you need some Scala YAML parsing examples using Snakeyaml parser, you've come to the right place. I just worked through some Snakeyaml issues related to Scala, in particular converting YAML to JavaBean classes written in Scala, so I thought I'd share the source code here.

Scala, YAML, and the Snakeyaml Constructor approach

I think the best way to load and parse YAML data with Snakeyaml is to use the Constructor approach, as shown here:

package yaml

import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.constructor.Constructor
import scala.collection.mutable.ListBuffer
import scala.reflect.BeanProperty

object YamlBeanTest1 {
  
  val text = """
accountName: Ymail Account
username: USERNAME
password: PASSWORD
mailbox: INBOX
imapServerUrl: imap.mail.yahoo.com
protocol: imaps
minutesBetweenChecks: 1
usersOfInterest: [barney, betty, wilma]
"""

  def main(args: Array[String]) {
    val yaml = new Yaml(new Constructor(classOf[EmailAccount]))
    val e = yaml.load(text).asInstanceOf[EmailAccount]
    println(e)
  }
  
}

/**
 * With the Snakeyaml Constructor approach shown in the main method,
 * this class must have a no-args constructor.
 */
class EmailAccount {
  @BeanProperty var accountName: String = null
  @BeanProperty var username: String = null
  @BeanProperty var password: String = null
  @BeanProperty var mailbox: String = null
  @BeanProperty var imapServerUrl: String = null
  @BeanProperty var minutesBetweenChecks: Int = 0
  @BeanProperty var protocol: String = null
  @BeanProperty var usersOfInterest = new java.util.ArrayList[String]()
  
  override def toString: String = {
    return format("acct (%s), user (%s), url (%s)", accountName, username, imapServerUrl)
  }
}

As you can see, using the Snakeyaml Constructor class makes for really clean code.

If there's a disadvantage to this approach, it's that you must have a no-args constructor for Snakeyaml to work with. I haven't dug into their source code, but based on the error messages I was getting with my earlier code, when using the Constructor approach, Snakeyaml appears to call the no-args constructor of your class, then calls all the "set" methods on your variables using reflection.

For the work I've done so far, this isn't much of a drawback, but I think it's worth mentioning.

One funky thing you can see in the code is that I had to use a Java List class, instead of using a Scala List. This could just be a limitation of my understanding, but what I did in my real-world code is to use the Java ArrayList as shown, then convert it to a Scala List in another 'get' method. (Related: Here's my tutorial on converting Java collections to Scala collections.)

Scala, YAML, and the load / asInstanceOf approach

This next Scala Snakeyaml source code example demonstrates a different way of working with Snakeyaml. In this case I load the Snakeyaml data into an object, then cast it to my Person object using the asInstanceOf method:

package yaml

import org.yaml.snakeyaml.Yaml
import scala.reflect.BeanProperty

object YamlBeanTest2 {
  
  def main(args: Array[String]) {
    val data = "--- !!yaml.Person [ Andrey, Somov, 99 ]"
    val yaml = new Yaml
    val obj = yaml.load(data)
    val person = obj.asInstanceOf[Person]
    println(person)
  }
  
}

/**
 * With the approach shown in the main method (load() plus
 * asInstanceOf), this class must declare its properties in the
 * constructor.
 */
// snakeyaml requires properties to be specified in the constructor
class Person(@BeanProperty var firstName: String, 
             @BeanProperty var lastName: String, 
             @BeanProperty var age: Int) {
  override def toString: String = return format("%s, %s, %d", firstName, lastName, age)
}

Note that with this approach, which does not use the Snakeyaml Constructor, you must do the opposite of the previous approach, and put all your properties in your class constructor. In this code I once again do that using the Scala @BeanProperty annotation, but you can also write out your "set" methods in long-hand if you prefer, like this:

// this works also
case class Person(var firstName: String, var lastName: String, var age: Int) {
  def setFirstName(s: String) {firstName = s} 
  def setLastName(s: String) {lastName = s}
  def setAge(i: Int) {age = i}
}

Scala YAML parsing with Snakeyaml: Summary

In summary, I hope these Scala YAML parsing examples using Snakeyaml have been helpful. Many thanks to @oxnrtr on Twitter for prompting me to look again at the Scala @BeanProperty approach shown here. In my first attempts of using Snakeyaml with Scala, I couldn't get the BeanProperty approach to work, so I was writing all my 'set' methods out long hand.