Working with YAML: Configuration Files

In game programming it’s generally considered good practice to specify game information in external configuration files.

  1. It gathers parameters that may be scattered across the code base into a couple of small files.
  2. The external files can be easier to read, understand, and modify.
  3. Modifications can be made without changing (and having to rebuild) the source.

In the Java world XML is the typical choice although JSON, properties, INI, and other formats are also used. Another, less well known format, is YAML(YAML Ain’t Markup Language). YAML is a structured text format which provides a viable alternative to XML and JSON as means of specifying configuration data, particularly when the files are being edited by hand.

This article gives a brief overview of YAML, with a focus on using it as a means for writing configuration files.

There are two main libraries for working with YAML in Java; snakeYAML and YamlBeans. Both have fairly equivalent functionality, and at the bottom of this article is a simple class that uses snakeYAML to read in a yaml file, convert the YAML result tree into a simple set of nested maps, and write the output to the console. Both snakeYAML and YamlBeans provide additional functionality such as loading YAML data directly into POJOs and walking the YAML parse result tree.

Here is a simple YAML file:
`

YAML comments start with a “#”

Properties and values are separated by a colon.

There has to be at least one space between the colon and the value.

menuFontFamily: Arial
menuFontColor: blue # Blue is my favorite color
menuFontSize: 14pt
menuBackgroundImage: menu.png
tileMenuBackgroudImage: true
`

Reading this in via the code at the end of the post gives us the following following simple map:
{menuFontFamily=Arial, menuFontColor=blue, menuFontSize=14pt, menuBackgroundImage=menu.png, tileMenuBackgroudImage=true}
So for cases where all you need is a simple set of key/value pairs, a YAML file is not any more verbose than a properties file. Imagine what the equivalent XML might look like.

Maps

YAML uses indentation to specify hierarchical data. If we change the file to:
`
menu:
font:
family: Arial
color: blue
size: 14pt

Font and BackgroundImage need to be indented the same amount.

backgroundImage:
image: menu.png
tile: true
...and run the code again we get a series of nested maps:
{menu={font={family=Arial, color=blue, size=14pt}, backgroundImage={image=menu.png, tile=true}}}
`
It does not matter how many spaces you indent, as long as it is consistent within a map. Try adding a space in front of “color: blue” and see what happens. You cannot use tabs for indentation.

YAML also allows you to define multiple keys on the same line. The previous example could also be written as:
`
menu:
font: { family: Arial, color: blue, size: 14pt }

backgroundImage:
{ image: menu.png, tile: true }
`

Lists

Let’s say we want to add to our configuration a list of sound files to load at start up. All of the files are in the same directory, and for each sound we want to specify both the name of the file and the name to be used to get the sound from the library (something like soundLibrary.getSound("PlayerDied")).

`
menu:
font: { family: Arial, color: blue, size: 14pt }

backgroundImage:
{ image: menu.png, tile: true }

audio:

Path is relative to the main game directory

base-path: .\sounds\raw\

Each entry in the list starts with a “-” and must be at the same level of indentation.

sounds:
- name: Hit
file: hit_1.ogg

 - name: PlayerDied
   file: player_death.ogg

 - { name: Miss, file: miss_target.ogg }  # We can also define a sound on one line.

`

This results in the following output:
{menu={font={family=Arial, color=blue, size=14pt}, backgroundImage={image=menu.png, tile=true}}, audio={base-path=.\sounds\raw\, sounds=[{name=Hit, file=hit_1.ogg}, {name=PlayerDied, file=player_death.ogg}, {name=Miss, file=miss_target.ogg}]}}

Like maps, lists can be defined on one line with list items separated with a comma and space.
[color=green] sounds: [ {name: Hit, file: hit_1.ogg}, { name: playerDied, file: player_death.ogg}, { name:Miss, file:miss_target.ogg}]

There is more to YAML than what I have shown such as:

  1. Support for multi-line strings
  2. Anchors and References which allow nodes to reference previous nodes in the document
  3. Specifying the type of values
  4. Automated mapping of YAML data to POJOs.
  5. Serialization of objects to YAML.

Some scattered, closing thoughts

Personally I like YAML. It keeps the simple things simple. When all I need is a flat list of properties, a YAML file has no more overhead than a .properties file. I can then add a bit of hierarchy for convenience without having to switch to a new format.

I find YAML to be easier to read and modify than either XML or JSON.

YAML’s use of whitespace may welcome or annoying depending on your preference.

XML and JSON have much larger communities with much richer toolset available.

Like JSON, YAML is not as self describing as XML and more care may be required to ensure that your format is understandable from a semantic standpoint.

Unlike XML, YAML has no standard way of defining a schema and validating a document against it, although projects such as Kwalify have been developed to meet that need.

References
Official YAML site
YAML Wikipedia Page

Java libraries for reading/writing YAML
snakeYAML
YamlBeans

Simple class to run example files


import java.io.*;
import java.util.Map;

// Download the snakeYaml jar at: http://code.google.com/p/snakeyaml/downloads/list
import org.yaml.snakeyaml.Yaml;


public class SimpleYAMLMain {

		
	public static void main(String[] args) {
		
		// The path of your YAML file.
		final String fileName = "path\\to\\yaml\\file\\test.yaml";
		
		Yaml yaml = new Yaml();
	
		try {
			InputStream ios = new FileInputStream(new File(fileName));
			
			// Parse the YAML file and return the output as a series of Maps and Lists
			Map<String,Object> result = (Map<String,Object>)yaml.load(ios);
			System.out.println(result.toString());
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

It’s probably worth mentioning that Jackson supports YAML with an extension library, so add this to the list of links:

jackson-dataformat-yaml

If you are in the position where you need data for an in-memory database (or unit tests), I have used Fixy for that purpose which is incredibly easy and built on top of SnakeYAML too.

That quote on the site is actually from me by the way :wink:

Not many people will actually utilize an embedded database in their game, but I thought I’d mention it all the same for the rare case where someone does want to play with it.