Есть ли способ сказать 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, хотя я не думаю, что это оптимально.


person Tim Visée    schedule 02.11.2018    source источник
comment
Что должно произойти, если есть повторяющиеся значения для name? Должен ли он просто генерировать неверные данные?   -  person Shepmaster    schedule 02.11.2018
comment
Хороший вопрос. Я считаю, что YAML предлагает перезаписывать повторяющиеся элементы по порядку, поэтому используется последнее вхождение, другие вхождения удаляются. Для моего случая это идеально.   -  person Tim Visée    schedule 02.11.2018
comment
Я бы, наверное, просто десериализовал в Map<String, PartialItem>, а затем преобразовал бы в Vec<Item>; Я полагаю, вы хотите избежать создания переходного процесса Map?   -  person Shepmaster    schedule 02.11.2018
comment
Верный. И я бы также предпочел не реализовывать дополнительную структуру PartialItem. Для этого можно было бы использовать Option<String> в качестве имени, хотя я не думаю, что это оптимально. Может быть, переходный Map - это то, к чему я должен пойти, если лучшего варианта нет.   -  person Tim Visée    schedule 02.11.2018
comment
Возможно, вам придется реализовать десериализацию самостоятельно serde.rs/deserialize-map.html   -  person PitaJ    schedule 02.11.2018
comment
В спецификации YAML четко указано, что ключи сопоставления должны быть уникальными. Повторяющиеся ключи недействительны, хотя есть парсер / загрузчик, которые (некоторые даже молча) разрешают их.   -  person Anthon    schedule 03.11.2018


Ответы (2)


Один из способов - самостоятельно десериализовать карту:

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));
}

Но это могло добавить бесполезную копию.

person Stargateur    schedule 03.11.2018

Определите значение по умолчанию для поля Item::name

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

Этот трюк Item может использоваться как для десериализации, так и для преобразования в Vec из Items:

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();
person attdona    schedule 02.11.2018
comment
OP заявляет, что они хотят избежать создания временной карты - person Shepmaster; 02.11.2018