You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何序列化与反序列化含任意非字符串键的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 15:17:53