A Short Serde Deserialize example

A Short Serde Deserialize example

Bradley Noyes published on
3 min, 509 words

In my previous post, I described taking a simple enum and creating a custom type in diesel. This post will take that same enum and implement deserialize.

I often get tripped up by the mechanics of deserializing so this simple enum makes for a good example. Again, this is to benefit anyone looking for more examples of Serde's Deserialize as well as for myself, so I can remember next time I need to do this.

Deserialize in Serde🔗

To refresh, the enum is as follows,

    #[derive(Debug,PartialEq,AsExpression,Clone,Serialize)]
    #[sql_type = "Bool"]
    pub enum PublishState
    {
        Published,
        Unpublished,
        Pending,
    }

The problem constraint is that PublishState can be represented as a boolean or a string, so deserialize needs to be able to handle both. The translation from serialized data happens in serde's Visitor trait. The first thing we need is to create a Visitor struct, let's call it PublishStateVisitor. It doesn't have any members, so it is a Unit Struct. Since this is a fairly simple case, let's just look at all the code at once.

    struct PublishStateVisitor
    
    impl<'de> Visitor<'de> for PublishStateVisitor {
        type Value = PublishState;
    
        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            formatter.write_str("a boolean or string of \"Published\", \"Unpublished\", \"Pending\".")
        }
    
        fn visit_bool<E>(self, value: bool) -> Result<PublishState, E>
        where
            E: de::Error,
        {
            match value {
                true => Ok(PublishState::Published),
                false => Ok(PublishState::Unpublished),
            }
        }
    
        fn visit_str<E>(self, value: &str) -> Result<PublishState, E>
        where
            E: de::Error,
        {
            match value {
                "Published" => Ok(PublishState::Published),
                "Unpublished" | "Pending" => Ok(PublishState::Unpublished),
                _s => Err(E::custom(format!("Unknown string value: {}", _s))),
            }
        }
    }

In this case, the Visitor trait will implement the expecting function and two translation functions, visit_bool and visit_str. Since I will accept deserializing from a bool type and a string type, those two translation functions needs to be implemented (see serde's docs for other visitor functions). The visitor translation functions are pretty simple. It's worth noting the return type; they need to return Result<PublishState, E> where the PublishState is the result of the translation from bool or str into a PublishState enum value.

Now we need to use the PublishStateVisitor somewhere; this is where the Deserialize trait comes into play.

    impl<'de> Deserialize<'de> for PublishState {
        fn deserialize<D>(deserializer: D) -> Result<PublishState, D::Error>
        where
            D: Deserializer<'de>,
        {
            deserializer.deserialize_any(PublishStateVisitor)
        }
    }

Those two trait implementations is all that is needed for this struct. In someways is a lot of code for such a simple translation, but in other ways, it starts to show you the power of serde.

Serde's docs are pretty good. I recommend their documentation page as well as their Github Page for JSON examples.

My Example code can be found in my gitlab examples repo.