Skip to content
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

examples: add flattened enum deserialization #738

Merged
merged 1 commit into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,8 @@ path = "tests/async-tokio.rs"
name = "read_nodes_serde"
required-features = ["serialize"]
path = "examples/read_nodes_serde.rs"

[[example]]
name = "flattened_enum"
required-features = ["serialize"]
path = "examples/flattened_enum.rs"
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ to get an offset of the error position. For `SyntaxError`s the range
- [#362]: Now default quote level is `QuoteLevel::Partial` when using serde serializer.
- [#689]: `buffer_position()` now always report the position the parser last seen.
To get an error position use `error_position()`.
- [#738]: Add an example of how to deserialize XML elements into Rust enums using an
intermediate custom deserializer.

[#362]: https://github.com/tafia/quick-xml/issues/362
[#513]: https://github.com/tafia/quick-xml/issues/513
Expand All @@ -70,6 +72,7 @@ to get an offset of the error position. For `SyntaxError`s the range
[#689]: https://github.com/tafia/quick-xml/pull/689
[#704]: https://github.com/tafia/quick-xml/pull/704
[#705]: https://github.com/tafia/quick-xml/pull/705
[#738]: https://github.com/tafia/quick-xml/pull/738


## 0.31.0 -- 2023-10-22
Expand Down
109 changes: 109 additions & 0 deletions examples/flattened_enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! This example demonstrates how to deserialize enum nodes using an intermediate
//! custom deserializer.
//! The `elem` node can either be a `Foo` or a `Bar` node, depending on the `type`.
//! The `type` attribute is used to determine which variant to deserialize.
//! This is a workaround for [serde's issue](https://github.com/serde-rs/serde/issues/1905)
//!
//! note: to use serde, the feature needs to be enabled
//! run example with:
//! cargo run --example flattened_enum --features="serialize"

use std::fmt;

use quick_xml::de::from_str;
use serde::de::value::MapAccessDeserializer;
use serde::de::{Error, MapAccess, Visitor};
use serde::Deserialize;

#[derive(Debug, Deserialize, PartialEq)]
struct Model {
elem: Vec<Elem>,
}

#[derive(Debug, PartialEq)]
enum Elem {
Foo(Foo),
Bar(Bar),
}

impl<'de> Deserialize<'de> for Elem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ElemVisitor;

impl<'de> Visitor<'de> for ElemVisitor {
type Value = Elem;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an object with a `type` field")
}

fn visit_map<A>(self, mut map: A) -> Result<Elem, A::Error>
where
A: MapAccess<'de>,
{
if let Some((key, value)) = map.next_entry::<String, String>()? {
return match key.as_str() {
"@type" => match value.as_str() {
"foo" => {
let f = Foo::deserialize(MapAccessDeserializer::new(map))?;
Ok(Elem::Foo(f))
}
"bar" => {
let f = Bar::deserialize(MapAccessDeserializer::new(map))?;
Ok(Elem::Bar(f))
}
t => Err(Error::custom(format!("unknown type attribute `{t}`"))),
},
a => Err(Error::custom(format!(
"expected attribute `type`, but found `{a}`"
))),
};
}
Err(Error::custom("expected `type` attribute"))
}
}
deserializer.deserialize_map(ElemVisitor)
}
}

#[derive(Debug, Deserialize, PartialEq)]
struct Foo {
a: String,
subfoo: SubFoo,
}

#[derive(Debug, Deserialize, PartialEq)]
struct SubFoo {
a1: String,
a2: String,
a3: String,
}

#[derive(Debug, Deserialize, PartialEq)]
struct Bar {
b: String,
}

fn main() {
let x = r#"
<model>
<elem type="foo">
<a>1</a>
<subfoo>
<a1>2</a1>
<a2>42</a2>
<a3>1337</a3>
</subfoo>
</elem>
<elem type="bar">
<b>22</b>
</elem>
</model>
"#;

let model: Model = from_str(&x).unwrap();
println!("{:?}", model);
}