compact_str/features/
serde.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3
4use serde::de::{Deserializer, Error, Unexpected, Visitor};
5
6use crate::CompactString;
7
8fn compact_string<'de: 'a, 'a, D: Deserializer<'de>>(
9    deserializer: D,
10) -> Result<CompactString, D::Error> {
11    struct CompactStringVisitor;
12
13    impl<'a> Visitor<'a> for CompactStringVisitor {
14        type Value = CompactString;
15
16        fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
17            formatter.write_str("a string")
18        }
19
20        fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
21            Ok(CompactString::from(v))
22        }
23
24        fn visit_borrowed_str<E: Error>(self, v: &'a str) -> Result<Self::Value, E> {
25            Ok(CompactString::from(v))
26        }
27
28        fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
29            Ok(CompactString::from(v))
30        }
31
32        fn visit_bytes<E: Error>(self, v: &[u8]) -> Result<Self::Value, E> {
33            match core::str::from_utf8(v) {
34                Ok(s) => Ok(CompactString::from(s)),
35                Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
36            }
37        }
38
39        fn visit_borrowed_bytes<E: Error>(self, v: &'a [u8]) -> Result<Self::Value, E> {
40            match core::str::from_utf8(v) {
41                Ok(s) => Ok(CompactString::from(s)),
42                Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
43            }
44        }
45
46        fn visit_byte_buf<E: Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
47            match String::from_utf8(v) {
48                Ok(s) => Ok(CompactString::from(s)),
49                Err(e) => Err(Error::invalid_value(
50                    Unexpected::Bytes(&e.into_bytes()),
51                    &self,
52                )),
53            }
54        }
55    }
56
57    deserializer.deserialize_str(CompactStringVisitor)
58}
59
60#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
61impl serde::Serialize for CompactString {
62    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
63        self.as_str().serialize(serializer)
64    }
65}
66
67#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
68impl<'de> serde::Deserialize<'de> for CompactString {
69    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
70        compact_string(deserializer)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use alloc::string::{String, ToString};
77    use alloc::vec::Vec;
78
79    use serde::{Deserialize, Serialize};
80    use test_strategy::proptest;
81
82    use crate::CompactString;
83
84    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
85    struct PersonString {
86        name: String,
87        phones: Vec<String>,
88        address: Option<String>,
89    }
90
91    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
92    struct PersonCompactString {
93        name: CompactString,
94        phones: Vec<CompactString>,
95        address: Option<CompactString>,
96    }
97
98    #[test]
99    fn test_roundtrip() {
100        let name = "Ferris the Crab";
101        let phones = ["1-800-111-1111", "2-222-222-2222"];
102        let address = Some("123 Sesame Street");
103
104        let std = PersonString {
105            name: name.to_string(),
106            phones: phones.iter().map(|s| s.to_string()).collect(),
107            address: address.as_ref().map(|s| s.to_string()),
108        };
109        let compact = PersonCompactString {
110            name: name.into(),
111            phones: phones.iter().map(|s| CompactString::from(*s)).collect(),
112            address: address.as_ref().map(|s| CompactString::from(*s)),
113        };
114
115        let std_json = serde_json::to_string(&std).unwrap();
116        let compact_json = serde_json::to_string(&compact).unwrap();
117
118        // the serialized forms should be the same
119        assert_eq!(std_json, compact_json);
120
121        let std_de_compact: PersonString = serde_json::from_str(&compact_json).unwrap();
122        let compact_de_std: PersonCompactString = serde_json::from_str(&std_json).unwrap();
123
124        // we should be able to deserailze from the opposite, serialized, source
125        assert_eq!(std_de_compact, std);
126        assert_eq!(compact_de_std, compact);
127    }
128
129    #[cfg_attr(miri, ignore)]
130    #[proptest]
131    fn proptest_roundtrip(name: String, phones: Vec<String>, address: Option<String>) {
132        let std = PersonString {
133            name: name.clone(),
134            phones: phones.to_vec(),
135            address: address.clone(),
136        };
137        let compact = PersonCompactString {
138            name: name.into(),
139            phones: phones.iter().map(CompactString::from).collect(),
140            address: address.map(CompactString::from),
141        };
142
143        let std_json = serde_json::to_string(&std).unwrap();
144        let compact_json = serde_json::to_string(&compact).unwrap();
145
146        // the serialized forms should be the same
147        assert_eq!(std_json, compact_json);
148
149        let std_de_compact: PersonString = serde_json::from_str(&compact_json).unwrap();
150        let compact_de_std: PersonCompactString = serde_json::from_str(&std_json).unwrap();
151
152        // we should be able to deserailze from the opposite, serialized, source
153        assert_eq!(std_de_compact, std);
154        assert_eq!(compact_de_std, compact);
155    }
156}