#[serde(flatten)] doesn't work with #[serde(default)] for externally tagged enums
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...
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.
Thanks, I would accept a PR to make this work.
Happy to give it a try, but I haven't dug too much into Serde's internals. Any pointers on where to start?
~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.
FTR, the issue isn't restricted to external tagging; other forms of tagging don't make it work, either.
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.
Is there any progress on this issue.
any solution so far?
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.
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.
(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.