Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom date deserialize but with Option<Datetime<Utc>> #1444

Closed
yageek opened this issue Dec 14, 2018 · 2 comments
Closed

Custom date deserialize but with Option<Datetime<Utc>> #1444

yageek opened this issue Dec 14, 2018 · 2 comments
Labels

Comments

@yageek
Copy link

yageek commented Dec 14, 2018

I found the example about this exanple about custom date formatting.

I want to apply exactly the same but with an Option<DateTime<Utc>>.

I found this stackoverflow thread and try to combine one deserialize_with and default attributes but no success:

fn datefmt<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
  D: Deserializer<'de>,
{
  let s = String::deserialize(deserializer)?;
  Utc
    .datetime_from_str(&s, FORMAT)
    .map_err(serde::de::Error::custom)
}

struct MyStruct {

  #[serde(default)]
  #[serde(deserialize_with = "datefmt")]
  expiration_date: Option<DateTime<Utc>>,
}

But I have the following error:

#[derive(Deserialize, Debug)]
   |          -----------
   |          |
   |          expected enum `std::option::Option`, found struct `chrono::datetime::DateTime`
   |          match arm with an incompatible type
   |          in this macro invocation
   |
   = note: expected type `std::option::Option<chrono::datetime::DateTime<_>>`
              found type `chrono::datetime::DateTime<_>`

How to handle the case of Option<T> where T has custom serialise/deserialize helpers ?

@dtolnay
Copy link
Member

dtolnay commented Dec 15, 2018

This is not well supported right now (but tracked in #723). You need to provide a separate deserialize_with function that produces Option<T>. I would write this as:

use chrono::{DateTime, TimeZone, Utc};
use serde::{Deserialize, Deserializer};

const FORMAT: &str = "%Y-%m-%d %H:%M:%S";

fn datefmt<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
    D: Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    Utc.datetime_from_str(&s, FORMAT)
        .map_err(serde::de::Error::custom)
}

fn option_datefmt<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    struct Wrapper(#[serde(deserialize_with = "datefmt")] DateTime<Utc>);

    let v = Option::deserialize(deserializer)?;
    Ok(v.map(|Wrapper(a)| a))
}

#[derive(Deserialize, Debug)]
struct MyStruct {
    #[serde(default, deserialize_with = "option_datefmt")]
    expiration_date: Option<DateTime<Utc>>,
}

fn main() {
    let j = r#" {"expiration_date": null} "#;
    println!("{:?}", serde_json::from_str::<MyStruct>(j).unwrap());

    let j = r#" {"expiration_date": "2017-02-16 21:54:30"} "#;
    println!("{:?}", serde_json::from_str::<MyStruct>(j).unwrap());
}

@yageek
Copy link
Author

yageek commented Dec 15, 2018

Thanks for the complete and clear response :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants