Skip to content

Properly use xsi:nil to deserialize null values via serde #850

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

Merged
merged 8 commits into from
Mar 25, 2025
2 changes: 1 addition & 1 deletion .github/workflows/cifuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
language: rust
fuzz-seconds: 600
- name: Upload Crash
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ name = "serde-de-seq"
required-features = ["serialize"]
path = "tests/serde-de-seq.rs"

[[test]]
name = "serde-de-xsi"
required-features = ["serialize"]
path = "tests/serde-de-xsi.rs"

[[test]]
name = "serde-se"
required-features = ["serialize"]
Expand Down
7 changes: 7 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@

### New Features

- [#850]: Add `Attribute::as_bool()` method to get an attribute value as a boolean.
- [#850]: Add `Attributes::has_nil()` method to check if attributes has `xsi:nil` attribute set to `true`.
- [#497]: Handle `xsi:nil` attribute in serde Deserializer to better process optional fields.

### Bug Fixes

### Misc Changes

[#497]: https://github.com/tafia/quick-xml/issues/497
[#850]: https://github.com/tafia/quick-xml/pull/850


## 0.37.2 -- 2024-12-29

Expand Down
23 changes: 22 additions & 1 deletion src/de/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,21 @@ where
has_value_field: fields.contains(&VALUE_KEY),
})
}

/// Determines if subtree started with the specified event shoould be skipped.
///
/// Used to map elements with `xsi:nil` attribute set to true to `None` in optional contexts.
///
/// We need to handle two attributes:
/// - on parent element: <map xsi:nil="true"><foo/></map>
/// - on this element: <map><foo xsi:nil="true"/></map>
///
/// We check parent element too because `xsi:nil` affects only nested elements of the
/// tag where it is defined. We can map structure with fields mapped to attributes to
/// the `<map>` element and set to `None` all its optional elements.
fn should_skip_subtree(&self, start: &BytesStart) -> bool {
self.de.reader.reader.has_nil_attr(&self.start) || self.de.reader.reader.has_nil_attr(start)
}
}

impl<'de, 'd, R, E> MapAccess<'de> for ElementMapAccess<'de, 'd, R, E>
Expand Down Expand Up @@ -540,8 +555,14 @@ where
where
V: Visitor<'de>,
{
match self.map.de.peek()? {
// We cannot use result of `peek()` directly because of borrow checker
let _ = self.map.de.peek()?;
match self.map.de.last_peeked() {
DeEvent::Text(t) if t.is_empty() => visitor.visit_none(),
DeEvent::Start(start) if self.map.should_skip_subtree(start) => {
self.map.de.skip_next_tree()?;
visitor.visit_none()
}
_ => visitor.visit_some(self),
}
}
Expand Down
Loading