Serde untagged Enum — No Variant Matched
Fix serde untagged enum failures by checking field overlap, ordering variants from most to least specific, and switching to a tagged representation.
Symptoms
- Deserialization fails with "data did not match any variant of untagged enum"
- One variant silently wins for the wrong input
- A field rename on one variant breaks parsing
Possible causes
- Variant field names overlap so the wrong variant matches first
- A required field is missing or renamed and no variant accepts the data
- untagged tries every variant in order and reports no match when all fail
Diagnosis steps
Print the failing payload and the enum definition
cargo test 2>&1 | grep -A 3 "did not match any variant"Interpretation
Knowing which field is unexpected shows where the variant definitions and data disagree.
Enable serde deny_unknown_fields to surface mismatches
cargo build --features serde/strictInterpretation
Denying unknown fields turns a silent no-match into a precise "unknown field" error.
Root cause
untagged tries each variant in declaration order and buffers the input, selecting the first that deserializes. Overlapping fields make an earlier, less specific variant win, and any unexpected field or rename makes every variant fail with the generic no-match error.
Fix
Order variants from most specific to most general, avoid overlapping field names, add deny_unknown_fields during development, or switch to an internally tagged representation when a discriminator exists.
Risk note
untagged forces serde to buffer the whole input, which costs memory on large payloads; a tagged enum avoids that.
Verify the fix
Run tests with representative payloads for each variant and confirm the correct variant is chosen; assert the wrong input now produces a clear error.
Symptoms
This guide profiles the failure behind “Serde untagged Enum — No Variant Matched”. It walks through the observable symptoms, a reproducible diagnosis path, the underlying root cause, the concrete fix, and how to verify the change and prevent a regression.
Observed symptoms include:
- Deserialization fails with “data did not match any variant of untagged enum”
- One variant silently wins for the wrong input
- A field rename on one variant breaks parsing
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Print the failing payload and the enum definition
cargo test 2>&1 | grep -A 3 "did not match any variant"
Knowing which field is unexpected shows where the variant definitions and data disagree.
Step 2: Enable serde deny_unknown_fields to surface mismatches
cargo build --features serde/strict
Denying unknown fields turns a silent no-match into a precise “unknown field” error.
Likely contributors, in order of frequency:
- Variant field names overlap so the wrong variant matches first
- A required field is missing or renamed and no variant accepts the data
- untagged tries every variant in order and reports no match when all fail
Root Cause
untagged tries each variant in declaration order and buffers the input, selecting the first that deserializes. Overlapping fields make an earlier, less specific variant win, and any unexpected field or rename makes every variant fail with the generic no-match error.
The Fix
Order variants from most specific to most general, avoid overlapping field names, add deny_unknown_fields during development, or switch to an internally tagged representation when a discriminator exists.
Watch out: untagged forces serde to buffer the whole input, which costs memory on large payloads; a tagged enum avoids that.
#[derive(Deserialize)]
#[serde(untagged)]
enum Event {
// most specific first
#[serde(deny_unknown_fields)]
Click { x: u32, y: u32, button: String },
#[serde(deny_unknown_fields)]
Key { code: String },
// keep a catch-all last, not first
Other(serde_json::Value),
}
Verify and Prevent
Run tests with representative payloads for each variant and confirm the correct variant is chosen; assert the wrong input now produces a clear error.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.