如何序列化与反序列化含任意非字符串键的BTreeMap/HashMap?
解决serde_json序列化非字符串键BTreeMap的问题
这个问题的根源很明确:JSON语法强制要求对象的键必须是字符串,而serde_json默认会尝试直接把非字符串类型(比如你的Foo结构体)作为键写入JSON,这就触发了报错。你想要的是把键先序列化成JSON字符串,再用这个字符串作为键,反序列化时再解析回原类型——这个需求完全可以通过通用的序列化/反序列化函数实现,不用为每个键类型单独写自定义逻辑。
通用解决方案:自定义序列化/反序列化函数
我们可以写两个通用函数,分别处理非字符串键Map的序列化和反序列化逻辑,然后通过serde的属性标注来复用它们:
use std::collections::BTreeMap; use serde::{Deserialize, Serialize, de::Error as DeError, ser::Error as SerError}; // 序列化非字符串键的BTreeMap:将每个键序列化为JSON字符串作为新键 fn serialize_btree_map<K, V, S>(map: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error> where K: Serialize, V: Serialize, S: serde::Serializer, { let string_key_map: BTreeMap<String, &V> = map .iter() .map(|(key, value)| { let key_json = serde_json::to_string(key) .map_err(|e| S::Error::custom(format!("Failed to serialize key: {}", e)))?; Ok((key_json, value)) }) .collect::<Result<_, _>>()?; string_key_map.serialize(serializer) } // 反序列化带字符串化键的BTreeMap:将键字符串解析回原类型 fn deserialize_btree_map<'de, K, V, D>(deserializer: D) -> Result<BTreeMap<K, V>, D::Error> where K: Deserialize<'de> + Ord, V: Deserialize<'de>, D: serde::Deserializer<'de>, { let string_key_map: BTreeMap<String, V> = BTreeMap::deserialize(deserializer)?; string_key_map .into_iter() .map(|(key_json, value)| { let key = serde_json::from_str(&key_json) .map_err(|e| D::Error::custom(format!("Failed to deserialize key: {}", e)))?; Ok((key, value)) }) .collect() } // --- 你的原有类型定义 --- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] struct Foo { bar: String, baz: Baz } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] enum Baz { Quux(u32), Flob, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] struct Bish { bash: u16, bosh: i8 } // 如果是结构体中的Map字段,直接用属性标注 #[derive(Debug, Serialize, Deserialize)] struct MyData { #[serde(serialize_with = "serialize_btree_map", deserialize_with = "deserialize_btree_map")] my_map: BTreeMap<Foo, Bish>, } fn main() -> std::io::Result<()> { let mut btree: BTreeMap<Foo, Bish> = BTreeMap::new(); let foo = Foo { bar: "thud".to_string(), baz: Baz::Flob }; let bish = Bish { bash: 1, bosh: 2 }; println!("foo: {}", serde_json::to_string(&foo)?); println!("bish: {}", serde_json::to_string(&bish)?); btree.insert(foo, bish); // 手动使用序列化函数测试 let serialized = serde_json::to_string(&serialize_btree_map(&btree, serde_json::Serializer::new(Vec::new()))?)?; println!("btree: {}", serialized); // 测试反序列化 let deserialized: BTreeMap<Foo, Bish> = deserialize_btree_map(&mut serde_json::Deserializer::from_str(&serialized))?; println!("deserialized btree: {:?}", deserialized); Ok(()) }
运行结果
现在执行代码会输出合法的JSON:
foo: {"bar":"thud","baz":"Flob"} bish: {"bash":1,"bosh":2} btree: {"{\"bar\":\"thud\",\"baz\":\"Flob\"}":{"bash":1,"bosh":2}} deserialized btree: {Foo { bar: "thud", baz: Flob }: Bish { bash: 1, bosh: 2 }}
更便捷的包装器(可选)
如果不想每次都写属性标注,可以封装一个透明的包装类型,让它自动处理逻辑:
#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(transparent)] struct StringKeyedBTreeMap<K, V>( #[serde(serialize_with = "serialize_btree_map", deserialize_with = "deserialize_btree_map")] BTreeMap<K, V> ); // 使用时直接替换BTreeMap为StringKeyedBTreeMap fn main() -> std::io::Result<()> { let mut btree = StringKeyedBTreeMap(BTreeMap::new()); let foo = Foo { bar: "thud".to_string(), baz: Baz::Flob }; let bish = Bish { bash: 1, bosh: 2 }; btree.0.insert(foo, bish); let serialized = serde_json::to_string(&btree)?; println!("btree: {}", serialized); Ok(()) }
这个方案的优势是完全通用,只要你的键类型实现了Serialize和Deserialize(以及BTreeMap需要的Ord),就可以直接用这套逻辑,不用为每个键类型单独编写代码。
内容的提问来源于stack exchange,提问作者fadedbee
相关产品推荐
相关产品推荐

