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

如何为含单元变体的无标签Rust枚举简化Serde实现?

带预定义单元变体与自定义字符串的枚举序列化/反序列化优化方案

问题背景

我们需要实现一个枚举,满足以下序列化/反序列化需求:

  • 预定义单元变体(如LessThan)序列化为固定蛇形字符串(如"less_than")
  • Custom(String)变体直接序列化为传入的字符串值
  • 所有变体都以纯字符串形式存在,而非带标签的JSON对象

理想的枚举定义如下:

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BinaryComparisonOperator {
    LessThan,
    GreaterThan,
    Equal,
    Custom(String),
}

期望的序列化映射:

Rust 枚举变体JSON 输出
BinaryComparisonOperator::LessThan"less_than"
BinaryComparisonOperator::GreaterThan"greater_than"
BinaryComparisonOperator::Equal"equal"
BinaryComparisonOperator::Custom("other_op")"other_op"

遇到的问题

  1. 默认自动推导:单元变体序列化符合预期,但Custom变体被序列化为带外部标签的对象:{ "custom": "other_op" },不符合需求。
  2. 添加#[serde(untagged)]:修复了Custom变体的序列化,但单元变体序列化后变为null,且反序列化时所有预定义字符串都会被解析为Custom变体,无法匹配单元变体。

原有复杂实现

我们最终实现了可用版本,但需要手动编写Serialize实现,并为每个单元变体单独编写反序列化解析函数,样板代码冗余:

use serde::{de, Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Deserialize)]
#[serde(untagged)]
pub enum BinaryComparisonOperator {
    #[serde(deserialize_with = "parse_less_than")]
    LessThan,
    #[serde(deserialize_with = "parse_greater_than")]
    GreaterThan,
    #[serde(deserialize_with = "parse_equal")]
    Equal,
    Custom(String),
}

impl Serialize for BinaryComparisonOperator {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            BinaryComparisonOperator::LessThan => serializer.serialize_str("less_than"),
            BinaryComparisonOperator::GreaterThan => serializer.serialize_str("greater_than"),
            BinaryComparisonOperator::Equal => serializer.serialize_str("equal"),
            BinaryComparisonOperator::Custom(s) => serializer.serialize_str(&s),
        }
    }
}

fn string_p<'de, D>(input: String, expected: &str) -> Result<(), D::Error>
where
    D: de::Deserializer<'de>,
{
    if input == expected {
        Ok(())
    } else {
        Err(de::Error::custom("invalid value"))
    }
}

fn parse_less_than<'de, D>(deserializer: D) -> Result<(), D::Error>
where
    D: de::Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    string_p(s, "less_than")
}

// 需重复编写GreaterThan、Equal对应的parse_xxx函数

优化解决方案

方案1:使用serde_with库简化实现

借助serde_with库的类型转换工具,只需编写一次字符串与枚举的双向转换逻辑,即可完成序列化/反序列化:

  1. 添加依赖:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_with = "3.0"
  1. 实现枚举:
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};

#[serde_as]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(try_from = "String", into = "String")]
pub enum BinaryComparisonOperator {
    LessThan,
    GreaterThan,
    Equal,
    Custom(String),
}

// 从String转换为枚举
impl TryFrom<String> for BinaryComparisonOperator {
    type Error = String;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        match s.as_str() {
            "less_than" => Ok(Self::LessThan),
            "greater_than" => Ok(Self::GreaterThan),
            "equal" => Ok(Self::Equal),
            other => Ok(Self::Custom(other.to_string())),
        }
    }
}

// 从枚举转换为String
impl From<BinaryComparisonOperator> for String {
    fn from(op: BinaryComparisonOperator) -> Self {
        match op {
            BinaryComparisonOperator::LessThan => "less_than".to_string(),
            BinaryComparisonOperator::GreaterThan => "greater_than".to_string(),
            BinaryComparisonOperator::Equal => "equal".to_string(),
            BinaryComparisonOperator::Custom(s) => s,
        }
    }
}

方案2:手动实现Deserialize(无额外依赖)

如果不想引入第三方库,可以通过自定义Visitor实现反序列化,同时手动编写简洁的Serialize逻辑:

use serde::{de, Deserialize, Serialize};
use serde::de::{Visitor, Error};
use std::fmt;

#[derive(Clone, Debug, PartialEq)]
pub enum BinaryComparisonOperator {
    LessThan,
    GreaterThan,
    Equal,
    Custom(String),
}

// 序列化逻辑:直接输出对应字符串
impl Serialize for BinaryComparisonOperator {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let s = match self {
            BinaryComparisonOperator::LessThan => "less_than",
            BinaryComparisonOperator::GreaterThan => "greater_than",
            BinaryComparisonOperator::Equal => "equal",
            BinaryComparisonOperator::Custom(s) => s,
        };
        serializer.serialize_str(s)
    }
}

// 反序列化逻辑:自定义Visitor匹配字符串
impl<'de> Deserialize<'de> for BinaryComparisonOperator {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        struct OperatorVisitor;

        impl<'de> Visitor<'de> for OperatorVisitor {
            type Value = BinaryComparisonOperator;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a comparison operator string")
            }

            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
            where
                E: Error,
            {
                match s {
                    "less_than" => Ok(BinaryComparisonOperator::LessThan),
                    "greater_than" => Ok(BinaryComparisonOperator::GreaterThan),
                    "equal" => Ok(BinaryComparisonOperator::Equal),
                    other => Ok(BinaryComparisonOperator::Custom(other.to_string())),
                }
            }
        }

        deserializer.deserialize_str(OperatorVisitor)
    }
}

这两种方案都大幅减少了样板代码,同时完全满足需求:单元变体和自定义变体都以纯字符串形式序列化/反序列化,且逻辑清晰易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 20:12:03