如何将YAML的Map反序列化为带键对应name字段的自定义结构体Vec
问题描述
给定如下YAML内容,其中items是一个键值对映射结构:
items: item1: uid: ab1234 foo: bar item2: uid: cd5678 foo: baz
需要用Serde将其解析为Vec<Item>,其中Item的name字段由原映射的键(如item1、item2)填充,Item定义如下:
struct Item { name: String, // 由映射键填充(item1, item2) uid: String, foo: String, }
尝试了以下代码,但报错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); }
解决方案
报错的核心原因是:YAML中的items是键值对映射类型,但代码里Schema的items字段定义为Vec<Item>(序列类型),Serde无法自动将映射转换为序列。要实现需求,需要手动处理映射到序列的转换,把映射的键赋值给Item的name字段。
下面提供两种可行的实现方式:
方式一:为Vec<Item>自定义反序列化逻辑
通过为Vec<Item>实现Deserialize trait,直接将YAML映射解析为目标数组:
use serde::{Deserialize, Deserializer}; use serde_derive::Deserialize; use std::collections::HashMap; #[derive(Debug, Deserialize)] struct Item { #[serde(skip_deserializing)] // 跳过自动反序列化,后续手动填充 name: String, uid: String, foo: String, } // 为Vec<Item>实现自定义反序列化逻辑 impl<'de> Deserialize<'de> for Vec<Item> { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { // 先将YAML映射解析为HashMap<String, Item> let map = HashMap::<String, Item>::deserialize(deserializer)?; // 遍历HashMap,将键赋值给Item的name字段,最终收集为Vec Ok(map.into_iter() .map(|(name, mut item)| { item.name = name; item }) .collect()) } } #[derive(Debug, Deserialize)] struct Schema { items: Vec<Item>, } fn main() { let input = r###" items: item1: uid: ab1234 foo: bar item2: uid: cd5678 foo: baz "###; let result = serde_yaml::from_str::<Schema>(input); println!("{:?}", result); }
方式二:先解析为HashMap再手动转换
如果不想自定义反序列化逻辑,可以先把items解析成中间类型的HashMap,再转换为目标Vec<Item>:
use serde_derive::Deserialize; use std::collections::HashMap; // 中间类型:仅包含Item除name外的字段 #[derive(Debug, Deserialize)] struct ItemRaw { uid: String, foo: String, } #[derive(Debug)] struct Item { name: String, uid: String, foo: String, } #[derive(Debug, Deserialize)] struct SchemaRaw { items: HashMap<String, ItemRaw>, } fn main() { let input = r###" items: item1: uid: ab1234 foo: bar item2: uid: cd5678 foo: baz "###; // 先解析为中间结构 let raw_schema = serde_yaml::from_str::<SchemaRaw>(input).unwrap(); // 转换为目标Vec<Item> let items: Vec<Item> = raw_schema.items .into_iter() .map(|(name, raw_item)| Item { name, uid: raw_item.uid, foo: raw_item.foo, }) .collect(); println!("{:?}", items); }
两种方式都能实现需求:方式一更简洁,适合需要直接反序列化为目标类型的场景;方式二更直观,适合新手理解转换逻辑。
内容的提问来源于stack exchange,提问作者Sparkler
相关产品推荐
相关产品推荐

