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")
}
@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)
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:
And this main.rs (run it with cargo test -- --nocapture)