Skip to content

Commit 8df645b

Browse files
committed
Properly use xsi:nil to deserialize null values via serde
This commit fixes an issue that causes `quick-xml` trying to deserialize empty tags via the serde interface even if these tags were explicitly marked as `xsi:nil="true"` For example the following XML failed to deserialize before this commit: ```xml <bar> <foo xsi:nil="true"/> </bar> ``` into the following rust type: ```rust #[derive(Deserialize)] struct Bar { foo: Option<Inner>, } #[derive(Deserialize)] struct Foo { baz: String, } ``` Before this commit this failed to deserialize with an error message that complained that the `baz` field was missing. After this commit this uses the `xsi:nil` attribute to deserialize this into `foo: None` instead. The standard (https://www.w3.org/TR/xmlschema-1/#xsi_nil) seems to support this behaviour. Fix #497
1 parent 8f91a9c commit 8df645b

File tree

3 files changed

+88
-0
lines changed

3 files changed

+88
-0
lines changed

src/de/map.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,13 @@ where
542542
{
543543
match self.map.de.peek()? {
544544
DeEvent::Text(t) if t.is_empty() => visitor.visit_none(),
545+
DeEvent::Start(start)
546+
// if the `xsi:nil` attribute is set to true we got a none value
547+
if start.attributes().is_xsi_nil() =>
548+
{
549+
self.map.de.skip_nil_tag()?;
550+
visitor.visit_none()
551+
}
545552
_ => visitor.visit_some(self),
546553
}
547554
}

src/de/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2764,6 +2764,33 @@ where
27642764
}
27652765
self.reader.read_to_end(name)
27662766
}
2767+
2768+
fn skip_nil_tag(&mut self) -> Result<(), DeError> {
2769+
use serde::de::Error;
2770+
let DeEvent::Start(start) = self.peek()? else {
2771+
unreachable!("Only call this if the next event is a start event")
2772+
};
2773+
let name = start.name().0.to_vec();
2774+
let name = QName(&name);
2775+
loop {
2776+
match self.next()? {
2777+
DeEvent::Eof => return Err(DeError::UnexpectedEof),
2778+
DeEvent::Start(s) if s.name() == name => {}
2779+
DeEvent::End(e) if e.name() == name => break,
2780+
DeEvent::Start(s) => return Err(DeError::UnexpectedStart(s.name().0.to_vec())),
2781+
DeEvent::Text(t) => {
2782+
return Err(DeError::custom(format!("Unexpected text found: `{t:?}`")))
2783+
}
2784+
DeEvent::End(e) => {
2785+
return Err(DeError::custom(format!(
2786+
"Unkexpected end tag found: `{:?}`",
2787+
e.name()
2788+
)))
2789+
}
2790+
}
2791+
}
2792+
Ok(())
2793+
}
27672794
}
27682795

27692796
impl<'de> Deserializer<'de, SliceReader<'de>> {
@@ -2948,6 +2975,13 @@ where
29482975
match self.peek()? {
29492976
DeEvent::Text(t) if t.is_empty() => visitor.visit_none(),
29502977
DeEvent::Eof => visitor.visit_none(),
2978+
DeEvent::Start(start)
2979+
// if the `xsi:nil` attribute is set to true we got a none value
2980+
if start.attributes().is_xsi_nil() =>
2981+
{
2982+
self.skip_nil_tag()?;
2983+
visitor.visit_none()
2984+
}
29512985
_ => visitor.visit_some(self),
29522986
}
29532987
}
@@ -4680,4 +4714,43 @@ mod tests {
46804714
}
46814715
}
46824716
}
4717+
4718+
#[test]
4719+
fn deserialize_nil() {
4720+
#[derive(Debug, serde::Deserialize, PartialEq)]
4721+
struct Foo {
4722+
bar: String,
4723+
}
4724+
4725+
let data = r#"<foo xsi:nil="true"/>"#;
4726+
4727+
let res = super::from_str::<Option<Foo>>(data).unwrap();
4728+
assert!(res.is_none());
4729+
4730+
let data = r#"<foo xsi:nil="false"><bar>Boom</bar></foo>"#;
4731+
let res = super::from_str::<Option<Foo>>(data).unwrap();
4732+
assert_eq!(
4733+
res,
4734+
Some(Foo {
4735+
bar: String::from("Boom")
4736+
})
4737+
);
4738+
}
4739+
4740+
#[test]
4741+
fn deserialize_nested_nil() {
4742+
#[derive(Debug, serde::Deserialize, PartialEq)]
4743+
struct Foo {
4744+
bar: String,
4745+
}
4746+
4747+
#[derive(Debug, serde::Deserialize, PartialEq)]
4748+
struct Bar {
4749+
foo: Option<Foo>,
4750+
}
4751+
4752+
let data = r#"<bar><foo xsi:nil="true"/></bar>"#;
4753+
let res = super::from_str::<Bar>(data).unwrap();
4754+
assert_eq!(res, Bar { foo: None });
4755+
}
46834756
}

src/events/attributes.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,14 @@ impl<'a> Attributes<'a> {
234234
self.state.check_duplicates = val;
235235
self
236236
}
237+
238+
pub(crate) fn is_xsi_nil(&self) -> bool {
239+
self.clone().any(|attr| {
240+
matches!(attr, Ok(crate::events::attributes::Attribute{
241+
key: QName(b"xsi:nil"), value
242+
}) if &*value == b"true")
243+
})
244+
}
237245
}
238246

239247
impl<'a> Iterator for Attributes<'a> {

0 commit comments

Comments
 (0)