在反序列化枚举时,如何忽略大小写?
我正在为一个项目的配置文件编写解析器,我希望某个枚举值能够实现不区分大小写地解析。也就是说,无论最终用户如何书写这个值的大小写,serde每次都应把它解析为几个候选值中的一个。 (我本来可以要求值必须小写,但我想在这里尽量宽松一些,而且由于我只在启动时解析配置一次,我想我可以多花点CPU周期把它处理得尽量正确。)
例如:
use serde_derive::Deserialize;
#[derive(Deserialize)]
struct SmtpConfig {
tls_mode: TlsMode,
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
enum TlsMode {
Smtps,
StartTls,
Unencrypted,
}
给出以下TOML...
tls_mode = 'startTLS'
…… 我希望 tls_mode 能解析为 TlsMode::StartTls。默认情况下不会,因为给定的数据与判别值的大小写并不完全匹配。
我期望 serde(rename_all) 在尝试解析之前就对传入的值进行重命名,但这似乎只是让serde期望数据已经是给定的大小写!这算合理,至少目前serde没有在解析时忽略大小写的方法(尽管该功能已被请求)。
这个情形类似于 How can I deserialize an enum when the case doesn't match?,但该问题的答案专门处理如何告诉serde期望哪种大小写,或者通过 serde(alias) 应用各种拼写,而不是直接强制将输入数据强制为某种大小写。
如何在不手动实现 Deserialize 的情况下,对这个枚举进行大小写不敏感的反序列化?
解决方案
(在大量浏览关于这一主题的 各种 文档 和 问题 之后,我终于搞定了😌)
最终,我结合 strum 与 serde_with 来实现这个效果:
use serde_with::DeserializeFromStr;
use strum::EnumString;
#[derive(DeserializeFromStr, EnumString)]
#[strum(ascii_case_insensitive)]
enum TlsMode {
Smtps,
StartTls,
Unencrypted,
}
请注意这里移除了 serde(rename_all)。
EnumString 派生宏为该枚举实现 FromStr,而 strum(ascii_case_insensitive) 使得生成的实现在解析时忽略(ASCII)大小写。然后 DeserializeFromStr 使用该枚举的 FromStr 实现来实现 Deserialize。就像魔法一样,现在枚举在反序列化时不再考虑大小写!
也许还有更好的方法来解决这个问题,但就不需要手动实现 FromStr 或 Deserialize,也不必在枚举随时间变化时持续跟踪 match 的大小写情况而言,我认为这是一个还算可以的办法。