可选包装枚举的反序列化

Deserialization of optionally-wrapped enum

提问人:superstator 提问时间:8/3/2023 最后编辑:cafce25superstator 更新时间:8/3/2023 访问量:69

问:

我有一个接受 JSON 有效负载的 Rust Web 服务端点。有效负载包含嵌套枚举结构,如下所示:

    #[derive(Serialize, Deserialize, Debug, PartialEq)]
    #[serde(rename_all = "snake_case")]
    enum InnerEnum {
        ValueA(String),
        ValueB
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq)]
    #[serde(rename_all = "snake_case")]
    enum OuterEnum {
        Wrapped(InnerEnum),
        Other
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq)]
    struct Message {
        message: OuterEnum
    }

序列化是小菜一碟,这适用于像这样的有效负载,但此 API 的旧版本没有进行蛇形大小写重命名,也没有包装 ,因此较旧的客户端倾向于使用像这样的有效负载进行调用{ "message": { "wrapped": { "value_a": "foo" } } }InnerEnumOuterEnum{ "message": { "ValueA": "foo" } }

我希望通过允许将任一有效负载反序列化到当前结构,在不公开多个版本的 API 或维护多个版本的端点的情况下支持这些旧客户端。

到目前为止,我已经尝试在现场调用类似#[serde(deserialize_with = "...")]message

    fn enum_deserializer<'de, D>(de: D) -> Result<OuterEnum, D::Error> where D: serde::Deserializer<'de> {
        if let Ok(inner) = InnerEnum::deserialize(de) {
            Ok(OuterEnum::Wrapped(inner))
        } else {
            OuterEnum::deserialize(de)
        }
    }

但是调用会消耗反序列化程序,因此我不能调用它两次。我尝试为 构建自定义反序列化程序,但我无法弄清楚如何以通用方式将字段反序列化为映射,或者在反序列化之前查看枚举标签。我什至尝试仅将 on 用作后备,但显然这要求枚举变体是单位类型,并且输入 json 字段是简单的字符串而不是映射/数组/等。有没有合理的方法可以做到这一点?deserializeOuterEnum#[serde(other)]OuterEnum::Other

rust serde serde-json

评论

1赞 PitaJ 8/3/2023
这看起来怎么样?: play.rust-lang.org/...

答:

1赞 PitaJ 8/3/2023 #1

在这些情况下,我通常会使用一些额外的辅助结构。结合大量使用 ,您应该能够处理各种遗留格式:#[serde(alias)]

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

#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum InnerEnum {
    #[serde(rename = "value_a")]
    #[serde(alias = "ValueA")]
    ValueA(String),
    #[serde(rename = "value_b")]
    #[serde(alias = "ValueB")]
    ValueB
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum OuterEnum {
    #[serde(rename = "wrapped")]
    #[serde(alias = "Wrapped")]
    Wrapped(InnerEnum),
    #[serde(rename = "other")]
    #[serde(alias = "Other")]
    Other
}

#[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)]
enum LegacyOuterEnum {
    #[serde(rename = "wrapped")]
    #[serde(alias = "Wrapped")]
    Wrapped(InnerEnum),
    #[serde(rename = "other")]
    #[serde(alias = "Other")]
    Other
}

#[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)]
enum MessageHelper {
    Normal {
        message: OuterEnum,
    },
    Legacy {
        message: LegacyOuterEnum,
    },
}

#[derive(Serialize, Debug, PartialEq)]
struct Message {
    message: OuterEnum,
}

impl<'de> Deserialize<'de> for Message {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(match <MessageHelper as Deserialize>::deserialize(deserializer)? {
            MessageHelper::Normal { message } => Message { message },
            MessageHelper::Legacy { message } => Message {
                message: match message {
                    LegacyOuterEnum::Wrapped(i) => OuterEnum::Wrapped(i),
                    LegacyOuterEnum::Other => OuterEnum::Other,
                }
            }
        })
    }
}

fn main() {
    dbg!(serde_json::from_str::<Message>(r##"{ "message": { "wrapped": { "value_a": "foo" } } }"##));
    dbg!(serde_json::from_str::<Message>(r##"{ "message": { "ValueA": "foo" } }"##));
}

操场

评论

1赞 superstator 8/5/2023
fwiw,我能够简化这一点,并通过使用以下方法避免重复的结构:play.rust-lang.org/...LegacyOuterEnumserialize_with