Как я могу реализовать serde для типа, которым я не владею, и чтобы он поддерживал составные / обертки / типы коллекций

Этот вопрос похож на Как мне реализовать черту, которой я не владею, для типа, которым я не владею?

Я написал сериализатор для 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.


person Avner Barr    schedule 13.12.2020    source источник


Ответы (2)


Вместо того, чтобы полагаться на типы оболочки, можно добиться тех же результатов с помощью serde_as из serde_with ящика. Он работает как serde с атрибутом, но также поддерживает типы оболочек и коллекций.

Поскольку у вас уже есть модуль для использования с serde, самая сложная часть уже сделана. Подробности можно найти в crate документацию. Вам нужно только добавить локальный тип и две стандартные реализации для признаков SerializeAs и DeserializeAs, чтобы использовать ваши пользовательские преобразования.

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
            })
    }
}
person jonasbb    schedule 29.03.2021

Самый простой способ - это сделать как обсуждает вопрос, который вы связали, т.е. создать новый тип, обернуть Date<Utc> и реализовать _ 2_ и _ 3_ для этого типа.

#[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 и _ 6_, а затем использование 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 и _ 11_, чтобы можно было легко конвертировать между FormattedDate и Date<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"  
}
person vallentin    schedule 13.12.2020
comment
Стоит отметить, что если вы хотите сохранить MyStruct, содержащий реальные объекты Date, вы можете создать структуру MyStructRepresentation с FormattedDate в ней и указать serde сериализовать и десериализовать через MyStructRepresentation. - person Michael Anderson; 14.12.2020