rustyamlserde

How to parse yaml conditional fields in rust?


In the yaml files below:

// 1. book yaml
kind: book
title: Someone's life

publisher: someone
published_at: 2023-02-24T09:31:00Z+09:00
// 2. movie yaml
kind: movie
title: Someone's life

director: someone else

kind is key for recognizing that yaml is what for. If kind is book, this matter have publisher and published_at. however, if kind is movie, this matter doesn't have publisher but director.

In my short thought, for parsing it, i have to make general parse struct. ex:

#[derive(Serialize, Deserialize, Debug)]
struct General {
  kind: String,
  title: String,
}

parse one time using General struct, then get parsed general's kind and re-parse with another struct. ex:

#[derive(Serialize, Deserialize, Debug)]
struct Book {
  kind: String,
  title: String,
  publisher: String,
  published_at: String,
}

Above scenario can be works, but i have to parse two time. But, Is this efficient?


Solution

  • You can use serde's tag attribute for this. Playground example

    use serde::Deserialize;
    use serde_yaml; // 0.8.26
    
    #[derive(Debug, Deserialize)]
    #[serde(tag="kind", rename_all="lowercase")]
    enum Media {
        Book(Book),
        Movie(Movie),
    }
    
    #[derive(Debug, Deserialize)]
    struct Book {
        title: String,
        publisher: String,
        published_at: String,
    }
    
    #[derive(Debug, Deserialize)]
    struct Movie {
        title: String,
        director: String,
    }
    
    fn main() {
        let book: Media = serde_yaml::from_str("kind: book
    title: Someone's life
    
    publisher: someone
    published_at: 2023-02-24T09:31:00Z+09:00").unwrap();
    
        let movie: Media = serde_yaml::from_str("kind: movie
    title: Someone's life
    
    director: someone else").unwrap();
    
        println!("{book:?}");
        println!("{movie:?}");
    
    }