如何使用serde_yaml将含任意键的YAML反序列化到Rust自定义结构体
方案1:不改动现有YAML结构
你之前的思路里将数组元素映射为BTreeMap<String, Node>不符合YAML的实际结构:每个数组元素中,动态键(如G1、Sn1)对应的值是字符串类型的节点名称,supportedBy是和动态键平级的字段。我们可以通过serde的#[serde(flatten)]属性捕获动态的ID-名称键值对,调整后的代码如下:
use indoc::indoc; use std::collections::BTreeMap; use serde::{Serialize, Deserialize}; #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct NodeEntry { supported_by: Option<Vec<String>>, // 捕获所有未匹配到结构体字段的键值对,也就是动态ID和名称的映射 #[serde(flatten)] id_name: BTreeMap<String, String>, } impl NodeEntry { // 辅助方法快速获取节点ID pub fn id(&self) -> &str { self.id_name.keys().next().unwrap() } // 辅助方法快速获取节点名称 pub fn name(&self) -> &str { self.id_name.values().next().unwrap() } } fn main() -> Result<(), serde_yaml::Error> { let test_yaml = indoc! {" --- - G1: \"Test\" supportedBy: [Sn1] - Sn1: \"Lala\" "}; let deser: Vec<NodeEntry> = serde_yaml::from_str(&test_yaml)?; println!("第一个节点ID:{},名称:{},依赖:{:?}", deser[0].id(), deser[0].name(), deser[0].supported_by); println!("第二个节点ID:{},名称:{},依赖:{:?}", deser[1].id(), deser[1].name(), deser[1].supported_by); Ok(()) }
运行输出如下:
第一个节点ID:G1,名称:Test,依赖:Some(["Sn1"]) 第二个节点ID:Sn1,名称:Lala,依赖:None
方案2:微调YAML结构(推荐)
如果可以接受极小的YAML结构调整,我们可以让结构更清晰,也避免每次取ID和名称都要遍历Map的开销:
调整后的YAML:
--- - id: G1 name: "Test" supportedBy: [Sn1] - id: Sn1 name: "Lala"
对应结构体可以直接写为:
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct Node { id: String, name: String, supported_by: Option<Vec<String>>, }
直接解析为Vec<Node>即可,人工编写的复杂度几乎没有提升,结构可读性也更好。
内容的提问来源于stack exchange,提问作者Jonas Wolf
相关产品推荐
相关产品推荐

