tafia / quick-xml

Rust high performance xml reader and writer
MIT License
1.22k stars 237 forks source link

Unable to serialize a struct that deserialization works for #654

Open tatupesonen opened 1 year ago

tatupesonen commented 1 year ago

I'm trying to serialize/deserialize some data that an API responds with. This is what the XML from the API looks like:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
        <LoginResponse xmlns="urn:vim25">
             <returnval>
                <userName>username_here</userName>
                <loggedInAt>datetime_here</loggedInAt>
            </returnval>
        </LoginResponse>
    </soapenv:Body>
</soapenv:Envelope>

And this is my code:

#[derive(Deserialize, Debug, Serialize)]
#[serde(rename = "soapenv:Envelope")]
pub struct SoapResponse {
    #[serde(rename = "@xmlns:soapenc")]
    soapenc: Option<String>,
    #[serde(rename = "@xmlns:soapenv")]
    soapenv: Option<String>,
    #[serde(rename = "@xmlns:xsd")]
    xsd: Option<String>,
    #[serde(rename = "@xmlns:xsi")]
    xsi: Option<String>,
    #[serde(rename = "$value")]
    pub body: SoapBody,
}

#[derive(Deserialize, Debug, Serialize)]
pub struct SoapBody {
    #[serde(rename = "$value")]
    pub response_type: Response,
}

#[derive(Deserialize, Debug, Serialize)]
pub enum Response {
    #[serde(rename = "LoginResponse")]
    LoginResponse {
        #[serde(rename = "@xmlns")]
        xmlns: String,
        #[serde(rename = "$value")]
        returnval: LoginResponse,
    },
    #[serde(rename = "Other")]
    SomeOther {},
}

#[derive(Deserialize, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LoginResponse {
    logged_in_at: String,
    user_name: String,
}

Deserialization works fine. However, when I try to serialize a structure like this:

use quick_xml::se::to_string;

let thing = SoapResponse {
    soapenc: None,
    soapenv: None,
    xsd: None,
    xsi: None,
    body: {
        SoapBody {
            response_type: Response::LoginResponse {
                xmlns: "urn:vim25".into(),
                returnval: LoginResponse {
                    logged_in_at: "example".into(),
                    user_name: "example".into(),
                },
            },
        }
    },
};
let thing = to_string(&thing);
dbg!(&thing);

I get

 Err(
    Unsupported(
        "serialization of struct `SoapBody` is not supported in `$value` field",
    ),
)

What could be the reason that causes this?

Versions:

quick-xml 0.30.0 with features = ["serde", "serialize", "arbitrary", "serde-types", "overlapped-lists", "document-features", "encoding", "encoding_rs"]
Rust 1.72
Mingun commented 1 year ago

You do not need to rename body to $value here:

    #[serde(rename = "$value")]
    pub body: SoapBody,

Use usual rename, for example, #[serde(rename = "soapenv:Body")]. $value is required for enumerated types, but SoapBody is a struct.

It is possible to fix this behavior, although.

tatupesonen commented 1 year ago

I changed the SoapResponse to this:

#[derive(Deserialize, Debug, Serialize)]
#[serde(rename = "soapenv:Envelope")]
pub struct SoapResponse {
    #[serde(rename = "@xmlns:soapenc")]
    soapenc: String,
    #[serde(rename = "@xmlns:soapenv")]
    soapenv: String,
    #[serde(rename = "@xmlns:xsd")]
    xsd: String,
    #[serde(rename = "@xmlns:xsi")]
    xsi: String,
    #[serde(rename = "soapenv:Body")]
    pub body: SoapBody,
}

and the SoapBody:

#[derive(Deserialize, Debug, Serialize)]
#[serde(rename = "soapenv:Body")]
pub struct SoapBody {
    #[serde(rename = "$value")]
    pub response_type: Response,
}

Serialization works fine now, but deserialization now doesn't work. Trying to use the same XML.

 Custom(
        "missing field `soapenv:Body`",
    ),

Edit: Interestingly, if I set the field to rename like this:

    #[serde(rename = "Body")]
    pub body: SoapBody,

And change the XML from <soapenv:Body> to <Body>, now both deserialization and serialization work fine.

Mingun commented 1 year ago

Yes, namespaced renames works only occasionally, in general we do not support namespaces in serde (de)serializer yet.

SarguelUnda commented 10 months ago

A trick that use to work for this case was :

#[serde(alias = "soapenv:Body", rename(serialize = "soapenv:Body"))]
pub struct SoapBody {
    #[serde(rename = "$value")]
    pub response_type: Response,
}