serde-xml-rs
serde-xml-rs copied to clipboard
Unable to handle structures with varying keys
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).
(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.)
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.
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 Unfortunately, attributes are handled by serde_derive, not something specific deserializers can add or control (easily at least).
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).
Pinging @dtolnay in case you have any input here (in case a serde_derive change is feasible).
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>,
}