serde_decimal/
nullable_float.rs

1//! Serialization and deserialization of required but nullable decimals as floats.
2
3use std::fmt::{self};
4
5use rust_decimal::Decimal;
6
7struct OptionDecimalVisitor;
8
9impl<'de> serde::de::Visitor<'de> for OptionDecimalVisitor {
10    type Value = Option<Decimal>;
11
12    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
13        formatter.write_str("a Decimal type representing a fixed-point number")
14    }
15
16    fn visit_none<E>(self) -> Result<Option<Decimal>, E>
17    where
18        E: serde::de::Error,
19    {
20        Ok(None)
21    }
22
23    fn visit_some<D>(self, d: D) -> Result<Option<Decimal>, D::Error>
24    where
25        D: serde::de::Deserializer<'de>,
26    {
27        <Decimal as serde::Deserialize>::deserialize(d).map(Some)
28    }
29
30    fn visit_unit<E>(self) -> Result<Self::Value, E>
31    where
32        E: serde::de::Error,
33    {
34        Ok(None)
35    }
36}
37
38/// Nullable float-form decimal deserializer.
39///
40/// See [module docs](self) for more.
41pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<rust_decimal::Decimal>, D::Error>
42where
43    D: serde::de::Deserializer<'de>,
44{
45    deserializer.deserialize_option(OptionDecimalVisitor)
46}
47
48/// Nullable float-form decimal serializer.
49///
50/// See [module docs](self) for more.
51pub fn serialize<S>(value: &Option<rust_decimal::Decimal>, serializer: S) -> Result<S::Ok, S::Error>
52where
53    S: serde::Serializer,
54{
55    rust_decimal::serde::float_option::serialize(value, serializer)
56}
57
58#[cfg(test)]
59mod tests {
60    use rust_decimal_macros::dec;
61
62    #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
63    struct Foo {
64        #[serde(with = "crate::nullable_float")]
65        foo: Option<rust_decimal::Decimal>,
66    }
67
68    #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
69    struct Bar {
70        #[serde(flatten)]
71        foo: Foo,
72    }
73
74    #[test]
75    fn foo_serialize_some() {
76        let serialized = serde_json::to_string(&Foo {
77            foo: Some(dec!(0.1)),
78        })
79        .unwrap();
80        assert_eq!(serialized, r#"{"foo":0.1}"#);
81    }
82
83    #[test]
84    fn foo_serialize_none() {
85        let serialized = serde_json::to_string(&Foo { foo: None }).unwrap();
86        assert_eq!(serialized, r#"{"foo":null}"#);
87    }
88
89    #[test]
90    fn foo_deserialize_some() {
91        let deserialized: Foo = serde_json::from_str(r#"{"foo":0.1}"#).unwrap();
92        assert!(matches!(deserialized, Foo { foo: Some(_) }));
93    }
94
95    #[test]
96    #[should_panic]
97    fn foo_deserialize_missing() {
98        serde_json::from_str::<Foo>(r#"{}"#).unwrap();
99    }
100
101    #[test]
102    fn foo_deserialize_null() {
103        let deserialized: Foo = serde_json::from_str(r#"{"foo":null}"#).unwrap();
104        assert!(matches!(deserialized, Foo { foo: None }));
105    }
106
107    #[test]
108    fn bar_serialize_some() {
109        let serialized = serde_json::to_string(&Bar {
110            foo: Foo {
111                foo: Some(dec!(0.1)),
112            },
113        })
114        .unwrap();
115        assert_eq!(serialized, r#"{"foo":0.1}"#);
116    }
117
118    #[test]
119    fn bar_serialize_none() {
120        let serialized = serde_json::to_string(&Bar {
121            foo: Foo { foo: None },
122        })
123        .unwrap();
124        assert_eq!(serialized, r#"{"foo":null}"#);
125    }
126
127    #[test]
128    fn bar_deserialize_some() {
129        let deserialized: Bar = serde_json::from_str(r#"{"foo":0.1}"#).unwrap();
130        assert!(matches!(
131            deserialized,
132            Bar {
133                foo: Foo { foo: Some(_) }
134            }
135        ));
136    }
137
138    #[test]
139    #[should_panic]
140    fn bar_deserialize_missing() {
141        serde_json::from_str::<Bar>(r#"{}"#).unwrap();
142    }
143
144    #[test]
145    fn bar_deserialize_null() {
146        let deserialized: Bar = serde_json::from_str(r#"{"foo":null}"#).unwrap();
147        assert!(matches!(
148            deserialized,
149            Bar {
150                foo: Foo { foo: None }
151            }
152        ));
153    }
154}