I am working on a Rust project where I need to deserialize YAML configuration data into a more structured Rust type. The YAML data represents output specifications for different data sinks (like CSV and JSON files) and looks something like this:
output_data_specs:
csv:
file_path: "/output-data.csv"
delimiter: ","
json:
file_path: "/output/"
another: "foo"
I want to deserialize this data into a Rust Vec<Sink>
, where Sink
is an enum that can represent either a CSV sink or a JSON sink:
pub enum Sink {
Csv(CsvSink),
Json(JsonSink),
}
pub struct CsvSink {
file_path: String,
append: bool,
}
pub struct JsonSink {
file_path: String,
another: String
}
I'm looking for a way to implement this with Serde, it should deserialize the map of output data specs map into a list of Sinks in something like:
pub struct InferenceConfig {
pub name: String,
pub output_data_specs: Vec<Sink>,
}
I'd preferably like to implement this without a custom deserializer. Any examples or pointers to relevant documentation would be very helpful.
If you can change your Map to a List (simply add -
in front of csv
and json
) — which it seems you want to do, since output_data_specs
is a Vec<Sink>
— you can use #[serde(with = "serde_yaml::with::singleton_map_recursive")]
to use the keys csv
and json
as enum tags in serde’s default enum representation.
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Sink {
Csv(CsvSink),
Json(JsonSink),
}
#[derive(Debug, Deserialize)]
pub struct CsvSink {
file_path: String,
append: bool,
delimiter: String,
}
#[derive(Debug, Deserialize)]
pub struct JsonSink {
file_path: String,
another: String,
}
#[derive(Debug, Deserialize)]
pub struct InferenceConfig {
pub name: String,
#[serde(with = "serde_yaml::with::singleton_map_recursive")]
pub output_data_specs: Vec<Sink>,
}
fn main() {
let s = r#"
name: "xyz"
output_data_specs:
- csv:
file_path: "/output-data.csv"
append: true
delimiter: ","
- json:
file_path: "/output/"
another: "foo"
"#;
let inference_config = serde_yaml::from_str::<InferenceConfig>(s).unwrap();
println!("{inference_config:#?}");
}
InferenceConfig {
name: "xyz",
output_data_specs: [
Csv(
CsvSink {
file_path: "/output-data.csv",
append: true,
delimiter: ",",
},
),
Json(
JsonSink {
file_path: "/output/",
another: "foo",
},
),
],
}