如何处理Serde字段默认值函数执行过程中产生的错误?
解决方案
这类场景有3种常用的惯用处理方案,都可以满足错误向上透传、不panic、不掩盖问题的要求:
方案1:两步反序列化+显式转换(最通用,推荐大多数场景使用)
这个方案不需要修改serde派生逻辑,只需要新增一个中间结构体做过渡,把默认值填充的逻辑放到类型转换阶段处理,就可以正常返回Result透传错误:
// 中间结构体,所有允许缺失的字段设为Option #[derive(Deserialize, Debug)] struct ConfigRaw { pub name: Option<String>, // 其余字段和原Config保持一致 } // 实现转换逻辑,处理默认值填充和错误 impl TryFrom<ConfigRaw> for Config { type Error = CustomError; fn try_from(raw: ConfigRaw) -> Result<Self, Self::Error> { let name = match raw.name { Some(v) => v, None => { // 原有默认值逻辑,可正常返回错误 let current_directory = env::current_dir() .map_err(|e| CustomError::new(ErrorKind::IoError, "There was an io err"))? .clean() .to_str() .ok_or_else(|| CustomError::new(ErrorKind::InvalidPath, "Path contains non-UTF8 characters"))?; current_directory.to_string() } }; Ok(Self { name }) } }上层调用逻辑只需修改为两步反序列化即可:
let config: Config = serde_json::from_str::<ConfigRaw>(&config_str)?.try_into()?;优势:逻辑直观,不需要了解serde的反序列化内部实现,维护成本极低,新增字段只需要同步修改两个结构体和转换逻辑即可。
方案2:手动实现
Deserializetrait如果你不想新增中间结构体,也可以直接为你的
Config结构体自定义反序列化逻辑,在反序列化过程中处理默认值的错误,错误会自动作为serde反序列化错误向上抛出:use serde::de::{Deserializer, MapAccess, Visitor}; use std::fmt; struct ConfigVisitor; impl<'de> Visitor<'de> for ConfigVisitor { type Value = Config; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a Config struct") } fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> where M: MapAccess<'de>, { let mut name = None; while let Some((key, value)) = map.next_entry()? { match key { "name" => name = Some(value), // 其余字段按相同逻辑处理 _ => {} // 按需处理未知字段:忽略或报错 } } // 填充默认值,把自定义错误转为serde反序列化错误抛出 let name = name.unwrap_or_else(|| { default_name().map_err(serde::de::Error::custom) })?; Ok(Config { name }) } } impl<'de> Deserialize<'de> for Config { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { deserializer.deserialize_struct("Config", &["name"], ConfigVisitor) } }优势:上层调用不需要做任何修改,直接反序列化为
Config即可,适合对serde使用比较熟悉的场景。方案3:使用
serde_with生态工具简化实现如果你的项目已经引入了
serde_with依赖,可以直接使用它封装好的TryDefault能力,不需要手写完整的反序列化逻辑:use serde_with::{serde_as, TryDefault}; struct DefaultName; impl TryDefault for DefaultName { type Ok = String; type Error = CustomError; fn try_default() -> Result<Self::Ok, Self::Error> { // 原有默认值逻辑 let current_directory = env::current_dir() .map_err(|e| CustomError::new(ErrorKind::IoError, "There was an io err"))? .clean() .to_str() .ok_or_else(|| CustomError::new(ErrorKind::InvalidPath, "Path contains non-UTF8 characters"))?; Ok(current_directory.to_string()) } } #[serde_as] #[derive(Deserialize, Debug)] pub struct Config { #[serde(default)] #[serde_as(as = "Option<_>, TryDefault<DefaultName>")] pub name: String, }优势:代码最简洁,不需要额外的中间结构体或者手写反序列化逻辑。
注意原有代码中的.unwrap()调用需要替换为显式的错误返回,避免在路径包含非UTF8字符时panic,不符合要求。
内容的提问来源于stack exchange,提问作者Nathaniel Ford
相关产品推荐
相关产品推荐

