Open allsey87 opened 4 years ago
I believe the problem is due to the way a struct with a flatten
attribute is deserialised. The derived implementation will:
Inner
is not available. Each collected value is parsed as a serde::private::de::Content
. The Deserialize
impl for Content
just calls deserialize_any
, which serde-xml-rs
always parses as a string.Inner
in your example), using a serde::private::de::FlatMapDeserializer
.
Inner
's Deserialize implementation will specify that it wants i32
fields, but the FlatMapDeserializer
contains a collection of string values for those fields, which will ultimately cause a call to visit_str
on Inner
's Visitor type - which returns the error.One workaround is to use a custom deserializer for each field of the inner type:
#[derive(Debug, Deserialize)]
struct Inner {
#[serde(deserialize_with = "string_as_i32")]
left: i32,
#[serde(deserialize_with = "string_as_i32")]
right: i32,
}
#[derive(Debug, Deserialize)]
struct Outer {
name: String,
#[serde(flatten)]
inner: Inner,
}
struct StringAsI32Visitor;
impl<'de> Visitor<'de> for StringAsI32Visitor {
type Value = i32;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("integer value string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value
.parse()
.map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
}
}
fn string_as_i32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<i32, D::Error> {
deserializer.deserialize_string(StringAsI32Visitor)
}
Since that was one of the issues which lead me to write a specialized XML deserializer, I thought I might mention it here. My solution was to use #[serde(rename="")] to pass flags like flatten to be interpreted by my custom XML deserializer. Here is an example specifying MusicXML. https://github.com/Matthias-Fauconneau/musicxml/blob/master/src/music_xml.rs
Previous bug: #112
This issue is also present in the quick-xml crate: discussion
I think i've found a suitable workaround for this issue. I've created a deserialize_with
function which should be applied to all primitive fields in the types being deserialized. It will attempt to deserialize as normal first, and then as a string. It will then parse the string into a char, number, or bool (with the same rules as serde-xml-rs
):
use crate::deserialize_with::flattened_xml_attr;
struct Foo {
// Easiest to just apply it to every primitive, but you could be more selective
#[serde(deserialize_with = "flattened_xml_attr")]
a: i32,
b: Bar,
#[flatten]
c: Baz
}
struct Bar {
#[serde(deserialize_with = "flattened_xml_attr")]
d: i32,
e: String,
#[serde(deserialize_with = "flattened_xml_attr")]
f: bool
}
struct Baz {
#[serde(deserialize_with = "flattened_xml_attr")]
g: bool
}
It's just implemented in my consuming crate for now; no PRs for anywhere else. You can find it here to use as necessary.
The following working example reproduces the problem. In a nutshell, there seems to be a problem with converting strings to the types inside
struct Inner
but only when it is flattened intostruct Outer
. These flattened members are unable to be parsed into booleans, integers etc.This prints:
Deserialization of
struct Outer
only works whenleft
andright
areString
. In this case, the output is: