serde-xml-rs icon indicating copy to clipboard operation
serde-xml-rs copied to clipboard

Unable to handle structures with varying keys

Open mathstuf opened this issue 8 years ago • 7 comments

I'm having trouble deserializing input like this:

<parent attr="value">
  <required_field>some other value</required_field>
  <kind_a>kind a</kind_a>
  <kind_b>32</kind_b>
</parent>

into a structure like this:

#[serde(untagged)]
enum AnEnum {
  KindA(String),
  KindB(i32),
}

struct MyStruct {
  attr: String,
  required_field: String,
  kinds: Vec<AnEnum>,
}

I can get it to sort of work if I use #[serde(rename="$value")] for kinds, but this fails because then all fields go there. Am I stuck with having to implement deserialization myself or is there something I can do to make this simpler? The XML is fixed and the rust is flexible here, so if structure changes are required, that's fine. Also, there is (luckily) at most one kinds field per structure, but the presence of the required_field makes things complicated to go down that route (AFAICT).

mathstuf avatar Mar 26 '17 21:03 mathstuf

(This may be a core serde issue rather than an XML-specific one, but the toggle of behavior based on the presence of a $value field is in this crate.)

mathstuf avatar Mar 26 '17 21:03 mathstuf

Hmm, yeah, I don't think this is possible right now without custom deserializer for that struct. I'll look into simplifying this use-case if possible.

RReverser avatar Mar 27 '17 12:03 RReverser

Thanks. I wonder if something like a #[serde(keys=["kind_a", "kind_b"])] attribute might make sense. Or to have some way to decorate the enum with a "tagged by key in the containing container".

mathstuf avatar Mar 27 '17 15:03 mathstuf

@mathstuf Unfortunately, attributes are handled by serde_derive, not something specific deserializers can add or control (easily at least).

RReverser avatar Mar 27 '17 16:03 RReverser

Agreed. The latter one probably makes more sense for such a level. The former sounds too XML-specific (since most other formats don't support multikey hashes).

mathstuf avatar Mar 27 '17 17:03 mathstuf

Pinging @dtolnay in case you have any input here (in case a serde_derive change is feasible).

mathstuf avatar Mar 28 '17 04:03 mathstuf

I found a way to make this example work. The code is based on this incomplete PR of me https://github.com/jonasbb/serde_with/pull/375. The additional deser_value_field function is necessary to 1) signal that the value of the tag should be parsed by using the rename to $value and 2) to parse the value from a String. Presumably, the flatten makes all values turn into a String (likely due to https://github.com/serde-rs/serde/issues/1183), so the code needs to make the String to desired type transformation explicit.

use std::str::FromStr;
use std::fmt::Display;

fn deser_value_field<'de, D: Deserializer<'de>, T>(deserializer: D) -> Result<T, D::Error>
where
    T: FromStr,
    T::Err: Display,
{
    #[serde_as]
    #[derive(Deserialize)]
    struct Field<T> {
        #[serde_as(as = "DisplayFromStr")]
        #[serde(rename = "$value", bound = "T: FromStr, T::Err: Display")]
        field: T
    }
    
    Ok(Field::deserialize(deserializer)?.field)
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
enum AnEnum {
    KindA(
        #[serde(deserialize_with = "deser_value_field")]
        String
    ),
    KindB(
        #[serde(deserialize_with = "deser_value_field")]
        i32
    ),
}

#[serde_as]
#[derive(Debug, Deserialize)]
struct MyStruct {
    attr: String,
    required_field: String,
    #[serde(flatten)]
    #[serde_as(as = "EnumMap")]
    kinds: Vec<AnEnum>,
}

jonasbb avatar Nov 07 '21 15:11 jonasbb