使用#[serde(untagged)]和#[serde(with)]的组合反序列化枚举

4
我正在尝试使用actix-web服务器作为小型堆栈的网关,以确保堆栈内部严格的数据格式,同时允许用户一些自由。为此,我希望将JSON字符串反序列化为结构体,然后验证它,再次序列化并发布到消息代理。主要的数据部分是包含整数、浮点数和日期时间的数组数组。我使用serde进行反序列化,并使用chrono处理日期时间。我尝试使用结构体结合枚举来允许不同的类型:
#[derive(Serialize, Deserialize)]
pub struct Data {
    pub column_names: Option<Vec<String>>,
    pub values: Vec<Vec<ValueType>>,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum ValueType {
    I32(i32),
    F64(f64),
    #[serde(with = "datetime_handler")]
    Dt(DateTime<Utc>),
}

由于chrono::DateTime<T>没有实现Serialize,因此我添加了一个自定义模块,类似于 serde文档中描述的方式

mod datetime_handler {
    use chrono::{DateTime, TimeZone, Utc};
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = dt.to_rfc3339();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        println!("Checkpoint 1");
        let s = String::deserialize(deserializer)?;
        println!("{}", s);
        println!("Checkpoint 2");
        let err1 = match DateTime::parse_from_rfc3339(&s) {
            Ok(dt) => return Ok(dt.with_timezone(&Utc)),
            Err(e) => Err(e),
        };
        println!("Checkpoint 3");

        const FORMAT1: &'static str = "%Y-%m-%d %H:%M:%S";
        match Utc.datetime_from_str(&s, FORMAT1) {
            Ok(dt) => return Ok(dt.with_timezone(&Utc)),
            Err(e) => println!("{}", e), // return first error not second if both fail
        };
        println!("Checkpoint 4");
        
        return err1.map_err(serde::de::Error::custom);
    }
}

这里尝试两种不同的时间格式,并适用于DateTime字符串。
问题在于Serde将按顺序匹配数据与每个变量,成功反序列化的第一个变量将被返回。
发生了什么?我该如何解决?
我的想法是,要么我没有以正确的方式进行反序列化,要么我以某种方式“覆盖”了派生的反序列化。
请注意保留HTML标签。

我无法理解你的问题。在我的测试环境中,它是可以正常工作的。也许你可以发一下你正在尝试反序列化的JSON,而不仅仅是Rust类型。https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2d3c77811bae9129a3e86d1346598181 - jonasbb
@jonasbb,这只会发生在表示数字的字符串(这可能已经是问题的解决方案)像这样:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6eaa91d2f3446814ec90426fceee832d - Haifischbecken
Serde不会自动执行类型转换。如果它看到一个JSON字符串,它将将其映射到Rust字符串。我喜欢使用serde_with中的DisplayFromStr类型来执行此类转换任务。https://docs.rs/serde_with/1.9.4/serde_with/struct.DisplayFromStr.html - jonasbb
看起来很不错,我找到了一个叫做serde-aux的小箱子,它可以满足我的需求,但我会看看这个变体,更少的依赖总是受欢迎的。 - Haifischbecken
2个回答

2

@jonasbb帮助我意识到当使用[0,16.9,"2020-12-23 00:23:14"]代码时,它是起作用的,但在尝试反序列化["0","16.9","2020-12-23 00:23:14"]时却不起作用。serde默认情况下不会将数字从字符串序列化,对于I32和F64的尝试会静默失败。这在这个serde问题中讨论,并且可以通过使用非官方serde-aux包来解决。


0

许多板条箱将实现serde和其他常见的实用程序板条箱,但会将它们作为可选功能留下。这可以帮助节省编译时间。您可以通过查看Cargo.toml文件来检查一个板条箱是否有相应的功能或依赖项是否包含但标记为可选。

在您的情况下,我可以前往crates.io上的chrono并选择存储库链接以查看板条箱的源代码。在Cargo.toml文件中,我可以看到使用了serde,但默认情况下未启用。

[features]
default = ["clock", "std", "oldtime"]
alloc = []
std = []
clock = ["libc", "std", "winapi"]
oldtime = ["time"]
wasmbind = ["wasm-bindgen", "js-sys"]
unstable-locales = ["pure-rust-locales", "alloc"]
__internal_bench = []
__doctest = []

[depenencies]
...
serde = { version = "1.0.99", default-features = false, optional = true }

要启用它,您可以进入项目的 Cargo.toml 并将其作为功能添加到 chrono

[depenencies]
chrono = { version: "0.4.19", features = ["serde"] }

另外,chrono 在他们的文档中列出了一些(但不是全部?)可选功能。然而,并非所有的crate都会这样做,文档有时可能过期,所以我通常更喜欢手动方法。

至于deserialize_withuntagged在枚举上相互作用的问题,我没有看到你的代码有任何问题。这可能是serde的一个bug,所以建议你在serde Repository上创建一个issue,以便他们进一步研究为什么会出现这个错误。


感谢您的快速回复,我仍在学习如何在 Rust 中运作,并没有考虑在 chrone 中寻找 serde 功能,只是反过来。我将记住这一点,以备将来之需。 - Haifischbecken

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接