web-dev-qa-db-ja.com

Serdeを使用してカスタム関数でオプションフィールドを逆シリアル化するにはどうすればよいですか?

カスタム関数を使用してchrono::NaiveDateをシリアル化および逆シリアル化したいのですが、Serdeブックではこの機能はカバーされておらず、コードド​​キュメントも役に立ちません。

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate chrono;

use chrono::NaiveDate;


mod date_serde {
    use chrono::NaiveDate;
    use serde::{self, Deserialize, Serializer, Deserializer};

    pub fn serialize<S>(date: &Option<NaiveDate>, s: S) -> Result<S::Ok, S::Error>
    where S: Serializer {
        if let Some(ref d) = *date {
            return s.serialize_str(&d.format("%Y-%m-%d").to_string())
        }
        s.serialize_none()
    }

    pub fn deserialize<'de, D>(deserializer: D)
        -> Result<Option<NaiveDate>, D::Error>
        where D: Deserializer<'de> {
        let s: Option<String> = Option::deserialize(deserializer)?;
        if let Some(s) = s {
            return Ok(Some(NaiveDate::parse_from_str(&s, "%Y-%m-%d").map_err(serde::de::Error::custom)?))
        }

        Ok(None)
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct Test {
    pub i: u64,
    #[serde(with = "date_serde")]
    pub date: Option<NaiveDate>,
}

fn main() {
    let mut test: Test = serde_json::from_str(r#"{"i": 3, "date": "2015-02-03"}"#).unwrap();
    assert_eq!(test.i, 3);
    assert_eq!(test.date, Some(NaiveDate::from_ymd(2015, 02, 03)));
    test = serde_json::from_str(r#"{"i": 5}"#).unwrap();
    assert_eq!(test.i, 5);
    assert_eq!(test.date, None);
}

ChronoはSerdeをサポートしているため、Option<chrono::NaiveDate>はSerdeによって簡単に逆シリアル化できることを知っていますbut私はSerdeを学習しようとしているので、自分で実装します。このコードを実行すると、エラーが発生します。

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ErrorImpl { code: Message("missing field `date`"), line: 1, column: 8 }', /checkout/src/libcore/result.rs:859
note: Run with `Rust_BACKTRACE=1` for a backtrace.
20
Victor Polevoy

構造体の逆シリアル化のデフォルトの動作では、フィールドがシリアル化された形式で存在しない場合、フィールドにそれぞれのデフォルト値が割り当てられます。これは、構造体のデフォルト値をフィールドに入力する container #[serde(default)] attribute とは異なることに注意してください。

_#[derive(Debug, PartialEq, Deserialize)]
pub struct Foo<'a> {
    x: Option<&'a str>,
}

let foo: Foo = serde_json::from_str("{}")?;
assert_eq!(foo, Foo { x: None });
_

ただし、別のデシリアライザ関数( #[serde(deserialize_with = "path")] )を使用すると、このルールは変更されます。タイプOptionのフィールドは、このフィールドが存在しない可能性があることをデシリアライザに伝えなくなりました。むしろ、空またはnullの可能性のあるフィールド(Serde用語ではnone)のあるフィールドがあることを示唆しています。たとえば_serde_json_では、_Option<String>_は「nullまたはstringのいずれか」(TypeScript /フロー表記では_null | string_)と同等のJavaScriptです。以下のこのコードは、指定された定義と日付デシリアライザで正常に機能します。

_let test: Test = serde_json::from_str(r#"{"i": 5, "date": null}"#)?;
assert_eq!(test.i, 5);
assert_eq!(test.date, None);
_

幸運なことに、serde(default)属性を追加するだけで、逆シリアル化プロセスの許容度が高くなります(_Option::default_はNoneを生成します):

_#[derive(Debug, Serialize, Deserialize)]
struct Test {
    pub i: u64,

    #[serde(default)]
    #[serde(with = "date_serde")]
    pub date: Option<NaiveDate>,
}
_

遊び場

以下も参照してください。