Skip to content

Change the representation of Enums #717

Open

Description

@dishmaker

serde_json supports enums:

Kind Enum field
Unit "field": "Unit",
Newtype "field": { "Newtype": 42 },
Tuple "field": { "Tuple": [42, "answer"] },
Struct "field": { "Struct": {"q": 42, "a":"answer"} },
enum Kinds {             // Enum name as:
    Unit,                 // Value
    Newtype(i32),         // Key
    Tuple((i32, String)), // Key
    Struct(MyStruct),     // Key
}

https://docs.rs/quick-xml/latest/quick_xml/de/index.html#normal-enum-variant

so quick-xml should too:

Kind Enum field
Unit <field>Unit</field>
Newtype <field><Newtype>42</Newtype></field>
Tuple <field><Tuple>42</Tuple><Tuple>answer</Tuple></field>
Struct <field><Struct><q>42</q><a>answer</a></Struct></field>
enum Kinds {             // Enum name as:
    Unit,                 // Value
    Newtype(i32),         // Tag
    Tuple((i32, String)), // Tag
    Struct(MyStruct),     // Tag
}

Currently the above XML won't be generated (gives an error) while JSON would even parse back into Rust enum.
Unsupported operation: cannot serialize enum newtype variant

[Details] Ser/deserialization of enum - crate comparison:
// [package]
// name = "xml_vs_json"
// version = "0.1.0"
// edition = "2021"

// [dependencies]
// eyre = "0.6.12"
// quick-xml = { version = "0.31.0", features = ["serde", "serialize"] }
// rmp-serde = "1.1.2"
// serde = { version = "1.0.197", features = ["derive"] }
// serde_json = "1.0.114"
// serde_yaml = "0.9.32"
// toml = "0.8.10"

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct MyStruct {
    pub i8_value: i8,
    pub choice_value: MyChoice,
    pub choice_list: Vec<MyChoice>,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub enum MyChoice {
    ChoiceA(MyChoiceA),
    ChoiceB(MyChoiceB),
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct MyChoiceA {
    pub choice_i32: i32,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct MyChoiceB {
    pub choice_i64: i64,
    pub choice_text: String,
}

fn main() {
    let result = run();
    if let Err(err) = result {
        println!("err: {err}");
    }
}

fn run() -> eyre::Result<()> {
    let a = MyStruct {
        i8_value: 8,
        choice_value: MyChoice::ChoiceA(MyChoiceA { choice_i32: 32 }),

        choice_list: vec![
            MyChoice::ChoiceA(MyChoiceA { choice_i32: 32 }),
            MyChoice::ChoiceB(MyChoiceB {
                choice_i64: 64,
                choice_text: "my text".to_string(),
            }),
            MyChoice::ChoiceA(MyChoiceA { choice_i32: 32 }),
        ],
    };

    try_reencode_all(&a)?;

    Ok(())
}


fn try_reencode_all(a: &MyStruct) -> eyre::Result<()> {
    // pass
    let a_json = reencode_json(&a)?;
    assert_eq!(a, &a_json);

    // pass
    let a_toml = reencode_toml(&a)?;
    assert_eq!(a, &a_toml);

    // pass
    let a_yaml = reencode_yaml(&a)?;
    assert_eq!(a, &a_yaml);

    // pass
    let a_msgpack = reencode_msgpack(&a)?;
    assert_eq!(a, &a_msgpack);


    // fail: Unsupported operation: cannot serialize enum newtype variant `MyChoice::ChoiceA`
    let a_qxml = reencode_quick_xml(&a)?;
    assert_eq!(a, &a_qxml);
    Ok(())
}



fn reencode_json(input: &MyStruct) -> eyre::Result<MyStruct> {
    let json_str: String = serde_json::to_string(&input)?;
    println!("serde_json: {json_str}");
    let output = serde_json::from_str(&json_str)?;
    Ok(output)
}

fn reencode_toml(input: &MyStruct) -> eyre::Result<MyStruct> {
    let toml_str: String = toml::to_string(&input)?;
    println!("toml: {toml_str}");
    let output = toml::from_str(&toml_str)?;
    Ok(output)
}

fn reencode_yaml(input: &MyStruct) -> eyre::Result<MyStruct> {
    let yaml_str: String = serde_yaml::to_string(&input)?;
    println!("yaml: {yaml_str}");
    let output = serde_yaml::from_str(&yaml_str)?;
    Ok(output)
}

fn reencode_msgpack(input: &MyStruct) -> eyre::Result<MyStruct> {
    let msgpack_bytes: Vec<u8> = rmp_serde::to_vec(&input)?;
    println!("rmp_serde: {msgpack_bytes:?}");
    let output = rmp_serde::from_slice(&msgpack_bytes)?;
    Ok(output)
}



fn reencode_quick_xml(input: &MyStruct) -> eyre::Result<MyStruct> {
    // serialization does not work
    let qxml_str: String = quick_xml::se::to_string(&input)?;
    println!("quick_xml::se: {qxml_str}");
    let output = quick_xml::de::from_str(&qxml_str)?;
    Ok(output)
}

Activity

  1. Mingun commented on Feb 24, 2024

    @Mingun
    Collaborator

    You can find answer in this chapter about mapping of Rust types to XML. TL;DR: your enum variant is not a unit variant and it cannot be serialized in arbitrary-named field of struct.

  2. added
    serdeIssues related to mapping from Rust types to XML
    on Feb 24, 2024
  3. dishmaker commented on Feb 26, 2024

    @dishmaker
    ContributorAuthor

    Then why does it work for toml, yaml, msgpack and der but does not for quick-xml?

    It forces me to use a feature flag in compile time - just to produce a little different code for quick-xml.

    Is there any way to show serde(rename) exclusively to this crate?

    struct AnyName {
      #[serde(rename = "$value")]
      any_name: Choice,
    }
  4. dishmaker commented on Feb 26, 2024

    @dishmaker
    ContributorAuthor

    But still, even when using #[serde(rename = "$value")] it does not serialize
    <any_name> ... </any_name> but only the inside variant.

  5. Mingun commented on Feb 26, 2024

    @Mingun
    Collaborator

    It is hard to answer to your questions because you do not provide your expectations. The mentioned piece of documentation shows how quick-xml performs mapping in a consistent manner. If you have concrete suggestions, please describe them and even better open a PR with them!

  6. dishmaker commented on Feb 26, 2024

    @dishmaker
    ContributorAuthor

    My expectations:

    Kind	In normal field
    Unit	<field>Unit</field>
    Newtype	<field><Newtype>42</Newtype></field>
    Tuple   <field><Tuple>42</Tuple><Tuple>answer</Tuple></field>
    Struct	<field><Struct><q>42</q><a>answer</a></Struct></field>
    

    Just like JSON:

    Unit	   "field": "Unit",
    Newtype	   "field": { "Newtype": 42 },
    Tuple      "field": [42, "answer"],
    Struct     "field": { "Struct": {"q": 42, "a":"answer"} },
    
  7. dishmaker commented on Mar 21, 2024

    @dishmaker
    ContributorAuthor

    Issue should be reopened - other crates support enum lists.
    Only quick-xml is a black sheep here.

  8. Mingun commented on Mar 21, 2024

    @Mingun
    Collaborator

    You feel free to submit PR that would implement the desired behavior and make it in the consistent way. Probably this is possible. We also should keep the ability to use tag name as enum discriminator, because this is natural way how xs:choice in XML is represented. It must be representable with Rust enum.

  9. reopened this on Mar 21, 2024
  10. changed the title [-]Enum/choice list does not deserialize[/-] [+]Change the representation of Enums[/+] on Mar 21, 2024
  11. flotang-gtt commented on Nov 12, 2024

    @flotang-gtt

    If we accept that quick-xml does not do this - is the only way to get around this to write a custom serializer/deserializer with kinda "hardcoding" the variants? Do you see any obstacles with that approach?

  12. DennisJensen95 commented on Nov 12, 2024

    @DennisJensen95

    @flotang-gtt I think you can get around it with this response on StackOverflow, I am working to try it out now: https://stackoverflow.com/questions/78444158/unsupportedcannot-serialize-enum-newtype-variant-exampledata 😊

  13. DennisJensen95 commented on Nov 12, 2024

    @DennisJensen95

    @flotang-gtt i managed to get stuff working with struct wrappers like so:

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub struct FooWrapper {
        #[serde(rename = "$value")]
        pub foo: Foo,
    }
    
    #[derive(Debug, Deserialize, Serialize, PartialEq)]
    #[serde(rename_all = "camelCase")]
    pub enum Foo {
        #[serde(rename = "tag:Point", alias = "Point")]
        Point(Point),
    
        #[serde(rename = "tag:LineString", alias = "LineString")]
        LineString(LineString),
    }

    Where i then have

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    #[serde(rename = "tag:Baz")]
    pub struct Baz {
        #[serde(rename = "tag:Foo", alias = "Foo")]
        pub foo: FooWrapper,
    }

    So here the wrappers can handle the enum variant where we then use $value that way it works for me at least 😊

  14. lethargy123 commented on Sep 10, 2025

    @lethargy123

    What is the current progress of this issue?

  15. dishmaker commented on Sep 16, 2025

    @dishmaker
    ContributorAuthor

    I tried some refactor but this trait system is so hard for me I couldn't even compile a proof of concept 🫠

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedquestionserdeIssues related to mapping from Rust types to XML

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions