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.
There is no method within serde_test
that can test these functions directly. While serde_test
uses its own Serializer
and Deserializer
internally, it does not expose these types, so you can't use them directly in your testing.
However, you can accomplish this with the serde_assert
crate (disclaimer: I wrote serde_assert
). serde_assert
directly exposes its Serializer
and Deserializer
specifically for testing, and works well for your use case:
use serde_assert::{Deserializer, Serializer, Token, Tokens};
#[test]
fn serialize() {
let serializer = Serializer::builder().build();
assert_eq!(
stringpair_serde::serialize(
&StringPair("foo".to_string(), "bar".to_string()),
&serializer
),
Ok(Tokens(vec![Token::Str("foo:bar".to_string())])),
);
}
#[test]
fn deserialize() {
let mut deserializer = Deserializer::builder()
.tokens(Tokens(vec![Token::Str("foo:bar".to_string())]))
.build();
assert_eq!(
stringpair_serde::deserialize(&mut deserializer),
Ok(StringPair("foo".to_string(), "bar".to_string()))
);
}