serialization - 有没有办法告诉 Serde 使用结构字段作为映射的键?

标签 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错误。

我宁愿避免创建 transient Map<String, PartialItem>转换为 Vec ,我也不想实现额外的 PartialItem结构。使用 Option<String>作为name有可能,尽管我认为这不是最佳选择。

最佳答案

一种方法是deserialize map 自己:

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 结构。你也可以使用这个:

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

while let Some((key, value)) = access.next_entry::<String, Item>()? {
    items.push(Item::new(key, value.some_field));
}

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

关于serialization - 有没有办法告诉 Serde 使用结构字段作为映射的键?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53123256/

相关文章:

symfony1 - Symfony 中的 YML 文件——你能得到多于一级的关联数组吗?

list - 如何合并 YAML 数组?

php - Mysql 将序列化表从一个数据库复制到另一个数据库

java - readObject 的 EOF 异常

rust - 不可变对象(immutable对象)根据函数签名更改为可变对象

rust - 为什么没有为 `Clone` 实现 `fn(&T)` 特征?

r - R : (Package which is only available in source form, 错误,可能需要编译 C/C++/Fortran)

java - 如何序列化单例对象?

c# - RestSharp序列化JSON数组请求参数

rust - 我应该为变量添加生命周期还是在 Rust 函数之间传递它