我如何为我不拥有的类型实现 serde 并让它支持复合 /wrapper/collection 类型

How can I implement serde for a type that I don't own and have it support compound /wrapper/collection types

这个问题类似 How do I implement a trait I don't own for a type I don't own?

我使用文档中描述的机制为 Date 编写了一个序列化程序,我的模块包装了一个序列化函数


pub mod my_date_format {
    use chrono::{Date, NaiveDate, Utc};
    use serde::{self, Deserialize, Deserializer, Serializer};

    const SERIALIZE_FORMAT: &'static str = "%Y-%m-%d";

    pub fn serialize<S>(date: &Date<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = format!("{}", date.format(SERIALIZE_FORMAT));
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Date<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        NaiveDate::parse_from_str(s.as_str(), SERIALIZE_FORMAT)
            .map_err(serde::de::Error::custom)
            .map(|x| {
                let now = Utc::now();
                let date: Date<Utc> = Date::from_utc(x, now.offset().clone());
                date
            })
    }
}

那我可以做:

struct MyStruct {
    #[serde(with = "my_date_format")]
    pub start: Date<Utc>,
}

问题是如果我用其他类型(它们本身是可序列化的)包装序列化的东西,我会得到错误:

#[serde(with = "my_date_format")]
pub dates: Vec<Date<Utc> // this won't work now since my function doesn't serialize vectors
pub maybe_date: Option<Date<Utc>>> // won't work
pub box_date: Box<Date<Utc>> // won't work...

如何在使用我自己的序列化程序时获得提供的实现?

https://docs.serde.rs/serde/ser/index.html#implementations-of-serialize-provided-by-serde

最直接的方法,就是按照 to talks about, i.e. create a new type, wrap Date<Utc>, and implement Serialize and Deserialize 的类型来做。

#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Debug)]
struct FormattedDate(Date<Utc>);

impl Serialize for FormattedDate {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // If you implement `Deref`, then you don't need to add `.0`
        let s = format!("{}", self.0.format(SERIALIZE_FORMAT));
        serializer.serialize_str(&s)
    }
}

impl<'de> Deserialize<'de> for FormattedDate {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        NaiveDate::parse_from_str(s.as_str(), SERIALIZE_FORMAT)
            .map_err(serde::de::Error::custom)
            .map(|x| {
                let now = Utc::now();
                let date: Date<Utc> = Date::from_utc(x, now.offset().clone());
                Self(date)
                // or
                // date.into()
            })
    }
}

为了让生活更轻松,您可以实施 Deref and DerefMut,然后使用 FormattedDate 透明地就像直接使用 Date<Utc> 一样。

use std::ops::{Deref, DerefMut};

impl Deref for FormattedDate {
    type Target = Date<Utc>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for FormattedDate {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

类似地,您可以实现 From and Into,这样您就可以轻松地在 FormattedDateDate<Utc> 之间进行转换。

impl From<Date<Utc>> for FormattedDate {
    fn from(date: Date<Utc>) -> Self {
        Self(date)
    }
}

impl Into<Date<Utc>> for FormattedDate {
    fn into(self) -> Date<Utc> {
        self.0
    }
}

现在您提供的所有示例都易于使用:

#[derive(Serialize, Deserialize, Debug)]
struct MyStruct {
    date: FormattedDate,
    dates: Vec<FormattedDate>,
    opt_date: Option<FormattedDate>,
    boxed_date: Box<FormattedDate>,
}

fn main() {
    let s = MyStruct {
        date: Utc::now().date().into(),
        dates: std::iter::repeat(Utc::now().date().into()).take(4).collect(),
        opt_date: Some(Utc::now().date().into()),
        boxed_date: Box::new(Utc::now().date().into()),
    };

    let json = serde_json::to_string_pretty(&s).unwrap();
    println!("{}", json);
}

输出:

{
  "date": "2020-12-13",       
  "dates": [
    "2020-12-13",
    "2020-12-13",
    "2020-12-13",
    "2020-12-13"
  ],
  "opt_date": "2020-12-13",   
  "boxed_date": "2020-12-13"  
}

不依赖包装器类型,可以使用 serde_with 包中的 serde_as 宏实现相同的结果。 它像带有属性的 serde 一样工作,但也支持包装器和集合类型。

由于您已经有了一个与 serde 一起使用的模块,所以困难的部分已经完成。 您可以在 crate documentation 中找到详细信息。 您只需为特征 SerializeAsDeserializeAs 添加本地类型和两个样板实现即可使用您的自定义转换。

use chrono::{Date, NaiveDate, Utc};

struct MyDateFormat;

impl serde_with::SerializeAs<Date<Utc>> for MyDateFormat {
    fn serialize_as<S>(value: &Date<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {  
        my_date_format::serialize(value, serializer)
    }
}

impl<'de> serde_with::DeserializeAs<'de, Date<Utc>> for MyDateFormat {
    fn deserialize_as<D>(deserializer: D) -> Result<Date<Utc>, D::Error>
    where
        D: serde::Deserializer<'de>,
    {  
        my_date_format::deserialize(deserializer)
    }
}

#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Debug)]
struct MyStruct {
    #[serde_as(as = "MyDateFormat")]
    date: Date<Utc>,
    #[serde_as(as = "Vec<MyDateFormat>")]
    dates: Vec<Date<Utc>>,
    #[serde_as(as = "Option<MyDateFormat>")]
    opt_date: Option<Date<Utc>>,
    #[serde_as(as = "Box<MyDateFormat>")]
    boxed_date: Box<Date<Utc>>,
}

fn main() {
    let s = MyStruct {
        date: Utc::now().date().into(),
        dates: std::iter::repeat(Utc::now().date().into()).take(4).collect(),
        opt_date: Some(Utc::now().date().into()),
        boxed_date: Box::new(Utc::now().date().into()),
    };

    let json = serde_json::to_string_pretty(&s).unwrap();
    println!("{}", json);
}

// This module is taken uunmodified from the question
pub mod my_date_format {
    use chrono::{Date, NaiveDate, Utc};
    use serde::{self, Deserialize, Deserializer, Serializer};

    const SERIALIZE_FORMAT: &'static str = "%Y-%m-%d";

    pub fn serialize<S>(date: &Date<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = format!("{}", date.format(SERIALIZE_FORMAT));
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Date<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        NaiveDate::parse_from_str(s.as_str(), SERIALIZE_FORMAT)
            .map_err(serde::de::Error::custom)
            .map(|x| {
                let now = Utc::now();
                let date: Date<Utc> = Date::from_utc(x, now.offset().clone());
                date
            })
    }
}