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

Rust中如何反序列化枚举:将未知字符串转为新类型变体并保留原字符串

解决Rust Serde外部标签枚举的未知值 fallback 问题

这个问题我之前也碰到过!serde对外部标签枚举(也就是直接序列化为字符串的枚举类型)确实不支持#[serde(other)]属性,因为它默认会严格匹配已知的变体重命名值,没有内置的 fallback 逻辑。不过有两种靠谱的解决办法,完全能实现你想要的反序列化行为:

方法一:手动实现自定义反序列化Visitor

这种方法不需要额外依赖,完全靠serde的原生API实现,适合不想引入第三方crate的场景。

步骤说明:

  1. 先移除枚举上的#[derive(Deserialize)],我们要手动实现Deserialize trait
  2. 定义一个Visitor结构体,用来处理字符串的匹配逻辑:先尝试匹配已知的重命名值,匹配失败就返回Other(String)
  3. 为枚举实现Deserialize trait,指定用我们的Visitor来处理字符串反序列化

完整代码示例:

use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer};
use std::fmt;

#[derive(Debug, PartialEq)]
enum ExampleEnum {
    Variant1,
    Variant2,
    Other(String),
}

// 定义自定义Visitor
struct ExampleEnumVisitor;

impl<'de> Visitor<'de> for ExampleEnumVisitor {
    type Value = ExampleEnum;

    // 反序列化失败时显示的提示信息
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a string matching 'variant1-rename', 'variant2-rename', or any other string")
    }

    // 核心匹配逻辑
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        match v {
            "variant1-rename" => Ok(ExampleEnum::Variant1),
            "variant2-rename" => Ok(ExampleEnum::Variant2),
            // 所有不匹配的字符串都进入Other变体
            s => Ok(ExampleEnum::Other(s.to_string())),
        }
    }
}

// 为枚举实现Deserialize trait
impl<'de> Deserialize<'de> for ExampleEnum {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(ExampleEnumVisitor)
    }
}

测试验证:

你可以用下面的测试代码验证是否符合预期:

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json;

    #[test]
    fn test_known_variants() {
        assert_eq!(
            serde_json::from_str::<ExampleEnum>(r#""variant1-rename""#).unwrap(),
            ExampleEnum::Variant1
        );
        assert_eq!(
            serde_json::from_str::<ExampleEnum>(r#""variant2-rename""#).unwrap(),
            ExampleEnum::Variant2
        );
    }

    #[test]
    fn test_unknown_variant() {
        assert_eq!(
            serde_json::from_str::<ExampleEnum>(r#""foobar""#).unwrap(),
            ExampleEnum::Other("foobar".to_string())
        );
    }

    #[test]
    fn test_struct_deserialization() {
        #[derive(Debug, Deserialize, PartialEq)]
        struct TestStruct {
            property: ExampleEnum,
        }
        let json = r#"{"property": "foobar"}"#;
        let result = serde_json::from_str::<TestStruct>(json).unwrap();
        assert_eq!(result.property, ExampleEnum::Other("foobar".to_string()));
    }
}

方法二:用serde_with crate简化实现

如果你不想写太多样板代码,可以用serde_with这个第三方crate,它提供了很多serde的辅助工具,能大幅简化这个场景的实现。

步骤说明:

  1. 在Cargo.toml中添加依赖:serde_with = "3.0"
  2. 用#[serde(try_from = "String")]属性告诉serde先把输入转成String,再通过TryFrom trait转换为枚举
  3. 为枚举实现TryFrom<String>,在里面处理匹配逻辑,未知值直接返回Other

完整代码示例:

use serde::Deserialize;
use serde_with::serde_as;

#[serde_as]
#[derive(Debug, PartialEq, Deserialize)]
#[serde(try_from = "String")]
enum ExampleEnum {
    #[serde(rename = "variant1-rename")]
    Variant1,
    #[serde(rename = "variant2-rename")]
    Variant2,
    Other(String),
}

// 实现TryFrom<String>处理转换逻辑
impl TryFrom<String> for ExampleEnum {
    type Error = (); // 这里不会返回错误,用()占位

    fn try_from(s: String) -> Result<Self, Self::Error> {
        match s.as_str() {
            "variant1-rename" => Ok(ExampleEnum::Variant1),
            "variant2-rename" => Ok(ExampleEnum::Variant2),
            _ => Ok(ExampleEnum::Other(s)),
        }
    }
}

这个方法的测试逻辑和方法一完全一致,同样能实现预期的反序列化行为。

两种方法的对比

  • 方法一:无额外依赖,完全原生实现,适合轻量场景或对依赖严格的项目
  • 方法二:代码更简洁,样板代码少,适合快速开发,但需要引入第三方crate

不管选哪种,都能完美解决你遇到的问题:已知的重命名值对应Variant1/Variant2,所有未知字符串都自动进入Other(String)变体。

内容的提问来源于stack exchange,提问作者valentinegb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 18:45:46