提问人:superstator 提问时间:8/3/2023 最后编辑:cafce25superstator 更新时间:8/3/2023 访问量:69
可选包装枚举的反序列化
Deserialization of optionally-wrapped enum
问:
我有一个接受 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" } } }
InnerEnum
OuterEnum
{ "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 字段是简单的字符串而不是映射/数组/等。有没有合理的方法可以做到这一点?deserialize
OuterEnum
#[serde(other)]
OuterEnum::Other
答:
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/...LegacyOuterEnum
serialize_with
评论