serde-rs / serde

Serialization framework for Rust
https://serde.rs/
Apache License 2.0
9.16k stars 774 forks source link

Document combination of custom (de)serializer for type in different crate #1455

Closed tomasol closed 1 year ago

tomasol commented 5 years ago

I am trying to implement custom deserialization for external type. I am struggling as there are no mentions of Visitor pattern in https://serde.rs/remote-derive.html . My sample code does not take the QoSExternalVisitor into account:

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::fmt;

mod other_crate {
    #[derive(Debug)]
    pub enum QualityOfServiceExternal {
        Level0 = 0,
        Level1 = 1,
        Level2 = 2,
    }
}

#[derive(Debug)]
pub enum QualityOfServiceInternal {
    Level0 = 0,
    Level1 = 1,
    Level2 = 2,
}

#[derive(Debug, Deserialize)]
#[serde(remote = "other_crate::QualityOfServiceExternal")]
pub enum QualityOfServiceDef {
    Level0 = 0,
    Level1 = 1,
    Level2 = 2,
}

struct QoSExternalVisitor;

impl<'de> serde::de::Visitor<'de> for QoSExternalVisitor {
    type Value = QualityOfServiceDef;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a number from range 0..2")
    }

    fn visit_u64<E>(self, n: u64) -> Result<Self::Value, E>
        where E: serde::de::Error
    {
        Ok(match n {
            0 => QualityOfServiceDef::Level0,
            1 => QualityOfServiceDef::Level1,
            2 => QualityOfServiceDef::Level2,
            n => {
                let err = format!(
                    "Could not deserialize '{}' as qos, expecting 0..2", n);
                return Err(E::custom(err));
            }
        })
    }
}

impl<'de> serde::de::Deserialize<'de> for QualityOfServiceDef {
    fn deserialize<D>(d: D) -> Result<QualityOfServiceDef, D::Error>
        where D: serde::de::Deserializer<'de>
    {
        d.deserialize_any(QoSExternalVisitor)
    }
}

struct QoSInternalVisitor;

impl<'de> serde::de::Visitor<'de> for QoSInternalVisitor {
    type Value = QualityOfServiceInternal;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a number from range 0..2")
    }

    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
    {
        Ok(match value {
            0 => QualityOfServiceInternal::Level0,
            1 => QualityOfServiceInternal::Level1,
            2 => QualityOfServiceInternal::Level2,
            n => {
                let err = format!(
                    "Could not deserialize '{}' as qos, expecting 0..2", n);
                return Err(E::custom(err));
            }
        })
    }
}

impl<'de> serde::de::Deserialize<'de> for QualityOfServiceInternal {
    fn deserialize<D>(d: D) -> Result<QualityOfServiceInternal, D::Error>
        where D: serde::de::Deserializer<'de>
    {
        d.deserialize_any(QoSInternalVisitor)
    }
}

#[derive(Deserialize, Debug)]
struct Test {
    #[serde(with = "QualityOfServiceDef")]
    qos_external: other_crate::QualityOfServiceExternal,
    qos_internal: QualityOfServiceInternal,
}

fn main() {
    // this works
    let _: Test = serde_json::from_str(&"{\"qos_external\":\"Level1\", \"qos_internal\":2}").unwrap();
    // however I want to use the custom deserializer that is not invoked
    // this fails
    let _: Test = serde_json::from_str(&"{\"qos_external\":1, \"qos_internal\":2}").unwrap();

}

Please advise and/or point me to the documentation.

dtolnay commented 5 years ago

I would write this as:

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

mod other_crate {
    #[derive(Debug)]
    pub enum QualityOfService {
        Level0 = 0,
        Level1 = 1,
        Level2 = 2,
    }
}

use crate::other_crate::QualityOfService;
use serde::{Deserialize, Deserializer};

fn deserialize_qos<'de, D>(deserializer: D) -> Result<QualityOfService, D::Error>
where
    D: Deserializer<'de>,
{
    match u64::deserialize(deserializer)? {
        0 => Ok(QualityOfService::Level0),
        1 => Ok(QualityOfService::Level1),
        2 => Ok(QualityOfService::Level2),
        n => Err(serde::de::Error::custom(format_args!(
            "invalid qos value: {}, expected 0 through 2",
            n
        ))),
    }
}

#[derive(Deserialize, Debug)]
struct Test {
    #[serde(deserialize_with = "deserialize_qos")]
    qos_external: QualityOfService,
}

fn main() {
    serde_json::from_str::<Test>(r#"{"qos_external":1, "qos_internal":2}"#).unwrap();
}
tomasol commented 5 years ago

Thanks @dtolnay , the code looks indeed much nicer (and works :)). I did not know about deserialize_with. Even though it is somehow mentioned in examples, I would like to suggest adding it to the Implementing Deserialize page as it solves a problem (custom deserialization for external type) and is much easier to read than the visitor pattern.

vmx commented 4 years ago

@dtolnay I've a very similar issue and would like to use a custom visitor. I think I can't use a similar solution as the one mentioned above, as most of the visitor functions return something custom (especially the visit_newtype_struct one). Is there a way to use a custom visitor together with remote?

I also tried it without using remote, but that broke the case for a definition like

pub enum RemoteEnum {
    Breaks(Vec<RemoteEnum>),
}

within visit_seq() due to RemoteEnum (which is the remote type) not implementing serde::de::Deserialize<'de>.

vmx commented 4 years ago

I ended up not using remote. In order to fix the problem inside visit_seq() I used:

/// We cannot directly implement `serde::Deserializer` for `RemoteEnum` as it is a remote type.
/// Instead wrap it into a newtype struct and implement `serde::Deserialize` for that one.
/// All the deserializer does is calling the our custom visitor which returns a `RemoteEnum` instance,
/// wraps it in `Wrapper` and returns it.
/// Users of this wrapper will then unwrap it again so that they can return the expected `RemoteEnum`
/// instance.
struct Wrapper(RemoteEnum);
impl<'de> Deserialize<'de> for Wrapper {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let deserialized = deserializer.deserialize_any(MyEnumVisitor);
        // Better version of Ok(Wrapper(deserialized.unwrap()))
        deserialized.map(Wrapper)
    }
}

struct MyEnumVisitor;
impl<'de> de::Visitor<'de> for MyEnumVisitor {
    type Value = RemoteEnum;
…
    fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
    where
        V: de::SeqAccess<'de>,
    {
        let mut vec: Vec<Wrapper> = Vec::new();

        while let Some(elem) = visitor.next_element()? {
            vec.push(elem);
        }

        let unwrapped = vec.into_iter().map(|Wrapper(enum)| enum).collect();
        Ok(Breaks(unwrapped))
    }
…
}