Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support (Feature Request?): Custom Serializer for Option<T>::None which results in {} instead of null #1104

Open
thehappycheese opened this issue Jan 21, 2024 · 0 comments

Comments

@thehappycheese
Copy link

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::EmptyObject is like Option::None
    • but has the desired property of being serialized to/from the JSON empty object {} instead of null
  • EmptyObjectOr::Object(T) ~~ Option::Some(T)
    • is serialized the same as serde_json::to_string(T), thanks to the flattening behaviour of serde(untagged)

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_with method 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant