serde icon indicating copy to clipboard operation
serde copied to clipboard

#[serde(flatten)] doesn't work with #[serde(default)] for externally tagged enums

Open slckl opened this issue 6 years ago • 9 comments

Given this:

#[derive(Deserialize, Debug)]
enum SomeEnum {
    A(u32),
    B(u32),
}

impl Default for SomeEnum {
    fn default() -> Self {
        SomeEnum::B(1)
    }
}

#[derive(Deserialize, Debug)]
struct SomeStruct {
    filler: u32,
    #[serde(default)]
    #[serde(flatten)]
    some_enum: SomeEnum,
}

trying to deserialize this yaml:

filler: 1

into SomeStruct panics with a message:

no variant of enum SomeEnum found in flattened data

I hoped it would pick up the default value specified for SomeEnum instead...

Playground example.

Is this a bug or is this not supported? Docs don't cover this, I tried searching issues, but couldn't find anything quite like this.

slckl avatar Sep 09 '19 11:09 slckl

Thanks, I would accept a PR to make this work.

dtolnay avatar Sep 09 '19 18:09 dtolnay

Happy to give it a try, but I haven't dug too much into Serde's internals. Any pointers on where to start?

slckl avatar Sep 09 '19 18:09 slckl

~I need the same behaviour, but with internal tagging. My example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3dcd2fca7a231d6c1d7d41ff830e8d2e~ Edit: never mind, I realize my problem can easily be solved by using external tagging and renaming the ty field to type.

Ploppz avatar Sep 13 '19 11:09 Ploppz

FTR, the issue isn't restricted to external tagging; other forms of tagging don't make it work, either.

heftig avatar Mar 27 '20 13:03 heftig

I encounter the same issue when using #[serde(other)] for the externally tagged enum, e.g.,:

#[derive(Deserialize, Debug)]
enum SomeEnum {
    A(u32),
    B(u32),
    #[serde(other)]
    C,
}

#[derive(Deserialize, Debug)]
struct SomeStruct {
    filler: u32,
    #[serde(flatten)]
    some_enum: SomeEnum,
}

My use-case specifically is to still be able to deserialize a collection of SomeStruct after the sender may have added another SomeEnum variant. I am interested in the SomeStructs existence, and shared metadata, without necessarily knowing all variants and details of SomeEnum that exist.

veeg avatar May 20 '21 14:05 veeg

Is there any progress on this issue.

gamife avatar Jun 21 '23 02:06 gamife

any solution so far?

ogios avatar Nov 21 '24 13:11 ogios

For anyone waiting for an official fix to be implemented, I came across the serde_aux crate, which provides a solution to use flatten with default values. serde_aux provides a custom deserializer called deserialize_default_from_empty_object that can be used with serde's deserialize_with field attribute. It respects the flattened type's default implementation.

serde_aux docs example

My example:

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Example {
    pub a: String,
    pub b: i16,
}

impl Default for Example {
    fn default() -> Self {
        Example {
            a: "example".to_string(),
            b: 0,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
    #[serde(flatten, deserialize_with = "deserialize_default_from_empty_object")]
    bla: Example,
}

Resulting JSON when serializing Config struct with serde-json:

{
  "a": "example",
  "b": 0
}

Deserializing this works too, of course.

The only downside I noticed this far is that error messages are a bit more abstract. For example, when I try to deserialize a struct holding an enum value with this, and the tag in the serialized data is incorrect (no enum variant matches), it says data did not match any variant of untagged enum EmptyOrNot at line 21 column 1.

Kitt3120 avatar Jun 22 '25 19:06 Kitt3120

(Sorry for cross-post, but I think it's relevant)

I outlined an alternative approach to implementing flatten in the derive macro here: https://github.com/serde-rs/serde/issues/2186#issuecomment-3563826268 and linked to a proof of concept,

I believe that my proof of concept shows that this issue could also be fixed with an alternative approach to flatten, because it already gives examples of using flatten when deriving, and having defaults which apply when the flattened struct doesn't appear.

cbeck88 avatar Nov 21 '25 19:11 cbeck88