如何在serde中反序列化具有自定义值类型的Map?

问题描述 投票:0回答:2

我有以下 json 文件,我想在 Rust 结构中反序列化:

{
  "name": "Manuel",
  "friends": {
    "id1": 1703692376,
    ...
  } 
  ...
}

好友标签包含 ID 和时间戳 我创建了一个像这样的 Rust 结构:

use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct MyStruct{
    name: String,
    friends: HashMap<String, i64>
}

这很好用,但我实际上想要这样的东西:

use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct MyStruct{
    name: String,
    friends: HashMap<String, SystemTime>
}

所以我想将整数值转换为 SystemTime 类型。可以用 serde 做到这一点吗?

json rust serialization serde
2个回答
4
投票

serde_with
已满足您的需求。它支持各种格式的
SystemTime
反/序列化,包括自纪元以来的秒数,并且与
#[serde(with)]
不同,它支持嵌套类型:

#[serde_with::serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MyStruct {
    name: String,
    #[serde_as(as = "HashMap<_, serde_with::TimestampSeconds>")]
    friends: HashMap<String, SystemTime>,
}

1
投票

首先,让我们创建转换函数

unix_seconds_to_system
system_to_unix_seconds
i64
SystemTime
之间进行转换。

fn unix_seconds_to_system(unix_seconds: i64) -> SystemTime {
    if unix_seconds.is_positive() {
        let d = Duration::from_secs(unix_seconds as u64);
        SystemTime::UNIX_EPOCH + d
    } else {
        let d = Duration::from_secs(-unix_seconds as u64);
        SystemTime::UNIX_EPOCH - d
    }
}

fn system_to_unix_seconds(system: SystemTime) -> i64 {
    match system.duration_since(SystemTime::UNIX_EPOCH) {
        Ok(t) => t.as_secs() as i64,
        Err(e) => -(e.duration().as_secs() as i64),
    }
}

然后,您可以使用

#[serde(with)]
指定用于反序列化和序列化的自定义函数,而不是使用
Deserialize
Serialize
HashMap<String, SystemTime>
实现。

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MyStruct {
    name: String,
    #[serde(with = "time_map")]
    friends: HashMap<String, SystemTime>,
}

mod time_map {
    use super::*;
    use serde::{Deserializer, Serializer};

    pub fn serialize<S>(map: &HashMap<String, SystemTime>, ser: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        todo!()
    }

    pub fn deserialize<'de, D>(de: D) -> Result<HashMap<String, SystemTime>, D::Error>
    where
        D: Deserializer<'de>,
    {
        todo!()
    }
}

有两种方法可以解决这个问题。更简单的解决方案是经过

HashMap<String, i64>
并转换。

pub fn serialize<S>(map: &HashMap<String, SystemTime>, ser: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    map.iter()
        .map(|(k, &v)| (k, system_to_unix_seconds(v)))
        .collect::<HashMap<_, _>>()
        // This serializes `HashMap<&String, i64>`, which has the same
        // representation as `HashMap<String, i64>`.
        .serialize(ser)
}

pub fn deserialize<'de, D>(de: D) -> Result<HashMap<String, SystemTime>, D::Error>
where
    D: Deserializer<'de>,
{
    Ok(HashMap::<String, i64>::deserialize(de)?
        .into_iter()
        .map(|(k, v)| (k, unix_seconds_to_system(v)))
        .collect())
}

缺点是,反序列化时分配两个

HashMap
,序列化时分配一个,而不是分别分配 1 和 0。为了避免这种情况,您可以直接使用
Serializer
Deserializer
方法创建完全自定义的函数。

use serde::de::Visitor;
use serde::ser::SerializeMap;

pub fn serialize<S>(map: &HashMap<String, SystemTime>, ser: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let mut ser_map = ser.serialize_map(Some(map.len()))?;
    for (k, &v) in map {
        let unix_seconds = system_to_unix_seconds(v);
        ser_map.serialize_entry(k, &unix_seconds)?;
    }
    ser_map.end()
}

pub fn deserialize<'de, D>(de: D) -> Result<HashMap<String, SystemTime>, D::Error>
where
    D: Deserializer<'de>,
{
    struct VisitTimeMap;
    
    impl<'de> Visitor<'de> for VisitTimeMap {
        type Value = HashMap<String, SystemTime>;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(formatter, "a map of strings to unix timestamps")
        }

        fn visit_map<A>(self, mut de_map: A) -> Result<Self::Value, A::Error>
        where
            A: serde::de::MapAccess<'de>,
        {
            let mut map = HashMap::with_capacity(de_map.size_hint().unwrap_or_default());

            while let Some((k, v)) = de_map.next_entry::<String, i64>()? {
                let system_time = unix_seconds_to_system(v);
                map.insert(k, system_time);
            }

            Ok(map)
        }
    }

    de.deserialize_map(VisitTimeMap)
}

游乐场

© www.soinside.com 2019 - 2024. All rights reserved.