使用`serde::Serialize`与`Option<chrono::DateTime>`

15
尝试对Option<chrono::DateTime<Utc>>进行序列化时,遇到了一个错误:
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)

代码(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>>,
    }
}

看着 chrono::ts_secondsserde_with。我该怎么办?

如果您正在使用PostgreSQL,我建议您使用DateTime<Utc>而不是Unix时间戳。 - simanacci
4个回答

13

Chrono已经有一个适用于 Option<DateTime<Utc>> 的函数,即chrono::serde::ts_seconds_option

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

使用serde_with的解决方案如下:

#[serde_as]
#[derive(Serialize, Debug)]
struct TestStruct {
    pub a: f32,
    #[serde_as(as = "Option<DurationSeconds<i64>>")]
    pub date: Option<DateTime<Utc>>,
}

3
这需要您在依赖列表(Cargo.toml)中指定chronos serde功能:例如,chrono = { version = "^0.4.1",features = ["serde", "rustc-serialize"] }。 - Dag Sondre Hansen

4
你可以编写自己的包装器,并将其与serialize_withskip_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>>,
}

游乐场


7
这个解决方案假定当日期为“None”时,希望跳过该字段。还可以通过将unreachable分支更改为None => serializer.serialize_none(),将处理None的任务留给序列化器实现。 - sebpuetz

1

当您想要使用DateTime<Utc>而不是Unix时间戳,并且跳过不是一个选项时。

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

{{链接1: Rust Playground }}


1

我用了不同的方法解决了这个问题。 我的问题是,我使用sqlx从数据库中获取数据。其中一个条目是chrono::DateType类型,因此我无法立即序列化它。

我通过迭代查询记录并将DateTime变量转换为字符串来解决了这个问题。

这是我的代码:

// 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
}

我相信有更好的方法。但我认为这段代码很容易理解,而且不需要实现。我希望我能够帮助到某些人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接