Reading and Writing YAML
Language
- unknown
by James Smith (Golang Project Structure Admin)
This post will discuss what YAML is and how its syntax works. We will then move on to a simple real-world example, so you can see how YAML looks.
Finally, we will learn how a program can be written in Go code that is able to read and write data formatted in YAML.
Table of Contents
What Is YAML?
YAML is a data-serialization language, like JSON, BSON and XML.
It’s often used for writing configuration files that can be easily read and written by both humans and machines.
YAML can, however, be used to structure almost any type of data, no matter how simple or complex.
The file extension that is used with files containing YAML data is either .yaml or .yml — both extensions are widely recognized.
What Do the Letters in YAML Stand For?
It’s a recursive acronym, standing for “YAML Ain’t Markup Language”.
The recursion means that you may send your brain into an infinite loop if you try to explain what the Y really stands for!
The term recursive acronym was first coined in Douglas Hofstader’s book Gödel, Escher, Bach, which became a cult classic among programmers and other geeky intellectuals.
There’s a reasonably large number of other recursive acronyms that are commonly used in computing. Here are a few of them:
- GNU (“GNU’s Not Unix”)
- PHP (“PHP: Hypertext Preprocessor”)
- RNP (“RPM Package Manager”)
What Syntax Rules Does YAML Use?
Every YAML document should begin with three hyphens placed together on a single line. This allows multiple documents to be stored within a single file, so long as each document begins with a row of three hyphens.
Comments in YAML begin with a hash character and run on until the end of the line. This is the same as in the Python programming language.
Since YAML is a structured form of plaintext, it can model basic data structures. The two types of structures that you should be aware of are lists and maps.
Lists are equivalent to Go slices (or arrays) and maps are equivalent to the Go data structure of the same name (sometimes called dictionaries in other programming languages).
A list is written by starting a new line with a hyphen and a space. The text that follows is the list item. So a list containing three items would run over three lines, each line containing a hyphen, a space and some text.
Lists can also be written in a short form, which looks more like how arrays are defined in JSON: the list begins with an opening-square-bracket character, then the text of each item follows, separated by a comma, and the list ends with a closing-square-bracket character.
Maps are written differently: the key for each value in the map begins a new line, followed by a comma. The value of the key is written on the next line with an indentation of two spaces — you must remember not to use tabs!
There can be multiple levels of indentation, since lists can contain maps and maps can contain lists.
What Does an Example YAML File Look Like?
We’ve talked about the layout of a basic YAML file, describing its syntax in words, but it may be more useful to see an example.
So the following is a valid YAML file that adheres to the rules we’ve discussed:
shopping__long-lists.yml
--- # Shopping List
food:
meat:
- chicken
- pork
- beef
fruit:
- apples
- pears
other stuff:
- cigarettes
- alcohol
Alternatively, the file above could be rewritten like so:
shopping__short-lists.yml
--- # Shopping List
food:
meat: [chicken, pork, beef]
fruit: [apples, pears]
other stuff: [cigarettes, alcohol]
Writing lists like this may make files use less disk space, but perhaps it is better to have a separate line for each item, since it could make the lists easier to scan with the eye.
This is simply a matter of style, however; both forms of list are perfectly valid YAML.
It is best to choose a single style of list and stick with it.
How Can I Validate YAML?
Inevitably, software has been developed to help you check whether the syntax of your YAML file is correct.
These can alert you to issues that may pose serious problems for computers — but which aren’t easy to spot with the human eye. For example, when an incorrect amount of whitespace has been used.
There are also websites where you can copy-and-paste your YAML and have it validated, such as yamllint.com and yamlchecker.com.
Some online editors will also display the contents of your YAML file with an attractive HTML-style presentation as you edit it, such as those hosted at code-beautify.com and swagger.io. These may help you to understand better the structure of your YAML.
Using a YAML Package in Go
Unlike the case with JSON, there is currently no support for YAML files in the Go standard library.
However, there are many effective and reliable packages that are maintained by other members of the Go community.
The example below shows how one of these packages can be used:
package main
import (
"fmt"
"log"
"time"
"gopkg.in/yaml.v3"
)
var (
yamlContent = []byte(`---
prod: true
server_opts:
ports: [80, 443]
timeout: 200s
`)
)
type Configuration struct {
IsInProductionMode bool `yaml:"prod"`
ServerOptions struct {
Ports []uint
Timeout time.Duration `yaml:"timeout"`
} `yaml:"server_opts"`
}
func main() {
var conf Configuration
err := yaml.Unmarshal(yamlContent, &conf)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("UNMARSHALLED:\n*****\n%+v\n*****\n\n", conf)
marshalData, err := yaml.Marshal(&conf)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("MARSHALLED:\n*****\n%s\n*****\n", marshalData)
}
All Go packages hosted at the gopkg.in domain are given a version number within the URL, so we are importing the third version of our YAML library here.
We declare a custom type to hold the configuration data that is stored in the YAML file. Notice how the structure of Configuration
mirrors the structure of YAMLContent
.
We use tags for some of the fields in the Configuration
type, which allows us to use shorter names for some of the fields in the YAML.
Notice how we don’t need to use a tag for the Ports
field, even though the capitalization is lost in the YAML, because this is handled automatically for us.
We use the Unmarshall
function in order to parse the YAML file and store the contents in our conf
variable.
We can also see the Marshall
function being used here, which performs the opposite operation, converting a Go variable into a YAML-encoded byte slice.
If you have ever read or written JSON data in Go, you will already recognize the names of these two functions.