Support (Feature Request?): Custom Serializer for Option<T>::None which results in `{}` instead of `null`
I tried really hard to create a generic custom serializer for structs T that are serialized as JSON Objects which encode/decode Option<T>::None as {} instead of null (because this behavior is required in the jupyter messaging protocol)
Work-around
I found a non-idiomatic work-around, but it basically requires that I re-implement a custom Option type like this
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(deny_unknown_fields)]
#[serde(untagged)]
pub enum EmptyObjectOr<T> {
EmptyObject {},
Object(T),
}
Where
EmptyObjectOr::EmptyObjectis likeOption::None- but has the desired property of being serialized to/from the JSON empty object
{}instead ofnull
- but has the desired property of being serialized to/from the JSON empty object
EmptyObjectOr::Object(T)~~Option::Some(T)- is serialized the same as
serde_json::to_string(T), thanks to the flattening behaviour ofserde(untagged)
- is serialized the same as
Now I can define other structs like
#[derive(Debug, Deserialize, Serialize)]
pub struct MessageParsed {
/// parent_header is sometimes a json object (de-serialized by the struct Header),
/// sometimes its an empty json object, de-serialized by EmptyObjectOr
pub parent_header: EmptyObjectOr<Header>,
// ...
}
But the question is
I still really want to use the more idiomatic Option type like this:
#[derive(Debug, Deserialize, Serialize)]
pub struct MessageParsed {
pub parent_header: Option<Header>,
// ...
}
I tried writing a custom serializer/de-serializer using
#[serde(deserialize_with="de_option_or", serialize_with="ser_option_or")]
But the trouble is that the visitor cannot tell if an object is empty without consuming it, then there is no way to push it down to a lower level deserialiser in a generic way.
Click to see the closest I got
This was the closest I got, but it does not compile because map.size_hint() appears to not be able to detect there are zero keys in the object being deserialized (and probably other reasons, i forget what all the compilation errors were).
pub fn deserialize_empty_object_as_none<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
struct EmptyObjectVisitor<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for EmptyObjectVisitor<T>
where
T: Deserialize<'de>,
{
type Value = Option<T>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an object or an empty object")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let is_empty = map.size_hint().map_or(true, |(size, _)| size == 0);
if is_empty {
Ok(None)
} else {
T::deserialize(serde::de::value::MapAccessDeserializer::new(map)).map(Some)
}
}
}
deserializer.deserialize_map(EmptyObjectVisitor(PhantomData))
}
- Is my work around the best possible compromise?
- is there a way to make the
deserialize_with/serialize_withmethod work? - Is there scope for some kind of future feature like
#[serde(option_none_as_empty_object)]
Many thanks for your time :) I decided to post here since I figured any discussion might be easier to find if others run into the same problem than if it were on discord.