From 5d761742c1b8906d9e8fd200c592b119c69b79e4 Mon Sep 17 00:00:00 2001 From: Frank Villaro-Dixon Date: Sat, 20 Apr 2024 00:16:08 +0200 Subject: [PATCH] examples: add flattened enum deserialization Add a small example about flattened enum deserialization. This is a small workaround around serde's [serde's issue](https://github.com/serde-rs/serde/issues/1905). Big thanks to @Mingun for (most) of the code + help! Signed-off-by: Frank Villaro-Dixon --- Cargo.toml | 5 ++ Changelog.md | 3 + examples/flattened_enum.rs | 109 +++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 examples/flattened_enum.rs diff --git a/Cargo.toml b/Cargo.toml index eb2f794d..46355ae1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/Changelog.md b/Changelog.md index 1ee04a0a..6b01f422 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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 @@ -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 diff --git a/examples/flattened_enum.rs b/examples/flattened_enum.rs new file mode 100644 index 00000000..1191638f --- /dev/null +++ b/examples/flattened_enum.rs @@ -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, +} + +#[derive(Debug, PartialEq)] +enum Elem { + Foo(Foo), + Bar(Bar), +} + +impl<'de> Deserialize<'de> for Elem { + fn deserialize(deserializer: D) -> Result + 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(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + if let Some((key, value)) = map.next_entry::()? { + 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#" + + + 1 + + 2 + 42 + 1337 + + + + 22 + + +"#; + + let model: Model = from_str(&x).unwrap(); + println!("{:?}", model); +}