如何借助TreeOne参考结构反序列化TreeTwo树形结构?
我有两个树形结构的Rust枚举:
// 用于匹配固定结构YAML的TreeOne enum TreeOne { End(EndTypeOne), Nesting(HashMap<String, TreeOne>) } struct EndTypeOne { // 内部字段省略 }
// 用于处理灵活结构YAML的TreeTwo enum TreeTwo { End(EndTypeTwo), Nesting(HashMap<String, TreeTwo>) } enum EndTypeTwo { String(String), U64(u64), Map(HashMap<String, String>), InnerYaml(serde_yaml::Value), }
核心问题是:反序列化TreeTwo时,无法区分TreeTwo::End(EndTypeTwo::InnerYaml(_))和TreeTwo::Nesting(HashMap<String, TreeTwo>)——两者在YAML中都是映射结构。想知道能否借助已有的TreeOne实例指定结构来完成TreeTwo的反序列化?还是应该自定义#[derive(Deserialize)]实现,或者先把YAML反序列化为serde_yaml::Value再手动处理?
1. 借助TreeOne结构引导反序列化
完全可以用TreeOne的结构作为“模板”区分两种映射场景:遍历TreeOne的结构,遇到TreeOne::End时,把对应YAML节点反序列化为TreeTwo::End(包括InnerYaml);遇到TreeOne::Nesting时,把对应节点反序列化为TreeTwo::Nesting。
示例代码:
use serde_yaml::Value; use std::collections::HashMap; use serde_yaml::Error; // 假设已有解析好的TreeOne实例和反序列化为Value的YAML内容 fn deserialize_tree_two_with_template(tree_one: &TreeOne, yaml_value: &Value) -> Result<TreeTwo, Error> { match (tree_one, yaml_value) { (TreeOne::End(_), value) => { let end_type = match value { Value::String(s) => EndTypeTwo::String(s.clone()), Value::Number(n) => n.as_u64() .map(EndTypeTwo::U64) .ok_or_else(|| Error::custom("不是有效的u64类型"))?, Value::Mapping(map) => match serde_yaml::from_value(Value::Mapping(map.clone())) { Ok(string_map) => EndTypeTwo::Map(string_map), Err(_) => EndTypeTwo::InnerYaml(value.clone()), }, _ => EndTypeTwo::InnerYaml(value.clone()), }; Ok(TreeTwo::End(end_type)) } (TreeOne::Nesting(nest_map), Value::Mapping(yaml_map)) => { let mut tree_two_map = HashMap::new(); for (key, tree_one_child) in nest_map { if let Some(yaml_child) = yaml_map.get(&Value::String(key.clone())) { tree_two_map.insert(key.clone(), deserialize_tree_two_with_template(tree_one_child, yaml_child)?); } // 可按需处理YAML中存在但TreeOne未定义的键 } Ok(TreeTwo::Nesting(tree_two_map)) } _ => Err(Error::custom("TreeOne结构与YAML内容不匹配")), } }
这种方案完全贴合已有结构模板,从根源上避免了歧义。
2. 自定义Deserialize实现
如果不想依赖TreeOne模板,可以给TreeTwo自定义反序列化逻辑,通过约定标识区分InnerYaml和Nesting。比如约定:映射中若包含__type: inner_yaml键,则解析为InnerYaml,否则解析为Nesting。
示例代码:
use serde::{de::Visitor, Deserialize, Deserializer}; use serde_yaml::Value; use std::collections::HashMap; use std::fmt; #[derive(Debug)] enum TreeTwo { End(EndTypeTwo), Nesting(HashMap<String, TreeTwo>), } #[derive(Debug)] enum EndTypeTwo { String(String), U64(u64), Map(HashMap<String, String>), InnerYaml(Value), } impl<'de> Deserialize<'de> for TreeTwo { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { struct TreeTwoVisitor; impl<'de> Visitor<'de> for TreeTwoVisitor { type Value = TreeTwo; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("TreeTwo的合法YAML结构") } fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error> where M: serde::de::MapAccess<'de>, { let mut inner_yaml_flag = false; let mut temp_map = HashMap::new(); while let Some((key, value)) = map.next_entry::<String, Value>()? { if key == "__type" { if let Value::String(s) = value { inner_yaml_flag = s == "inner_yaml"; } } else { temp_map.insert(key, value); } } if inner_yaml_flag { let yaml_map = Value::Mapping( temp_map.into_iter().map(|(k, v)| (Value::String(k), v)).collect() ); Ok(TreeTwo::End(EndTypeTwo::InnerYaml(yaml_map))) } else { let mut nest_map = HashMap::new(); for (key, value) in temp_map { nest_map.insert( key, TreeTwo::deserialize(serde_yaml::value::Deserializer::new(value))? ); } Ok(TreeTwo::Nesting(nest_map)) } } fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: serde::de::Error, { Ok(TreeTwo::End(EndTypeTwo::String(v.to_string()))) } fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: serde::de::Error, { Ok(TreeTwo::End(EndTypeTwo::U64(v))) } fn visit_value<E>(self, v: Value) -> Result<Self::Value, E> where E: serde::de::Error, { Ok(TreeTwo::End(EndTypeTwo::InnerYaml(v))) } } deserializer.deserialize_any(TreeTwoVisitor) } }
这种方案无需依赖外部模板,但要求YAML数据遵循约定的标识规则。
3. 先反序列化为Value再手动处理
这是最灵活的方案:先把整个YAML转成serde_yaml::Value,再递归遍历,根据自定义逻辑判断每个节点该转为TreeTwo::End还是TreeTwo::Nesting。比如可以根据节点是否符合业务规则、值的类型分布等条件区分。
示例代码片段:
use serde_yaml::Value; use std::collections::HashMap; fn value_to_tree_two(value: Value) -> TreeTwo { match value { Value::String(s) => TreeTwo::End(EndTypeTwo::String(s)), Value::Number(n) => { if let Some(u) = n.as_u64() { TreeTwo::End(EndTypeTwo::U64(u)) } else { TreeTwo::End(EndTypeTwo::InnerYaml(Value::Number(n))) } } Value::Mapping(map) => { // 自定义判断:若映射所有值都是基本类型,转InnerYaml;否则转Nesting let all_primitive = map.values().all(|v| matches!( v, Value::String(_) | Value::Number(_) | Value::Bool(_) | Value::Null )); if all_primitive { match serde_yaml::from_value(Value::Mapping(map.clone())) { Ok(string_map) => TreeTwo::End(EndTypeTwo::Map(string_map)), Err(_) => TreeTwo::End(EndTypeTwo::InnerYaml(Value::Mapping(map))), } } else { let mut nest_map = HashMap::new(); for (k, v) in map { if let Value::String(key) = k { nest_map.insert(key, value_to_tree_two(v)); } } TreeTwo::Nesting(nest_map) } } other => TreeTwo::End(EndTypeTwo::InnerYaml(other)), } }
这种方案无需依赖外部模板或修改YAML格式,完全靠自定义逻辑区分,适合复杂多变的场景。
内容的提问来源于stack exchange,提问作者DavSanchez

