如何通过serde中的内部字段将`Option`设置为None?

ims*_*ace 2 rust 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` ?
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

我希望 的其余字段Remote是可选的,并且Config::remote当设置为 falseNone时。enabled

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

ddu*_*ney 5

不管你相信与否,您实际上可以使用该#[serde(deserialize_with = "...")]属性来做到这一点。它允许您覆盖应用于特定字段的反序列化器。这里的目标是使用一个覆盖的反序列化器来调用常规反序列化器,然后手动检查启用的字段。serde 网站上有(薄弱的)记录。

首先,代码:

// 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>,
}
Run Code Online (Sandbox Code Playgroud)

铁锈游乐场

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

fn none_if_disabled<'de, D>(deserializer: D) -> Result<Option<Remote>, D::Error>
where
    D: Deserializer<'de>,
Run Code Online (Sandbox Code Playgroud)

D是实现的东西Deserializer。这通常由解析器箱提供,在本例中为toml,尽管其他解析器也有自己的。我们可以要求D尝试为我们提供各种原语,或者我们可以将其传递给实现的东西Deserialize,这将为我们消耗这些原语。我们需要返回成功解析的值 ( Option<Remote>) 或冒出我们收到的任何错误。

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

let remote = Option::<Remote>::deserialize(deserializer)?;
Run Code Online (Sandbox Code Playgroud)

然后,我们需要过滤逻辑。只要它输出一个Result<Option<Remote>, _>.

Ok(remote.and_then(|r| if r.enabled { Some(r) } else { None }))
Run Code Online (Sandbox Code Playgroud)

最后,我们需要告诉 serde 我们精美的解串器。

#[serde(deserialize_with = "none_if_disabled")]
Run Code Online (Sandbox Code Playgroud)

您的函数的输出main是:

Config { remote: Some(Remote { enabled: true, address: "example.com" }) }
Config { remote: None }
Run Code Online (Sandbox Code Playgroud)

这是可行的,但只会带来一些额外的复杂性。@Skarlett 是完全正确的,serde 并不是真正为这种配置管理而构建的,并且您可能最好对 serde 直接为您解析的内容进行后处理传递。我的首选方法是保留所有Remotes 并根据标志显式更改行为enabled- 这允许您使用禁用的配置执行操作,例如显式记录未尝试连接的情况。

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