Skip to content

#[serde(flatten)] incompatible with $value #83

@adetaylor

Description

@adetaylor

This test case results in Error(Custom("missing field `$value`"):

#[derive(Debug, Deserialize, PartialEq)]
struct BC {
    b: String,
    c: String,
}

#[derive(Debug, Deserialize, PartialEq)]
struct D {
    d: String,
}

#[derive(Debug, Deserialize, PartialEq)]
struct A {
    #[serde(flatten)]
    bc: BC,
    #[serde(rename = "$value")]
    d: D,
}

#[test]
fn flatten_and_value() {
    init_logger();
    let s = r##"
        <A b="1" c="2">
            <D d="3"/>
        </A>
    "##;

    let project: A = from_str(s).unwrap();
    assert_eq!(
        project,
        A {
            bc: BC {
                b: "1".to_string(),
                c: "2".to_string(),
            },
            d: D { d: "3".to_string() }
        }
    );
}

The reason is this code in serde_derive's deserialize_struct:

    } else if cattrs.has_flatten() {
        quote! {
            _serde::Deserializer::deserialize_map(__deserializer, #visitor_expr)
        }
    } else {
        let type_name = cattrs.name().deserialize_name();
        quote! {
            _serde::Deserializer::deserialize_struct(__deserializer, #type_name, FIELDS, #visitor_expr)
        }
    };

#[serde(flatten)] causes the generated deserialization code to call deserialize_map which does not have access to the field names in FIELD and thus can't spot the $value and give it special handling.

I'd be keen to fix this but I need some advice about how. I'm brand new to serde.

Options would seem to be:

  • Modify serde and serde_derive to pass field names into deserialize_map in case it wants to use them (or, to put it another way, don't assume that things are necessarily a map rather than a struct just due to the presence of flatten - after all, the struct flattening page explicitly lists two reasons you might want to use flatten)
  • Modify serde_derive to have a new attribute which forces things into deserialize_struct rather than deserialize_map even if flatten is present
  • Add a new attribute specific to serde-xml-rs which indicates that there is an inner value. For example ``#[serde-xml-rs(has-inner-value)]. This would result in MapAccess.inner_value` being true even if we couldn't determine it by the field names because we're in `deserialize_map` rather than `deserialize_struct`. It seems a shame to have this redundancy of course.
  • Failing all else, just update the documentation to explain this pitfall!

Please let me As well as being new to Serde, I'm pretty new to Rust so it may be that one or more of the above ideas are complete bobbins and/or the syntax is nonsense.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions