Search code examples
rustdeserializationserde

Rust Serde create deserializer for Option::<String>


I have two custom functions used for serde deserialize_with

// Converts string to u32
pub fn de_u32_from_str<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
    D: Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    s.parse::<u32>().map_err(de::Error::custom)
}

// Converts string that may be `null` to either `Some(u32)` or `None`
pub fn de_opt_u32_from_opt_str<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
where
    D: Deserializer<'de>,
{
    let s = Option::<String>::deserialize(deserializer)?;
    s.map(|s| s.parse::<u32>().map_err(de::Error::custom))
        .transpose()
}

I tested de_u32_from_str like this:

fn deserialize_u32_from_str(string: &str, expected: u32) {
    let de: StrDeserializer<ValueError> = string.into_deserializer();
    let result = de_u32_from_str(de).unwrap();

    assert_eq!(result, expected);
}

However I struggle creating deserializer for testing de_opt_u32_from_opt_str

fn deserialize_opt_u32_from_opt_str(maybe_string: Option<&str>, expected: Option<u32>) {
    // I need a `deserializer` here
    let de: ... = ...;
    let result = de_opt_u32_from_opt_str(de).unwrap();

    assert_eq!(result, expected);
}

How can I create a deserializer like

let de: StrDeserializer<ValueError> = string.into_deserializer();

but holding Option<String>to satisfy

let s = Option::<String>::deserialize(deserializer)?;

Solution

  • Here is a deserializer for Option that will work with any type that implements IntoDeserializer (built to mimic the existing implementations in serde::de::value):

    use std::marker::PhantomData;
    
    use serde::de::{Deserializer, Error, IntoDeserializer, Visitor}; // 1.0.160
    
    struct OptionDeserializer<T, E> {
        value: Option<T>,
        marker: PhantomData<E>,
    }
    
    impl<T, E> OptionDeserializer<T, E> {
        pub fn new(value: Option<T>) -> Self {
            OptionDeserializer {
                value,
                marker: PhantomData,
            }
        }
    }
    
    impl<'de, T, E> Deserializer<'de> for OptionDeserializer<T, E>
    where
        T: IntoDeserializer<'de, E>,
        E: Error,
    {
        type Error = E;
    
        fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
        where
            V: Visitor<'de>,
        {
            match self.value {
                Some(value) => visitor.visit_some(value.into_deserializer()),
                None => visitor.visit_none(),
            }
        }
    
        serde::forward_to_deserialize_any! {
            bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
            bytes byte_buf option unit unit_struct newtype_struct seq tuple
            tuple_struct map struct enum identifier ignored_any
        }
    }
    

    Using it, the missing line in your function would be:

    let de: OptionDeserializer<&str, ValueError> = OptionDeserializer::new(maybe_string);
    

    See a full demo with tests on the playground.