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