如何使用任意键类型序列化和反序列化 BTreeMaps?

How to serialise and deserialise BTreeMaps with arbitrary key types?

此示例代码:

use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};

#[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
}

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);
    println!("btree: {}", serde_json::to_string(&btree)?);

    Ok(())
}

给出运行时间output/error:

foo: {"bar":"thud","baz":"Flob"}
bish: {"bash":1,"bosh":2}
Error: Custom { kind: InvalidData, error: Error("key must be a string", line: 0, column: 0) }

我用谷歌搜索了一下,发现问题是序列化程序会尝试写入:

{{"bar":"thud","baz":"Flob"}:{"bash":1,"bosh":2}}}

无效 JSON,因为键必须是字符串。

互联网告诉我编写自定义序列化程序。

这不是一个实用的选项,因为我有大量不同的非字符串键。

如何使 serde_json 序列化为(并反序列化自):

{"{\"bar\":\"thud\",\"baz\":\"Flob\"}":{"bash":1,"bosh":2}}

对于 BTreeMap 和 HashMap 中的任意非字符串键?

发现 Rusty Object Notation 后,我意识到我正在将一个 RON 形的钉子推入一个 JSON 形的孔中。

正确的解决方案是使用 JSON 作为与外界的接口,并使用 RON 作为人类可读的本地数据存储。

尽管 OP 最终决定不使用 JSON,但我写了一个箱子,它完全符合原始问题的要求:https://crates.io/crates/serde_json_any_key。使用它就像调用一个函数一样简单。

因为这是 Whosebug,仅 link 是不够的答案,这里是一个完整的实现,将板条箱中的代码与 OP 的主要功能相结合,仅替换对 serde_json::to_string 的最终调用:

extern crate serde;
extern crate serde_json;
use serde::{Serialize, Deserialize};
use std::collections::BTreeMap;

mod serde_json_any_key {
  use std::any::{Any, TypeId};
  use serde::ser::{Serialize, Serializer, SerializeMap, Error};
  use std::cell::RefCell;
  struct SerializeMapIterWrapper<'a, K, V>
  {
    pub iter: RefCell<&'a mut (dyn Iterator<Item=(&'a K, &'a V)> + 'a)>
  }

  impl<'a, K, V> Serialize for SerializeMapIterWrapper<'a, K, V> where
    K: Serialize + Any,
    V: Serialize
  {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
      S: Serializer
    {
      let mut ser_map = serializer.serialize_map(None)?;
      let mut iter = self.iter.borrow_mut();
      // handle strings specially so they don't get escaped and wrapped inside another string
      if TypeId::of::<K>() == TypeId::of::<String>() {
        while let Some((k, v)) = iter.next() {
          let s = (k as &dyn Any).downcast_ref::<String>().ok_or(S::Error::custom("Failed to serialize String as string"))?;
          ser_map.serialize_entry(s, &v)?;
        }
      } else {
        while let Some((k, v)) = iter.next() {
          ser_map.serialize_entry(match &serde_json::to_string(&k)
          {
            Ok(key_string) => key_string,
            Err(e) => { return Err(e).map_err(S::Error::custom); }
          }, &v)?;
        }
      }
      ser_map.end()
    }
  }

  pub fn map_iter_to_json<'a, K, V>(iter: &'a mut dyn Iterator<Item=(&'a K, &'a V)>) -> Result<String, serde_json::Error> where
  K: Serialize + Any,
  V: Serialize
  {
    serde_json::to_string(&SerializeMapIterWrapper {
      iter: RefCell::new(iter)
    })
  }
}


#[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
}

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);
    println!("btree: {}", serde_json_any_key::map_iter_to_json(&mut btree.iter())?);
    Ok(())
}

输出:

foo: {"bar":"thud","baz":"Flob"}
bish: {"bash":1,"bosh":2}
btree: {"{\"bar\":\"thud\",\"baz\":\"Flob\"}":{"bash":1,"bosh":2}}