@dimo414's answer is correct about the enum
being necessary, but the code sample will not function in the way the question is described. This is caused by a couple factors related to how enum
s are deserialized. Mainly, it will not enforce mutual exclusion of the two variants and will silently pick the first variant that matches and ignore extraneous fields. Another issue is the enum will be treated as a separate structure within MyStruct
(Ex: {"one_of_field":{"F1":123},"other_field":"abc"}
in JSON).
Solution
For anyone wanting an easy solution, here it is. However keep in mind that variants of the mutually exclusive type can not contain #[serde(flatten)]
fields (more information in the issue section). To accommodate neither field_1
or field_2
, Option<MutuallyExclusive>
can be used in MyStruct
.
/// Enum containing mutually exclusive fields. Variants names will be used as the
/// names of fields unless annotated with `#[serde(rename = "field_name")]`.
#[derive(Deserialize)]
enum MutuallyExclusive {
field_1(usize),
field_2(usize),
}
#[derive(Deserialize)]
/// `deny_unknown_fields` is required. If not included, it will not error when both
/// `field_1` and `field_2` are both present.
#[serde(deny_unknown_fields)]
struct MyStruct {
/// Flatten makes it so the variants of MutuallyExclusive are seen as fields of
/// this struct. Without it, foo would be treated as a separate struct/object held
/// within this struct.
#[serde(flatten)]
foo: MutuallyExclusive,
other_field: String,
}
The Issue
TL;DR: It should be fine to use deny_unknown_fields
with flatten
in this way so long as types used in MutuallyExclusive
do not use flatten
.
If you read the serde
documentation, you may notice it warns that using deny_unknown_fields
in conjunction with flatten
is unsupported. This is problematic as it throws the long-term reliability of the above code into question. As of writing this, serde
will not produce any errors or warnings about this configuration and is able to handle it as intended.
The pull request adding this warning cited 3 issues when doing so:
To be honest, I don't really care about the first one. I personally feel it is a bit overly pedantic. It simply states that the error message is not exactly identical between a type and a wrapper for that type using flatten
for errors triggered by deny_unknown_fields
. This should not have any effect on the functionality of the code above.
However, the other two errors are relevant. They both relate to nested flatten
types within a deny_unknown_fields
type. Technically the second issue uses untagged
for the second layer of nesting, but it has the same effect as flatten
in this context. The main idea is that deny_unknown_fields
is unable to handle more than a single level of nesting without causing issues. The use case is in any way at fault, but the way deny_unknown_fields
and flattened
are handled makes it difficult to implement a workaround.
Alternative
However, if anyone still feels uncomfortable with using the above code, you can use this version instead. It will be a pain to work with if there are a lot of other fields, but sidesteps the warning in the documentation.
#[derive(Debug, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
enum MyStruct {
WithField1 {
field_1: usize,
other_field: String,
},
WithField2 {
field_2: usize,
other_field: String,
},
}