Serde derive macros come with the ability to control how a field is serialized/deserialized through the #[serde(with = "module")]
field attribute. The "module"
should have serialize and deserialize functions with the right arguments and return types.
An example that unfortunately got a bit too contrived:
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, PartialEq, Eq)]
pub struct StringPair(String, String);
mod stringpair_serde {
pub fn serialize<S>(sp: &super::StringPair, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.serialize_str(format!("{}:{}", sp.0, sp.1).as_str())
}
pub fn deserialize<'de, D>(d: D) -> Result<super::StringPair, D::Error>
where
D: serde::Deserializer<'de>,
{
d.deserialize_str(Visitor)
}
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = super::StringPair;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a pair of strings separated by colon (:)")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(s.split_once(":")
.map(|tup| super::StringPair(tup.0.to_string(), tup.1.to_string()))
.unwrap_or(Default::default()))
}
}
}
#[derive(Serialize, Deserialize)]
struct UsesStringPair {
// Other fields ...
#[serde(with = "stringpair_serde")]
pub stringpair: StringPair,
}
fn main() {
let usp = UsesStringPair {
stringpair: StringPair("foo".to_string(), "bar".to_string()),
};
assert_eq!(
serde_json::json!(&usp).to_string(),
r#"{"stringpair":"foo:bar"}"#
);
let usp: UsesStringPair = serde_json::from_str(r#"{"stringpair":"baz:qux"}"#).unwrap();
assert_eq!(
usp.stringpair,
StringPair("baz".to_string(), "qux".to_string())
)
}
Testing derived serialization for UsesStringPair
is trivial with simple assertions. But I have looked at serde_test
example as that makes sense to me too.
However, I want to be able to independently test the stringpair_serde::{serialize, deserialize}
functions (e.g. if my crate provides just mycrate::StringPair
and mycrate::stringpair_serde
, and UsesStringPair
is for the crate users to implement).
One way I've looked into is creating a serde_json::Serializer
(using new
, requires a io::Write
implementation, which I couldn't figure out how to create and use trivially, but that's a separate question) and calling serialize with the created Serializer, then making assertions on the result as before. However, that does not test any/all implementations of serde::Serializer
, just the one provided in serde_json
.
I'm wondering if there's a method like in the serde_test
example that works for ser/deser functions provided by a module.
StringPair
itself? – Pernambuco&mut Vec<u8>
implementsio::Write
. – Scharagachrono::DateTime<Tz>
. It can be serialized as a string, as a Unix timestamp, as an integer timestamp but with higher resolution (e.g. milliseconds etc.) So the type does itself implement the Serialize, Deserialize traits, but chrono also provides submodules that can be used withwith = ...
to serializeDateTime
value in different ways. – MuldoonStringPair
and it does implement the ser/de traits, but the module writer also wishes to provide ser/de for monadic containers of that type e.g.Option<StringPair>
gets serialized differently fromStringPair
. Lots of reasons for needing a stand-alone ser/de module instead of type implementing traits. – Muldoon