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

标签 rust hashmap serde btreemap

此示例代码:

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(())
}

给出运行时输出/错误:

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 中的任意非字符串键?

最佳答案

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

因为这是 StackOverflow 并且仅仅一个链接是不够的,这里是一个完整的实现,将来自 v1.1 的 crate 代码与 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}}

关于rust - 如何使用任意键类型序列化和反序列化 BTreeMaps?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62977485/

相关文章:

multithreading - 如何从另一个线程终止或挂起 Rust 线​​程?

java - HashTable的get方法打印所有元素

java - 我应该使用哪种数据结构?

performance - 使用 Serde 和 Bincode 将大型结构序列化到磁盘很慢

rust - 可序列化结构体字段的多种可能类型

pointers - 是否有一种通用的 Rust 指针类型可以存储任何其他类型的指针,类似于 C 的 void *?

generics - 枚举通用结构

multithreading - 如何在 Rust 中进行 "fire and forget"调用?

java - 如何使用 lambda 和流避免此映射的嵌套循环

rust - 使用 serde 序列化时如何对 HashMap 键进行排序?