Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialization with custom deserializer does not work according to serde spec #189

Open
diegoefe opened this issue Nov 21, 2022 · 1 comment

Comments

@diegoefe
Copy link

The customization works using serde-json but not the serde-xml-rs' one.

The example is based on this doc page: Deserialize either a string or a struct

Using this Cargo.toml's dependencies:

[dependencies]
serde = { version = "1.0", features = ["derive", "serde_derive"] }
serde-xml-rs = "0.6.0"
serde_json = "1.0.88"
serde_yaml = "0.9.14"
void = "1.0.2"

And this main.rs (run it with cargo test -- --nocapture)

use serde::{Deserialize,Deserializer};

use std::marker::PhantomData;
use std::str::FromStr;
use void::Void;
use serde::de::{self, Visitor, MapAccess};
use std::fmt;

// #[serde(deserialize_with = "string_or_struct")]
#[derive(Debug, Deserialize)]
pub struct Artist {
    pub name: String,
    pub mbid: Option<String>,
    pub url: Option<String>,
    pub listeners: Option<usize>,
    pub streamable: Option<bool>,
}

impl FromStr for Artist {
    // This implementation of `from_str` can never fail, so use the impossible
    // `Void` type as the error type.
    type Err = Void;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Artist {
            name: s.to_string(),
            mbid: None,
            url: None,
            listeners: None,
            streamable: None,
        })
    }
}

fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
    T: Deserialize<'de> + FromStr<Err = Void>,
    D: Deserializer<'de>,
{

    // This is a Visitor that forwards string types to T's `FromStr` impl and
    // forwards map types to T's `Deserialize` impl. The `PhantomData` is to
    // keep the compiler from complaining about T being an unused generic type
    // parameter. We need T in order to know the Value type for the Visitor
    // impl.
    struct StringOrStruct<T>(PhantomData<fn() -> T>);

    impl<'de, T> Visitor<'de> for StringOrStruct<T>
    where
        T: Deserialize<'de> + FromStr<Err = Void>,
    {
        type Value = T;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("string or map")
        }

        fn visit_str<E>(self, value: &str) -> Result<T, E>
        where
            E: de::Error,
        {
            println!("visit_string ({value})");
            Ok(FromStr::from_str(value).unwrap())
        }

        fn visit_map<M>(self, map: M) -> Result<T, M::Error>
        where
            M: MapAccess<'de>,
        {
            // `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
            // into a `Deserializer`, allowing it to be used as the input to T's
            // `Deserialize` implementation. T then deserializes itself using
            // the entries from the map visitor.
            Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
        }
    }

    deserializer.deserialize_any(StringOrStruct(PhantomData))
}

#[derive(Deserialize, Debug)]
pub struct Album {
    pub name: String,
    #[serde(deserialize_with = "string_or_struct")]
    pub artist: Artist,
}

type Albums = Vec<Album>;

#[derive(Deserialize, Debug)]
pub struct Search {
    pub status: String,
    pub album:Albums,
}

#[cfg(test)]
mod tests {

use super::*;

    #[test]
    fn test_json_deserialization() {
    let src =
r#"
{
    "status":"ok",
    "album": [
        {
            "name": "Youthanasia",
            "artist": {
                "name":"Megadeth",
                "mbid": "a9044915-8be3-4c7e-b11f-9e2d2ea0a91e",
                "url": "https://www.last.fm/music/Megadeth2"
            }    
        },
        {
            "name": "Dystopia",
            "artist": "Megadeth"
        }
    ]
}
"#;
        match serde_json::from_str::<Search>(src) {
            Ok(nfo) => {
                println!("Search: {:#?}", nfo)
            },
            Err(e)=> {
                println!("Error: {}", e);
                assert!(false)
            }
        }
    }


// this fails
    #[test]
    fn test_xml_deserialization() {
        let src =
r#"
<?xml version="1.0" encoding="UTF-8" ?>
<lfm status="ok">
    <album>
        <name>Youthanasia</name>
        <artist>
            <name>Megadeth</name>
            <mbid>a9044915-8be3-4c7e-b11f-9e2d2ea0a91e</mbid>
            <url>https://www.last.fm/music/Megadeth</url>
        </artist>
    </album>
    <album>
        <name>Youthanasia</name>
        <artist>Metallica</artist>
    </album>
 /lfm>
"#;
    match serde_xml_rs::from_str::<Search>(src) {
        Ok(nfo) => {
            println!("Search: {:#?}", nfo)
        },
        Err(e)=> {
            println!("Error: {}", e);
            assert!(false)
        }
    }
}


}


fn main() {
    println!("Dummy main")
}
@NuSkooler
Copy link

@diegoefe Did you find a resolution to this issue? I'm attempting to de-serialize Windows Event Log XML which has exactly this issue (string vs struct)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants