Rust Serde自定义反序列化:支持Map与List转为列表
Serde自定义反序列化:将列表/Map统一转为列表的通用实现问题
需求是:将结构体列表,或以结构体某个字段(如示例中的class)为键的结构体Map,统一反序列化为Vec<T>。比如示例中,entries字段既可以是Entry的列表,也可以是键为Class枚举tag、值为Entry剩余字段的Map,最终都要反序列化为Vec<Entry>。
示例测试代码如下:
#[cfg(test)] mod tests { #[test] fn deserialize() { #[derive(Debug, serde::Deserialize)] struct List { entries: Vec<Entry>, } #[derive(Debug, serde::Deserialize)] struct Entry { #[serde(flatten)] class: Class, label: Box<str>, } #[derive(Debug, serde::Deserialize)] #[serde(tag = "class")] enum Class { ClassOne(ClassOne), ClassTwo(ClassTwo), } #[derive(Debug, serde::Deserialize)] struct ClassOne { value: u32, } #[derive(Debug, serde::Deserialize)] struct ClassTwo { value: f32, } // ------------------------------ let input_list = r#" entries: - class: ClassOne value: 1234 label: Test#1 - class: ClassTwo value: 1.234 label: Test#2 - class: ClassOne value: 4321 label: Test#3 "#; let input_map = r#" entries: ClassOne: value: 1234 label: Test#1 ClassTwo: value: 1.234 label: Test#2 ClassOne: value: 4321 label: Test#3 "#; // 正常运行 let list = serde_yaml::from_str::<List>(input_list).unwrap(); println!("{list:?}"); // 会panic,需要让它正常运行 //let list = serde_yaml::from_str::<List>(input_map).unwrap(); //println!("{list:?}"); } }
尝试用Visitor实现通用处理,但遇到类型不匹配错误:
use serde::{ de::{MapAccess, SeqAccess, Visitor}, Deserialize, }; pub(crate) struct MapOrListVisitor<T, A> { insert: Box<dyn Fn(&str, &mut A) -> T>, } impl<'de, T, A> MapOrListVisitor<T, A> where T: Deserialize<'de>, A: MapAccess<'de>, { pub fn new(insert: impl Fn(&str, &mut A) -> T + 'static) -> Self { Self { insert: Box::new(insert), } } } impl<'de, T, A> Visitor<'de> for MapOrListVisitor<T, A> where T: Deserialize<'de>, A: MapAccess<'de> { type Value = Box<[T]>; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { todo!() } fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> where M: MapAccess<'de>, { let mut entries = Vec::<T>::with_capacity(map.size_hint().unwrap_or_default()); while let Some(key) = map.next_key::<Box<str>>()? { let entry = (&self.insert)(&key, &mut map); // 类型不匹配错误: // expected mutable reference `&mut A`, found mutable reference `&mut M` todo!() } Ok(entries.into_boxed_slice()) } fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error> where S: SeqAccess<'de>, { todo!() } }
问题分析与解决方案
错误原因
你定义的MapOrListVisitor将MapAccess的具体类型A作为泛型参数,导致闭包insert只能接收&mut A类型的参数,但visit_map方法会传入任意实现MapAccess<'de>的类型M,二者类型不兼容,从而引发错误。
修正思路
调整Visitor设计,不再绑定具体的MapAccess类型,而是在visit_map中自行处理键值对的反序列化,再构造目标类型T。对于列表场景直接反序列化元素;对于Map场景,将键补充到对应值的结构中,再反序列化为T。
通用实现代码
use serde::{ de::{self, MapAccess, SeqAccess, Visitor}, Deserialize, Deserializer, }; use std::fmt; use serde_yaml::Value as YamlValue; // 通用反序列化函数:支持列表/Map转Vec<T> // Map场景下会将键作为"class"字段插入值结构,再反序列化为T pub fn deserialize_map_or_list<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error> where D: Deserializer<'de>, T: Deserialize<'de>, { struct MapOrListVisitor<T>(std::marker::PhantomData<T>); impl<'de, T> Visitor<'de> for MapOrListVisitor<T> where T: Deserialize<'de>, { type Value = Vec<T>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a list or a map") } // 处理列表:直接反序列化每个元素 fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error> where S: SeqAccess<'de>, { let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0)); while let Some(item) = seq.next_element()? { vec.push(item); } Ok(vec) } // 处理Map:将key作为class字段插入value,再反序列化为T fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> where M: MapAccess<'de>, { let mut vec = Vec::with_capacity(map.size_hint().unwrap_or(0)); while let Some((key, mut value)) = map.next_entry::<Box<str>, YamlValue>()? { // 将key转为class字段插入value的Map中 let value_map = match &mut value { YamlValue::Mapping(m) => m, _ => return Err(de::Error::custom("map values must be objects")), }; value_map.insert( YamlValue::String("class".into()), YamlValue::String(key.into()), ); // 反序列化为目标类型T let item: T = serde_yaml::from_value(value).map_err(de::Error::custom)?; vec.push(item); } Ok(vec) } } deserializer.deserialize_any(MapOrListVisitor(std::marker::PhantomData)) }
在示例中使用该实现
修改List结构体的entries字段,指定自定义反序列化函数:
#[derive(Debug, serde::Deserialize)] struct List { #[serde(deserialize_with = "deserialize_map_or_list")] entries: Vec<Entry>, }
此时input_map即可正常反序列化为List,因为Map的每个键被自动作为class字段插入对应值的结构中,符合Entry的反序列化要求。
内容的提问来源于stack exchange,提问作者Giuseppe
相关产品推荐
相关产品推荐

