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

如何通过Rust Serde实现枚举条件扁平化,适配JSON多Body字段场景?

解决Rust Serde中条件扁平化枚举处理TextBody/HtmlBody的问题

你需要处理三种JSON场景:仅含TextBody、仅含HtmlBody、同时含两者,并且希望序列化/反序列化时不生成嵌套的Body字段,而是将这两个字段直接平级放在外层结构中。原方案中试图给枚举变体加#[serde(flatten)]但该属性不支持枚举变体,导致无法实现需求。

解决方案

核心思路是通过中间结构体+自定义Serde序列化/反序列化逻辑,将枚举的三种状态映射到对应的JSON字段组合,同时通过flatten属性实现字段平级输出。

步骤1:定义核心枚举和中间结构体

首先定义代表三种Body状态的枚举,以及用于对接JSON字段的中间结构体:

use serde::{Serialize, Deserialize, Deserializer, Serializer};
use serde_json;

// 代表三种Body状态的枚举
#[derive(Debug, Clone, PartialEq)]
pub enum Body {
    Text(String),
    Html(String),
    HtmlAndText { html: String, text: String },
}

// 中间结构体,对应JSON中的TextBody和HtmlBody字段
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct BodyFields {
    #[serde(rename = "TextBody", skip_serializing_if = "Option::is_none")]
    text: Option<String>,
    #[serde(rename = "HtmlBody", skip_serializing_if = "Option::is_none")]
    html: Option<String>,
}

步骤2:实现枚举与中间结构体的转换

实现From trait,完成Body和BodyFields的双向转换:

// 从Body引用转换为BodyFields
impl From<&Body> for BodyFields {
    fn from(body: &Body) -> Self {
        match body {
            Body::Text(text) => BodyFields { text: Some(text.clone()), html: None },
            Body::Html(html) => BodyFields { text: None, html: Some(html.clone()) },
            Body::HtmlAndText { text, html } => BodyFields { text: Some(text.clone()), html: Some(html.clone()) },
        }
    }
}

// 从Body转换为BodyFields
impl From<Body> for BodyFields {
    fn from(body: Body) -> Self {
        (&body).into()
    }
}

步骤3:自定义Serde序列化/反序列化逻辑

创建模块封装序列化和反序列化函数,对接Serde框架:

mod body_serde {
    use super::*;
    use serde::{Serialize, Deserialize, Deserializer, Serializer};

    // 序列化:将Body转换为BodyFields后序列化
    pub fn serialize<S>(body: &Body, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let fields: BodyFields = body.into();
        fields.serialize(serializer)
    }

    // 反序列化:先解析为BodyFields,再转换为Body枚举
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Body, D::Error>
    where
        D: Deserializer<'de>,
    {
        let fields = BodyFields::deserialize(deserializer)?;
        match (fields.text, fields.html) {
            (Some(text), None) => Ok(Body::Text(text)),
            (None, Some(html)) => Ok(Body::Html(html)),
            (Some(text), Some(html)) => Ok(Body::HtmlAndText { text, html }),
            (None, None) => Err(serde::de::Error::custom("必须包含TextBody或HtmlBody字段")),
        }
    }
}

步骤4:定义外层消息结构体

在包含Body的外层结构体中,使用#[serde(with = "body_serde")]和#[serde(flatten)]属性实现字段平级处理:

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Message {
    pub Field1: String,
    #[serde(with = "body_serde")]
    #[serde(flatten)]
    pub body: Body,
    pub Field4: String,
}

测试验证

通过以下用例验证功能是否正常:

fn main() {
    // 测试反序列化仅TextBody的场景
    let text_json = r#"{
        "Field1": "test_value",
        "TextBody": "sample text content",
        "Field4": "another_value"
    }"#;
    let text_msg: Message = serde_json::from_str(text_json).unwrap();
    assert_eq!(text_msg.body, Body::Text("sample text content".to_string()));

    // 测试反序列化仅HtmlBody的场景
    let html_json = r#"{
        "Field1": "test_value",
        "HtmlBody": "<html><body>Hello World!</body></html>",
        "Field4": "another_value"
    }"#;
    let html_msg: Message = serde_json::from_str(html_json).unwrap();
    assert_eq!(html_msg.body, Body::Html("<html><body>Hello World!</body></html>".to_string()));

    // 测试反序列化同时包含两个字段的场景
    let both_json = r#"{
        "Field1": "test_value",
        "TextBody": "sample text",
        "HtmlBody": "<html><body>Hello!</body></html>",
        "Field4": "another_value"
    }"#;
    let both_msg: Message = serde_json::from_str(both_json).unwrap();
    assert_eq!(both_msg.body, Body::HtmlAndText {
        text: "sample text".to_string(),
        html: "<html><body>Hello!</body></html>".to_string()
    });

    // 测试序列化场景
    let serialized_msg = Message {
        Field1: "test".to_string(),
        body: Body::HtmlAndText {
            text: "serialized text".to_string(),
            html: "<html>serialized</html>".to_string()
        },
        Field4: "test4".to_string()
    };
    let json_output = serde_json::to_string_pretty(&serialized_msg).unwrap();
    println!("序列化后的JSON:\n{}", json_output);
    // 输出的JSON中不会有"body"字段,而是直接包含TextBody和HtmlBody
}

原方案不可行的原因

  • #[serde(flatten)]仅支持结构体字段,无法作用于枚举变体;
  • 带#[serde(untagged)]的枚举会按顺序尝试匹配变体:当JSON同时包含TextBody和HtmlBody时,前两个单字段变体都会因存在额外字段匹配失败,第三个结构体变体由于没有flatten支持,无法平级解析字段,最终导致编译或运行错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 21:02:08