如何让Serde支持带字段枚举变体同时匹配字符串形式与对象形式的反序列化
如何让Serde支持带字段枚举变体同时匹配字符串形式与对象形式的反序列化
这个需求我之前做项目的时候刚好碰到过,太懂这种卡壳的感觉了!咱们不用搞花里胡哨的额外变体,也不用绕弯路,用Serde的自定义反序列化就能完美解决,我给你一步步说清楚:
首先,核心问题是咱们的Three变体既要能匹配字符串"three",又要能匹配带value字段的对象结构。Serde默认对带字段的变体只会识别对象形式,所以得手动给它加个“兼容字符串”的逻辑。
具体实现步骤
- 先保留你原来的枚举结构,然后我们给它实现自定义的反序列化逻辑,替代默认的自动派生行为。
- 我们需要写一个访客(Visitor)类型——这是Serde反序列化的核心工具,用来告诉程序怎么处理不同格式的输入。
直接上可运行的代码示例:
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; use std::fmt; use rust_decimal::Decimal; // 这是我们最终要的目标枚举,和你原来的结构完全一致 #[derive(Debug, PartialEq)] pub enum Flag { One, Two, Three { value: Option<Decimal> }, } impl<'de> Deserialize<'de> for Flag { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { // 定义专属的访客类型,用来处理Flag的反序列化逻辑 struct FlagVisitor; impl<'de> Visitor<'de> for FlagVisitor { type Value = Flag; // 反序列化失败时会显示这个提示,方便调试 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("字符串形式(如\"one\")或对象形式(如{\"three\": {\"value\": 123}})的Flag枚举值") } // 处理纯字符串格式的输入,比如"one"、"two"、"three" fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: de::Error, { match s.to_lowercase().as_str() { "one" => Ok(Flag::One), "two" => Ok(Flag::Two), "three" => Ok(Flag::Three { value: None }), // 字符串形式直接映射成value为None的Three变体 _ => Err(de::Error::unknown_variant(s, &["one", "two", "three"])), } } // 处理对象格式的输入,比如{"one": {}}、{"three": {"value": 456}} fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de>, { // 先读取对象的键(也就是枚举变体的名称) let variant_name: String = map.next_key()? .ok_or_else(|| de::Error::custom("空对象无法匹配任何Flag变体"))?; match variant_name.to_lowercase().as_str() { "one" => { // One变体没有字段,所以如果后面还有额外值就是非法输入 if map.next_value::<()>().is_ok() { Err(de::Error::custom("变体'one'不应该包含任何字段")) } else { Ok(Flag::One) } } "two" => { // 和One变体一样,不能有额外字段 if map.next_value::<()>().is_ok() { Err(de::Error::custom("变体'two'不应该包含任何字段")) } else { Ok(Flag::Two) } } "three" => { // 反序列化three对应的value字段,自动处理None的情况 let value: Option<Decimal> = map.next_value()?; Ok(Flag::Three { value }) } _ => Err(de::Error::unknown_variant(&variant_name, &["one", "two", "three"])), } } } // 告诉反序列化器使用我们自定义的访客来处理输入 deserializer.deserialize_any(FlagVisitor) } }
为什么你之前的尝试没生效?
我帮你复盘下之前的方案问题:
#[serde(default)]:这个属性是给字段设置默认值,只能处理空对象{"three": {}},管不到变体本身的字符串格式,所以不符合你的需求。#[serde(untagged)]:这个属性是给整个枚举用的,会让所有变体都变成无标签模式,很容易和其他变体的结构产生冲突(比如One字符串可能会被误判成其他结构),风险太高。- 加
ThreeNone变体:虽然能临时解决问题,但会让枚举变得冗余,后续处理枚举时还要多分支判断,不够优雅,也不符合代码简洁性的要求。
测试验证
你可以用下面的代码测试所有场景,确保覆盖你要的所有情况:
fn main() { // 测试字符串形式的three let s1 = r#""three""#; let flag1: Flag = serde_json::from_str(s1).unwrap(); assert_eq!(flag1, Flag::Three { value: None }); // 测试带value的对象形式 let s2 = r#"{"three": {"value": 123.45}}"#; let flag2: Flag = serde_json::from_str(s2).unwrap(); assert_eq!(flag2, Flag::Three { value: Some(Decimal::from_f64(123.45).unwrap()) }); // 测试空对象形式的three let s3 = r#"{"three": {}}"#; let flag3: Flag = serde_json::from_str(s3).unwrap(); assert_eq!(flag3, Flag::Three { value: None }); // 测试其他正常变体 let s4 = r#""one""#; let flag4: Flag = serde_json::from_str(s4).unwrap(); assert_eq!(flag4, Flag::One); }
这样所有你要的场景就都能完美支持啦!
备注:内容来源于stack exchange,提问作者Mindxxxd
相关产品推荐
相关产品推荐

