如何为含单元变体的无标签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" |
遇到的问题
- 默认自动推导:单元变体序列化符合预期,但
Custom变体被序列化为带外部标签的对象:{ "custom": "other_op" },不符合需求。 - 添加
#[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库的类型转换工具,只需编写一次字符串与枚举的双向转换逻辑,即可完成序列化/反序列化:
- 添加依赖:
[dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_with = "3.0"
- 实现枚举:
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
相关产品推荐
相关产品推荐

