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

Enum serialization/deserialization problem + #[serde(flatten)] inconsistency #600

Closed
horvbalint opened this issue May 9, 2023 · 4 comments · Fixed by #601
Closed

Enum serialization/deserialization problem + #[serde(flatten)] inconsistency #600

horvbalint opened this issue May 9, 2023 · 4 comments · Fixed by #601
Labels
documentation Issues about improvements or bugs in documentation enhancement question serde Issues related to mapping from Rust types to XML

Comments

@horvbalint
Copy link

horvbalint commented May 9, 2023

Hi, first of all thank you for this crate!

I am trying to communicate with an API that uses xml for the data exchange, so I have to both serialize and deserialize data from string and I would like to achive this using serde.
Sadly I have the following problem with enums:

Take this enum for example:

#[derive(Serialize, Deserialize)]
enum Type {
    Local,
    Online
}

and the usage in a struct like:

#[derive(Serialize, Deserialize)]
struct Software {
    type: Type
}

I would like this to serialize into:

<software>
    <type>Local</type>
</software>

but sadly serializing this enum (even when non of the variants have inner data) produces the following:

<software>
    <Local/>
</software>

after some trial and error I could come up with the following 'solution' that works for serialization, but is not too elegant/convenient:

#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Type {
    Local,
    Online
}

#[derive(Serialize, Deserialize)]
struct Software {
    #[serde(flatten)]
    type: Type
}

Unfortunately this only works for serialization, but not for deserialization :/ If I try to deserialize the xml from above it produces the error: Error: invalid type: map, expected variant identifier
For this error I was not able to find a solution or combination of serde attributes, that would work.

Is there a way currently to deserialize an Enum with no inner data?
Would it be possible for quick-xml to change the default serialization of enums so that if they hold no inner data, they get serialized into a tag like in the xml above?
I saw that previously, there was a #[serde(rename = "$primitive")] attributum for this, but it got removed later (which I understand)

@Mingun Mingun added the serde Issues related to mapping from Rust types to XML label May 10, 2023
@Mingun
Copy link
Collaborator

Mingun commented May 10, 2023

Your problem in that you have a wrong model in you mind, this is usual mistake when we all deal with XML. The correct way of mapping of the XML

<software>
    <type>Local</type>
</software>

is to use three types:

  • one for <software> with one field r#type
  • one for <type> with one field $text
  • one for Local
#[test]
fn issue600() {
    #[derive(Serialize, Deserialize)]
    enum TypeEnum {
        Local,
        Online,
    }

    #[derive(Serialize, Deserialize)]
    struct Type {
        #[serde(rename = "$text")]
        content: TypeEnum,
    }

    #[derive(Serialize, Deserialize)]
    #[serde(rename = "software")]
    struct Software {
        r#type: Type,
    }

    assert_eq!(
        to_string(
            &Software {
                r#type: Type { content: TypeEnum::Local },
            }
        )
        .unwrap(),
        "<software><type>Local</type></software>"
    );
}

I'll close this issue when I'll add an example of this question to the documentation.

This will be easier to understand, if you remember, that <type> tag also can have attributes. Where they should be placed in the Rust struct?

@Mingun Mingun added enhancement question documentation Issues about improvements or bugs in documentation labels May 10, 2023
@horvbalint
Copy link
Author

Thank you for the answer!

While this solves the problem (I haven't tried it out yet), it creates a rather unpleasant api for a user of my lib, because the 'Type' struct is basically just boilerplate (since it has only one field, as I do not expect any attributes on the <type> tag). I guess I can implement From<TypeEnum> for Type, so one can create a Type struct like TypeEnum::Local.into().

On the other hand, I am not sure how the default serialization of an enum with no data:<Local/>
makes more sense, then what I would suggest:<type>Local</type>
since the <Local/> tag might also contain attributes, that can not be represented inside the enum when deserialzied.

Thanks again for the quick answer!

@Mingun
Copy link
Collaborator

Mingun commented May 10, 2023

One possible way to express you API as you want is to use #[serde(with = "")] attribute on a r#type field, or #[serde(from = "", into = "")] on the TypeEnum enum

@Mingun
Copy link
Collaborator

Mingun commented May 10, 2023

On the other hand, I am not sure how the default serialization of an enum with no data:<Local/> makes more sense, then what I would suggest:<type>Local</type>

This simple logic will break, when you faced with more complicated cases:

enum Complicated {
  // <Unit/>
  //
  // suggestion:
  // <field>Unit</field>
  Unit,

  // <Struct><a/></Struct>
  Struct {
    a: ()
  },

  // Things are... complicated :)
  StructAsUnit {
    #[serde(skip_serializing_if = "...")]
    b: ()
  },
}

If accept you suggestion, then StructAsUnit would be serialized differently depending on the condition on b field, that, I'm sure, would be very, very unintuitive.

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

Successfully merging a pull request may close this issue.

2 participants