有没有办法告诉Serde使用struct字段作为地图的键?

时间:2018-11-02 17:25:00

标签: serialization rust yaml serde

我有一张要序列化为一系列结构的项目图,每个结构都有一个对应键的字段。

想象一下,有一个这样的YAML文件:

name_a:
    some_field: 0
name_b:
    some_field: 0
name_c:
    some_field: 0

以及类似的结构:

struct Item {
    name: String,
    some_field: usize,
}

我想将命名项反序列化为Vec<Item>而不是Map<String, Item>。项目名称(name_a,...)放在name对象的Item字段中。

我尝试了以下操作:

extern crate serde_yaml;
use std::fs::read_to_string;

let contents = read_to_string("file.yml").unwrap();
let items: Vec<Item> = serde_yaml::from_str(&contents).unwrap();

但这不起作用,并产生invalid type: map, expected a sequence错误。

我宁愿避免创建转换为Map<String, PartialItem>的瞬态Vec,而且我也不想实施其他PartialItem结构。尽管我认为这不是最佳选择,但可以将Option<String>用作name

2 个答案:

答案 0 :(得分:2)

一种方法是自己deserialize地图:

common

输出:

use std::fmt;

use serde::de::{Deserialize, Deserializer, MapAccess, Visitor};
use serde_derive::Deserialize;

struct ItemMapVisitor {}

impl ItemMapVisitor {
    fn new() -> Self {
        Self {}
    }
}

#[derive(Debug, Deserialize)]
struct SomeField {
    some_field: u32,
}

#[derive(Debug)]
struct Item {
    name: String,
    some_field: u32,
}

#[derive(Debug)]
struct VecItem(Vec<Item>);

impl Item {
    fn new(name: String, some_field: u32) -> Self {
        Self { name, some_field }
    }
}

impl<'de> Visitor<'de> for ItemMapVisitor {
    type Value = VecItem;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("name: somefield:")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        let mut items = Vec::with_capacity(access.size_hint().unwrap_or(0));
        while let Some((key, value)) = access.next_entry::<String, SomeField>()? {
            items.push(Item::new(key, value.some_field));
        }
        Ok(VecItem(items))
    }
}

impl<'de> Deserialize<'de> for VecItem {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_map(ItemMapVisitor::new())
    }
}

fn main() {
    let contents = r#"
name_a:
    some_field: 0
name_b:
    some_field: 1
name_c:
    some_field: 2
"#;

    let items: VecItem = serde_yaml::from_str(&contents).unwrap();
    println!("{:#?}", items);
}

如果您不想使用VecItem( [ Item { name: "name_a", some_field: 0 }, Item { name: "name_b", some_field: 1 }, Item { name: "name_c", some_field: 2 } ] ) 结构。您还可以使用以下方法:

Somefield

但这可能会添加一些无用的副本。

答案 1 :(得分:1)

Item::name字段定义默认值

#[derive(Debug, Serialize, Deserialize)]
struct Item {
    #[serde(default)]
    name: String,
    some_field: usize,
}

有了这个技巧,Item既可以用于反序列化,也可以转换为VecItem

let contents = read_to_string("file.yml").unwrap();

let items: HashMap<String, Item> = serde_yaml::from_str(&contents).unwrap();

let slist: Vec<Item> = items
    .into_iter()
    .map(|(k, v)| Item {
        name: k,
        some_field: v.some_field,
    })
    .collect();