serde_helper.rs

  1use serde::Serializer;
  2
  3/// Serializes an f32 value with 2 decimal places of precision.
  4///
  5/// This function rounds the value to 2 decimal places and formats it as a string,
  6/// then parses it back to f64 before serialization. This ensures clean JSON output
  7/// without IEEE 754 floating-point artifacts.
  8///
  9/// # Arguments
 10///
 11/// * `value` - The f32 value to serialize
 12/// * `serializer` - The serde serializer to use
 13///
 14/// # Returns
 15///
 16/// Result of the serialization operation
 17///
 18/// # Usage
 19///
 20/// This function can be used with Serde's `serialize_with` attribute:
 21/// ```
 22/// use serde::Serialize;
 23/// use settings::serialize_f32_with_two_decimal_places;
 24///
 25/// #[derive(Serialize)]
 26/// struct ExampleStruct(#[serde(serialize_with = "serialize_f32_with_two_decimal_places")] f32);
 27/// ```
 28pub fn serialize_f32_with_two_decimal_places<S>(
 29    value: &f32,
 30    serializer: S,
 31) -> Result<S::Ok, S::Error>
 32where
 33    S: Serializer,
 34{
 35    let rounded = (value * 100.0).round() / 100.0;
 36    let formatted = format!("{:.2}", rounded);
 37    let clean_value: f64 = formatted.parse().unwrap_or(rounded as f64);
 38    serializer.serialize_f64(clean_value)
 39}
 40
 41/// Serializes an optional f32 value with 2 decimal places of precision.
 42///
 43/// This function handles `Option<f32>` types, serializing `Some` values with 2 decimal
 44/// places of precision and `None` values as null. For `Some` values, it rounds to 2 decimal
 45/// places and formats as a string, then parses back to f64 before serialization. This ensures
 46/// clean JSON output without IEEE 754 floating-point artifacts.
 47///
 48/// # Arguments
 49///
 50/// * `value` - The optional f32 value to serialize
 51/// * `serializer` - The serde serializer to use
 52///
 53/// # Returns
 54///
 55/// Result of the serialization operation
 56///
 57/// # Behavior
 58///
 59/// * `Some(v)` - Serializes the value rounded to 2 decimal places
 60/// * `None` - Serializes as JSON null
 61///
 62/// # Usage
 63///
 64/// This function can be used with Serde's `serialize_with` attribute:
 65/// ```
 66/// use serde::Serialize;
 67/// use settings::serialize_optional_f32_with_two_decimal_places;
 68///
 69/// #[derive(Serialize)]
 70/// struct ExampleStruct {
 71///     #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
 72///     optional_value: Option<f32>,
 73/// }
 74/// ```
 75pub fn serialize_optional_f32_with_two_decimal_places<S>(
 76    value: &Option<f32>,
 77    serializer: S,
 78) -> Result<S::Ok, S::Error>
 79where
 80    S: Serializer,
 81{
 82    match value {
 83        Some(v) => {
 84            let rounded = (v * 100.0).round() / 100.0;
 85            let formatted = format!("{:.2}", rounded);
 86            let clean_value: f64 = formatted.parse().unwrap_or(rounded as f64);
 87            serializer.serialize_some(&clean_value)
 88        }
 89        None => serializer.serialize_none(),
 90    }
 91}
 92
 93#[cfg(test)]
 94mod tests {
 95    use super::*;
 96    use serde::{Deserialize, Serialize};
 97
 98    #[derive(Serialize, Deserialize)]
 99    struct TestOptional {
100        #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
101        value: Option<f32>,
102    }
103
104    #[derive(Serialize, Deserialize)]
105    struct TestNonOptional {
106        #[serde(serialize_with = "serialize_f32_with_two_decimal_places")]
107        value: f32,
108    }
109
110    #[test]
111    fn test_serialize_optional_f32_with_two_decimal_places() {
112        let cases = [
113            (Some(123.456789), r#"{"value":123.46}"#),
114            (Some(1.2), r#"{"value":1.2}"#),
115            (Some(300.00000), r#"{"value":300.0}"#),
116        ];
117        for (value, expected) in cases {
118            let value = TestOptional { value };
119            assert_eq!(serde_json::to_string(&value).unwrap(), expected);
120        }
121    }
122
123    #[test]
124    fn test_serialize_f32_with_two_decimal_places() {
125        let cases = [
126            (123.456789, r#"{"value":123.46}"#),
127            (1.200, r#"{"value":1.2}"#),
128            (300.00000, r#"{"value":300.0}"#),
129        ];
130        for (value, expected) in cases {
131            let value = TestNonOptional { value };
132            assert_eq!(serde_json::to_string(&value).unwrap(), expected);
133        }
134    }
135}