rust 如何将一个map转换成一个自定义结构的vector,并有一个匹配键的字段?

x4shl7ld  于 11个月前  发布在  其他
关注(0)|答案(4)|浏览(148)

给定一个带有项目Map的yaml,

items:
  item1:
    uid: ab1234
    foo: bar
  item2:
    uid: cd5678
    foo: baz

字符串
如何使用serde将其解析为具有新字段“name”的Vec<Item>,该字段从原始map的键生成,

struct Item {
  name: String,  // to be populated by the key (item1, item2)
  uid: String,
  foo: String,
}


我试图让下面的代码(rustexplorer)工作,目前错误与Err(Error("items: invalid type: map, expected a sequence", line: 3, column: 3))

/*
[dependencies]
serde = "1.0.193"
serde_derive = "1.0.193"
serde_yaml = "0.9.27"
*/

use serde_derive::{Deserialize, Serialize};
use std::vec::Vec;

#[derive(Debug, Serialize, Deserialize)]
struct Item {
  name: String,
  uid: String,
  foo: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Schema {
    items: Vec<Item>,
}

fn main() {
    let input = r###"
items:
  item1:
    uid: ab1234
    foo: bar
  item2:
    uid: cd5678
    foo: baz
"###;

    let items = serde_yaml::from_str::<Schema>(input);
    println!("{:?}", items);
}

suzh9iv8

suzh9iv81#

serde_with有这样的东西:KeyValueMap

use serde::{Deserialize, Serialize};
use serde_with::{serde_as, KeyValueMap};

#[derive(Debug, Serialize, Deserialize)]
struct Item {
    #[serde(rename = "$key$")]
    name: String,
    uid: String,
    foo: String,
}

#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
struct Schema {
    #[serde_as(as = "KeyValueMap<_>")]
    items: Vec<Item>,
}

字符串

57hvy0tb

57hvy0tb2#

您可以使用自定义的解析器和Visitor来完成此操作,而无需中间分配:

use serde::Deserializer;
use serde::de::MapAccess;
use serde::de::Visitor;
use serde_derive::{Deserialize, Serialize};
use std::fmt;
use std::vec::Vec;

#[derive(Debug, Serialize, Deserialize)]
struct Item {
  name: String,
  uid: String,
  foo: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Schema {
    #[serde(deserialize_with = "deserialize_items")]
    items: Vec<Item>,
}

fn deserialize_items<'de, D> (deserializer: D) -> Result<Vec<Item>, D::Error>
where D: Deserializer<'de>,
{
    struct ItemsVisitor;

    impl<'de> Visitor<'de> for ItemsVisitor
    {
        type Value = Vec<Item>;

        fn expecting (&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("an items map")
        }
        
        fn visit_map<M> (self, mut map: M) -> Result<Vec<Item>, M::Error>
        where M: MapAccess<'de>,
        {
            #[derive(Deserialize)]
            struct ItemFields {
                uid: String,
                foo: String,
            }

            let mut result = vec![];
            while let Some ((name, fields)) = map.next_entry::<_, ItemFields>()? {
                result.push (Item { name, uid: fields.uid, foo: fields.foo });
            }
            Ok (result)
        }
    }
    
    deserializer.deserialize_map (ItemsVisitor)
}

fn main() {
    let input = r###"
items:
  item1:
    uid: ab1234
    foo: bar
  item2:
    uid: cd5678
    foo: baz
"###;

    let items = serde_yaml::from_str::<Schema>(input);
    println!("{:?}", items);
}

字符串
Playground
标签:https://serde.rs/stream-array.html

vnzz0bqm

vnzz0bqm3#

虽然这肯定不是解决这个问题的最好的方法,但你可以将tuple_vec_map crate和一个自定义的函数组合起来:

#[derive(Debug, Serialize, Deserialize)]
struct Schema {
    #[serde(deserialize_with = "vec_item")]
    items: Vec<Item>,
}

fn vec_item<'de, D>(de: D) -> Result<Vec<Item>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    struct ItemSerde {
        uid: String,
        foo: String,
    }
    #[derive(Deserialize)]
    #[serde(transparent)]
    struct SchemaSerde(#[serde(with = "tuple_vec_map")] Vec<(String, ItemSerde)>);

    let SchemaSerde(items) = Deserialize::deserialize(de)?;
    Ok(items
        .into_iter()
        .map(|(name, ItemSerde { uid, foo })| Item { name, uid, foo })
        .collect())
}

字符串
Explorer

5fjcxozz

5fjcxozz4#

你可以读入String-> RawItem(没有名字的项目)的临时Map,并将其转换为你需要的结构:

#[derive(Debug, Deserialize)]
struct Schema {
    #[serde(deserialize_with = "deserialize_schema_items")]
    items: Vec<Item>,
}

fn deserialize_schema_items<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Item>, D::Error> {
    #[derive(Deserialize)]
    struct RawItem {
        uid: String,
        foo: String,
    }
    let raw_items: IndexMap<String, RawItem> = Deserialize::deserialize(d)?;
    let items = raw_items
        .into_iter()
        .map(|(name, raw_item)| Item {
            name,
            uid: raw_item.uid,
            foo: raw_item.foo,
        })
        .collect();
    Ok(items)
}

字符串
上面的代码使用indexmap::IndexMap来保持顺序。如果你不关心顺序,你可以使用HashMap。如果你选择IndexMap,请注意,你需要打开indexmap机箱的serde功能。

相关问题