RReverser / serde-xml-rs

xml-rs based deserializer for Serde (compatible with 1.0+)
https://crates.io/crates/serde-xml-rs
MIT License
269 stars 90 forks source link

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

Open diegoefe opened 1 year ago

diegoefe commented 1 year ago

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 commented 7 months ago

@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)