Skip to main content

axum_bootstrap/util/
json.rs

1//! # JSON 工具模块
2//!
3//! 提供 JSON 序列化和反序列化的辅助工具
4//!
5//! # 主要组件
6//! - `StupidValue<T>`: 宽松类型转换的 JSON 值包装器
7//! - `my_date_format`: 日期时间格式化模块 (`%Y-%m-%d %H:%M:%S`)
8//! - `my_date_format_option`: 可选日期时间格式化模块
9//! - `empty_string_as_none`: 将空字符串转换为 None 的反序列化装饰器
10
11use std::fmt::Display;
12use std::str::FromStr;
13use std::{borrow::Cow, fmt};
14
15use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
16
17/// Serde 反序列化装饰器,将空字符串映射为 None
18///
19/// # 类型参数
20/// - `D`: 反序列化器类型
21/// - `T`: 目标类型,必须实现 `FromStr`
22///
23/// # 返回
24/// - `Ok(Some(T))`: 非空字符串,成功解析为 T
25/// - `Ok(None)`: 空字符串或 null
26/// - `Err`: 解析失败
27///
28/// # 示例
29///
30/// ```
31/// use serde::{Deserialize};
32///
33/// #[derive(Deserialize)]
34/// struct MyStruct {
35///     #[serde(deserialize_with = "axum_bootstrap::util::json::empty_string_as_none")]
36///     value: Option<i32>,
37/// }
38/// ```
39#[allow(dead_code)]
40pub fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
41where
42    D: Deserializer<'de>,
43    T: FromStr,
44    T::Err: fmt::Display,
45{
46    let opt = Option::<String>::deserialize(de)?;
47    match opt.as_deref() {
48        None | Some("") => Ok(None),
49        Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some),
50    }
51}
52
53/// 宽松类型转换的 JSON 值包装器
54///
55/// 可以同时接受字符串和原始类型的 JSON 值,并自动进行类型转换
56///
57/// # 类型参数
58/// - `T`: 目标类型,必须实现 `FromStr` 和 `Deserialize`
59///
60/// # 使用场景
61/// 当 API 返回的数据类型不一致时(有时是字符串,有时是数字),
62/// 可以使用 StupidValue 来统一处理
63///
64/// # 示例
65///
66/// ```
67/// use serde::{Deserialize};
68/// use axum_bootstrap::util::json::StupidValue;
69///
70/// #[derive(Deserialize)]
71/// struct Response {
72///     // 可以接受 "123" 或 123
73///     count: StupidValue<i32>,
74/// }
75///
76/// let json1 = r#"{"count": "123"}"#;
77/// let json2 = r#"{"count": 123}"#;
78/// // 两者都能正确解析
79/// ```
80#[derive(Debug, Clone, Default, PartialEq, Eq)]
81pub struct StupidValue<T>(pub T);
82
83impl<T> Serialize for StupidValue<T>
84where
85    T: ToString,
86{
87    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88    where
89        S: Serializer,
90    {
91        self.0.to_string().serialize(serializer)
92    }
93}
94
95impl<'de, T> Deserialize<'de> for StupidValue<T>
96where
97    T: FromStr + Deserialize<'de>,
98    T::Err: Display,
99{
100    fn deserialize<D>(deserializer: D) -> Result<StupidValue<T>, D::Error>
101    where
102        D: Deserializer<'de>,
103    {
104        #[derive(Deserialize)]
105        #[serde(untagged)]
106        enum StrOrValue<'a, T> {
107            Str(Cow<'a, str>),
108            Value(T),
109        }
110
111        let str_or_val = StrOrValue::<T>::deserialize(deserializer)?;
112        Ok(StupidValue(match str_or_val {
113            StrOrValue::Value(val) => val,
114            StrOrValue::Str(s) => s.parse().map_err(serde::de::Error::custom)?,
115        }))
116    }
117}
118
119impl<T> From<T> for StupidValue<T> {
120    fn from(val: T) -> Self {
121        StupidValue(val)
122    }
123}
124
125/// 日期时间格式化模块
126///
127/// 提供 `%Y-%m-%d %H:%M:%S` 格式的日期时间序列化和反序列化
128///
129/// # 使用示例
130///
131/// ```
132/// use serde::{Serialize, Deserialize};
133/// use chrono::NaiveDateTime;
134///
135/// #[derive(Serialize, Deserialize)]
136/// struct Event {
137///     #[serde(with = "axum_bootstrap::util::json::my_date_format")]
138///     created_at: NaiveDateTime,
139/// }
140/// ```
141pub mod my_date_format {
142    use chrono::NaiveDateTime;
143    use serde::{self, Deserialize, Deserializer, Serializer};
144
145    /// 日期时间格式: `YYYY-MM-DD HH:MM:SS`
146    const FORMAT: &str = "%Y-%m-%d %H:%M:%S";
147
148    /// 将 NaiveDateTime 序列化为字符串
149    ///
150    /// # 参数
151    /// - `date`: 要序列化的日期时间
152    /// - `serializer`: 序列化器
153    ///
154    /// # 格式
155    /// `YYYY-MM-DD HH:MM:SS`
156    pub fn serialize<S>(date: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
157    where
158        S: Serializer,
159    {
160        let s = format!("{}", date.format(FORMAT));
161        serializer.serialize_str(&s)
162    }
163
164    /// 从字符串反序列化为 NaiveDateTime
165    ///
166    /// # 参数
167    /// - `deserializer`: 反序列化器
168    ///
169    /// # 返回
170    /// - `Ok(NaiveDateTime)`: 成功解析的日期时间
171    /// - `Err`: 格式错误
172    pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
173    where
174        D: Deserializer<'de>,
175    {
176        let s = String::deserialize(deserializer)?;
177        let dt = NaiveDateTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)?;
178        Ok(dt)
179    }
180}
181
182/// 可选日期时间格式化模块
183///
184/// 提供 `Option<NaiveDateTime>` 的序列化和反序列化,格式为 `%Y-%m-%d %H:%M:%S`
185///
186/// # 使用示例
187///
188/// ```
189/// use serde::{Serialize, Deserialize};
190/// use chrono::NaiveDateTime;
191///
192/// #[derive(Serialize, Deserialize)]
193/// struct Event {
194///     #[serde(with = "axum_bootstrap::util::json::my_date_format_option")]
195///     updated_at: Option<NaiveDateTime>,
196/// }
197/// ```
198pub mod my_date_format_option {
199    use super::my_date_format;
200    use chrono::NaiveDateTime;
201    use serde::{self, Deserialize, Deserializer, Serializer};
202
203    /// 日期时间格式: `YYYY-MM-DD HH:MM:SS`
204    const FORMAT: &str = "%Y-%m-%d %H:%M:%S";
205
206    /// 将 `Option<NaiveDateTime>` 序列化为字符串或 null
207    ///
208    /// # 参数
209    /// - `opt`: 可选的日期时间
210    /// - `serializer`: 序列化器
211    ///
212    /// # 行为
213    /// - `Some(date)`: 序列化为格式化字符串
214    /// - `None`: 序列化为 null
215    pub fn serialize<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
216    where
217        S: Serializer,
218    {
219        match *opt {
220            Some(ref dt) => my_date_format::serialize(dt, serializer),
221            None => serializer.serialize_none(),
222        }
223    }
224
225    /// 从字符串或 null 反序列化为 `Option<NaiveDateTime>`
226    ///
227    /// # 参数
228    /// - `deserializer`: 反序列化器
229    ///
230    /// # 返回
231    /// - `Ok(Some(NaiveDateTime))`: 成功解析的日期时间
232    /// - `Ok(None)`: 输入为 null
233    /// - `Err`: 格式错误
234    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<NaiveDateTime>, D::Error>
235    where
236        D: Deserializer<'de>,
237    {
238        match Option::<String>::deserialize(deserializer)? {
239            Some(s) => {
240                let dt = NaiveDateTime::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)?;
241                Ok(Some(dt))
242            }
243            None => Ok(None),
244        }
245    }
246}