Using `serde::Serialize` with `Option<chrono::DateTime>`
Asked Answered
F

4

19

When trying to serialize Option<chrono::DateTime<Utc>> I'm encountering an error:

error[E0308]: mismatched types
  --> src/main.rs:39:14
   |
39 |     #[derive(Serialize, Debug)]
   |              ^^^^^^^^^ expected struct `DateTime`, found enum `std::option::Option`
   |
   = note: expected reference `&DateTime<Utc>`
              found reference `&'__a std::option::Option<DateTime<Utc>>`
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

Code (Playground):

use chrono::{serde::ts_seconds, DateTime, NaiveDate, Utc};
use serde::Serialize;

fn main() {
    let test_struct = TestStruct {
        a: 2.45,
        date: Some(DateTime::from_utc(
            NaiveDate::from_ymd(2000, 1, 1).and_hms(1, 1, 1),
            Utc,
        )),
    };
    let string = serde_json::to_string(&test_struct).unwrap();
    println!("default: {}", string);
    
    #[derive(Serialize, Debug)]
    struct TestStruct {
        pub a: f32,
        #[serde(with = "ts_seconds")]
        pub date: Option<DateTime<Utc>>,
    }
}

Looking at chrono::ts_seconds and serde_with. How can I move forward here?

Flameproof answered 2/6, 2021 at 10:44 Comment(1)
If you are using PostgreSQL, I'd recommend you use DateTime<Utc> instead of Unix timestampBarbarism
S
5

You can write your own wrapper and combine it with serialize_with and skip_serializing_if:

pub fn serialize_dt<S>(
    dt: &Option<DateTime<Utc>>, 
    serializer: S
) -> Result<S::Ok, S::Error> 
where
    S: Serializer {
    match dt {
        Some(dt) => ts_seconds::serialize(dt, serializer),
        _ => unreachable!(),
    }
}

#[derive(Serialize, Debug)]
struct TestStruct {
    pub a: f32,
    #[serde(serialize_with = "serialize_dt", skip_serializing_if  = "Option::is_none")]
    pub date: Option<DateTime<Utc>>,
}

Playground

Sessoms answered 2/6, 2021 at 11:4 Comment(1)
This solution assumes that skipping the field is desired when the date is None. It's also possible to leave handling None up to the serializer implementation by changing the unreachable arm to None => serializer.serialize_none().Scever
V
16

Chrono already has a function for Option<DateTime<Utc>>, namely chrono::serde::ts_seconds_option.

#[derive(Serialize, Debug)]
struct TestStruct {
    pub a: f32,
    #[serde(with = "ts_seconds_option")]
    pub date: Option<DateTime<Utc>>,
}

The solution with serde_with looks like this:

#[serde_as]
#[derive(Serialize, Debug)]
struct TestStruct {
    pub a: f32,
    #[serde_as(as = "Option<DurationSeconds<i64>>")]
    pub date: Option<DateTime<Utc>>,
}
Valerivaleria answered 2/6, 2021 at 11:38 Comment(1)
This requires that you specify the chronos serde feature in the dependensies list (Cargo.toml): e.g. chrono = { version = "^0.4.1", features = ["serde", "rustc-serialize"] }Lin
S
5

You can write your own wrapper and combine it with serialize_with and skip_serializing_if:

pub fn serialize_dt<S>(
    dt: &Option<DateTime<Utc>>, 
    serializer: S
) -> Result<S::Ok, S::Error> 
where
    S: Serializer {
    match dt {
        Some(dt) => ts_seconds::serialize(dt, serializer),
        _ => unreachable!(),
    }
}

#[derive(Serialize, Debug)]
struct TestStruct {
    pub a: f32,
    #[serde(serialize_with = "serialize_dt", skip_serializing_if  = "Option::is_none")]
    pub date: Option<DateTime<Utc>>,
}

Playground

Sessoms answered 2/6, 2021 at 11:4 Comment(1)
This solution assumes that skipping the field is desired when the date is None. It's also possible to leave handling None up to the serializer implementation by changing the unreachable arm to None => serializer.serialize_none().Scever
B
2

For when you want to use DateTime<Utc> instead of a Unix timestamp and skipping is not an option.

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

pub fn serialize<S>(date: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    match date.is_some() {
        true => {
            let s = format!("{}", date.as_ref().unwrap().format(FORMAT));
            serializer.serialize_str(&s)
        }
        false => serializer.serialize_none(),
    }
}

"{\"a\":2.45,\"date\":\"2022-12-16 16:40:36\"}"
TestStruct { a: 2.45, date: Some(2022-12-16T16:40:36Z) }

"{\"a\":2.45,\"date\":null}"
TestStruct { a: 2.45, date: None }

Rust Playground

Barbarism answered 16/12, 2022 at 16:43 Comment(0)
Q
1

I solved it differently. I had the Problem, that I got Data from a Database using sqlx. One Entry was of type chrono::DateType so I was not able to immediatly serialize it.

I solved the Problem by iterating over the Records of the Query and converted the DateTime Variable to a String.

Here is my Code:

// the next line is not relevant for this example
pub async fn all_users(Extension(pool): Extension<PgPool>) -> impl IntoResponse { 

// not serializeable
#[derive(sqlx::FromRow)]
struct QueryStruct {
    id: i32,
    username: String,
    create_date: chrono::DateTime<Utc>
}

// serializable (no DateTime)
#[derive(sqlx::FromRow, Deserialize, Serialize)]
struct User {
    id: i32,
    username: String,
    create_date: String
}

let sql = "SELECT * FROM useraccounts";
let queried_users = sqlx::query_as::<_, QueryStruct>(&sql).fetch_all(&pool).await.unwrap();
// now I have a the Variable queried_users, which is of type Vec<QueryStruct> 
// but this is not serializable

// so next i "convert" the Data to the serializable Type
let user_array = users.into_iter().map(|queried_users|{
     User {
         id: user.id,
         username: user.username.clone(),
         create_date: user.create_date.to_string() 
         // you should be able to convert it back with 
         // let chrono_datetime = parse_from_str("2023-02-11 15:53:14.062881 UTC","%F %X%.6f %Z")
    }
}).collect::<Vec<user::User>>();

// here my user_array gets successfully serialized
(StatusCode::OK, Json(user_array)) 

// closing the async fn
}

I'm sure, there is a better way. But I think this Code is very easy to understand and does not require an implementation. I hope, I was able to help somebody.

Quickie answered 11/2, 2023 at 20:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.