简体   繁体   English

如何使用 Serde 序列化 HashMap 并将结构作为 JSON 的键?

[英]How do I use Serde to serialize a HashMap with structs as keys to JSON?

I want to serialize a HashMap with structs as keys:我想用结构作为键序列化一个HashMap

use serde::{Deserialize, Serialize}; // 1.0.68
use std::collections::HashMap;

fn main() {
    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
    struct Foo {
        x: u64,
    }

    #[derive(Serialize, Deserialize, Debug)]
    struct Bar {
        x: HashMap<Foo, f64>,
    }

    let mut p = Bar { x: HashMap::new() };
    p.x.insert(Foo { x: 0 }, 0.0);
    let serialized = serde_json::to_string(&p).unwrap();
}

This code compiles, but when I run it I get an error:这段代码可以编译,但是当我运行它时出现错误:

Error("key must be a string", line: 0, column: 0)'

I changed the code:我更改了代码:

#[derive(Serialize, Deserialize, Debug)]
struct Bar {
    x: HashMap<u64, f64>,
}

let mut p = Bar { x: HashMap::new() };
p.x.insert(0, 0.0);
let serialized = serde_json::to_string(&p).unwrap();

The key in the HashMap is now a u64 instead of a string. HashMap中的密钥现在是u64而不是字符串。 Why does the first code give an error?为什么第一个代码会出错?

According to JSONs specification , JSON keys must be strings.根据JSONs 规范,JSON 键必须是字符串。 serde_json uses fmt::Display in here , for some non-string keys, to allow serialization of wider range of HashMap s. serde_json 在这里使用fmt::Display ,对于一些非字符串键,允许序列化更广泛的HashMap s。 That's why HashMap<u64, f64> works as well as HashMap<String, f64> would.这就是HashMap<u64, f64>HashMap<String, f64>一样有效的原因。 However, not all types are covered ( Foo's case here ).但是,并非所有类型都被涵盖( 此处为 Foo 的情况)。

That's why we need to provide our own Serialize implementation:这就是为什么我们需要提供我们自己的Serialize实现:

impl Display for Foo {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(f, "{}", self.x)
    }
}

impl Serialize for Bar {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(self.x.len()))?;
        for (k, v) in &self.x {
            map.serialize_entry(&k.to_string(), &v)?;
        }
        map.end()
    }
}

( playground ) 操场

You can use serde_as from the serde_with crate to encode the HashMap as a sequence of key-value pairs:您可以使用serde_as crate 中serde_withHashMap编码为键值对序列:

use serde_with::serde_as; // 1.5.1

#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
    #[serde_as(as = "Vec<(_, _)>")]
    x: HashMap<Foo, f64>,
}

Which will serialize to (and deserialize from) this:这将序列化为(和反序列化):

{
  "x":[
    [{"x": 0}, 0.0],
    [{"x": 1}, 0.0],
    [{"x": 2}, 0.0]
  ]
}

There is likely some overhead from converting the HashMap to Vec , but this can be very convenient.HashMap转换为Vec可能会有一些开销,但这可能非常方便。

I've found the bulletproof solution 😃我找到了防弹解决方案😃

  • Extra dependencies not required不需要额外的依赖
  • Compatible with HashMap , BTreeMap and other iterable types兼容HashMapBTreeMap等可迭代类型
  • Works with flexbuffersflexbuffers一起flexbuffers

The following code converts a field (map) to the intermediate Vec representation:以下代码将字段(映射)转换为中间Vec表示:

pub mod vectorize {
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    use std::iter::FromIterator;

    pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
        T: IntoIterator<Item = (&'a K, &'a V)>,
        K: Serialize + 'a,
        V: Serialize + 'a,
    {
        let container: Vec<_> = target.into_iter().collect();
        serde::Serialize::serialize(&container, ser)
    }

    pub fn deserialize<'de, T, K, V, D>(des: D) -> Result<T, D::Error>
    where
        D: Deserializer<'de>,
        T: FromIterator<(K, V)>,
        K: Deserialize<'de>,
        V: Deserialize<'de>,
    {
        let container: Vec<_> = serde::Deserialize::deserialize(des)?;
        Ok(T::from_iter(container.into_iter()))
    }
}

To use it just add the module's name as an attribute:要使用它,只需添加模块的名称作为属性:

#[derive(Debug, Serialize, Deserialize)]
struct MyComplexType {
    #[serde(with = "vectorize")]
    map: HashMap<MyKey, String>,
}

The remained part if you want to check it locally:如果要在本地检查,剩下的部分:

use anyhow::Error;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MyKey {
    one: String,
    two: u16,
    more: Vec<u8>,
}

#[derive(Debug, Serialize, Deserialize)]
struct MyComplexType {
    #[serde(with = "vectorize")]
    map: HashMap<MyKey, String>,
}

fn main() -> Result<(), Error> {
    let key = MyKey {
        one: "1".into(),
        two: 2,
        more: vec![1, 2, 3],
    };
    let mut map = HashMap::new();
    map.insert(key.clone(), "value".into());
    let instance = MyComplexType { map };
    let serialized = serde_json::to_string(&instance)?;
    println!("JSON: {}", serialized);
    let deserialized: MyComplexType = serde_json::from_str(&serialized)?;
    let expected_value = "value".to_string();
    assert_eq!(deserialized.map.get(&key), Some(&expected_value));
    Ok(())
}

And on the Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bf1773b6e501a0ea255ccdf8ce37e74d在 Rust 游乐场: https ://play.rust-lang.org/?version = stable&mode = debug&edition = 2018&gist =bf1773b6e501a0ea255ccdf8ce37e74d

While all provided answers will fulfill the goal of serializing your HashMap to json they are ad hoc or hard to maintain.虽然所有提供的答案都将实现将您的HashMap序列化为 json 的目标,但它们是临时的或难以维护的。

One correct way to allow a specific data structure to be serialized with serde as keys in a map, is the same way serde handles integer keys in HashMap s (which works): They serialize the value to String .允许使用serde作为 map 中的键序列化特定数据结构的一种正确方法与serde处理 HashMap 中的HashMap键的方式相同(有效):它们将值序列化为String This has a few advantages;这有几个优点; namely

  1. Intermediate data-structure omitted,省略了中间数据结构,
  2. no need to clone the entire HashMap ,无需克隆整个HashMap
  3. easier maintained by applying OOP concepts, and通过应用 OOP 概念更容易维护,并且
  4. serialization usable in more complex structures such as MultiMap .序列化可用于更复杂的结构,例如MultiMap

This can be done by manually implementing Serialize and Deserialize for your data-type.这可以通过为您的数据类型手动实现SerializeDeserialize序列化来完成。

I use composite ids for maps.我对地图使用复合 ID。

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Proj {
    pub value: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Doc {
    pub proj: Proj,
    pub value: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Sec {
    pub doc: Doc,
    pub value: u32,
}

So now manually implementing serde serialization for them is kind of a hassle, so instead we delegate the implementation to the FromStr and From<Self> for String ( Into<String> blanket) traits.所以现在为它们手动实现serde序列化有点麻烦,所以我们将实现委托给FromStrFrom<Self> for String ( Into<String> blanket) 特征。

impl From<Doc> for String {
    fn from(val: Doc) -> Self {
        format!("{}{:08X}", val.proj, val.value)
    }
}
impl FromStr for Doc {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match parse_doc(s) {
            Ok((_, p)) => Ok(p),
            Err(e) => Err(e.to_string()),
        }
    }
}

In order to parse the Doc we make use of nom .为了解析Doc ,我们使用了nom The parse functionality below is explained in their examples.下面的解析功能在他们的示例中进行了解释。

fn is_hex_digit(c: char) -> bool {
    c.is_digit(16)
}

fn from_hex8(input: &str) -> Result<u32, std::num::ParseIntError> {
    u32::from_str_radix(input, 16)
}

fn parse_hex8(input: &str) -> IResult<&str, u32> {
    map_res(take_while_m_n(8, 8, is_hex_digit), from_hex8)(input)
}

fn parse_doc(input: &str) -> IResult<&str, Doc> {
    let (input, proj) = parse_proj(input)?;
    let (input, value) = parse_hex8(input)?;
    Ok((input, Doc { value, proj }))
}

Now we need to hook up self.to_string() and str::parse(&str) to serde we can do this using a simple macro.现在我们需要将self.to_string()str::parse(&str)连接到serde我们可以使用一个简单的宏来完成。

macro_rules! serde_str {
    ($type:ty) => {
        impl Serialize for $type {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                let s: String = self.clone().into();
                serializer.serialize_str(&s)
            }
        }

        impl<'de> Deserialize<'de> for $type {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                paste! {deserializer.deserialize_string( [<$type Visitor>] {})}
            }
        }

        paste! {struct [<$type Visitor>] {}}

        impl<'de> Visitor<'de> for paste! {[<$type Visitor>]} {
            type Value = $type;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("\"")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match str::parse(v) {
                    Ok(id) => Ok(id),
                    Err(_) => Err(serde::de::Error::custom("invalid format")),
                }
            }
        }
    };
}

Here we are using paste to interpolate the names.这里我们使用paste来插入名称。 Beware that now the struct will always serialize as defined above.请注意,现在该结构将始终按照上面的定义进行序列化。 Never as a struct, always as a string.从不作为结构,始终作为字符串。

It is important to implement fn visit_str instead of fn visit_string because visit_string defers to visit_str .实施fn visit_str而不是fn visit_string很重要,因为visit_string visit_str

Finally, we have to call the macro for our custom struct s最后,我们必须为我们的自定义struct调用宏

serde_str!(Sec);
serde_str!(Doc);
serde_str!(Proj);

Now the specified types can be serialized to and from string with serde.现在可以使用 serde 将指定的类型序列化为字符串或从字符串序列化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM