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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With