LoginSignup
12
7

More than 1 year has passed since last update.

Option<T> を serde(with="...")するイディオム

Last updated at Posted at 2019-03-29

追記: chrono 0.4.10 以降の場合

chrono の場合は chrono::serde::ts_milliseconds_optionできるようになりました

use chrono::{Utc, DateTime};
#[derive(serde::Deserialize, serde::Serialize, Debug)]
struct A{
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(with = "chrono::serde::ts_milliseconds_option")]
    #[serde(default)]
    pub time: Option<DateTime<Utc>>,
}

Option<T>serde(with="...") するイディオム

type UnixTimeMillis = number;

interface Job {
    started_at: UnixTimeMillis;
    ended_at?: UnixTimeMillis;
}

のような JSON を chrono::serde::ts_milliseconds でシリアライズ|デシリアライズしたい。

直感的に書くと以下のようになるが、 ended_at??? の部分はどうしたらよいのか、という問題が起きる。

#[derive(Serialize, Deserialize)]
pub struct Job {
    #[serde(with = "::chrono::serde::ts_milliseconds")]
    pub started_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(with = "???")]
    pub ended_at: Option<DateTime<Utc>>,
}

現状の serde では Option などにラップされた型を with を使ったカスタムシリアライザで扱うことができない ためである。

そこで、 以下のような Helper 構造体を介してパースしてやると良い

#[derive(Serialize, Deserialize)]
pub struct Job {
    #[serde(with = "::chrono::serde::ts_milliseconds")]
    pub started_at: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(with = "option_ts_milliseconds")]
    #[serde(default="default_opt_date")]
    pub ended_at: Option<DateTime<Utc>>,
}

fn default_opt_date() -> Option<DateTime<Utc>> {
    None
}

mod option_ts_milliseconds {
    use serde_derive::*;
    use chrono::{Utc, DateTime};
    use serde::ser::{Serialize, Serializer};
    use serde::de::{Deserialize, Deserializer};

    pub fn serialize<S>(value: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        struct Helper<'a>(#[serde(with = "::chrono::serde::ts_milliseconds")] &'a DateTime<Utc>);
        value.as_ref().map(Helper).serialize(serializer)
    }
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper(#[serde(with = "::chrono::serde::ts_milliseconds")] DateTime<Utc>);
        let helper = Option::deserialize(deserializer)?;
        Ok(helper.map(|Helper(o)| o))
    }
}

skip_serializing_if を使う場合は Default で None を返すようにしてやる必要がある

参考

12
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7