Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to tell Serde to use a struct field as a map's key?

I have a map of items that I would like to serialize to a list of structs, each having a field for the corresponding key.

Imagine having a YAML file like this:

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

And a corresponding structure like this:

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

I would like to deserialize the named items into a Vec<Item> instead of a Map<String, Item>. The item names (name_a, ...) are put into the name field of the Item objects.

I've attempted the following:

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

This however doesn't work and produces the invalid type: map, expected a sequence error.

I'd prefer to avoid creating a transient Map<String, PartialItem> that is converted to a Vec, and I would also prefer not to implement an additional PartialItem struct. Using an Option<String> as name would be possible, although I don't think this is optimal.

like image 499
Tim Visée Avatar asked Nov 02 '18 17:11

Tim Visée


People also ask

Is SerDe fast?

Serde, the incumbent serialization/deserialization library, is elegant, flexible, fast to run, and slow to compile. The API has three facets. The Serialize and Deserialize traits bridge the formats to the data structures the user might want to serialize and deserialize.

How does SerDe work rust?

Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. The Serde ecosystem consists of data structures that know how to serialize and deserialize themselves along with data formats that know how to serialize and deserialize other things.

What is SerDe name?

SerDe is a short name for "Serializer and Deserializer." Hive uses SerDe (and FileFormat) to read and write table rows. HDFS files --> InputFileFormat --> <key, value> --> Deserializer --> Row object.

What is serialization and Deserialization in Rust?

Rustc has to serialize and deserialize various data during compilation. Specifically: "Crate metadata", mainly query outputs, are serialized in a binary format into rlib and rmeta files that are output when compiling a library crate, these are then deserialized by crates that depend on that library.


1 Answers

One way is to deserialize the map yourself:

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

Output:

VecItem(
    [
        Item {
            name: "name_a",
            some_field: 0
        },
        Item {
            name: "name_b",
            some_field: 1
        },
        Item {
            name: "name_c",
            some_field: 2
        }
    ]
)

If you don't want of Somefield structure. You could also use this:

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

But this could add some useless copy.

like image 137
Stargateur Avatar answered Sep 27 '22 17:09

Stargateur