[英]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_with
将HashMap
编码为键值对序列:
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 😃我找到了防弹解决方案😃
HashMap
, BTreeMap
and other iterable typesHashMap
、 BTreeMap
等可迭代类型flexbuffers
flexbuffers
一起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
即
HashMap
,HashMap
,MultiMap
.MultiMap
。 This can be done by manually implementing Serialize
and Deserialize
for your data-type.这可以通过为您的数据类型手动实现
Serialize
和Deserialize
序列化来完成。
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
序列化有点麻烦,所以我们将实现委托给FromStr
和From<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.