如何通过 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` ?
}
当 enabled
设置为 false 时,我希望 Remote
的其余字段是可选的,Config::remote
是 None
。
我尝试了 tag
属性,但没有按预期工作。
正如@pretzelhammer 所说,这可能是最公平的方法。这是您无法轻易表达给 serde 的东西。 Serde 主要用于序列化,而不是数据管理。作为程序员,您有责任推断规则背后的逻辑。
信不信由你,您实际上可以使用 #[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>,
}
让我们分解一下。如果您不熟悉 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 的这些用途确实有一席之地,即使配置文件可能不是。
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` ?
}
当 enabled
设置为 false 时,我希望 Remote
的其余字段是可选的,Config::remote
是 None
。
我尝试了 tag
属性,但没有按预期工作。
正如@pretzelhammer 所说,这可能是最公平的方法。这是您无法轻易表达给 serde 的东西。 Serde 主要用于序列化,而不是数据管理。作为程序员,您有责任推断规则背后的逻辑。
信不信由你,您实际上可以使用 #[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>,
}
让我们分解一下。如果您不熟悉 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 的这些用途确实有一席之地,即使配置文件可能不是。