基于Serde加载嵌套YAML/JSON 解析跨文件关联的License引用
1. 运行报错根因
第一个方案报错
你反序列化时指定了Vec<&str>类型,&str是借用字符串,serde_yaml从文件流反序列化时无法返回生命周期为'de的借用字符串(数据是从文件动态读入的,没有提前存在的可借用内存),改成Vec<String>即可解决该类型错误。
第二个方案报错
你为License实现反序列化时调用了deserializer.deserialize_seq,要求输入是序列类型,但你YAML里licenses数组的元素是字符串类型,自然匹配失败。需要改成调用deserialize_str,并实现visit_str方法处理字符串输入,才可以正确读取许可证ID。
小提示:注意检查Source结构体的字段名和YAML配置的字段名是否匹配,你示例YAML里签名的字段是
signature,而结构体里写的是signature_url,会导致反序列化失败,需要统一字段名。
2. License私有字段访问的优雅方案
不需要把字段改成公有或者加setter,你可以在licenses模块内为License实现公开的构造/加载方法,把许可证的读取、初始化逻辑全部封装在模块内部,对外只暴露方法入口即可:
// licenses.rs use serde::Deserialize; use std::fs::File; use std::path::Path; #[derive(Debug, Clone, Deserialize)] struct LicenseInner { short: String, name: String, url: String, } #[derive(Debug, Clone)] pub struct License { id: String, short: String, name: String, url: String, } impl License { pub fn load(id: &str) -> Result<Self, Box<dyn std::error::Error>> { let path = format!("licenses/{0}/{0}.yaml", id); let inner: LicenseInner = serde_yaml::from_reader(File::open(Path::new(&path))?)?; Ok(Self { id: id.to_string(), short: inner.short, name: inner.name, url: inner.url, }) } // 按需添加字段的getter方法 pub fn id(&self) -> &str { &self.id } }
这样外部模块只需要调用License::load("gpl_2_0")就能拿到实例,不需要操作License的内部字段,封装性也得到了保证。
3. 许可证去重实现
serde的反序列化逻辑默认是无状态的,无法直接传入全局缓存对象,所以不推荐在反序列化阶段做ID转License的逻辑,建议拆分流程:
- 先反序列化Source得到许可证ID列表
- 单独做ID到License实例的转换,用全局缓存去重
调整后的代码示例:
调整Source定义
// sources.rs use serde::Deserialize; use super::licenses::License; use std::sync::Arc; #[derive(Deserialize, Debug)] pub struct SourceRaw { pub url: String, pub signature: String, pub licenses: Vec<String>, // 先存ID pub target: String, } #[derive(Debug)] pub struct Source { pub url: String, pub signature: String, pub licenses: Vec<Arc<License>>, // 用Arc共享实例避免重复 pub target: String, }
main逻辑
use std::collections::HashMap; use std::sync::Arc; use licenses::License; use sources::{Source, SourceRaw}; fn main() -> Result<(), Box<dyn std::error::Error>> { // 全局许可证缓存,key是许可证ID let mut license_cache: HashMap<String, Arc<License>> = HashMap::new(); // 第一步:反序列化Source得到原始数据 let config_file = std::fs::File::open("sources/binutils/config.yaml")?; let raw_source: SourceRaw = serde_yaml::from_reader(&config_file)?; // 第二步:转换许可证ID为实例,走缓存去重 let mut licenses = Vec::new(); for license_id in raw_source.licenses { let license = license_cache .entry(license_id.clone()) .or_insert(Arc::new(License::load(&license_id)?)); licenses.push(license.clone()); } // 组装成最终的Source实例 let source = Source { url: raw_source.url, signature: raw_source.signature, licenses, target: raw_source.target, }; println!("{:?}", source); Ok(()) }
如果你不需要多个Source共享License实例的所有权,也可以去掉Arc,直接存License实例,缓存里可以存ID对应的实例克隆即可。
内容的提问来源于stack exchange,提问作者Phidelux

