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

Cannot Deserialize a Serializable Enum #808

Closed
MasterTemple opened this issue Sep 26, 2024 · 3 comments
Closed

Cannot Deserialize a Serializable Enum #808

MasterTemple opened this issue Sep 26, 2024 · 3 comments
Labels
question serde Issues related to mapping from Rust types to XML

Comments

@MasterTemple
Copy link

Hello, thank you for creating and maintaining this library.

I am unsure if this is a skill issue on my part or if there is a problem with this library.

Basically, I am unable to deserialize an enum (but I can serialize it).
Even when I try to deserialize the serialized output, I get an error.

I am trying to parse XML in one of the following two forms:

  1. Individual item
<some_struct>
    <some_enum>123</some_enum>
</some_struct>
  1. Multiple items (distinguished by a field id)
<some_struct>
    <some_enum id="0">123</some_enum>
    <some_enum id="1">456</some_enum>
</some_struct>

I have modelled them as follows:

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct SingleElement {
    #[serde(rename = "$value")]
    value: i32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct ListElement {
    #[serde(rename = "@id")]
    id: i32,
    #[serde(rename = "$value")]
    value: i32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
enum SomeEnum {
    SingleElement(SingleElement),
    ListElements(Vec<ListElement>),
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename = "some_struct")]
struct SomeStruct {
    #[serde(rename = "some_enum")]
    some_enum: SomeEnum,
}

Can Serialize Single Element Variant

dbg!(quick_xml::se::to_string(&SomeStruct {
        some_enum: SomeEnum::SingleElement(SingleElement { value: 123 })
}));
[src/main.rs:41:5] quick_xml::se::to_string(&SomeStruct {
            some_enum: SomeEnum::SingleElement(SingleElement { value: 123 }),
        }) = Ok(
    "<some_struct><some_enum>123</some_enum></some_struct>",
)

which is

<some_struct>
    <some_enum>123</some_enum>
</some_struct>

Cannot Deserialize Single Element Variant

dbg!(quick_xml::de::from_str::<SomeStruct>(
    &r#"
    <some_struct>
        <some_enum>123</some_enum>
    </some_struct>
    "#
));
[src/main.rs:33:5] quick_xml::de::from_str::<SomeStruct>(&r#"
        <some_struct>
        <some_enum>123</some_enum>
        </some_struct>
        "#) = Err(
    Custom(
        "data did not match any variant of untagged enum SomeEnum",
    ),
)

Can Serialize List Element Variant

dbg!(quick_xml::se::to_string(&SomeStruct {
    some_enum: SomeEnum::ListElements(vec![
            ListElement { id: 0, value: 123 },
            ListElement { id: 1, value: 456 }
    ])
}));
[src/main.rs:55:5] quick_xml::se::to_string(&SomeStruct {
            some_enum: SomeEnum::ListElements(vec![ListElement
                    { id: 0, value: 123 }, ListElement { id: 1, value: 456 }]),
        }) = Ok(
    "<some_struct><some_enum id=\"0\">0</some_enum><some_enum id=\"1\">456</some_enum></some_struct>",
)

which is

<some_struct>
    <some_enum id="0">123</some_enum>
    <some_enum id="1">456</some_enum>
</some_struct>

Cannot Deserialize List Element Variant

dbg!(quick_xml::de::from_str::<SomeStruct>(
    &r#"
    <some_struct>
        <some_enum id="0">123</some_enum>
        <some_enum id="1">456</some_enum>
    </some_struct>
    "#
));
[src/main.rs:46:5] quick_xml::de::from_str::<SomeStruct>(&r#"
        <some_struct>
            <some_enum id="0">123</some_enum>
            <some_enum id="1">456</some_enum>
        </some_struct>
        "#) = Err(
    Custom(
        "data did not match any variant of untagged enum SomeEnum",
    ),
)

Thank you for your help and support!

@Mingun Mingun added the serde Issues related to mapping from Rust types to XML label Sep 26, 2024
@Mingun
Copy link
Collaborator

Mingun commented Sep 26, 2024

Basically, serde features that requires internal bufferisation, will not work with XML deserializer, because only XML deserializer knows how to convert strings from XML into other types (such as i32). Also, conversion of specially named fields (such as @... and $value or $text) also only possible when type deserialized from the XML deserializer. When bufferisation is performed, XML deserializer provides data in very generic form, i. e. as strings and maps only, without special processing of specially named fields.

Serde features that require internal bufferisation and therefore not working with quick-xml deserializer:

The problems with bufferisation would be fixed when serde-rs/serde#1183 would be solved. But even it that problem would be solved, your untagged enum specifies variants in the wrong order. The SingleElement is a full subset of ListElement, so if it would be deserialized (that is impossible now due to the reasons outlined above), the ListElement will never be tried, and any representation of one element of ListElement is a valid SingleElement.

You should avoid bufferisation, for example, try to use serde-untagged or implement Deserialize for SomeEnum manually. It may worth to first deserialize into intermediate representation which is like ListElement, but with optional id and then convert it to the final form. Something like (not tested):

#[derive(Deserialize)]
struct IntermediateItem {
    #[serde(rename = "@id")]
    id: Option<i32>,
    #[serde(rename = "$value")]
    value: i32,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct IntermediateStruct {
    #[serde(rename = "some_enum")]
    some_enum: Vec<IntermediateItem>,
}
impl TryFrom<Vec<IntermediateItem>> for SomeEnum {
  type Error = ...;

  fn try_from(raw: Vec<IntermediateItem>) -> Result<Self, Self::Error> {
    if let [first] = raw.as_slice() {
      if first.id.is_none() {
        return SomeEnum::SingleElement(SingleElement { value: first.value });
      }
    }
    let vec: Result<Vec<_>> = raw.into_iter().map(|e| ListElement { id: e.id?, value: e.value }).collect();
    SomeEnum::ListElement(vec)
  }
}
impl From<IntermediateStruct> for SomeStruct {
  fn from(raw: IntermediateStruct) -> Self {
    Self { some_enum: raw.some_enum.map(Into::into).collect() }
  }
}

@Chaoses-Ib
Copy link

Great explanation! Maybe these paragraphs should be added to the docs? Currently untagged enums are not mentioned but only internal tagged enums.

@Mingun
Copy link
Collaborator

Mingun commented Oct 10, 2024

@Chaoses-Ib, feel free to submit a PR which would add things that, in your opinion, will improve understanding of untagged enums.

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

3 participants