Read a simple YAML file into a struct

YAML struct serde Deserialize

In an earlier article we saw how to read an arbitrary YAML file and then access the individual fields.

A more time consuming, but more robust way is to define a struct mapping all the fields of the YAML file. We'll see several such examples.

For all of them we'll need both serde_yaml and serde as you can see in the Cargo.toml file:

examples/read-simple-yaml/Cargo.toml

[package]
name = "read-simple-yaml"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"

Data

Let's see this simple YAML file:

examples/read-simple-yaml/data.yaml

fname: Foo
lname: Bar
year: 2023
height: 6.1
married: true
# A comment


The code

examples/read-simple-yaml/src/main.rs

use std::env;
use std::fs::File;

use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Data {
    fname: String,
    lname: String,
    year: u16,
    height: f32,
    married: bool,
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        eprintln!("Usage: {} FILENAME", args[0]);
        std::process::exit(1);
    }
    let filename = &args[1];

    let data: Data = match File::open(filename) {
        Ok(file) => match serde_yaml::from_reader(file) {
            Ok(data) => data,
            Err(err) => {
                eprintln!("There was an error parsing the YAML file {}", err);
                std::process::exit(1);
            }
        },
        Err(error) => {
            eprintln!("Error opening file {}: {}", filename, error);
            std::process::exit(1);
        }
    };
    println!("{:#?}", &data);
    println!();

    println!("{}", data.fname);
    assert_eq!(data.lname, "Bar");
    assert_eq!(data.year, 2023);
    assert_eq!(data.height, 6.1);
    assert!(data.married);

}

Before getting to the main function we define a struct with the fields of the YAML file and the type of values the YAML file has. We add the Deserialize trait to it.

The first few lines in the main function is to accept the name of the YAML file on the command line as described in the article on how to expect one command line parameter.

The we open the YAML file using the std::fs::File::open function and then we call the serde_yaml::from_reader function to read the content of the file and parse it. The most important part is to assign to a variable that was defined to be type of the struct we have defined earlier. In this example I cleverly used the name Data. Don't do that! Find some more descriptive name in real code!

let data: Data =

The content of the resulting variable looks like this:

Data {
    fname: "Foo",
    lname: "Bar",
    year: 2023,
    height: 6.1,
    married: true,
}

Very nice.

We can access the individual values using the dot-notation. We can then print the values or, as shown in this case, we can use assert_eq! or assert! to verify the values.

Getting started and extra data in the YAML file

What happens if there are extra fields in the YAML file that were not declared in the struct?

In this file there is an extra field called address that was not defined in the struct.

examples/read-simple-yaml/more.yaml

fname: Foo
lname: Bar
year: 2023
height: 6.1
married: true
address: Some place
# A comment


By default the YAML parser of Serde will ignore these extra fields. This is great as it allows us start using the struct even before we manage to map out all the fields.

On the other hand this is also problematic as it means we won't notice when the YAML contains fields that we don't handle. If we also setup default values for some of the fields then a typo in the name of a field will be hard to notice.

Disallow extra, unknown fields

Serde has various container attributes we can apply to the struct. One of them is called deny_unknown_fields.

We can add it to the definition of the struct:

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Data {
    fname: String,
    lname: String,
    year: u16,
    height: f32,
    married: bool,
}

If we make that addition and run the program again with the YAML file that has the extra field

cargo run more.yaml

we get a panic!:

There was an error parsing the YAML file unknown field `address`, expected one of `fname`, `lname`, `year`, `height`, `married` at line 6 column 1

Related Pages

YAML and Rust
Set default values while deserializing YAML in Rust
Deserialize complex YAML file in Rust

Author

Gabor Szabo (szabgab)

Gabor Szabo, the author of the Rust Maven web site maintains several Open source projects in Rust and while he still feels he has tons of new things to learn about Rust he already offers training courses in Rust and still teaches Python, Perl, git, GitHub, GitLab, CI, and testing.

Gabor Szabo