如何用Actix-web、Serde、Chrono将特定ISO 8601时间/时长序列化为Rust结构体?
实现自定义ISO 8601格式到Rust结构体的序列化/反序列化
完全可行,你需要通过Serde的自定义序列化/反序列化逻辑处理这种复合ISO格式,结合Chrono解析时间和时长部分,以下是具体实现方案:
1. 依赖配置
首先在Cargo.toml中添加所需依赖,确保Chrono启用必要特性:
[dependencies] actix-web = "4" serde = { version = "1", features = ["derive"] } chrono = { version = "0.4", features = ["serde", "fixed-offset", "format"] }
2. 定义核心结构体
先定义存储激活时间和时长的结构体,以及用于接收请求的外层结构体:
use chrono::{DateTime, Duration, FixedOffset}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct ActivationRequest { #[serde(serialize_with = "serialize_schedule", deserialize_with = "deserialize_schedule")] pub activation_schedule: ActivationDetail, } #[derive(Debug)] pub struct ActivationDetail { pub activation_time: DateTime<FixedOffset>, pub duration: Duration, }
3. 实现自定义反序列化逻辑
编写Serde Visitor解析Thhmmss-hhmm/PTmm格式的字符串,拆分时间和时长部分分别用Chrono解析:
use serde::de::{self, Deserializer}; use std::fmt; fn deserialize_schedule<'de, D>(deserializer: D) -> Result<ActivationDetail, D::Error> where D: Deserializer<'de>, { struct ScheduleVisitor; impl<'de> de::Visitor<'de> for ScheduleVisitor { type Value = ActivationDetail; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("字符串格式需为 'Thhmmss-hhmm/PTmm'") } fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: de::Error, { // 按 '/' 分割时间部分和时长部分 let parts: Vec<&str> = s.split('/').collect(); if parts.len() != 2 { return Err(E::invalid_value(de::Unexpected::Str(s), &self)); } // 解析带时区的激活时间(如 "T063000-0600") let activation_time = DateTime::parse_from_str(parts[0], "%T%z") .map_err(|_| E::custom(format!("无效的时间格式: {}", parts[0])))?; // 解析ISO 8601时长(如 "PT10M") let duration = Duration::parse(parts[1]) .map_err(|_| E::custom(format!("无效的时长格式: {}", parts[1])))?; Ok(ActivationDetail { activation_time, duration }) } } deserializer.deserialize_str(ScheduleVisitor) }
4. 实现自定义序列化逻辑
把结构体数据格式化为指定的字符串格式:
use serde::ser::Serializer; fn serialize_schedule<S>(detail: &ActivationDetail, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { // 将时间格式化为 "Thhmmss-hhmm" 格式 let time_str = detail.activation_time.format("%T%z").to_string(); // Chrono的Duration转字符串默认就是ISO 8601格式 let duration_str = detail.duration.to_string(); // 拼接成目标格式 let full_str = format!("{}/{}", time_str, duration_str); serializer.serialize_str(&full_str) }
5. 在Actix-web中使用
直接将结构体作为请求体接收,或者作为响应返回:
use actix_web::{post, web, HttpResponse}; #[post("/activation")] async fn set_activation(schedule: web::Json<ActivationRequest>) -> HttpResponse { println!("激活计划: {:?}", schedule.activation_schedule); HttpResponse::Ok().json(schedule) } #[actix_web::main] async fn main() -> std::io::Result<()> { actix_web::HttpServer::new(|| { actix_web::App::new() .service(set_activation) }) .bind(("127.0.0.1", 8080))? .run() .await }
注意事项
- 如果你的格式存在变体(比如时区为
+hhmm、时长包含小时/秒等),Chrono的DateTime::parse_from_str和Duration::parse都能兼容标准ISO格式,无需额外修改 - 若不需要外层
ActivationRequest结构体,可以直接为ActivationDetail实现Serialize和Deserializetrait,替代使用serde属性的方式
内容的提问来源于stack exchange,提问作者Jeremy Jones
相关产品推荐
相关产品推荐

