Search code examples
rustserderust-chrono

How to use a custom serde deserializer for chrono timestamps?


I'm trying to parse JSON into a struct which has a chrono::DateTime field. The JSON has the timestamps saved in a custom format that I wrote a deserializer for.

How do I connect the two and get it working using #[serde(deserialize_with)]?

I'm using NaiveDateTime for simpler code

extern crate serde;
extern crate serde_json;
use serde::Deserialize;
extern crate chrono;
use chrono::NaiveDateTime;

fn from_timestamp(time: &String) -> NaiveDateTime {
    NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M:%S.%f").unwrap()
}

#[derive(Deserialize, Debug)]
struct MyJson {
    name: String,
    #[serde(deserialize_with = "from_timestamp")]
    timestamp: NaiveDateTime,
}

fn main() {
    let result: MyJson =
        serde_json::from_str(r#"{"name": "asdf", "timestamp": "2019-08-15T17:41:18.106108"}"#)
            .unwrap();
    println!("{:?}", result);
}

I'm getting three different compile errors:

error[E0308]: mismatched types
  --> src/main.rs:11:10
   |
11 | #[derive(Deserialize, Debug)]
   |          ^^^^^^^^^^^ expected reference, found type parameter
   |
   = note: expected type `&std::string::String`
              found type `__D`

error[E0308]: mismatched types
  --> src/main.rs:11:10
   |
11 | #[derive(Deserialize, Debug)]
   |          ^^^^^^^^^^-
   |          |         |
   |          |         this match expression has type `chrono::NaiveDateTime`
   |          expected struct `chrono::NaiveDateTime`, found enum `std::result::Result`
   |          in this macro invocation
   |
   = note: expected type `chrono::NaiveDateTime`
              found type `std::result::Result<_, _>`

error[E0308]: mismatched types
  --> src/main.rs:11:10
   |
11 | #[derive(Deserialize, Debug)]
   |          ^^^^^^^^^^-
   |          |         |
   |          |         this match expression has type `chrono::NaiveDateTime`
   |          expected struct `chrono::NaiveDateTime`, found enum `std::result::Result`
   |          in this macro invocation
   |
   = note: expected type `chrono::NaiveDateTime`
              found type `std::result::Result<_, _>`

I'm pretty sure the from_timestamp function is returning a DateTime struct and not a Result, so I don't know what "expected struct chrono::NaiveDateTime, found enum std::result::Result" may mean.


Solution

  • This is rather involved, but the following works:

    use chrono::NaiveDateTime;
    use serde::de;
    use serde::Deserialize;
    use std::fmt;
    
    struct NaiveDateTimeVisitor;
    
    impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor {
        type Value = NaiveDateTime;
    
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            write!(formatter, "a string represents chrono::NaiveDateTime")
        }
    
        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            match NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%f") {
                Ok(t) => Ok(t),
                Err(_) => Err(de::Error::invalid_value(de::Unexpected::Str(s), &self)),
            }
        }
    }
    
    fn from_timestamp<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        d.deserialize_str(NaiveDateTimeVisitor)
    }
    
    #[derive(Deserialize, Debug)]
    struct MyJson {
        name: String,
        #[serde(deserialize_with = "from_timestamp")]
        timestamp: NaiveDateTime,
    }
    
    fn main() {
        let result: MyJson =
            serde_json::from_str(r#"{"name": "asdf", "timestamp": "2019-08-15T17:41:18.106108"}"#)
                .unwrap();
        println!("{:?}", result);
    }