You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 08:39:55