如何用serde-yaml将YAML键值对解析为指定Rust结构体?
问题:将动态键的YAML反序列化为Rust结构体
需要解析的YAML内容:
networks: foo1: 192.168.1.0/24 bar1: 192.168.2.0/24 foo2: 2001:CAFE:1::/64 bar2: 2001:CAFE:2::/64
目标Rust结构体:
#[derive(Debug, PartialEq, Serialize, Deserialize)] struct Networks { networks: Vec<Network> } #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Network { name: String, subnet: String, }
偏好当前YAML的简洁写法,而非冗余的列表式结构,但现有方法仅能将网络条目解析为HashMap或元组向量,无法直接映射到带命名字段的Network结构体。
解决方案:自定义Serde反序列化逻辑
通过为Networks结构体实现自定义Deserialize trait,可直接将YAML中的动态键值对转换为Network实例的向量,无需额外转换步骤。
步骤1:添加依赖
在Cargo.toml中声明必要依赖:
[dependencies] serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9"
步骤2:实现自定义反序列化逻辑
use serde::de::{MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; use std::fmt; #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Networks { networks: Vec<Network>, } #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Network { name: String, subnet: String, } // 为Networks实现自定义反序列化 impl<'de> Deserialize<'de> for Networks { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { // 定义Visitor处理网络条目映射 struct NetworksVisitor; impl<'de> Visitor<'de> for NetworksVisitor { type Value = Vec<Network>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map of network names to subnets") } fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> where M: MapAccess<'de>, { let mut networks = Vec::new(); while let Some((name, subnet)) = map.next_entry()? { networks.push(Network { name, subnet }); } Ok(networks) } } // 定义外层结构,指定用自定义逻辑解析networks字段 #[derive(Deserialize)] struct Outer { #[serde(deserialize_with = "deserialize_networks")] networks: Vec<Network>, } fn deserialize_networks<'de, D>(deserializer: D) -> Result<Vec<Network>, D::Error> where D: Deserializer<'de>, { deserializer.deserialize_map(NetworksVisitor) } let outer = Outer::deserialize(deserializer)?; Ok(Networks { networks: outer.networks }) } } // 测试验证 fn main() -> Result<(), serde_yaml::Error> { let yaml = r#" networks: foo1: 192.168.1.0/24 bar1: 192.168.2.0/24 foo2: 2001:CAFE:1::/64 bar2: 2001:CAFE:2::/64 "#; let networks: Networks = serde_yaml::from_str(yaml)?; println!("{:?}", networks); // 验证解析结果正确性 assert_eq!( networks.networks, vec![ Network { name: "foo1".to_string(), subnet: "192.168.1.0/24".to_string() }, Network { name: "bar1".to_string(), subnet: "192.168.2.0/24".to_string() }, Network { name: "foo2".to_string(), subnet: "2001:CAFE:1::/64".to_string() }, Network { name: "bar2".to_string(), subnet: "2001:CAFE:2::/64".to_string() }, ] ); Ok(()) }
逻辑说明
- 自定义
NetworksVisitor遍历YAML中的键值对,将每个键作为name字段、对应值作为subnet字段,构建Network实例并收集为向量。 - 通过
serde(deserialize_with)属性,指定用自定义逻辑处理networks字段的反序列化流程。 - 既保留了偏好的简洁YAML写法,又直接映射到带命名字段的结构体,避免额外转换操作。
内容的提问来源于stack exchange,提问作者Giftzwerg02
相关产品推荐
相关产品推荐

