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

如何在Rust中为Serde内部标记枚举实现自定义反序列化器

Serde内部标记枚举适配数组类型Tag的实现方案

问题背景

从DynamoDB获取的实体数据中,type字段为仅包含单个字符串的列表格式(示例:{ type: ["Story"], ... })。目前已实现该字段的自定义反序列化逻辑,但希望改为使用Serde的内部标记枚举特性,直接完成全字段的反序列化,无需先转换为简化结构体。

现有代码实现

use serde::{Deserialize, Deserializer, Serialize};
use types::*;

#[derive( Debug)]
enum Type {
    Story,
    Layer,
    Unknown(String),
}

impl<'de> Deserialize<'de> for Type {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = <Vec<String>>::deserialize(deserializer)?;
        let k: &str = s[0].as_str();
        Ok(match k {
            "Story" => Type::Story,
            "Layer" => Type::Layer,
            v => Type::Unknown(v.to_string()),
        })
    }
}

#[derive(Deserialize, Serialize, Debug)]
pub struct SimpleItem {
    #[serde(rename = "type")]
    item_type: Type,
}

理想实现目标

use serde::{Deserialize, Deserializer, Serialize};
use types::*;

#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Type {
    Story { id: String, name: String, duration: u32 },
    Layer { id: String, layout: String },
    Unknown,
}

解决方案

核心问题在于Serde内部标记默认期望tag为字符串类型,而当前type是单元素字符串数组。通过给枚举的tag字段指定自定义反序列化逻辑,即可结合内部标记特性实现需求:

  1. 定义辅助反序列化类型
    复用原有数组转字符串的逻辑,封装为辅助类型:
use serde::{Deserialize, Deserializer};

// 将单元素字符串数组转为字符串的辅助类型
#[derive(Deserialize)]
struct ArrayToString(String);

impl<'de> Deserialize<'de> for ArrayToString {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = <Vec<String>>::deserialize(deserializer)?;
        s.into_iter()
            .next()
            .map(ArrayToString)
            .ok_or_else(|| serde::de::Error::custom("type数组不能为空"))
    }
}
  1. 修改枚举的Serde属性及实现
    指定tag的自定义反序列化函数,并处理未知类型:
use serde::{Deserialize, Debug};

#[derive(Deserialize, Debug)]
#[serde(tag = "type", deserialize_with = "deserialize_type_tag")]
enum Item {
    Story { id: String, name: String, duration: u32 },
    Layer { id: String, layout: String },
    #[serde(other)]
    Unknown,
}

// 自定义tag反序列化函数:将数组转为字符串供Serde匹配枚举变体
fn deserialize_type_tag<'de, D>(deserializer: D) -> Result<&'de str, D::Error>
where
    D: Deserializer<'de>,
{
    let tag = ArrayToString::deserialize(deserializer)?;
    Ok(tag.0.as_str())
}
  1. 验证使用
    直接反序列化DynamoDB数据到Item枚举即可:
let json = r#"{
    "type": ["Story"],
    "id": "123",
    "name": "My Story",
    "duration": 60
}"#;

let item: Item = serde_json::from_str(json).unwrap();
println!("{:?}", item); // 输出:Item::Story { id: "123", name: "My Story", duration: 60 }

实现说明

  • #[serde(tag = "type", deserialize_with = "deserialize_type_tag")]:指定枚举使用内部标记模式,并为type字段绑定自定义反序列化函数。
  • 自定义函数先将数组格式的tag转为字符串,再交给Serde匹配对应的枚举变体。
  • #[serde(other)]:捕获所有未匹配的类型,对应原有需求中的Unknown变体。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 13:17:51