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

Impossible Deserialization with a flattened enum / Custom deserializer? #737

Closed
Frankkkkk opened this issue Apr 17, 2024 · 2 comments
Closed
Labels
question serde Issues related to mapping from Rust types to XML

Comments

@Frankkkkk
Copy link
Contributor

Hello!

I'm trying to parse the following XML with this cool library:

<model>
    <elem type="foo">
        <a>1</a>
    </elem>
    <elem type="bar">
        <b>22</b>
    </elem>
   ...
</model>

My code is:

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

#[derive(Debug, Deserialize, PartialEq)]
struct Elem {
    #[serde(flatten)]
    elem: ElemEnum,
}

#[derive(Debug, Deserialize, PartialEq)]
//#[serde(tag = "@type", rename_all = "lowercase")]
#[serde(rename_all = "lowercase")]
enum ElemEnum {
    Foo(Foo),
    Bar(Bar),
}

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

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

But this results in : no variant of enum ElemEnum found in flattened data. If I'm not mistaken, this is due to #203

But is it possible to write a custom deserializer instead ? I've tried to, but I can't seem to delegate the parsing of the Foo/Bar structs. I don't really wan't to implement the parsing of foo/bar in the deserializer because they are big structs:

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>,
            {
                let mut type_str = None;
                let mut elem = None;

                while let Some(key) = map.next_key::<String>()? {
                    match key.as_str() {
                        "@type" => {
                            type_str = Some(map.next_value::<String>()?);
                        }
                        _ => {
                            if let Some(ref ty) = type_str {
                                match ty.as_str() {
                                    "foo" => {
                                        let f = map.next_key::<Foo>().unwrap();
                                        elem = Some(ElemChoice::Foo(f))
                                    }
                                    "bar" => {
                                        let f = map.next_value::<Bar>().unwrap();
                                        elem = Some(ElemChoice::Bar(f))
                                    }
                                    _ => return Err(DeError::custom("unknown type attribute")),
                                }
                            }
                        }
                    }
                }
            }
        }
        deserializer.deserialize_map(ElemVisitor)
    }
}

My question is: Is there a way to give the nested/child elements to the deserializer of Foo/Bar ?

Thanks!

@Mingun Mingun added question serde Issues related to mapping from Rust types to XML labels Apr 17, 2024
@Mingun
Copy link
Collaborator

Mingun commented Apr 18, 2024

Try to use MapAccessDeserializer:

#[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(DeError::custom(format!("unknown type attribute `{t}`"))),
            }
            a => Err(DeError::custom(format!("expected attribute `type`, but found `{a}`"))),
          };
        }
        Err(DeError::custom("expected `type` attribute")),
      }
    }
    deserializer.deserialize_map(ElemVisitor)
  }
}

This solution however, has drawbacks, because it does not use intermediate buffer: the type attribute should be the first attribute in the <elem>.

Elem itself should be the enum in that case.

@Frankkkkk
Copy link
Contributor Author

Thank you very much; works perfectly! I've added your solution as an example file in #738 as I'm sure it could help others!

cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question serde Issues related to mapping from Rust types to XML
Projects
None yet
Development

No branches or pull requests

2 participants