如何通过 serde 中的内部字段使 `Option` 成为 None?

How to make an `Option` to be None by inner field in serde?

use serde_derive::Deserialize; // 1.0.118
use toml; // 0.5.8

#[derive(Debug, Deserialize)]
struct Remote {
    enabled: bool,
    address: String,
}

#[derive(Debug, Deserialize)]
struct Config {
    remote: Option<Remote>,
}


fn main() {
    let config: Config = toml::from_str(r#"
    [remote]
    enabled = true
    address = "example.com"
    "#).unwrap();
    println!("{:?}", config);

    let config: Config = toml::from_str(r#"
    [remote]
    enabled = false
    address = "example.com"
    "#).unwrap();
    println!("{:?}", config);
    // How to make `config.remote` `None` ?
}

Playground link

enabled 设置为 false 时,我希望 Remote 的其余字段是可选的,Config::remoteNone

我尝试了 tag 属性,但没有按预期工作。

正如@pretzelhammer 所说,这可能是最公平的方法。这是您无法轻易表达给 serde 的东西。 Serde 主要用于序列化,而不是数据管理。作为程序员,您有责任推断规则背后的逻辑。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2a79969788b9f07e15b1430efc512861

信不信由你,您实际上可以使用 #[serde(deserialize_with = "...")] 属性来做到这一点。它允许您覆盖应用于特定字段的反序列化器。这里的目标是使用调用常规反序列化器的覆盖反序列化器,然后手动检查启用字段。这在 serde website.

上有(薄薄的)记录

一、代码:

// Important: Deserialize is needed to access T::deserialize() methods.
use serde::{Deserialize, Deserializer};

fn none_if_disabled<'de, D>(deserializer: D) -> Result<Option<Remote>, D::Error>
where
    D: Deserializer<'de>,
{
    let remote = Option::<Remote>::deserialize(deserializer)?;

    Ok(remote.and_then(|r| if r.enabled { Some(r) } else { None }))
}

#[derive(Debug, Deserialize)]
struct Remote {
    enabled: bool,
    address: String,
}

#[derive(Debug, Deserialize)]
struct Config {
    #[serde(deserialize_with = "none_if_disabled")]
    remote: Option<Remote>,
}

Rust Playground

让我们分解一下。如果您不熟悉 serde 的内部结构,函数签名会有点粗糙。

fn none_if_disabled<'de, D>(deserializer: D) -> Result<Option<Remote>, D::Error>
where
    D: Deserializer<'de>,

D 是实现 Deserializer 的东西。这通常由解析器 crate 提供,在这种情况下 toml, though other parsers 有自己的。我们可以要求 D 尝试给我们各种原语,或者我们可以将它传递给实现 Deserialize 的东西,它会为我们消耗这些原语。我们需要 return 成功解析的值 (Option<Remote>) 或冒泡我们得到的任何错误。

在函数内部,第一步是使用我们在 Remote 上从 #[derive(Deserialize)] 获得的常规 Deserialize 实现。这是“正常”解析步骤,如果我们没有 deserialize_with 属性,大致就是这样调用的。

let remote = Option::<Remote>::deserialize(deserializer)?;

然后,我们需要过滤逻辑。这可以像您需要的那样复杂,只要它输出 Result<Option<Remote>, _>.

Ok(remote.and_then(|r| if r.enabled { Some(r) } else { None }))

最后,我们需要告诉 serde 我们花哨的反序列化器。

#[serde(deserialize_with = "none_if_disabled")]

您的 main 函数的输出是:

Config { remote: Some(Remote { enabled: true, address: "example.com" }) }
Config { remote: None }

这行得通,但只是增加了一些额外的复杂性。 @Skarlett 是完全正确的,serde 并不是真正为这种配置管理而构建的,您最好直接对 serde 解析的内容进行 post-processing pass。我的首选方法是保留所有 Remote 并根据 enabled 标志显式更改行为——这允许您使用禁用的配置进行操作,例如显式记录一个未尝试连接。

也就是说,serde 的这些用途确实有一席之地,即使配置文件可能不是。