serde_decimal/
non_required_float.rs

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