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.