我有一个接受JSON有效负载的Rust Web服务端点.有效负载包含嵌套的枚举 struct ,类似于:

    #[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
    }

序列化是一块蛋糕,对于像{ "message": { "wrapped": { "value_a": "foo" } } }这样的有效负载,序列化效果很好,但是该API的旧版本没有对蛇的情况进行重命名,也没有用OuterEnum包装InnerEnum,所以较老的客户端倾向于使用像{ "message": { "ValueA": "foo" } }这样的有效负载进行调用

我希望通过允许将任一有效负载反序列化为当前 struct 来支持那些旧的客户端,而无需公开API的多个版本或维护端点的多个版本.

到目前为止,我已经try 在message字段上使用#[serde(deserialize_with = "...")]来调用如下函数

    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)
        }
    }

但是deserialize调用使用反序列化程序,所以我不能两次调用它.我曾try 为OuterEnum构建一个定制的反序列化程序,但我不知道如何以通用的方式将字段反序列化为映射,或者在反序列化之前查看枚举标记.我甚至try 在OuterEnum::Other上使用#[serde(other)]作为备用,但显然这既要求枚举变量是单元类型,也要求输入json字段是简单的字符串而不是映射/数组/等等.有什么合理的方法可以实现这一点吗?

推荐答案

在这些情况下,我通常使用一些额外的帮助器 struct .再加上#[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" } }"##));
}

playground

Rust相关问答推荐

什么是Rust惯用的方式来使特征向量具有单个向量项的别名?

什么时候铁 rust FFI边界上的panic 是未定义的行为?

integer cast as pointer是什么意思

RUST应用程序正在退出,错误代码为:(退出代码:0xc0000005,STATUS_ACCESS_VIOLATION)

为什么Rust不支持带关联常量的特征对象?

正在将带有盒的异步特征迁移到新的异步_fn_in_特征功能

如何将生存期参数添加到框<>;具有dyn类型别名

如何将带有嵌套borrow /NLL 的 Rust 代码提取到函数中

如何对一个特征的两个实现进行单元测试?

仅在使用 &mut 或线程时borrow 的数据在闭包之外转义?

中文优化标题:跳出特定循环并返回一个值

Rust 编译器不统一在 if let 表达式的分支中都 impl Future 的类型

Rust 中的生命周期:borrow 的 mut 数据

仅当函数写为闭包时才会出现生命周期错误

错误:将自定义 proc_macro 与用Rust 的宝贝编写的属性一起使用时,无法在此范围内找到属性

`移动||异步移动{...}`,如何知道哪个移动正在移动哪个?

没有分号的返回表达式的性能是否比使用返回更好?在Rust ?

为什么指定生命周期让我返回一个引用?

Rustlings 切片原语

意外的正则表达式模式匹配