Search code examples
rustyamldeserializationserde

How to deserialize a map into a vector of a custom struct, with a field matching the key?


Given a yaml with a map of items,

items:
  item1:
    uid: ab1234
    foo: bar
  item2:
    uid: cd5678
    foo: baz

how can I parse it with serde into a Vec<Item> with a new field, "name", generated from the key of the original map,

struct Item {
  name: String,  // to be populated by the key (item1, item2)
  uid: String,
  foo: String,
}

I'm trying to get the following code (rustexplorer) to work, which currently errors out with Err(Error("items: invalid type: map, expected a sequence", line: 3, column: 3)).

/*
[dependencies]
serde = "1.0.193"
serde_derive = "1.0.193"
serde_yaml = "0.9.27"
*/

use serde_derive::{Deserialize, Serialize};
use std::vec::Vec;


#[derive(Debug, Serialize, Deserialize)]
struct Item {
  name: String,
  uid: String,
  foo: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Schema {
    items: Vec<Item>,
}

fn main() {
    let input = r###"
items:
  item1:
    uid: ab1234
    foo: bar
  item2:
    uid: cd5678
    foo: baz
"###;

    let items = serde_yaml::from_str::<Schema>(input);
    println!("{:?}", items);
}

Solution

  • serde_with has something for exactly that: KeyValueMap:

    use serde::{Deserialize, Serialize};
    use serde_with::{serde_as, KeyValueMap};
    
    #[derive(Debug, Serialize, Deserialize)]
    struct Item {
        #[serde(rename = "$key$")]
        name: String,
        uid: String,
        foo: String,
    }
    
    #[serde_as]
    #[derive(Debug, Serialize, Deserialize)]
    struct Schema {
        #[serde_as(as = "KeyValueMap<_>")]
        items: Vec<Item>,
    }