What is wrong with my serde deserializer/visitor?

I am trying to write a deserializer that is able to deserialize i64 both from a JSON numeric and a parsable string.

struct I64Visitor;
impl Visitor<'_> for I64Visitor {
    type Value = i64;
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("Only numeric strings or i64 can be deserialised to i64")
    }
    fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(v)
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: Error,
    {
        let res = i64::from_str(v);
        match res {
            Ok(r) => Ok(r),
            Err(err) => Err(E::custom(format!(
                "Cannot decode i64 out of: {}. error: {:?}",
                v, err
            ))),
        }
    }
    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
    where
        E: Error,
    {
        self.visit_str(&v)
    }
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct TwitterId(pub i64);
scalar!(TwitterId);
impl<'de> Deserialize<'de> for TwitterId {
    fn deserialize<D>(deserializer: D) -> Result<TwitterId, D::Error>
    where
        D: Deserializer<'de>,
    {
        let int = deserializer.deserialize_i64(I64Visitor)?;
        Ok(TwitterId(int))
    }
}

Currently it throws the "expecting" error when it encounters the string variant:

invalid type: string "1097211439159365632", expected Only numeric strings or i64 can be deserialized to i64 at line 1 column 22

I have no idea what's wrong

That's probably because you are calling deserialize_i64(). If you need dynamic typing, you should use deserialize_any(); however, this means that your type will only be able to be deserialized from self-describing formats.

Here's the fixed playground. I have also simplified your code and made it more idiomatic in the following ways:

  • I replaced some repetition and matching with combinator methods (notably, Result::map_err)
  • I removed visit_string(), because you don't need it. Its default implementation forwards to visit_str().
  • I aligned the error messages with the guidelines of Serde. In particular, you shouldn't write full sentences; the expected() method should really just return what is expected, and not any other elaboration.
  • I replaced format!() with format_args!(). There is no need to allocate a temporary String just to have it to_string()'d immediately.
3 Likes

Thank you!

Huh, and now it does not work for the actual numeric values. I started experimenting with visit_i32, visit_u32 etc, but there has to be some "catch all" way?

That's probably because the JSON deserializer calls visit_u64 for non-negative intgers. Here's the updated code.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.